Compare commits
730
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f5c21c631 | ||
|
|
4ad18dc963 | ||
|
|
881535af7d | ||
|
|
1e66fb6ab3 | ||
|
|
e1b9e5efad | ||
|
|
28570b43e8 | ||
|
|
a01382d756 | ||
|
|
1d6e7f15b9 | ||
|
|
97cc3ca581 | ||
|
|
0bcae3e87b | ||
|
|
ebd228c3d7 | ||
|
|
4b83a74bdb | ||
|
|
b348b84b63 | ||
|
|
7f4a8d5974 | ||
|
|
4d8ca457ec | ||
|
|
d677e948f1 | ||
|
|
d4e6af50bd | ||
|
|
c609cafa9b | ||
|
|
5556b0fe0c | ||
|
|
eee04f4703 | ||
|
|
127c6c4b53 | ||
|
|
7d6a1e54e6 | ||
|
|
8433b2b637 | ||
|
|
87b2153794 | ||
|
|
0fc5b2a95c | ||
|
|
6c2f93e85d | ||
|
|
4f1604fea1 | ||
|
|
059766291b | ||
|
|
e471c1fc8b | ||
|
|
0fb91ef859 | ||
|
|
c0a0a34cbb | ||
|
|
3d44a6fdf9 | ||
|
|
0eee307622 | ||
|
|
6a4aa04b47 | ||
|
|
643f073945 | ||
|
|
5ba40c65fc | ||
|
|
dcbbed4160 | ||
|
|
e94e165593 | ||
|
|
34709be6cc | ||
|
|
83107bb31a | ||
|
|
3caa43a5bf | ||
|
|
1efe860a89 | ||
|
|
d49965549c | ||
|
|
8d85259025 | ||
|
|
097d0456cc | ||
|
|
a62fa93c77 | ||
|
|
a22dcf05f2 | ||
|
|
afa6fa5ca2 | ||
|
|
c6effef622 | ||
|
|
ab31fbb0aa | ||
|
|
7d9a2c6e70 | ||
|
|
a486d5df33 | ||
|
|
3c8a621817 | ||
|
|
0e0aca55af | ||
|
|
0f02584143 | ||
|
|
a3db45609a | ||
|
|
d60a2a217c | ||
|
|
3e9ff6b5cb | ||
|
|
05a7d337de | ||
|
|
fdca086a47 | ||
|
|
42a81beeac | ||
|
|
9284b9e0b1 | ||
|
|
eb91f4fef4 | ||
|
|
68acc2aa51 | ||
|
|
5570583f70 | ||
|
|
eb979625d1 | ||
|
|
e1e5f527e4 | ||
|
|
f016dbd223 | ||
|
|
b2aa04ae1a | ||
|
|
e270adc50f | ||
|
|
fd5b8de533 | ||
|
|
e3d9b042c2 | ||
|
|
f4ae796b30 | ||
|
|
367d0386b6 | ||
|
|
5b54bed406 | ||
|
|
d3b60277d7 | ||
|
|
2ec6ff4ebc | ||
|
|
78a8d017a2 | ||
|
|
1a205d8727 | ||
|
|
396b5ea211 | ||
|
|
3cf9099f56 | ||
|
|
451b5f96e7 | ||
|
|
15ebae5025 | ||
|
|
32bfbadb8e | ||
|
|
5ed09ebefa | ||
|
|
eaea7a67ba | ||
|
|
de2114b8c5 | ||
|
|
64607ddbaa | ||
|
|
db1d999721 | ||
|
|
18047a7cfb | ||
|
|
def7ebd48c | ||
|
|
8c2b1ebdc5 | ||
|
|
9227517a50 | ||
|
|
5322d0f7b5 | ||
|
|
a282cc5691 | ||
|
|
048f8bcdf9 | ||
|
|
7bcbc748d4 | ||
|
|
f30e6cfe7f | ||
|
|
026e80e026 | ||
|
|
a3b77cff63 | ||
|
|
4ca68d6995 | ||
|
|
280cb7f2b1 | ||
|
|
095a73132c | ||
|
|
cfb0fc23ac | ||
|
|
6620aca9fe | ||
|
|
2422a02b23 | ||
|
|
b4a9dc0770 | ||
|
|
e7411244fd | ||
|
|
9f357173c3 | ||
|
|
7dc6e3ed5b | ||
|
|
5dfff212d0 | ||
|
|
17615f966b | ||
|
|
7a2d6b6c63 | ||
|
|
343867f6ee | ||
|
|
b9837ac77c | ||
|
|
bb73ec6c68 | ||
|
|
ee2d7bbb1b | ||
|
|
5d48bc7371 | ||
|
|
59a4649528 | ||
|
|
f54b6033ab | ||
|
|
bf340cd7d4 | ||
|
|
313417cb91 | ||
|
|
7850078623 | ||
|
|
44bf3b77b3 | ||
|
|
8485458896 | ||
|
|
3d253f5f06 | ||
|
|
64287d7e1a | ||
|
|
edc78a5a4c | ||
|
|
1a18d6c035 | ||
|
|
3e877182b5 | ||
|
|
0e4f883bc1 | ||
|
|
265cc62ab8 | ||
|
|
800c16970e | ||
|
|
6bd11bf233 | ||
|
|
a2fd82a3f3 | ||
|
|
7567990da1 | ||
|
|
c7d157bbfd | ||
|
|
2161edb871 | ||
|
|
e77e9c5691 | ||
|
|
e934a943b5 | ||
|
|
95eb58587b | ||
|
|
25628a8b76 | ||
|
|
7552e315fb | ||
|
|
d15b5b221e | ||
|
|
60e3fa97a9 | ||
|
|
fd1bd51aff | ||
|
|
b12620af9c | ||
|
|
d1af59fdf0 | ||
|
|
1b050ebc47 | ||
|
|
ff3e457c58 | ||
|
|
2ffaede07b | ||
|
|
2a83f57262 | ||
|
|
5be9193978 | ||
|
|
d3f264fc54 | ||
|
|
04ea002bf3 | ||
|
|
8f42a3a0ea | ||
|
|
4dd8fbe25d | ||
|
|
ee727c43e1 | ||
|
|
395213946b | ||
|
|
ed0b236208 | ||
|
|
8d56e75257 | ||
|
|
b3c27415d1 | ||
|
|
9839b0ee66 | ||
|
|
f3f57f9748 | ||
|
|
85d16a4354 | ||
|
|
c0517c3562 | ||
|
|
92e535a1c9 | ||
|
|
7411c2552b | ||
|
|
f2410f376e | ||
|
|
a9313c1367 | ||
|
|
5b3b9d770a | ||
|
|
4a54215fa1 | ||
|
|
52fbb34ddd | ||
|
|
c96dc7eaa1 | ||
|
|
f19409ba5b | ||
|
|
679c42ef90 | ||
|
|
3304c6f472 | ||
|
|
34f09fa260 | ||
|
|
fd4efd38cd | ||
|
|
e4e9bfc3e9 | ||
|
|
3774dbe38c | ||
|
|
4ef50642ad | ||
|
|
f95fb78022 | ||
|
|
0f9a8bde61 | ||
|
|
129d369c1a | ||
|
|
dd2c591a5b | ||
|
|
16749a724d | ||
|
|
a627930b58 | ||
|
|
404a62aa0b | ||
|
|
2d9cfc880b | ||
|
|
ee8bdfc4ac | ||
|
|
b55d484863 | ||
|
|
ed0058a503 | ||
|
|
b0aed393d6 | ||
|
|
a1ea203281 | ||
|
|
4590739fa5 | ||
|
|
7c617f2976 | ||
|
|
9141f61708 | ||
|
|
ba4120050f | ||
|
|
cbd4dc6e0d | ||
|
|
c7014e159b | ||
|
|
9febbc7db1 | ||
|
|
ec8e10f406 | ||
|
|
3233b24a4e | ||
|
|
5775e9cb18 | ||
|
|
570005f5e1 | ||
|
|
fbaa3440f2 | ||
|
|
e662468ecc | ||
|
|
c8efc30654 | ||
|
|
79d117026f | ||
|
|
2709054bdb | ||
|
|
2d4230c001 | ||
|
|
f7729508cf | ||
|
|
31e1acce67 | ||
|
|
aa1f3df76b | ||
|
|
3431f76320 | ||
|
|
1bc5466a8d | ||
|
|
d83daf44d3 | ||
|
|
2ad5ecd5d5 | ||
|
|
35c51a029a | ||
|
|
8c1128c85b | ||
|
|
c9021ab2d2 | ||
|
|
67a71f8f82 | ||
|
|
97d74f39e7 | ||
|
|
edd0a4a4a9 | ||
|
|
8a22672c7f | ||
|
|
11270eaaf5 | ||
|
|
1e32d59ecb | ||
|
|
eab27123dd | ||
|
|
a6253957c8 | ||
|
|
a8f5631144 | ||
|
|
6d98162e19 | ||
|
|
ee37ad0f4b | ||
|
|
2d71b5a63b | ||
|
|
f4f83bccbe | ||
|
|
30b114d538 | ||
|
|
c3db3457e7 | ||
|
|
7787af2df1 | ||
|
|
3730109cce | ||
|
|
b814608c06 | ||
|
|
9bab74d30e | ||
|
|
63a487bc0b | ||
|
|
a40eadcfce | ||
|
|
9b19d63a6a | ||
|
|
6939472f37 | ||
|
|
0387307301 | ||
|
|
25a5dde8e9 | ||
|
|
2e068bb73e | ||
|
|
842cacde19 | ||
|
|
6a453a0910 | ||
|
|
ff9903b14d | ||
|
|
d64bf5de22 | ||
|
|
9300d867fb | ||
|
|
eb0f8b5c24 | ||
|
|
d58768f26d | ||
|
|
3667217053 | ||
|
|
b237285703 | ||
|
|
a6c5581942 | ||
|
|
9a088d36b9 | ||
|
|
407734ff4a | ||
|
|
a74d963cfa | ||
|
|
02a47695e7 | ||
|
|
0f37cd3a45 | ||
|
|
5fe18bb78c | ||
|
|
b16dfa9ca1 | ||
|
|
3e6116fcba | ||
|
|
ef3bf496e9 | ||
|
|
bff293213d | ||
|
|
a47a1775f2 | ||
|
|
b2a2df7a8a | ||
|
|
34179f5b7d | ||
|
|
d38b744c08 | ||
|
|
ef58416814 | ||
|
|
62b7c9468e | ||
|
|
bafdb21b45 | ||
|
|
79080ffa93 | ||
|
|
f99f821a6d | ||
|
|
3b51475150 | ||
|
|
b41de5764b | ||
|
|
bb1c36f591 | ||
|
|
09f16c33d1 | ||
|
|
9d94658372 | ||
|
|
81f36df066 | ||
|
|
7afc2b9963 | ||
|
|
2e3369a09a | ||
|
|
212b2c999f | ||
|
|
2761f5cd20 | ||
|
|
d5831fea94 | ||
|
|
71afa21701 | ||
|
|
2736a513a3 | ||
|
|
bb654360bf | ||
|
|
ee0fd7dd5f | ||
|
|
dfe41d7f76 | ||
|
|
be81586b46 | ||
|
|
c33b06274d | ||
|
|
d72fc5c4e8 | ||
|
|
a41555cc0d | ||
|
|
c1bf3a7e15 | ||
|
|
5f6be40bdf | ||
|
|
8dc02fa081 | ||
|
|
a7ff32891e | ||
|
|
f6deaca452 | ||
|
|
ef01c27fad | ||
|
|
d8b4361936 | ||
|
|
0afb4f7283 | ||
|
|
4e746d367e | ||
|
|
e3d65967aa | ||
|
|
73408f1cce | ||
|
|
6e3a6917c8 | ||
|
|
b2b112eb8b | ||
|
|
56710d5b5b | ||
|
|
00ca9e0d2d | ||
|
|
65c76fcfab | ||
|
|
a686ba2a0e | ||
|
|
344245b1ea | ||
|
|
82b4f506c2 | ||
|
|
5e31fde416 | ||
|
|
b60665d096 | ||
|
|
24abc117fe | ||
|
|
5ae5fe338a | ||
|
|
5f8eac0464 | ||
|
|
fc951ee100 | ||
|
|
78bf04ef67 | ||
|
|
ae7db56f77 | ||
|
|
3dace4fc9d | ||
|
|
32912b0c35 | ||
|
|
f15be3ed65 | ||
|
|
46e99910e7 | ||
|
|
c3e7dad236 | ||
|
|
100f2c3bcd | ||
|
|
0aaac78cfa | ||
|
|
5795cf119a | ||
|
|
528583430f | ||
|
|
696a71166c | ||
|
|
5f082df3ab | ||
|
|
eb6bb99eaf | ||
|
|
fd2138c1fd | ||
|
|
0f3160f727 | ||
|
|
04ed514a21 | ||
|
|
4f382abc66 | ||
|
|
60f656006d | ||
|
|
5c777f3182 | ||
|
|
0ee0db8071 | ||
|
|
9bacd13ce2 | ||
|
|
c17d4e1a60 | ||
|
|
5f24de3085 | ||
|
|
d8df405db4 | ||
|
|
94cef504a3 | ||
|
|
ab32bac5d9 | ||
|
|
18765465ae | ||
|
|
ba9ddd11da | ||
|
|
cfcdc9c98d | ||
|
|
23d0a6b9b2 | ||
|
|
fee222daff | ||
|
|
edb516f71a | ||
|
|
b6031dea1a | ||
|
|
5b46c718e6 | ||
|
|
599ec18ecc | ||
|
|
cae106bfe8 | ||
|
|
119fde2033 | ||
|
|
0980350172 | ||
|
|
a27a4dcd84 | ||
|
|
adc2435073 | ||
|
|
a325ba339b | ||
|
|
096991ac79 | ||
|
|
a6a76d1815 | ||
|
|
6379f2fd35 | ||
|
|
2bb56f1b81 | ||
|
|
e993c23d5c | ||
|
|
71e16d20ef | ||
|
|
682b7656bc | ||
|
|
d765e69713 | ||
|
|
43920f9adc | ||
|
|
fdea8c28c8 | ||
|
|
ce372abf8a | ||
|
|
b1c3473856 | ||
|
|
52c21b0fca | ||
|
|
b7533b57b4 | ||
|
|
0dc13cf23f | ||
|
|
d3b96a163f | ||
|
|
95aba6d696 | ||
|
|
9030b1c19f | ||
|
|
79d241a910 | ||
|
|
c48d6f5a38 | ||
|
|
d9e890e954 | ||
|
|
ecdbd9653b | ||
|
|
e0bb4b6a23 | ||
|
|
2c41388096 | ||
|
|
0ec9423e0a | ||
|
|
efe24289bf | ||
|
|
03aa17e602 | ||
|
|
99d5a7e609 | ||
|
|
383617ec17 | ||
|
|
a39030f4ba | ||
|
|
e267b41575 | ||
|
|
6e3496d3f5 | ||
|
|
14f3336941 | ||
|
|
da43d2f08c | ||
|
|
827fa188f4 | ||
|
|
352b6fa45b | ||
|
|
8789a643ec | ||
|
|
8696f66915 | ||
|
|
98de8acece | ||
|
|
0f076f7a13 | ||
|
|
7e1d1a1968 | ||
|
|
b743f6a2df | ||
|
|
7e822bb8a1 | ||
|
|
3e08ce1efb | ||
|
|
77bfff83f2 | ||
|
|
a34c63a313 | ||
|
|
6ea84db0ae | ||
|
|
a2511ff09b | ||
|
|
2c23e67c65 | ||
|
|
3718a9cdd4 | ||
|
|
e03ff2661b | ||
|
|
d82e262e13 | ||
|
|
0e7e16f455 | ||
|
|
7585918821 | ||
|
|
0e43c43609 | ||
|
|
233452cb55 | ||
|
|
659e74d831 | ||
|
|
b4e2191a4d | ||
|
|
fc86f2222c | ||
|
|
26958df79e | ||
|
|
8e837d387d | ||
|
|
801fb5f885 | ||
|
|
ed517ecd86 | ||
|
|
4a977b85a2 | ||
|
|
2fbda6e64b | ||
|
|
ac5539a8be | ||
|
|
d8b0616434 | ||
|
|
d97cbf486d | ||
|
|
90864d8e58 | ||
|
|
7813acfa2b | ||
|
|
90820483d5 | ||
|
|
b86eeb6b52 | ||
|
|
bf942d17bf | ||
|
|
3344274616 | ||
|
|
7dff3a3755 | ||
|
|
ce769ffff4 | ||
|
|
3b4ca0b0f5 | ||
|
|
ca9a3bbf1b | ||
|
|
21126b7ba6 | ||
|
|
0cf436fa5c | ||
|
|
4f9c793d00 | ||
|
|
eff25b5abe | ||
|
|
beb431900a | ||
|
|
7a7b8461c5 | ||
|
|
f17f5522c0 | ||
|
|
99f841b7ae | ||
|
|
3e1abd1404 | ||
|
|
a7cacc9bb3 | ||
|
|
5978412b23 | ||
|
|
129adf2699 | ||
|
|
12355219d4 | ||
|
|
8145a90094 | ||
|
|
3d7e79c3e5 | ||
|
|
43e373d352 | ||
|
|
f78c5675ea | ||
|
|
3553e7546a | ||
|
|
37d96db979 | ||
|
|
9ed2e48ff4 | ||
|
|
14be246802 | ||
|
|
3e871f2c33 | ||
|
|
ea7ae74ccb | ||
|
|
9e5eb2da08 | ||
|
|
7046c3585b | ||
|
|
318d994cf3 | ||
|
|
e1b4d1b982 | ||
|
|
839b2ff6a2 | ||
|
|
2221c4f1c7 | ||
|
|
5d0486fddc | ||
|
|
0e7d5464ae | ||
|
|
073e59c89a | ||
|
|
742e12c9c3 | ||
|
|
3a12b6591d | ||
|
|
897af1b013 | ||
|
|
b5e46ba880 | ||
|
|
cd47bdbb0c | ||
|
|
1b712fe6ec | ||
|
|
cbf9f9bcd1 | ||
|
|
d090176272 | ||
|
|
6b8067815c | ||
|
|
3ce438399a | ||
|
|
ec26e5eabf | ||
|
|
36402a149a | ||
|
|
0765a1d809 | ||
|
|
30c1baecd7 | ||
|
|
b5b9c13bad | ||
|
|
0e1ca76d6e | ||
|
|
802758ed59 | ||
|
|
1e72b1cf0b | ||
|
|
5c33c6fbf6 | ||
|
|
836d45e5c5 | ||
|
|
c18ec80e34 | ||
|
|
13299a2c73 | ||
|
|
49acf8b35c | ||
|
|
23ee6fa83f | ||
|
|
adb67c709b | ||
|
|
2735f17ed8 | ||
|
|
25139dabe0 | ||
|
|
e7c53d4768 | ||
|
|
4aed6a1034 | ||
|
|
ac02e97e0c | ||
|
|
e37cf30690 | ||
|
|
118ac8e230 | ||
|
|
3e28e10cdf | ||
|
|
94b8c11f0f | ||
|
|
5146f05625 | ||
|
|
7379a752f1 | ||
|
|
5b102e3b8e | ||
|
|
b18b6cc5e7 | ||
|
|
db545a33e1 | ||
|
|
7a04baf9ef | ||
|
|
bc1ddaed8c | ||
|
|
860afb35f2 | ||
|
|
ec25c3a61d | ||
|
|
b22c7b5350 | ||
|
|
f9bdbb0539 | ||
|
|
2d2d17d928 | ||
|
|
7256d49761 | ||
|
|
9799c550f4 | ||
|
|
860acd7ba1 | ||
|
|
62bc748a87 | ||
|
|
f1db40392f | ||
|
|
fd8c31908d | ||
|
|
802e5a8c27 | ||
|
|
0821fb64bb | ||
|
|
a12361deb5 | ||
|
|
61a28c9b34 | ||
|
|
c24cd2e53b | ||
|
|
1886553148 | ||
|
|
b7e43a8def | ||
|
|
b3d6c60ce2 | ||
|
|
4421b920f0 | ||
|
|
b853a5e14a | ||
|
|
088f907dcc | ||
|
|
5b44503577 | ||
|
|
e55921ce80 | ||
|
|
e04c4a89ad | ||
|
|
5940a54585 | ||
|
|
cdbbd6a921 | ||
|
|
f3ed935213 | ||
|
|
dd3ab3a0dc | ||
|
|
bee20b525f | ||
|
|
f5096ba8f3 | ||
|
|
fbf3e6dc71 | ||
|
|
ce22315520 | ||
|
|
5198e7c52d | ||
|
|
4bc36a7eb8 | ||
|
|
bc803ae78b | ||
|
|
b0b98865c6 | ||
|
|
9df6e65552 | ||
|
|
9196d7ad00 | ||
|
|
14299b6674 | ||
|
|
8088b726b4 | ||
|
|
a70c606c27 | ||
|
|
49f5d2e2d4 | ||
|
|
597d8553c0 | ||
|
|
57b3a196bf | ||
|
|
d3dc85f31f | ||
|
|
b916c85738 | ||
|
|
83a0bf4f4b | ||
|
|
9da46462f7 | ||
|
|
dff90c6ec0 | ||
|
|
f381e8391c | ||
|
|
a4f6c5d355 | ||
|
|
4463d04850 | ||
|
|
2e61d6449a | ||
|
|
e8eec901d1 | ||
|
|
94e8bc1db6 | ||
|
|
704aebed0c | ||
|
|
040495f359 | ||
|
|
06fdf8d3dc | ||
|
|
77861ae55b | ||
|
|
cceabf88cd | ||
|
|
425023f83b | ||
|
|
6c725dbd67 | ||
|
|
99119a506f | ||
|
|
a36c7beabf | ||
|
|
6258fa6a35 | ||
|
|
8b82addd53 | ||
|
|
3538737681 | ||
|
|
b0c5101b2b | ||
|
|
23341c8f35 | ||
|
|
4489cf45d1 | ||
|
|
91e37b80e3 | ||
|
|
a2ad95d965 | ||
|
|
64f2447c0e | ||
|
|
b648126d19 | ||
|
|
52810b9ee1 | ||
|
|
f705479767 | ||
|
|
812b414d96 | ||
|
|
5b29843b9f | ||
|
|
c1766b0c57 | ||
|
|
851e060a25 | ||
|
|
0b811d6648 | ||
|
|
28fc85a96c | ||
|
|
b1d9573e47 | ||
|
|
ec91c47158 | ||
|
|
e8a81e30b1 | ||
|
|
e04a41be45 | ||
|
|
78850087f8 | ||
|
|
38f7df2b0c | ||
|
|
f9ea546c66 | ||
|
|
d5156aced2 | ||
|
|
fa9b0e4718 | ||
|
|
bf498b411c | ||
|
|
8ecc5552ab | ||
|
|
c9feaa150e | ||
|
|
38879e11df | ||
|
|
8a9e6ee82d | ||
|
|
bd3a26801d | ||
|
|
5a4aaae7f2 | ||
|
|
4e12f1e4e6 | ||
|
|
923c98a29f | ||
|
|
8915064780 | ||
|
|
c2c1285d17 | ||
|
|
73fab105d2 | ||
|
|
0c355b0cf1 | ||
|
|
0ed0338d8f | ||
|
|
677ff59d04 | ||
|
|
1fe2ec08be | ||
|
|
4a0d364d10 | ||
|
|
5dfa76600a | ||
|
|
4b78fabd54 | ||
|
|
9f375ede65 | ||
|
|
83bcd37250 | ||
|
|
237e4e56b2 | ||
|
|
4a7ad4a562 | ||
|
|
42ae37e502 | ||
|
|
cdd4d2a000 | ||
|
|
3331788c5c | ||
|
|
be5a740c96 | ||
|
|
50a107f885 | ||
|
|
13cc320f19 | ||
|
|
3a697d90fc | ||
|
|
b5544aa520 | ||
|
|
422218ea15 | ||
|
|
6f582451ed | ||
|
|
258ab59286 | ||
|
|
cf2ec50d15 | ||
|
|
614cb5da20 | ||
|
|
9e3f8c7d0c | ||
|
|
75ec1f82ac | ||
|
|
67c8f098a0 | ||
|
|
87b8023f2c | ||
|
|
cb76554455 | ||
|
|
b97564ba3a | ||
|
|
4bea40dfb4 | ||
|
|
5a159a756d | ||
|
|
024a57e098 | ||
|
|
312276bde7 | ||
|
|
a31d4746b3 | ||
|
|
17eda54bfb | ||
|
|
d3f0506f0b | ||
|
|
051dfd1fff | ||
|
|
37e4d80ce8 | ||
|
|
14a64857df | ||
|
|
79af9e8e2b | ||
|
|
319d98ef23 | ||
|
|
941705b03d | ||
|
|
2a56d4e376 | ||
|
|
c6d01178c0 | ||
|
|
7f4378066d | ||
|
|
d315a1082e | ||
|
|
d5d4edbe92 | ||
|
|
0a718b0585 | ||
|
|
8c23075ec5 | ||
|
|
86d98658f1 | ||
|
|
e947daff45 | ||
|
|
ee8339d563 | ||
|
|
7e26d09f77 | ||
|
|
eaf07d2189 | ||
|
|
1d5a684fd5 | ||
|
|
c893027f7c | ||
|
|
320310fbc0 | ||
|
|
6b599b641c | ||
|
|
b74aadff97 | ||
|
|
0a852aeb07 | ||
|
|
ade61f7ec5 | ||
|
|
e80d117a2a | ||
|
|
1dacab8b9a | ||
|
|
58e693a75d | ||
|
|
d34be8ce34 | ||
|
|
6aca62b202 | ||
|
|
7bad6b9fd8 | ||
|
|
d709994c61 | ||
|
|
93415a9957 | ||
|
|
963e57723a | ||
|
|
e5c8627abf | ||
|
|
a2a2680def | ||
|
|
53b152d501 | ||
|
|
5c514ca6c6 | ||
|
|
e17fa957d6 | ||
|
|
a907be54b4 | ||
|
|
7886af4397 | ||
|
|
c7f9417063 | ||
|
|
7b0ae3c5d9 | ||
|
|
7a57e8861d | ||
|
|
69139eda52 | ||
|
|
c30ffa9ac3 | ||
|
|
67fe7e7e11 | ||
|
|
c19e747bbe | ||
|
|
b1b48ec5cd | ||
|
|
8e609af901 | ||
|
|
51553e0270 | ||
|
|
38a0417ef3 | ||
|
|
d5859669f6 | ||
|
|
634afc4b18 | ||
|
|
ca5dce571c | ||
|
|
31e22710f0 | ||
|
|
d1da22b665 | ||
|
|
b423329d97 | ||
|
|
223598b037 | ||
|
|
7c59a81e5c | ||
|
|
71c00579e4 | ||
|
|
d749731413 | ||
|
|
ed006931a4 | ||
|
|
16c5c4c4e8 | ||
|
|
f3028a00b3 | ||
|
|
f8b66aaf50 | ||
|
|
0c82b9b700 | ||
|
|
b26709b0af | ||
|
|
74af2a0700 | ||
|
|
c0b01807a4 | ||
|
|
74d01c6573 | ||
|
|
d4d594bdb5 | ||
|
|
b9c7de39ed | ||
|
|
fb73a6a66b |
+2
-2
@@ -1,3 +1,3 @@
|
||||
**/*
|
||||
!**/bin/**
|
||||
!**/obj/**
|
||||
!tests/Umbraco.Tests.Integration/bin/**
|
||||
!tests/Umbraco.Tests.UnitTests/bin/**
|
||||
|
||||
+9
-5
@@ -40,18 +40,22 @@ You can also run the tasks manually on the command line:
|
||||
|
||||
```
|
||||
cd src\Umbraco.Web.UI.Client
|
||||
npm install
|
||||
npm i
|
||||
npm run dev
|
||||
```
|
||||
|
||||
or
|
||||
If you just want to build the UI Client to `Umbraco.Web.UI` then instead of running `dev`, you can do: `npm run build`.
|
||||
|
||||
The login screen is a different frontend build, for that one you can run it as follows:
|
||||
|
||||
```
|
||||
cd src\Umbraco.Web.UI.Client
|
||||
npm install
|
||||
gulp dev
|
||||
cd src\Umbraco.Web.UI.Login
|
||||
npm i
|
||||
npm run dev
|
||||
```
|
||||
|
||||
If you just want to build the Login screen to `Umbraco.Web.UI` then instead of running `dev`, you can do: `npm run build`.
|
||||
|
||||
**The initial Gulp build might take a long time - don't worry, this will be faster on subsequent runs.**
|
||||
|
||||
You might run into [Gulp quirks](#gulp-quirks).
|
||||
|
||||
+23
-235
@@ -4,268 +4,56 @@
|
||||
|
||||
These contribution guidelines are mostly just that - guidelines, not rules. This is what we've found to work best over the years, but if you choose to ignore them, we still love you! 💖 Use your best judgement, and feel free to propose changes to this document in a pull request.
|
||||
|
||||
## Coding not your thing? Or want more ways to contribute?
|
||||
## Getting Started
|
||||
We have a guide on [what to consider before you start](contributing-before-you-start.md) and more detailed guides at the end of this article.
|
||||
|
||||
This document covers contributing to the codebase of the CMS but [the community site has plenty of inspiration for other ways to get involved.][get involved]
|
||||
|
||||
If you don't feel you'd like to make code changes here, you can visit our [documentation repository][docs repo] and use your experience to contribute to making the docs we have, even better.
|
||||
|
||||
We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Collaborators and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Before you start](#before-you-start)
|
||||
* [Code of Conduct](#code-of-conduct)
|
||||
* [What can I contribute?](#what-can-i-contribute)
|
||||
+ [Making larger changes](#making-larger-changes)
|
||||
+ [Pull request or package?](#pull-request-or-package)
|
||||
+ [Unwanted changes](#unwanted-changes)
|
||||
+ [Ownership and copyright](#ownership-and-copyright)
|
||||
- [Finding your first issue: Up for grabs](#finding-your-first-issue-up-for-grabs)
|
||||
- [Making your changes](#making-your-changes)
|
||||
+ [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
|
||||
+ [Style guide](#style-guide)
|
||||
+ [Questions?](#questions)
|
||||
- [Creating a pull request](#creating-a-pull-request)
|
||||
- [The review process](#the-review-process)
|
||||
* [Dealing with requested changes](#dealing-with-requested-changes)
|
||||
+ [No longer available?](#no-longer-available)
|
||||
* [The Core Collaborators team](#the-core-collaborators-team)
|
||||
|
||||
## Before you start
|
||||
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
This project and everyone participating in it, is governed by the [our Code of Conduct][code of conduct].
|
||||
|
||||
### What can I contribute?
|
||||
|
||||
We categorise pull requests (PRs) into two categories:
|
||||
|
||||
| PR type | Definition |
|
||||
| --------- | ------------------------------------------------------------ |
|
||||
| Small PRs | Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files. |
|
||||
| Large PRs | New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.). |
|
||||
|
||||
We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process][review process].
|
||||
|
||||
We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations.
|
||||
|
||||
Not all changes are wanted, so on occasion we might close a PR without merging it but if we do, we will give you feedback why we can't accept your changes. **So make sure to [talk to us before making large changes][making larger changes]**, so we can ensure that you don't put all your hard work into something we would not be able to merge.
|
||||
|
||||
#### Making larger changes
|
||||
|
||||
[making larger changes]: #making-larger-changes
|
||||
|
||||
Please make sure to describe your larger ideas in an [issue (bugs)][issues] or [discussion (new features)][discussions], it helps to put in mock up screenshots or videos. If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented.
|
||||
|
||||
If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the small PRs process above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
|
||||
|
||||
#### Pull request or package?
|
||||
|
||||
[pr or package]: #pull-request-or-package
|
||||
|
||||
If you're unsure about whether your changes belong in the core Umbraco CMS or if you should turn your idea into a package instead, make sure to [talk to us][making larger changes].
|
||||
|
||||
If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS.
|
||||
|
||||
#### Unwanted changes
|
||||
While most changes are welcome, there are certain types of changes that are discouraged and might get your pull request refused.
|
||||
Of course this will depend heavily on the specific change, but please take the following examples in mind.
|
||||
|
||||
- **Breaking changes (code and/or behavioral) 💥** - sometimes it can be a bit hard to know if a change is breaking or not. Fortunately, if it relates to code, the build will fail and warn you.
|
||||
- **Large refactors 🤯** - the larger the refactor, the larger the probability of introducing new bugs/issues.
|
||||
- **Changes to obsolete code and/or property editors ✍️**
|
||||
- **Adding new config options 🦾** - while having more flexibility is (most of the times) better, having too many options can also become overwhelming/confusing, especially if there are other (good/simple) ways to achieve it.
|
||||
- **Whitespace changes 🫥** - while some of our files might not follow the formatting/whitespace rules (mostly old ones), changing several of them in one go would cause major merge conflicts with open pull requests or other work in progress. Do feel free to fix these when you are working on another issue/feature and end up "touching" those files!
|
||||
- **Adding new extension/helper methods ✋** - keep in mind that more code also means more to maintain, so if a helper is only meaningful for a few, it might not be worth adding it to the core.
|
||||
|
||||
While these are only a few examples, it is important to ask yourself these questions before making a pull request:
|
||||
|
||||
- How many will benefit from this change?
|
||||
- Are there other ways to achieve this? And if so, how do they compare?
|
||||
- How maintainable is the change?
|
||||
- What would be the effort to test it properly?
|
||||
- Do the benefits outweigh the risks?
|
||||
|
||||
#### Ownership and copyright
|
||||
|
||||
It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code.
|
||||
|
||||
Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS.
|
||||
|
||||
If you're not sure, leave a note on your contribution and we will be happy to guide you.
|
||||
|
||||
When your contribution has been accepted, it will be [MIT licensed][MIT license] from that time onwards.
|
||||
|
||||
## Finding your first issue: Up for grabs
|
||||
|
||||
Umbraco HQ will regularly mark newly created issues on the issue tracker with [the `community/up-for-grabs` tag][up for grabs issues]. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
|
||||
|
||||
If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
|
||||
|
||||
## Making your changes
|
||||
|
||||
Great question! The short version goes like this:
|
||||
The following steps are a quick-start guide:
|
||||
|
||||
1. **Fork**
|
||||
|
||||
Create a fork of [`Umbraco-CMS` on GitHub][Umbraco CMS repo]
|
||||
Create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS)
|
||||
|
||||

|
||||
|
||||
1. **Clone**
|
||||
2. **Clone**
|
||||
|
||||
When GitHub has created your fork, you can clone it in your favorite Git tool
|
||||
When GitHub has created your fork, you can clone it in your favorite Git tool or on the command line with `git clone https://github.com/[YourUsername]/Umbraco-CMS`.
|
||||
|
||||

|
||||
|
||||
1. **Switch to the correct branch**
|
||||
3. **Switch to the correct branch**
|
||||
|
||||
Switch to the `contrib` branch
|
||||
|
||||
1. **Build**
|
||||
4. **Build**
|
||||
|
||||
Build your fork of Umbraco locally as described in the build documentation: you can [debug with Visual Studio Code][build - debugging with code] or [with Visual Studio][build - debugging with vs].
|
||||
Build your fork of Umbraco locally [as described in the build documentation](BUILD.md), you can build with any IDE that supports dotnet or the command line.
|
||||
|
||||
1. **Branch**
|
||||
5. **Branch**
|
||||
|
||||
Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first.
|
||||
Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp/12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first.
|
||||
|
||||
1. **Change**
|
||||
6. **Change**
|
||||
|
||||
Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback][questions].
|
||||
Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](contributing-first-issue.md#questions).
|
||||
|
||||
1. **Commit and push**
|
||||
7. **Commit and push**
|
||||
|
||||
Done? Yay! 🎉
|
||||
|
||||
Remember to commit to your new `temp` branch, and don't commit to `contrib`. Then you can push the changes up to your fork on GitHub.
|
||||
|
||||
#### Keeping your Umbraco fork in sync with the main repository
|
||||
[sync fork]: #keeping-your-umbraco-fork-in-sync-with-the-main-repository
|
||||
8. **Create pull request**
|
||||
|
||||
Once you've already got a fork and cloned your fork locally, you can skip steps 1 and 2 going forward. Just remember to keep your fork up to date before making further changes.
|
||||
On GitHub, in your forked repository (`https://github.com/[YourUsername]/Umbraco-CMS`) you will see a banner saying that you pushed a new branch and a button to make a pull request. Tap the button and follow the instuctions.
|
||||
|
||||
To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once:
|
||||
Want to read further? [Creating a pull request and what happens next](contributing-creating-a-pr.md).
|
||||
|
||||
```
|
||||
git remote add upstream https://github.com/umbraco/Umbraco-CMS.git
|
||||
```
|
||||
## Further contribution guides
|
||||
|
||||
Then when you want to get the changes from the main repository:
|
||||
- [Before you start](contributing-before-you-start.md)
|
||||
- [Finding your first issue: Up for grabs](contributing-before-you-start.md)
|
||||
- [Contributing to the new backoffice](https://docs.umbraco.com/umbraco-backoffice/)
|
||||
- [Unwanted changes](contributing-unwanted-changes.md)
|
||||
- [Other ways to contribute](contributing-other-ways-to-contribute.md)
|
||||
|
||||
```
|
||||
git fetch upstream
|
||||
git rebase upstream/contrib
|
||||
```
|
||||
|
||||
In this command we're syncing with the `contrib` branch, but you can of course choose another one if needed.
|
||||
|
||||
[More information on how this works can be found on the thoughtbot blog.][sync fork ext]
|
||||
|
||||
#### Style guide
|
||||
|
||||
To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
|
||||
|
||||
That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
|
||||
|
||||
#### Questions?
|
||||
[questions]: #questions
|
||||
|
||||
You can get in touch with [the core contributors team][core collabs] in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
|
||||
|
||||
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
|
||||
- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs].
|
||||
- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
|
||||
|
||||
## Creating a pull request
|
||||
|
||||
Exciting! You're ready to show us your changes.
|
||||
|
||||
We recommend you to [sync with our repository][sync fork] before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
|
||||
|
||||
GitHub will have picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
|
||||

|
||||
|
||||
We like to use [git flow][git flow] as much as possible, but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to `contrib`. This is the branch you should be targeting.
|
||||
|
||||
Please note: we are no longer accepting features for v8 and below but will continue to merge security fixes as and when they arise.
|
||||
|
||||
## The review process
|
||||
[review process]: #the-review-process
|
||||
|
||||
You've sent us your first contribution - congratulations! Now what?
|
||||
|
||||
The [Core Collaborators team][Core collabs] can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
|
||||
|
||||
You will get an initial automated reply from our [Friendly Umbraco Robot, Umbrabot][Umbrabot], to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. You can take this opportunity to double check everything is in order based off the handy checklist Umbrabot provides.
|
||||
|
||||
You will get feedback as soon as the [Core Collaborators team][Core collabs] can after opening the PR. You’ll most likely get feedback within a couple of weeks. Then there are a few possible outcomes:
|
||||
|
||||
- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
|
||||
- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
|
||||
- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
|
||||
- Your proposed change is awesome but... not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!). See [making larger changes][making larger changes] and [pull request or package?][pr or package]
|
||||
|
||||
### Dealing with requested changes
|
||||
|
||||
If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
|
||||
|
||||
#### No longer available?
|
||||
|
||||
We understand you have other things to do and can't just drop everything to help us out.
|
||||
|
||||
So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.
|
||||
|
||||
If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.
|
||||
|
||||
There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
|
||||
|
||||
### The Core Collaborators team
|
||||
[Core collabs]: #the-core-collaborators-team
|
||||
|
||||
The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan][Sebastiaan], who gets assistance from the following community members who have committed to volunteering their free time:
|
||||
|
||||
- [Busra Sengul][Busra Sengul]
|
||||
- [Emma Garland][Emma Garland]
|
||||
- [George Bidder][George Bidder]
|
||||
- [Jason Elkin][Jason Elkin]
|
||||
- [Laura Neto][Laura Neto]
|
||||
- [Michael Latouche][Michael Latouche]
|
||||
- [Sebastiaan][Sebastiaan]
|
||||
|
||||
|
||||
These wonderful people aim to provide you with a reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. HQ will have final sign-off and will check the work again before it is merged.
|
||||
|
||||
<!-- Reference links for easy updating -->
|
||||
|
||||
<!-- Local -->
|
||||
|
||||
[MIT license]: ../LICENSE.md "Umbraco's license declaration"
|
||||
[build - debugging with vs]: BUILD.md#debugging-with-visual-studio "Details on building and debugging Umbraco with Visual Studio"
|
||||
[build - debugging with code]: BUILD.md#debugging-with-vs-code "Details on building and debugging Umbraco with Visual Studio Code"
|
||||
|
||||
<!-- External -->
|
||||
|
||||
[Busra Sengul]: https://github.com/busrasengul "Busra's GitHub profile"
|
||||
[Emma Garland]: https://github.com/emmagarland "Emma's GitHub profile"
|
||||
[George Bidder]: https://github.com/georgebid "George's GitHub profile"
|
||||
[Jason Elkin]: https://github.com/jasonelkin "Jason's GitHub profile"
|
||||
[Kyle Eck]: https://github.com/teckspeed "Kyle's GitHub profile"
|
||||
[Laura Neto]: https://github.com/lauraneto "Laura's GitHub profile"
|
||||
[Michael Latouche]: https://github.com/mikecp "Michael's GitHub profile"
|
||||
[Sebastiaan]: https://github.com/nul800sebastiaan "Sebastiaan's GitHub profile"
|
||||
[ Umbrabot ]: https://github.com/umbrabot
|
||||
[git flow]: https://jeffkreeftmeijer.com/git-flow/ "An explanation of git flow"
|
||||
[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated "Details on keeping a git fork updated"
|
||||
[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Github's blog post providing details on draft pull requests"
|
||||
[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/
|
||||
[get involved]: https://community.umbraco.com/get-involved/
|
||||
[docs repo]: https://github.com/umbraco/UmbracoDocs
|
||||
[code of conduct]: https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md
|
||||
[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs
|
||||
[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS
|
||||
[issues]: https://github.com/umbraco/Umbraco-CMS/issues
|
||||
[discussions]: https://github.com/umbraco/Umbraco-CMS/discussions
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
## Before you start
|
||||
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
This project and everyone participating in it, is governed by the [our Code of Conduct][code of conduct].
|
||||
|
||||
### What can I contribute?
|
||||
|
||||
We categorise pull requests (PRs) into two categories:
|
||||
|
||||
| PR type | Definition |
|
||||
| --------- | ------------------------------------------------------------ |
|
||||
| Small PRs | Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files. |
|
||||
| Large PRs | New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.). |
|
||||
|
||||
We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process][review process].
|
||||
|
||||
We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations.
|
||||
|
||||
Not all changes are wanted, so on occasion we might close a PR without merging it but if we do, we will give you feedback why we can't accept your changes. **So make sure to [talk to us before making large changes][making larger changes]**, so we can ensure that you don't put all your hard work into something we would not be able to merge.
|
||||
|
||||
#### Making larger changes
|
||||
|
||||
[making larger changes]: #making-larger-changes
|
||||
|
||||
Please make sure to describe your larger ideas in an [issue (bugs)][issues] or [discussion (new features)][discussions], it helps to put in mock up screenshots or videos. If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented.
|
||||
|
||||
If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the small PRs process above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
|
||||
|
||||
#### Pull request or package?
|
||||
|
||||
[pr or package]: #pull-request-or-package
|
||||
|
||||
If you're unsure about whether your changes belong in the core Umbraco CMS or if you should turn your idea into a package instead, make sure to [talk to us][making larger changes].
|
||||
|
||||
If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS.
|
||||
|
||||
#### Ownership and copyright
|
||||
|
||||
It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code.
|
||||
|
||||
Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS.
|
||||
|
||||
If you're not sure, leave a note on your contribution and we will be happy to guide you.
|
||||
|
||||
When your contribution has been accepted, it will be [MIT licensed][MIT license] from that time onwards.
|
||||
|
||||
|
||||
[MIT license]: ../LICENSE.md "Umbraco's license declaration"
|
||||
|
||||
|
||||
[issues]: https://github.com/umbraco/Umbraco-CMS/issues
|
||||
[discussions]: https://github.com/umbraco/Umbraco-CMS/discussions
|
||||
@@ -0,0 +1,28 @@
|
||||
### The Core Collaborators team
|
||||
[Core collabs]: #the-core-collaborators-team
|
||||
|
||||
The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan][Sebastiaan], who gets assistance from the following community members who have committed to volunteering their free time:
|
||||
|
||||
- [Busra Sengul][Busra Sengul]
|
||||
- [Emma Garland][Emma Garland]
|
||||
- [George Bidder][George Bidder]
|
||||
- [Jason Elkin][Jason Elkin]
|
||||
- [Laura Neto][Laura Neto]
|
||||
- [Kyle Eck][Kyle Eck]
|
||||
- [Michael Latouche][Michael Latouche]
|
||||
- [Sebastiaan][Sebastiaan]
|
||||
|
||||
|
||||
These wonderful people aim to provide you with a reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. HQ will have final sign-off and will check the work again before it is merged.
|
||||
|
||||
|
||||
<!-- External -->
|
||||
|
||||
[Busra Sengul]: https://github.com/busrasengul "Busra's GitHub profile"
|
||||
[Emma Garland]: https://github.com/emmagarland "Emma's GitHub profile"
|
||||
[George Bidder]: https://github.com/georgebid "George's GitHub profile"
|
||||
[Jason Elkin]: https://github.com/jasonelkin "Jason's GitHub profile"
|
||||
[Kyle Eck]: https://github.com/teckspeed "Kyle's GitHub profile"
|
||||
[Laura Neto]: https://github.com/lauraneto "Laura's GitHub profile"
|
||||
[Michael Latouche]: https://github.com/mikecp "Michael's GitHub profile"
|
||||
[Sebastiaan]: https://github.com/nul800sebastiaan "Sebastiaan's GitHub profile"
|
||||
@@ -0,0 +1,51 @@
|
||||
## Creating a pull request
|
||||
|
||||
Exciting! You're ready to show us your changes.
|
||||
|
||||
We recommend you to [sync with our repository][sync fork] before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
|
||||
|
||||
GitHub will have picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
|
||||

|
||||
|
||||
We like to use [git flow][git flow] as much as possible, but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to `contrib`. This is the branch you should be targeting.
|
||||
|
||||
Please note: we are no longer accepting features for v8 and below but will continue to merge security fixes as and when they arise.
|
||||
|
||||
## The review process
|
||||
[review process]: #the-review-process
|
||||
|
||||
You've sent us your contribution - congratulations! Now what?
|
||||
|
||||
The [Core Collaborators team][Core collabs] can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
|
||||
|
||||
You will get an initial automated reply from our [Friendly Umbraco Robot, Umbrabot][Umbrabot], to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. You can take this opportunity to double check everything is in order based off the handy checklist Umbrabot provides.
|
||||
|
||||
You will get feedback as soon as the [Core Collaborators team][Core collabs] can after opening the PR. You’ll most likely get feedback within a couple of weeks. Then there are a few possible outcomes:
|
||||
|
||||
- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
|
||||
- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
|
||||
- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
|
||||
- Your proposed change is awesome but... not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!). See [making larger changes][making larger changes] and [pull request or package?][pr or package]
|
||||
|
||||
### Dealing with requested changes
|
||||
|
||||
If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
|
||||
|
||||
#### No longer available?
|
||||
|
||||
We understand you have other things to do and can't just drop everything to help us out.
|
||||
|
||||
So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.
|
||||
|
||||
If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.
|
||||
|
||||
There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
|
||||
|
||||
|
||||
[ Umbrabot ]: https://github.com/umbrabot
|
||||
[git flow]: https://jeffkreeftmeijer.com/git-flow/ "An explanation of git flow"
|
||||
|
||||
|
||||
[making larger changes]: contributing-before-you-start.md#making-large-changes
|
||||
[pr or package]: contributing-before-you-start.md#pull-request-or-package
|
||||
[Core collabs]: contributing-core-collabs-team.md
|
||||
@@ -0,0 +1,93 @@
|
||||
## Finding your first issue: Up for grabs
|
||||
|
||||
Umbraco HQ will regularly mark newly created issues on the issue tracker with [the `community/up-for-grabs` tag][up for grabs issues]. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
|
||||
|
||||
If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
|
||||
|
||||
## Making your changes
|
||||
|
||||
Great question! The short version goes like this:
|
||||
|
||||
1. **Fork**
|
||||
|
||||
Create a fork of [`Umbraco-CMS` on GitHub][Umbraco CMS repo]
|
||||
|
||||

|
||||
|
||||
1. **Clone**
|
||||
|
||||
When GitHub has created your fork, you can clone it in your favorite Git tool
|
||||
|
||||

|
||||
|
||||
1. **Switch to the correct branch**
|
||||
|
||||
Switch to the `contrib` branch
|
||||
|
||||
1. **Build**
|
||||
|
||||
Build your fork of Umbraco locally as described in the build documentation: you can [debug with Visual Studio Code][build - debugging with code] or [with Visual Studio][build - debugging with vs].
|
||||
|
||||
1. **Branch**
|
||||
|
||||
Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first.
|
||||
|
||||
1. **Change**
|
||||
|
||||
Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback][questions].
|
||||
|
||||
1. **Commit and push**
|
||||
|
||||
Done? Yay! 🎉
|
||||
|
||||
Remember to commit to your new `temp` branch, and don't commit to `contrib`. Then you can push the changes up to your fork on GitHub.
|
||||
|
||||
#### Keeping your Umbraco fork in sync with the main repository
|
||||
[sync fork]: #keeping-your-umbraco-fork-in-sync-with-the-main-repository
|
||||
|
||||
Once you've already got a fork and cloned your fork locally, you can skip steps 1 and 2 going forward. Just remember to keep your fork up to date before making further changes.
|
||||
|
||||
To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once:
|
||||
|
||||
```
|
||||
git remote add upstream https://github.com/umbraco/Umbraco-CMS.git
|
||||
```
|
||||
|
||||
Then when you want to get the changes from the main repository:
|
||||
|
||||
```
|
||||
git fetch upstream
|
||||
git rebase upstream/contrib
|
||||
```
|
||||
|
||||
In this command we're syncing with the `contrib` branch, but you can of course choose another one if needed.
|
||||
|
||||
[More information on how this works can be found on the thoughtbot blog.][sync fork ext]
|
||||
|
||||
#### Style guide
|
||||
|
||||
To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
|
||||
|
||||
That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
|
||||
|
||||
#### Questions?
|
||||
[questions]: #questions
|
||||
|
||||
You can get in touch with [the core contributors team][core collabs] in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
|
||||
|
||||
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
|
||||
- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs].
|
||||
- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
|
||||
|
||||
|
||||
<!-- Local -->
|
||||
|
||||
[build - debugging with vs]: BUILD.md#debugging-with-visual-studio "Details on building and debugging Umbraco with Visual Studio"
|
||||
[build - debugging with code]: BUILD.md#debugging-with-vs-code "Details on building and debugging Umbraco with Visual Studio Code"
|
||||
|
||||
|
||||
[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated "Details on keeping a git fork updated"
|
||||
[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Github's blog post providing details on draft pull requests"
|
||||
[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/
|
||||
[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS
|
||||
[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs
|
||||
@@ -0,0 +1,10 @@
|
||||
## Coding not your thing? Or want more ways to contribute?
|
||||
|
||||
This document covers contributing to the codebase of the CMS but [the community site has plenty of inspiration for other ways to get involved.][get involved]
|
||||
|
||||
If you don't feel you'd like to make code changes here, you can visit our [documentation repository][docs repo] and use your experience to contribute to making the docs we have, even better.
|
||||
|
||||
We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Collaborators and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
|
||||
|
||||
[get involved]: https://community.umbraco.com/get-involved/
|
||||
[docs repo]: https://github.com/umbraco/UmbracoDocs
|
||||
@@ -0,0 +1,18 @@
|
||||
## Unwanted changes
|
||||
While most changes are welcome, there are certain types of changes that are discouraged and might get your pull request refused.
|
||||
Of course this will depend heavily on the specific change, but please take the following examples in mind.
|
||||
|
||||
- **Breaking changes (code and/or behavioral) 💥** - sometimes it can be a bit hard to know if a change is breaking or not. Fortunately, if it relates to code, the build will fail and warn you.
|
||||
- **Large refactors 🤯** - the larger the refactor, the larger the probability of introducing new bugs/issues.
|
||||
- **Changes to obsolete code and/or property editors ✍️**
|
||||
- **Adding new config options 🦾** - while having more flexibility is (most of the times) better, having too many options can also become overwhelming/confusing, especially if there are other (good/simple) ways to achieve it.
|
||||
- **Whitespace changes 🫥** - while some of our files might not follow the formatting/whitespace rules (mostly old ones), changing several of them in one go would cause major merge conflicts with open pull requests or other work in progress. Do feel free to fix these when you are working on another issue/feature and end up "touching" those files!
|
||||
- **Adding new extension/helper methods ✋** - keep in mind that more code also means more to maintain, so if a helper is only meaningful for a few, it might not be worth adding it to the core.
|
||||
|
||||
While these are only a few examples, it is important to ask yourself these questions before making a pull request:
|
||||
|
||||
- How many will benefit from this change?
|
||||
- Are there other ways to achieve this? And if so, how do they compare?
|
||||
- How maintainable is the change?
|
||||
- What would be the effort to test it properly?
|
||||
- Do the benefits outweigh the risks?
|
||||
@@ -29,13 +29,13 @@
|
||||
<!-- Package Validation -->
|
||||
<PropertyGroup>
|
||||
<GenerateCompatibilitySuppressionFile>false</GenerateCompatibilitySuppressionFile>
|
||||
<EnablePackageValidation>false</EnablePackageValidation>
|
||||
<EnablePackageValidation>true</EnablePackageValidation>
|
||||
<PackageValidationBaselineVersion>13.0.0</PackageValidationBaselineVersion>
|
||||
<EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
|
||||
<EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Calculate version only once for the whole repository -->
|
||||
<!-- Calculate version only once for the whole repository -->
|
||||
<PropertyGroup>
|
||||
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
+43
-32
@@ -5,34 +5,35 @@
|
||||
</PropertyGroup>
|
||||
<!-- Global packages (private, build-time packages for all projects) -->
|
||||
<ItemGroup>
|
||||
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" />
|
||||
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.6.139" />
|
||||
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507" />
|
||||
<GlobalPackageReference Include="Umbraco.Code" Version="2.1.0" />
|
||||
<GlobalPackageReference Include="Umbraco.Code" Version="2.2.0" />
|
||||
<GlobalPackageReference Include="Umbraco.GitVersioning.Extensions" Version="0.2.0" />
|
||||
</ItemGroup>
|
||||
<!-- Microsoft packages -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
<!-- Umbraco packages -->
|
||||
<ItemGroup>
|
||||
@@ -44,13 +45,13 @@
|
||||
<PackageVersion Include="Asp.Versioning.Mvc" Version="7.1.1" />
|
||||
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="7.1.0" />
|
||||
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
|
||||
<PackageVersion Include="Examine" Version="3.1.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.1.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.54" />
|
||||
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.6" />
|
||||
<PackageVersion Include="MailKit" Version="4.3.0" />
|
||||
<PackageVersion Include="Examine" Version="3.7.1" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.7.1" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.71" />
|
||||
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
|
||||
<PackageVersion Include="MailKit" Version="4.8.0" />
|
||||
<PackageVersion Include="Markdown" Version="2.2.1" />
|
||||
<PackageVersion Include="MessagePack" Version="2.5.140" />
|
||||
<PackageVersion Include="MessagePack" Version="2.5.192" />
|
||||
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.3.8" />
|
||||
<PackageVersion Include="MiniProfiler.Shared" Version="4.3.8" />
|
||||
<PackageVersion Include="ncrontab" Version="3.3.3" />
|
||||
@@ -61,32 +62,42 @@
|
||||
<PackageVersion Include="OpenIddict.AspNetCore" Version="4.10.1" />
|
||||
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="4.10.1" />
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Process" Version="2.0.2" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageVersion Include="Serilog.Expressions" Version="4.0.0" />
|
||||
<PackageVersion Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact" Version="2.0.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.4" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.0" />
|
||||
<PackageVersion Include="Smidge.InMemory" Version="4.3.0" />
|
||||
<PackageVersion Include="Smidge.Nuglify" Version="4.3.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.3" />
|
||||
<PackageVersion Include="Smidge.InMemory" Version="4.4.0" />
|
||||
<PackageVersion Include="Smidge.Nuglify" Version="4.5.1" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.9.0" />
|
||||
</ItemGroup>
|
||||
<!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) -->
|
||||
<ItemGroup>
|
||||
<!-- Both Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer bring in a vulnerable version of Azure.Identity -->
|
||||
<PackageVersion Include="Azure.Identity" Version="1.10.4" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.13.1" />
|
||||
<!-- Dazinator.Extensions.FileProviders brings in a vulnerable version of System.Net.Http -->
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<!-- Examine brings in a vulnerable version of System.Security.Cryptography.Xml -->
|
||||
<PackageVersion Include="System.Security.Cryptography.Xml" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Security.Cryptography.Xml" Version="8.0.2" />
|
||||
<!-- Both Dazinator.Extensions.FileProviders and MiniProfiler.AspNetCore.Mvc bring in a vulnerable version of System.Text.RegularExpressions -->
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.7.1" />
|
||||
<!-- Examine.Lucene bring in a vulnerable version of Lucene.Net.Replicator -->
|
||||
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
|
||||
<!-- Both OpenIddict.AspNetCore, Microsoft.EntityFrameworkCore.* bring in a vulnerable version of Microsoft.Extensions.Caching.Memory -->
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||
<!-- Both Azure.Identity, Microsoft.EntityFrameworkCore.SqlServer,NPoco.SqlServer, and more bring in a vulnerable version of System.Text.Json -->
|
||||
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
|
||||
<!-- Both Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer bring in a vulnerable version of Microsoft.Data.SqlClient -->
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
+399
-181
@@ -5,6 +5,10 @@ parameters:
|
||||
displayName: Run SQL Server Integration Tests
|
||||
type: boolean
|
||||
default: false
|
||||
- name: sqlServerAcceptanceTests
|
||||
displayName: Run SQL Server Acceptance Tests
|
||||
type: boolean
|
||||
default: false
|
||||
- name: myGetDeploy
|
||||
displayName: Deploy to MyGet
|
||||
type: boolean
|
||||
@@ -34,11 +38,11 @@ parameters:
|
||||
type: string
|
||||
default: ' '
|
||||
- name: nonWindowsIntegrationNonReleaseTestFilter
|
||||
displayName: TestFilter used for non-release type builds on non windows agents
|
||||
displayName: TestFilter used for non-release type builds on non Windows agents
|
||||
type: string
|
||||
default: '--filter TestCategory!=LongRunning&TestCategory!=NonCritical'
|
||||
- name: nonWindowsIntegrationReleaseTestFilter
|
||||
displayName: TestFilter used for release type builds on non windows agents
|
||||
displayName: TestFilter used for release type builds on non Windows agents
|
||||
type: string
|
||||
default: ' '
|
||||
- name: isNightly
|
||||
@@ -48,8 +52,6 @@ parameters:
|
||||
|
||||
variables:
|
||||
nodeVersion: 20
|
||||
dotnetVersion: 8.x
|
||||
dotnetIncludePreviewVersions: true
|
||||
solution: umbraco.sln
|
||||
buildConfiguration: Release
|
||||
UMBRACO__CMS__GLOBAL__ID: 00000000-0000-0000-0000-000000000042
|
||||
@@ -69,8 +71,12 @@ stages:
|
||||
- job: A
|
||||
displayName: Build Umbraco CMS
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
vmImage: 'windows-latest'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 500
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
retryCountOnTaskFailure: 3
|
||||
@@ -108,11 +114,9 @@ stages:
|
||||
verbose: false
|
||||
customCommand: run build
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET $(dotnetVersion)
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
performMultiLevelLookup: true
|
||||
includePreviewVersions: $(dotnetIncludePreviewVersions)
|
||||
useGlobalJson: true
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet restore
|
||||
inputs:
|
||||
@@ -150,10 +154,9 @@ stages:
|
||||
vmImage: 'windows-latest'
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET $(dotnetVersion)
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
includePreviewVersions: $(dotnetIncludePreviewVersions)
|
||||
useGlobalJson: true
|
||||
- task: PowerShell@2
|
||||
displayName: Install DocFX
|
||||
inputs:
|
||||
@@ -199,6 +202,11 @@ stages:
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 1
|
||||
fetchFilter: tree:0
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js 10.15.x
|
||||
retryCountOnTaskFailure: 3
|
||||
@@ -250,22 +258,25 @@ stages:
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 1
|
||||
fetchFilter: tree:0
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download build artifacts
|
||||
inputs:
|
||||
artifact: build_output
|
||||
path: $(Build.SourcesDirectory)
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET $(dotnetVersion)
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
performMultiLevelLookup: true
|
||||
includePreviewVersions: $(dotnetIncludePreviewVersions)
|
||||
useGlobalJson: true
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.UnitTests.csproj'
|
||||
projects: 'tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj'
|
||||
arguments: '--configuration $(buildConfiguration) --no-build'
|
||||
testRunTitle: Unit Tests - $(Agent.OS)
|
||||
|
||||
@@ -288,258 +299,461 @@ stages:
|
||||
vmImage: 'macOS-latest'
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
variables:
|
||||
Tests__Database__DatabaseType: 'Sqlite'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 1
|
||||
fetchFilter: tree:0
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download build artifacts
|
||||
inputs:
|
||||
artifact: build_output
|
||||
path: $(Build.SourcesDirectory)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET $(dotnetVersion)
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
performMultiLevelLookup: true
|
||||
includePreviewVersions: $(dotnetIncludePreviewVersions)
|
||||
useGlobalJson: true
|
||||
|
||||
# Test
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test Windows
|
||||
condition: eq(variables['Agent.OS'],'Windows_NT')
|
||||
displayName: Run dotnet test
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.Integration.csproj'
|
||||
projects: 'tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj'
|
||||
testRunTitle: Integration Tests SQLite - $(Agent.OS)
|
||||
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
|
||||
${{ if and(eq(variables['Agent.OS'],'Windows_NT'), or(variables.releaseTestFilter, parameters.forceReleaseTestFilter)) }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
|
||||
${{ else }}:
|
||||
arguments: '--configuration $(buildConfiguration) ${{parameters.integrationNonReleaseTestFilter}}'
|
||||
env:
|
||||
Tests__Database__DatabaseType: 'Sqlite'
|
||||
Umbraco__CMS__Global__MainDomLock: 'FileSystemMainDomLock'
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test Non Windows
|
||||
condition: ne(variables['Agent.OS'],'Windows_NT')
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.Integration.csproj'
|
||||
testRunTitle: Integration Tests SQLite - $(Agent.OS)
|
||||
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
|
||||
${{ elseif eq(variables['Agent.OS'],'Windows_NT') }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}'
|
||||
${{ elseif or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
|
||||
${{ else }}:
|
||||
arguments: '--configuration $(buildConfiguration) ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
|
||||
env:
|
||||
Tests__Database__DatabaseType: 'Sqlite'
|
||||
Umbraco__CMS__Global__MainDomLock: 'FileSystemMainDomLock'
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
|
||||
|
||||
# Integration Tests (SQL Server)
|
||||
- job:
|
||||
timeoutInMinutes: 120
|
||||
condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerIntegrationTests}})
|
||||
# We are currently encountering issues when running SQL Server Linux tests Microsoft.Data.SqlClient.SqlException (0x80131904)
|
||||
# condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerIntegrationTests}})
|
||||
condition: eq(${{parameters.sqlServerIntegrationTests}}, True)
|
||||
displayName: Integration Tests (SQL Server)
|
||||
strategy:
|
||||
matrix:
|
||||
Windows:
|
||||
vmImage: 'windows-latest'
|
||||
testDb: LocalDb
|
||||
connectionString: N/A
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
Linux:
|
||||
vmImage: 'ubuntu-latest'
|
||||
testDb: SqlServer
|
||||
connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=true'
|
||||
SA_PASSWORD: UmbracoIntegration123!
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: 'Server=(local);User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True'
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
variables:
|
||||
SA_PASSWORD: UmbracoIntegration123!
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download build artifacts
|
||||
inputs:
|
||||
artifact: build_output
|
||||
path: $(Build.SourcesDirectory)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET $(dotnetVersion)
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
includePreviewVersions: $(dotnetIncludePreviewVersions)
|
||||
- powershell: sqllocaldb start mssqllocaldb
|
||||
displayName: Start localdb (Windows only)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e ACCEPT_EULA=Y -e SA_PASSWORD=$(SA_PASSWORD) -e MSSQL_PID=Developer mcr.microsoft.com/mssql/server:2019-latest
|
||||
displayName: Start SQL Server (Linux only)
|
||||
useGlobalJson: true
|
||||
|
||||
# Start SQL Server
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Test
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test Windows
|
||||
condition: eq(variables['Agent.OS'],'Windows_NT')
|
||||
displayName: Run dotnet test
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.Integration.csproj'
|
||||
projects: 'tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj'
|
||||
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
|
||||
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
|
||||
${{ if and(eq(variables['Agent.OS'],'Windows_NT'), or(variables.releaseTestFilter, parameters.forceReleaseTestFilter)) }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
|
||||
${{ else }}:
|
||||
${{ elseif eq(variables['Agent.OS'],'Windows_NT') }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}'
|
||||
env:
|
||||
Tests__Database__DatabaseType: $(testDb)
|
||||
Tests__Database__SQLServerMasterConnectionString: $(connectionString)
|
||||
Umbraco__CMS__Global__MainDomLock: 'SqlMainDomLock'
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test NonWindows
|
||||
condition: ne(variables['Agent.OS'],'Windows_NT')
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.Integration.csproj'
|
||||
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
|
||||
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
|
||||
${{ elseif or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
|
||||
${{ else }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
|
||||
env:
|
||||
Tests__Database__DatabaseType: $(testDb)
|
||||
Tests__Database__SQLServerMasterConnectionString: $(connectionString)
|
||||
Umbraco__CMS__Global__MainDomLock: 'SqlMainDomLock'
|
||||
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
- stage: E2E
|
||||
variables:
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
|
||||
displayName: E2E Tests
|
||||
dependsOn: Build
|
||||
variables:
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
|
||||
# Enable console logging in Release mode
|
||||
SERILOG__WRITETO__0__NAME: Async
|
||||
SERILOG__WRITETO__0__ARGS__CONFIGURE__0__NAME: Console
|
||||
# Set unattended install settings
|
||||
UMBRACO__CMS__UNATTENDED__INSTALLUNATTENDED: true
|
||||
UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERNAME: Playwright Test
|
||||
UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD: UmbracoAcceptance123!
|
||||
UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL: playwright@umbraco.com
|
||||
# Custom Umbraco settings
|
||||
UMBRACO__CMS__CONTENT__CONTENTVERSIONCLEANUPPOLICY__ENABLECLEANUP: false
|
||||
UMBRACO__CMS__GLOBAL__DISABLEELECTIONFORSINGLESERVER: true
|
||||
UMBRACO__CMS__GLOBAL__INSTALLMISSINGDATABASE: true
|
||||
UMBRACO__CMS__GLOBAL__ID: 00000000-0000-0000-0000-000000000042
|
||||
UMBRACO__CMS__GLOBAL__VERSIONCHECKPERIOD: 0
|
||||
UMBRACO__CMS__GLOBAL__USEHTTPS: true
|
||||
UMBRACO__CMS__HEALTHCHECKS__NOTIFICATION__ENABLED: false
|
||||
UMBRACO__CMS__KEEPALIVE__DISABLEKEEPALIVETASK: true
|
||||
UMBRACO__CMS__WEBROUTING__UMBRACOAPPLICATIONURL: https://localhost:44331/
|
||||
ASPNETCORE_URLS: https://localhost:44331
|
||||
jobs:
|
||||
# E2E Tests
|
||||
- job:
|
||||
displayName: E2E Tests
|
||||
timeoutInMinutes: 120
|
||||
displayName: E2E Tests (SQLite)
|
||||
variables:
|
||||
Umbraco__CMS__Unattended__UnattendedUserName: Playwright Test
|
||||
Umbraco__CMS__Unattended__UnattendedUserPassword: UmbracoAcceptance123!
|
||||
Umbraco__CMS__Unattended__UnattendedUserEmail: playwright@umbraco.com
|
||||
ASPNETCORE_URLS: https://localhost:8443
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: 'ubuntu-latest'
|
||||
dockerfile: umbraco-linux.docker
|
||||
dockerImageName: umbraco-linux
|
||||
Windows:
|
||||
vmImage: 'windows-latest'
|
||||
DOTNET_GENERATE_ASPNET_CERTIFICATE: true # Automatically generate HTTPS development certificate on Windows
|
||||
# Enable console logging in Release mode
|
||||
Serilog__WriteTo__0__Name: Async
|
||||
Serilog__WriteTo__0__Args__configure__0__Name: Console
|
||||
# Set unattended install settings
|
||||
Umbraco__CMS__Unattended__InstallUnattended: true
|
||||
Umbraco__CMS__Global__InstallMissingDatabase: true
|
||||
UmbracoDatabaseServer: (LocalDB)\MSSQLLocalDB
|
||||
UmbracoDatabaseName: AcceptanceTestDB
|
||||
ConnectionStrings__umbracoDbDSN: Server=$(UmbracoDatabaseServer);Database=$(UmbracoDatabaseName);Integrated Security=true;
|
||||
# Custom Umbraco settings
|
||||
Umbraco__CMS__Global__VersionCheckPeriod: 0
|
||||
Umbraco__CMS__Global__UseHttps: true
|
||||
Umbraco__CMS__HealthChecks__Notification__Enabled: false
|
||||
Umbraco__CMS__KeepAlive__DisableKeepAliveTask: true
|
||||
LinuxPart1Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testCommand: "npm run test -- --shard=1/3"
|
||||
LinuxPart2Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testCommand: "npm run test -- --shard=2/3"
|
||||
LinuxPart3Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testCommand: "npm run test -- --shard=3/3"
|
||||
WindowsPart1Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run test -- --shard=1/3"
|
||||
WindowsPart2Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run test -- --shard=2/3"
|
||||
WindowsPart3Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run test -- --shard=3/3"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download nupkg
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/misc/nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
retryCountOnTaskFailure: 3
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
- task: Cache@2
|
||||
displayName: Cache node_modules
|
||||
inputs:
|
||||
key: '"npm_e2e" | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
"npm_e2e" | "$(Agent.OS)"
|
||||
"npm_e2e"
|
||||
path: $(npm_config_cache)
|
||||
- pwsh: |
|
||||
New-Item -Path "." -Name ".env" -ItemType "file" -Value "UMBRACO_USER_LOGIN=$(Umbraco__CMS__Unattended__UnattendedUserEmail)
|
||||
UMBRACO_USER_PASSWORD=$(Umbraco__CMS__Unattended__UnattendedUserPassword)
|
||||
URL=$(ASPNETCORE_URLS)"
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/
|
||||
displayName: Run npm ci
|
||||
- pwsh: sqllocaldb start mssqllocaldb
|
||||
displayName: Start localdb (Windows only)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
- pwsh: Invoke-Sqlcmd -Query "CREATE DATABASE $env:UmbracoDatabaseName" -ServerInstance $env:UmbracoDatabaseServer
|
||||
displayName: Create database (Windows only)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET $(dotnetVersion)
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
performMultiLevelLookup: true
|
||||
includePreviewVersions: $(dotnetIncludePreviewVersions)
|
||||
useGlobalJson: true
|
||||
|
||||
- pwsh: |
|
||||
$sha = 'g$(Build.SourceVersion)'.substring(0, 8)
|
||||
docker build -t $(dockerImageName):$sha -f $(dockerfile) .
|
||||
mkdir -p $(Build.ArtifactStagingDirectory)/docker-images
|
||||
docker save -o $(Build.ArtifactStagingDirectory)/docker-images/$(dockerImageName).$sha.tar $(dockerImageName):$sha
|
||||
dotnet dev-certs https -ep ${HOME}/.aspnet/https/aspnetapp.pfx -p $(Umbraco__CMS__Unattended__UnattendedUserPassword)
|
||||
docker run --name $(dockerImageName) -dp 8080:5000 -dp 8443:5001 -e UMBRACO__CMS__GLOBAL__ID=$(UMBRACO__CMS__GLOBAL__ID) -e ASPNETCORE_Kestrel__Certificates__Default__Password="$(Umbraco__CMS__Unattended__UnattendedUserPassword)" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx -v ${HOME}/.aspnet/https:/https/ $(dockerImageName):$sha
|
||||
docker ps
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
displayName: Build and run container (Linux only)
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest/misc
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
dotnet new --install ./nupkg/Umbraco.Templates.*.nupkg
|
||||
dotnet new umbraco --name AcceptanceTestProject --no-restore --output .
|
||||
dotnet restore --configfile ./nuget.config
|
||||
dotnet build --configuration $(buildConfiguration) --no-restore
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
dotnet dev-certs https
|
||||
$process = Start-Process -FilePath "dotnet" -ArgumentList "run --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Run application
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
displayName: Build and run app (Windows only)
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest/misc
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for app
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
- pwsh: npm run test --ignore-certificate-errors
|
||||
displayName: Run Playwright (Desktop)
|
||||
|
||||
# Test
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
docker logs $(dockerImageName) > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1
|
||||
docker stop $(dockerImageName)
|
||||
condition: eq(variables['Agent.OS'], 'Linux')
|
||||
displayName: Stop app (Linux only)
|
||||
- pwsh: Stop-Process $env:AcceptanceTestProcessId
|
||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
||||
displayName: Stop app (Windows only)
|
||||
- task: PowerShell@2
|
||||
displayName: Check if artifacts folder exists
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
$MyVariable = Test-Path -Path $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/results
|
||||
Write-Host "##vso[task.setvariable variable=resultFolderExists;]$MyVariable"
|
||||
- task: CopyFiles@2
|
||||
displayName: Prepare artifacts
|
||||
condition: eq(variables.resultFolderExists, 'True')
|
||||
inputs:
|
||||
sourceFolder: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/results/
|
||||
targetFolder: $(Build.ArtifactStagingDirectory)/playwright
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish test artifacts
|
||||
- task: PublishPipelineArtifact@1
|
||||
condition: always()
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'E2E artifacts - $(Agent.OS) - Attempt #$(System.JobAttempt)'
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerAcceptanceTests}})
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
strategy:
|
||||
matrix:
|
||||
LinuxPart1Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
LinuxPart2Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
LinuxPart3Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
WindowsPart1Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
WindowsPart2Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
WindowsPart3Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Start SQL Server
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Run application
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish test artifacts
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
|
||||
###############################################
|
||||
## Release
|
||||
@@ -553,6 +767,8 @@ stages:
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}}))
|
||||
jobs:
|
||||
- job:
|
||||
pool:
|
||||
vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available.
|
||||
displayName: Push to pre-release feed
|
||||
steps:
|
||||
- checkout: none
|
||||
@@ -579,6 +795,8 @@ stages:
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.nuGetDeploy}}))
|
||||
jobs:
|
||||
- job:
|
||||
pool:
|
||||
vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available.
|
||||
displayName: Push to NuGet
|
||||
steps:
|
||||
- checkout: none
|
||||
@@ -599,7 +817,7 @@ stages:
|
||||
pool:
|
||||
vmImage: 'windows-latest' # Apparently AzureFileCopy is windows only :(
|
||||
variables:
|
||||
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
|
||||
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
|
||||
displayName: Upload API Documention
|
||||
dependsOn:
|
||||
- Build
|
||||
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.0",
|
||||
"rollForward": "latestFeature",
|
||||
"allowPrerelease": false
|
||||
"version": "8.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
</configSections>
|
||||
|
||||
<appSettings>
|
||||
<add key="Umbraco.Core.ConfigurationStatus" value="6.0.0"/>
|
||||
<add key="Umbraco.Core.ReservedUrls" value="~/config/splashes/booting.aspx,~/install/default.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd,~/.well-known" />
|
||||
<add key="Umbraco.Core.ReservedPaths" value="~/install/"/>
|
||||
<add key="Umbraco.Core.Path" value="~/umbraco"/>
|
||||
<add key="Umbraco.Core.HideTopLevelNodeFromPath" value="true"/>
|
||||
<add key="Umbraco.Core.TimeOutInMinutes" value="20"/>
|
||||
<add key="Umbraco.Core.DefaultUILanguage" value="en"/>
|
||||
<add key="Umbraco.Core.UseHttps" value="false"/>
|
||||
<add key="dataAnnotations:dataTypeAttribute:disableRegEx" value="false"/>
|
||||
</appSettings>
|
||||
|
||||
<connectionStrings>
|
||||
<add name="umbracoDbDSN" connectionString="Datasource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1;" providerName="System.Data.SqlServerCe.4.0"/>
|
||||
</connectionStrings>
|
||||
|
||||
<system.data>
|
||||
<DbProviderFactories>
|
||||
<remove invariant="System.Data.SqlServerCe.4.0"/>
|
||||
<add name="Microsoft SQL Server Compact Data Provider 4.0" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91"/>
|
||||
</DbProviderFactories>
|
||||
</system.data>
|
||||
|
||||
<system.web>
|
||||
<httpRuntime targetFramework="4.5"/>
|
||||
<compilation defaultLanguage="c#" debug="true" batch="false" targetFramework="4.0"></compilation>
|
||||
<machineKey validationKey="5E7B955FCE36F5F2A867C2A0D85DC61E7FEA9E15F1561E8386F78BFE9EE23FF18B21E6A44AA17300B3B9D5DBEB37AA61A2C73884A5BBEDA6D3B14BA408A7A8CD" decryptionKey="116B853D031219E404E088FCA0986D6CF2DFA77E1957B59FCC9404B8CA3909A1" validation="SHA1" decryption="AES"/>
|
||||
<!--<trust level="Medium" originUrl=".*"/>-->
|
||||
<!-- Sitemap provider-->
|
||||
<siteMap defaultProvider="UmbracoSiteMapProvider" enabled="true">
|
||||
<providers>
|
||||
<clear/>
|
||||
<add name="UmbracoSiteMapProvider" type="umbraco.presentation.nodeFactory.UmbracoSiteMapProvider" defaultDescriptionAlias="description" securityTrimmingEnabled="true"/>
|
||||
</providers>
|
||||
</siteMap>
|
||||
<!-- Membership Provider -->
|
||||
<membership defaultProvider="UmbracoMembershipProvider" userIsOnlineTimeWindow="15">
|
||||
<providers>
|
||||
<clear/>
|
||||
<add name="UmbracoMembershipProvider" type="Umbraco.Web.Security.Providers.MembersMembershipProvider, Umbraco.Web" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="false" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Member" passwordFormat="Hashed"/>
|
||||
</providers>
|
||||
</membership>
|
||||
</system.web>
|
||||
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||
</startup>
|
||||
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.2.5.0" newVersion="1.2.5.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ComponentModel.Annotations" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.2.1.0" newVersion="4.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="4.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="4.0.0.0-4.0.1.1" newVersion="4.0.1.1"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="4.0.0.0-4.1.4.0" newVersion="4.1.4.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
||||
</configuration>
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Umbraco.Tests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Umbraco.Tests")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2012")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("33ddf9b7-505c-4a12-8370-7fee9de5df6d")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
||||
// Internals must be visible to DynamicProxyGenAssembly2
|
||||
// in order to mock loggers loggers with types from the assembly
|
||||
// I.E. Mock.Of<ILogger<TestControllerFactory>>()
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
@@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Tests.Published
|
||||
{
|
||||
[TestFixture]
|
||||
public class ModelTypeTests
|
||||
{
|
||||
|
||||
//TODO these is not easy to move to the Unittest project due to underlysing NotImplementedException of Type.IsSZArray
|
||||
[Test]
|
||||
public void ModelTypeToStringTests()
|
||||
{
|
||||
var modelType = ModelType.For("alias1");
|
||||
var modelTypeArray = modelType.MakeArrayType();
|
||||
|
||||
Assert.AreEqual("{alias1}", modelType.ToString());
|
||||
|
||||
// there's an "*" there because the arrays are not true SZArray - but that changes when we map
|
||||
|
||||
Assert.AreEqual("{alias1}[*]", modelTypeArray.ToString());
|
||||
var enumArray = typeof(IEnumerable<>).MakeGenericType(modelTypeArray);
|
||||
Assert.AreEqual("System.Collections.Generic.IEnumerable`1[{alias1}[*]]", enumArray.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ModelTypeFullNameTests()
|
||||
{
|
||||
Assert.AreEqual("{alias1}", ModelType.For("alias1").FullName);
|
||||
|
||||
Type type = ModelType.For("alias1");
|
||||
Assert.AreEqual("{alias1}", type.FullName);
|
||||
|
||||
// there's an "*" there because the arrays are not true SZArray - but that changes when we map
|
||||
Assert.AreEqual("{alias1}[*]", ModelType.For("alias1").MakeArrayType().FullName);
|
||||
// note the inner assembly qualified name
|
||||
Assert.AreEqual("System.Collections.Generic.IEnumerable`1[[{alias1}[*], Umbraco.Core, Version=0.5.0.0, Culture=neutral, PublicKeyToken=null]]", typeof(IEnumerable<>).MakeGenericType(ModelType.For("alias1").MakeArrayType()).FullName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
{
|
||||
public abstract class BaseUrlProviderTest : BaseWebTest
|
||||
{
|
||||
protected IUmbracoContextAccessor UmbracoContextAccessor { get; } = new TestUmbracoContextAccessor();
|
||||
|
||||
protected abstract bool HideTopLevelNodeFromPath { get; }
|
||||
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
Builder.Services.AddTransient<ISiteDomainMapper, SiteDomainMapper>();
|
||||
}
|
||||
|
||||
protected override void ComposeSettings()
|
||||
{
|
||||
var contentSettings = new ContentSettings();
|
||||
var userPasswordConfigurationSettings = new UserPasswordConfigurationSettings();
|
||||
|
||||
Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(contentSettings));
|
||||
Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(userPasswordConfigurationSettings));
|
||||
}
|
||||
|
||||
protected IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext, DefaultUrlProvider urlProvider)
|
||||
{
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
return new UrlProvider(
|
||||
new TestUmbracoContextAccessor(umbracoContext),
|
||||
Microsoft.Extensions.Options.Options.Create(webRoutingSettings),
|
||||
new UrlProviderCollection(new[] { urlProvider }),
|
||||
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
|
||||
Mock.Of<IVariationContextAccessor>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Tests.PublishedContent;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
|
||||
public class MediaUrlProviderTests : BaseWebTest
|
||||
{
|
||||
private DefaultMediaUrlProvider _mediaUrlProvider;
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
|
||||
var loggerFactory = NullLoggerFactory.Instance;
|
||||
var mediaFileManager = new MediaFileManager(Mock.Of<IFileSystem>(), Mock.Of<IMediaPathScheme>(),
|
||||
loggerFactory.CreateLogger<MediaFileManager>(), Mock.Of<IShortStringHelper>());
|
||||
var contentSettings = new ContentSettings();
|
||||
var dataTypeService = Mock.Of<IDataTypeService>();
|
||||
var propertyEditors = new MediaUrlGeneratorCollection(new IMediaUrlGenerator[]
|
||||
{
|
||||
new FileUploadPropertyEditor(DataValueEditorFactory, mediaFileManager, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, LocalizedTextService, UploadAutoFillProperties, ContentService),
|
||||
new ImageCropperPropertyEditor(DataValueEditorFactory, loggerFactory, mediaFileManager, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, IOHelper, UploadAutoFillProperties, ContentService),
|
||||
});
|
||||
_mediaUrlProvider = new DefaultMediaUrlProvider(propertyEditors, UriUtility);
|
||||
}
|
||||
|
||||
public override void TearDown()
|
||||
{
|
||||
base.TearDown();
|
||||
|
||||
_mediaUrlProvider = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Resolves_Url_From_Upload_Property_Editor()
|
||||
{
|
||||
const string expected = "/media/rfeiw584/test.jpg";
|
||||
|
||||
var umbracoContext = GetUmbracoContext("/");
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null);
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto);
|
||||
|
||||
Assert.AreEqual(expected, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Resolves_Url_From_Image_Cropper_Property_Editor()
|
||||
{
|
||||
const string expected = "/media/rfeiw584/test.jpg";
|
||||
|
||||
var configuration = new ImageCropperConfiguration();
|
||||
var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue
|
||||
{
|
||||
Src = expected
|
||||
});
|
||||
|
||||
var umbracoContext = GetUmbracoContext("/");
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration);
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto);
|
||||
|
||||
Assert.AreEqual(expected, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Can_Resolve_Absolute_Url()
|
||||
{
|
||||
const string mediaUrl = "/media/rfeiw584/test.jpg";
|
||||
var expected = $"http://localhost{mediaUrl}";
|
||||
|
||||
var umbracoContext = GetUmbracoContext("http://localhost");
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, mediaUrl, null);
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Absolute);
|
||||
|
||||
Assert.AreEqual(expected, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Returns_Absolute_Url_If_Stored_Url_Is_Absolute()
|
||||
{
|
||||
const string expected = "http://localhost/media/rfeiw584/test.jpg";
|
||||
|
||||
var umbracoContext = GetUmbracoContext("http://localhost");
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null);
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Relative);
|
||||
|
||||
Assert.AreEqual(expected, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Returns_Empty_String_When_PropertyType_Is_Not_Supported()
|
||||
{
|
||||
var umbracoContext = GetUmbracoContext("/");
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.Boolean, "0", null);
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Absolute, propertyAlias: "test");
|
||||
|
||||
Assert.AreEqual(string.Empty, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Can_Resolve_Variant_Property_Url()
|
||||
{
|
||||
var umbracoContext = GetUmbracoContext("http://localhost");
|
||||
|
||||
var umbracoFilePropertyType = CreatePropertyType(Constants.PropertyEditors.Aliases.UploadField, null, ContentVariation.Culture);
|
||||
|
||||
const string enMediaUrl = "/media/rfeiw584/en.jpg";
|
||||
const string daMediaUrl = "/media/uf8ewud2/da.jpg";
|
||||
|
||||
var property = new SolidPublishedPropertyWithLanguageVariants
|
||||
{
|
||||
Alias = "umbracoFile",
|
||||
PropertyType = umbracoFilePropertyType,
|
||||
};
|
||||
|
||||
property.SetSourceValue("en", enMediaUrl, true);
|
||||
property.SetSourceValue("da", daMediaUrl);
|
||||
|
||||
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), new [] { umbracoFilePropertyType }, ContentVariation.Culture);
|
||||
var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}};
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto, "da");
|
||||
Assert.AreEqual(daMediaUrl, resolvedUrl);
|
||||
}
|
||||
|
||||
private IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext)
|
||||
{
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
return new UrlProvider(
|
||||
new TestUmbracoContextAccessor(umbracoContext),
|
||||
Microsoft.Extensions.Options.Options.Create(webRoutingSettings),
|
||||
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
|
||||
new MediaUrlProviderCollection(new []{_mediaUrlProvider}),
|
||||
Mock.Of<IVariationContextAccessor>()
|
||||
);
|
||||
}
|
||||
|
||||
private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, string propertyValue, object dataTypeConfiguration)
|
||||
{
|
||||
var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing);
|
||||
|
||||
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(),
|
||||
new[] {umbracoFilePropertyType}, ContentVariation.Nothing);
|
||||
|
||||
return new SolidPublishedContent(contentType)
|
||||
{
|
||||
Id = 1234,
|
||||
Key = Guid.NewGuid(),
|
||||
Properties = new[]
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "umbracoFile",
|
||||
SolidSourceValue = propertyValue,
|
||||
SolidHasValue = true,
|
||||
PropertyType = umbracoFilePropertyType
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static PublishedPropertyType CreatePropertyType(string propertyEditorAlias, object dataTypeConfiguration, ContentVariation variation)
|
||||
{
|
||||
var uploadDataType = new PublishedDataType(1234, propertyEditorAlias, new Lazy<object>(() => dataTypeConfiguration));
|
||||
|
||||
var propertyValueConverters = new PropertyValueConverterCollection(new IPropertyValueConverter[]
|
||||
{
|
||||
new UploadPropertyConverter(),
|
||||
new ImageCropperValueConverter(Mock.Of<ILogger<ImageCropperValueConverter>>()),
|
||||
});
|
||||
|
||||
var publishedModelFactory = Mock.Of<IPublishedModelFactory>();
|
||||
var publishedContentTypeFactory = new Mock<IPublishedContentTypeFactory>();
|
||||
publishedContentTypeFactory.Setup(x => x.GetDataType(It.IsAny<int>()))
|
||||
.Returns(uploadDataType);
|
||||
|
||||
return new PublishedPropertyType("umbracoFile", 42, true, variation, propertyValueConverters, publishedModelFactory, publishedContentTypeFactory.Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{5D3B8245-ADA6-453F-A008-50ED04BFE770}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Umbraco.Tests</RootNamespace>
|
||||
<AssemblyName>Umbraco.Tests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.Entity.Design" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Runtime.Caching" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Text.Encoding" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.ApplicationServices" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
<Reference Include="System.Web.Services" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Castle.Core" Version="4.4.1" />
|
||||
<PackageReference Include="Examine.Core">
|
||||
<Version>2.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Castle.Core" Version="4.3.1" />
|
||||
<PackageReference Include="Examine" Version="2.0.0" />
|
||||
<PackageReference Include="HtmlAgilityPack">
|
||||
<Version>1.11.31</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Lucene.Net.Contrib" Version="3.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.Mvc" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Owin" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.SelfHost" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Tracing" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.WebHost" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions">
|
||||
<Version>5.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console">
|
||||
<Version>5.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Owin" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.Owin.Hosting" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.Owin.Security" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.Owin.Testing" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.Web.Infrastructure" Version="1.0.0.0" />
|
||||
<PackageReference Include="MiniProfiler" Version="4.2.22" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="MiniProfiler" Version="4.0.138" />
|
||||
<PackageReference Include="Moq" Version="4.10.1" />
|
||||
<PackageReference Include="Moq" Version="4.14.5" />
|
||||
<PackageReference Include="NPoco" Version="3.9.4" />
|
||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.12.0" />
|
||||
<PackageReference Include="NPoco" Version="4.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<PackageReference Include="Owin" Version="1.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="Umbraco.SqlServerCE" Version="4.0.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Published\ModelTypeTests.cs" />
|
||||
<Compile Include="Routing\BaseUrlProviderTest.cs" />
|
||||
<Compile Include="Routing\MediaUrlProviderTests.cs" />
|
||||
<Compile Include="Scoping\ScopedNuCacheTests.cs" />
|
||||
<Compile Include="Web\Controllers\AuthenticationControllerTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="unit-test-logger.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Umbraco.Core\Umbraco.Core.csproj">
|
||||
<Project>{29aa69d9-b597-4395-8d42-43b1263c240a}</Project>
|
||||
<Name>Umbraco.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Umbraco.Examine.Lucene\Umbraco.Examine.Lucene.csproj">
|
||||
<Project>{0fad7d2a-d7dd-45b1-91fd-488bb6cdacea}</Project>
|
||||
<Name>Umbraco.Examine.Lucene</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="...\..\srcUmbraco.Infrastructure\Umbraco.Infrastructure.csproj">
|
||||
<Project>{3ae7bf57-966b-45a5-910a-954d7c554441}</Project>
|
||||
<Name>Umbraco.Infrastructure</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Umbraco.Persistence.SqlCe\Umbraco.Persistence.SqlCe.csproj">
|
||||
<Project>{33085570-9bf2-4065-a9b0-a29d920d13ba}</Project>
|
||||
<Name>Umbraco.Persistence.SqlCe</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj">
|
||||
<Project>{f6de8da0-07cc-4ef2-8a59-2bc81dbb3830}</Project>
|
||||
<Name>Umbraco.PublishedCache.NuCache</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj">
|
||||
<Project>{a499779c-1b3b-48a8-b551-458e582e6e96}</Project>
|
||||
<Name>Umbraco.Tests.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Umbraco.Web\Umbraco.Web.csproj">
|
||||
<Project>{651E1350-91B6-44B7-BD60-7207006D7003}</Project>
|
||||
<Name>Umbraco.Web</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<!-- get NuGet packages directory -->
|
||||
<PropertyGroup>
|
||||
<NuGetPackages>$(NuGetPackageFolders.Split(';')[0])</NuGetPackages>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="BeforeBuild">
|
||||
<Message Text="-BeforeBuild-" Importance="high" />
|
||||
<Message Text="MSBuildExtensionsPath: $(MSBuildExtensionsPath)" Importance="high" />
|
||||
<Message Text="WebPublishingTasks: $(WebPublishingTasks)" Importance="high" />
|
||||
<Message Text="NuGetPackageFolders: $(NuGetPackageFolders)" Importance="high" />
|
||||
<Message Text="NuGetPackages: $(NuGetPackages)" Importance="high" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,122 +0,0 @@
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Features;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
|
||||
namespace Umbraco.Tests.Web.Controllers
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.None)]
|
||||
public class AuthenticationControllerTests : TestWithDatabaseBase
|
||||
{
|
||||
protected override void ComposeApplication(bool withApplication)
|
||||
{
|
||||
base.ComposeApplication(withApplication);
|
||||
//if (!withApplication) return;
|
||||
|
||||
// replace the true IUserService implementation with a mock
|
||||
// so that each test can configure the service to their liking
|
||||
Builder.Services.AddUnique(f => Mock.Of<IUserService>());
|
||||
|
||||
// kill the true IEntityService too
|
||||
Builder.Services.AddUnique(f => Mock.Of<IEntityService>());
|
||||
|
||||
Builder.Services.AddUnique<UmbracoFeatures>();
|
||||
}
|
||||
|
||||
|
||||
// TODO Reintroduce when moved to .NET Core
|
||||
// [Test]
|
||||
// public async System.Threading.Tasks.Task GetCurrentUser_Fips()
|
||||
// {
|
||||
// ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
// {
|
||||
// //setup some mocks
|
||||
// var userServiceMock = Mock.Get(ServiceContext.UserService);
|
||||
// userServiceMock.Setup(service => service.GetUserById(It.IsAny<int>()))
|
||||
// .Returns(() => null);
|
||||
//
|
||||
// if (Thread.GetDomain().GetData(".appPath") != null)
|
||||
// {
|
||||
// HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter()));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var baseDir = IOHelper.MapPath("").TrimEnd(Path.DirectorySeparatorChar);
|
||||
// HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter()));
|
||||
// }
|
||||
//
|
||||
// var usersController = new AuthenticationController(
|
||||
// new TestUserPasswordConfig(),
|
||||
// Factory.GetInstance<IGlobalSettings>(),
|
||||
// Factory.GetInstance<IHostingEnvironment>(),
|
||||
// umbracoContextAccessor,
|
||||
// Factory.GetInstance<ISqlContext>(),
|
||||
// Factory.GetInstance<ServiceContext>(),
|
||||
// Factory.GetInstance<AppCaches>(),
|
||||
// Factory.GetInstance<IProfilingLogger>(),
|
||||
// Factory.GetInstance<IRuntimeState>(),
|
||||
// Factory.GetInstance<UmbracoMapper>(),
|
||||
// Factory.GetInstance<ISecuritySettings>(),
|
||||
// Factory.GetInstance<IPublishedUrlProvider>(),
|
||||
// Factory.GetInstance<IRequestAccessor>(),
|
||||
// Factory.GetInstance<IEmailSender>()
|
||||
// );
|
||||
// return usersController;
|
||||
// }
|
||||
//
|
||||
// Mock.Get(Current.SqlContext)
|
||||
// .Setup(x => x.Query<IUser>())
|
||||
// .Returns(new Query<IUser>(Current.SqlContext));
|
||||
//
|
||||
// var syntax = new SqlCeSyntaxProvider();
|
||||
//
|
||||
// Mock.Get(Current.SqlContext)
|
||||
// .Setup(x => x.SqlSyntax)
|
||||
// .Returns(syntax);
|
||||
//
|
||||
// var mappers = new MapperCollection(new[]
|
||||
// {
|
||||
// new UserMapper(new Lazy<ISqlContext>(() => Current.SqlContext), new ConcurrentDictionary<Type, ConcurrentDictionary<string, string>>())
|
||||
// });
|
||||
//
|
||||
// Mock.Get(Current.SqlContext)
|
||||
// .Setup(x => x.Mappers)
|
||||
// .Returns(mappers);
|
||||
//
|
||||
// // Testing what happens if the system were configured to only use FIPS-compliant algorithms
|
||||
// var typ = typeof(CryptoConfig);
|
||||
// var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
|
||||
// var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy");
|
||||
// var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy");
|
||||
// var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms;
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// if (!originalFipsValue)
|
||||
// {
|
||||
// haveFld.SetValue(null, true);
|
||||
// isFld.SetValue(null, true);
|
||||
// }
|
||||
//
|
||||
// var runner = new TestRunner(CtrlFactory);
|
||||
// var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get);
|
||||
//
|
||||
// var obj = JsonConvert.DeserializeObject<UserDetail>(response.Item2);
|
||||
// Assert.AreEqual(-1, obj.UserId);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// if (!originalFipsValue)
|
||||
// {
|
||||
// haveFld.SetValue(null, false);
|
||||
// isFld.SetValue(null, false);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<log4net>
|
||||
<root>
|
||||
<priority value="OFF"/>
|
||||
</root>
|
||||
</log4net>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<!-- Global Log Level event -->
|
||||
<add key="serilog:minimum-level" value="Warning" />
|
||||
|
||||
<!-- Write to console -->
|
||||
<add key="serilog:using:Console" value="Serilog.Sinks.Console" />
|
||||
<add key="serilog:write-to:Console.theme" value="Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console" />
|
||||
<add key="serilog:write-to:Console.outputTemplate" value="[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}" />
|
||||
|
||||
<!-- Namespace log levels -->
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.Publishing.PublishingStrategy" value="Warning" />
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.TypeLoader" value="Warning" />
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.Persistence.UmbracoDatabase" value="Debug" />
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.Persistence.Migrations.Initial.DatabaseSchemaCreation" value="Warning" />
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.Persistence.Migrations.Initial.BaseDataCreation" value="Warning" />
|
||||
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -40,7 +40,9 @@ public static class UmbracoBuilderAuthExtensions
|
||||
.SetLogoutEndpointUris(
|
||||
Paths.MemberApi.LogoutEndpoint.TrimStart(Constants.CharArrays.ForwardSlash))
|
||||
.SetRevocationEndpointUris(
|
||||
Paths.MemberApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash));
|
||||
Paths.MemberApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash))
|
||||
.SetUserinfoEndpointUris(
|
||||
Paths.MemberApi.UserinfoEndpoint.TrimStart(Constants.CharArrays.ForwardSlash));
|
||||
|
||||
// Enable authorization code flow with PKCE
|
||||
options
|
||||
@@ -52,7 +54,8 @@ public static class UmbracoBuilderAuthExtensions
|
||||
options
|
||||
.UseAspNetCore()
|
||||
.EnableAuthorizationEndpointPassthrough()
|
||||
.EnableLogoutEndpointPassthrough();
|
||||
.EnableLogoutEndpointPassthrough()
|
||||
.EnableUserinfoEndpointPassthrough();
|
||||
|
||||
// Enable reference tokens
|
||||
// - see https://documentation.openiddict.com/configuration/token-storage.html
|
||||
|
||||
@@ -12,6 +12,8 @@ public class EnumSchemaFilter : ISchemaFilter
|
||||
{
|
||||
if (context.Type.IsEnum)
|
||||
{
|
||||
model.Type = "string";
|
||||
model.Format = null;
|
||||
model.Enum.Clear();
|
||||
foreach (var name in Enum.GetNames(context.Type))
|
||||
{
|
||||
|
||||
@@ -14,6 +14,8 @@ public static class Paths
|
||||
|
||||
public static readonly string RevokeEndpoint = EndpointPath($"{EndpointTemplate}/revoke");
|
||||
|
||||
public static readonly string UserinfoEndpoint = EndpointPath($"{EndpointTemplate}/userinfo");
|
||||
|
||||
// NOTE: we're NOT using /api/v1.0/ here because it will clash with the Delivery API docs
|
||||
private static string EndpointPath(string relativePath) => $"/umbraco/delivery/api/v1/{relativePath}";
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="OpenIddict.Abstractions" />
|
||||
<PackageReference Include="OpenIddict.AspNetCore" />
|
||||
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens"/>
|
||||
|
||||
<!-- Take top-level depedendency on OpenIddict.AspNetCore depends on a vulnerable version -->
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -8,6 +8,5 @@ internal static class DeliveryApiConfiguration
|
||||
|
||||
internal const string ApiDocumentationContentArticleLink = "https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api";
|
||||
|
||||
// TODO: update this when the Media article is out
|
||||
internal const string ApiDocumentationMediaArticleLink = "https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api";
|
||||
internal const string ApiDocumentationMediaArticleLink = "https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api/media-delivery-api";
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Controllers.Content;
|
||||
|
||||
@@ -16,10 +15,11 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Content;
|
||||
[ApiVersion("2.0")]
|
||||
public class ByRouteContentApiController : ContentApiItemControllerBase
|
||||
{
|
||||
private readonly IRequestRoutingService _requestRoutingService;
|
||||
private readonly IApiContentPathResolver _apiContentPathResolver;
|
||||
private readonly IRequestRedirectService _requestRedirectService;
|
||||
private readonly IRequestPreviewService _requestPreviewService;
|
||||
private readonly IRequestMemberAccessService _requestMemberAccessService;
|
||||
private const string PreviewContentRequestPathPrefix = $"/{Constants.DeliveryApi.Routing.PreviewContentPathPrefix}";
|
||||
|
||||
[Obsolete($"Please use the constructor that does not accept {nameof(IPublicAccessService)}. Will be removed in V14.")]
|
||||
public ByRouteContentApiController(
|
||||
@@ -58,7 +58,7 @@ public class ByRouteContentApiController : ContentApiItemControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
[Obsolete($"Please use the constructor that accepts {nameof(IApiContentPathResolver)}. Will be removed in V15.")]
|
||||
public ByRouteContentApiController(
|
||||
IApiPublishedContentCache apiPublishedContentCache,
|
||||
IApiContentResponseBuilder apiContentResponseBuilder,
|
||||
@@ -66,12 +66,50 @@ public class ByRouteContentApiController : ContentApiItemControllerBase
|
||||
IRequestRedirectService requestRedirectService,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IRequestMemberAccessService requestMemberAccessService)
|
||||
: this(
|
||||
apiPublishedContentCache,
|
||||
apiContentResponseBuilder,
|
||||
requestRedirectService,
|
||||
requestPreviewService,
|
||||
requestMemberAccessService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IApiContentPathResolver>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete($"Please use the non-obsolete constructor. Will be removed in V15.")]
|
||||
public ByRouteContentApiController(
|
||||
IApiPublishedContentCache apiPublishedContentCache,
|
||||
IApiContentResponseBuilder apiContentResponseBuilder,
|
||||
IPublicAccessService publicAccessService,
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IRequestRedirectService requestRedirectService,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IRequestMemberAccessService requestMemberAccessService,
|
||||
IApiContentPathResolver apiContentPathResolver)
|
||||
: this(
|
||||
apiPublishedContentCache,
|
||||
apiContentResponseBuilder,
|
||||
requestRedirectService,
|
||||
requestPreviewService,
|
||||
requestMemberAccessService,
|
||||
apiContentPathResolver)
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ByRouteContentApiController(
|
||||
IApiPublishedContentCache apiPublishedContentCache,
|
||||
IApiContentResponseBuilder apiContentResponseBuilder,
|
||||
IRequestRedirectService requestRedirectService,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IRequestMemberAccessService requestMemberAccessService,
|
||||
IApiContentPathResolver apiContentPathResolver)
|
||||
: base(apiPublishedContentCache, apiContentResponseBuilder)
|
||||
{
|
||||
_requestRoutingService = requestRoutingService;
|
||||
_requestRedirectService = requestRedirectService;
|
||||
_requestPreviewService = requestPreviewService;
|
||||
_requestMemberAccessService = requestMemberAccessService;
|
||||
_apiContentPathResolver = apiContentPathResolver;
|
||||
}
|
||||
|
||||
[HttpGet("item/{*path}")]
|
||||
@@ -105,10 +143,13 @@ public class ByRouteContentApiController : ContentApiItemControllerBase
|
||||
private async Task<IActionResult> HandleRequest(string path)
|
||||
{
|
||||
path = DecodePath(path);
|
||||
|
||||
path = path.TrimStart("/");
|
||||
path = path.Length == 0 ? "/" : path;
|
||||
|
||||
if (_apiContentPathResolver.IsResolvablePath(path) is false)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
IPublishedContent? contentItem = GetContent(path);
|
||||
if (contentItem is not null)
|
||||
{
|
||||
@@ -128,17 +169,12 @@ public class ByRouteContentApiController : ContentApiItemControllerBase
|
||||
}
|
||||
|
||||
private IPublishedContent? GetContent(string path)
|
||||
=> path.StartsWith(Constants.DeliveryApi.Routing.PreviewContentPathPrefix)
|
||||
=> path.StartsWith(PreviewContentRequestPathPrefix)
|
||||
? GetPreviewContent(path)
|
||||
: GetPublishedContent(path);
|
||||
|
||||
private IPublishedContent? GetPublishedContent(string path)
|
||||
{
|
||||
var contentRoute = _requestRoutingService.GetContentRoute(path);
|
||||
|
||||
IPublishedContent? contentItem = ApiPublishedContentCache.GetByRoute(contentRoute);
|
||||
return contentItem;
|
||||
}
|
||||
=> _apiContentPathResolver.ResolveContentPath(path);
|
||||
|
||||
private IPublishedContent? GetPreviewContent(string path)
|
||||
{
|
||||
@@ -147,7 +183,7 @@ public class ByRouteContentApiController : ContentApiItemControllerBase
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Guid.TryParse(path.AsSpan(Constants.DeliveryApi.Routing.PreviewContentPathPrefix.Length).TrimEnd("/"), out Guid contentId) is false)
|
||||
if (Guid.TryParse(path.AsSpan(PreviewContentRequestPathPrefix.Length).TrimEnd("/"), out Guid contentId) is false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Content;
|
||||
[ApiExplorerSettings(GroupName = "Content")]
|
||||
[LocalizeFromAcceptLanguageHeader]
|
||||
[ValidateStartItem]
|
||||
[AddVaryHeader]
|
||||
[OutputCache(PolicyName = Constants.DeliveryApi.OutputCache.ContentCachePolicy)]
|
||||
public abstract class ContentApiControllerBase : DeliveryApiControllerBase
|
||||
{
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
using Umbraco.Cms.Api.Delivery.Routing;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Controllers.Security;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedDeliveryApiRoute(Common.Security.Paths.MemberApi.EndpointTemplate)]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
|
||||
public class CurrentMemberController : DeliveryApiControllerBase
|
||||
{
|
||||
private readonly ICurrentMemberClaimsProvider _currentMemberClaimsProvider;
|
||||
|
||||
public CurrentMemberController(ICurrentMemberClaimsProvider currentMemberClaimsProvider)
|
||||
=> _currentMemberClaimsProvider = currentMemberClaimsProvider;
|
||||
|
||||
[HttpGet("userinfo")]
|
||||
public async Task<IActionResult> Userinfo()
|
||||
{
|
||||
Dictionary<string, object> claims = await _currentMemberClaimsProvider.GetClaimsAsync();
|
||||
return Ok(claims);
|
||||
}
|
||||
}
|
||||
@@ -160,11 +160,12 @@ public class MemberController : DeliveryApiControllerBase
|
||||
claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken);
|
||||
}
|
||||
|
||||
if (request.GetScopes().Contains(OpenIddictConstants.Scopes.OfflineAccess))
|
||||
{
|
||||
// "offline_access" scope is required to use refresh tokens
|
||||
memberPrincipal.SetScopes(OpenIddictConstants.Scopes.OfflineAccess);
|
||||
}
|
||||
// "openid" and "offline_access" are the only scopes allowed for members; explicitly ensure we only add those
|
||||
// NOTE: the "offline_access" scope is required to use refresh tokens
|
||||
IEnumerable<string> allowedScopes = request
|
||||
.GetScopes()
|
||||
.Intersect(new[] { OpenIddictConstants.Scopes.OpenId, OpenIddictConstants.Scopes.OfflineAccess });
|
||||
memberPrincipal.SetScopes(allowedScopes);
|
||||
|
||||
return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, memberPrincipal);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using Umbraco.Cms.Api.Delivery.Rendering;
|
||||
using Umbraco.Cms.Api.Delivery.Routing;
|
||||
using Umbraco.Cms.Api.Delivery.Security;
|
||||
using Umbraco.Cms.Api.Delivery.Services;
|
||||
using Umbraco.Cms.Api.Delivery.Services.QueryBuilders;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
@@ -55,9 +56,11 @@ public static class UmbracoBuilderExtensions
|
||||
builder.Services.AddSingleton<IApiAccessService, ApiAccessService>();
|
||||
builder.Services.AddSingleton<IApiContentQueryService, ApiContentQueryService>();
|
||||
builder.Services.AddSingleton<IApiContentQueryProvider, ApiContentQueryProvider>();
|
||||
builder.Services.AddSingleton<IApiContentQueryFactory, ApiContentQueryFactory>();
|
||||
builder.Services.AddSingleton<IApiMediaQueryService, ApiMediaQueryService>();
|
||||
builder.Services.AddTransient<IMemberApplicationManager, MemberApplicationManager>();
|
||||
builder.Services.AddTransient<IRequestMemberAccessService, RequestMemberAccessService>();
|
||||
builder.Services.AddTransient<ICurrentMemberClaimsProvider, CurrentMemberClaimsProvider>();
|
||||
|
||||
builder.Services.ConfigureOptions<ConfigureUmbracoDeliveryApiSwaggerGenOptions>();
|
||||
builder.AddUmbracoApiOpenApiUI();
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Filters;
|
||||
|
||||
public sealed class AddVaryHeaderAttribute : ActionFilterAttribute
|
||||
{
|
||||
private const string Vary = "Accept-Language, Preview, Start-Item";
|
||||
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
=> context.HttpContext.Response.Headers.Vary = context.HttpContext.Response.Headers.Vary.Count > 0
|
||||
? $"{context.HttpContext.Response.Headers.Vary}, {Vary}"
|
||||
: Vary;
|
||||
}
|
||||
@@ -12,23 +12,36 @@ public class DeliveryApiJsonTypeResolver : DefaultJsonTypeInfoResolver
|
||||
{
|
||||
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
|
||||
|
||||
if (jsonTypeInfo.Type == typeof(IApiContent))
|
||||
Type[] derivedTypes = GetDerivedTypes(jsonTypeInfo);
|
||||
if (derivedTypes.Length > 0)
|
||||
{
|
||||
ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(ApiContent));
|
||||
}
|
||||
else if (jsonTypeInfo.Type == typeof(IApiContentResponse))
|
||||
{
|
||||
ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(ApiContentResponse));
|
||||
}
|
||||
else if (jsonTypeInfo.Type == typeof(IRichTextElement))
|
||||
{
|
||||
ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(RichTextRootElement), typeof(RichTextGenericElement), typeof(RichTextTextElement));
|
||||
ConfigureJsonPolymorphismOptions(jsonTypeInfo, derivedTypes);
|
||||
}
|
||||
|
||||
return jsonTypeInfo;
|
||||
}
|
||||
|
||||
private void ConfigureJsonPolymorphismOptions(JsonTypeInfo jsonTypeInfo, params Type[] derivedTypes)
|
||||
protected virtual Type[] GetDerivedTypes(JsonTypeInfo jsonTypeInfo)
|
||||
{
|
||||
if (jsonTypeInfo.Type == typeof(IApiContent))
|
||||
{
|
||||
return new[] { typeof(ApiContent) };
|
||||
}
|
||||
|
||||
if (jsonTypeInfo.Type == typeof(IApiContentResponse))
|
||||
{
|
||||
return new[] { typeof(ApiContentResponse) };
|
||||
}
|
||||
|
||||
if (jsonTypeInfo.Type == typeof(IRichTextElement))
|
||||
{
|
||||
return new[] { typeof(RichTextRootElement), typeof(RichTextGenericElement), typeof(RichTextTextElement) };
|
||||
}
|
||||
|
||||
return Array.Empty<Type>();
|
||||
}
|
||||
|
||||
protected void ConfigureJsonPolymorphismOptions(JsonTypeInfo jsonTypeInfo, params Type[] derivedTypes)
|
||||
{
|
||||
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Umbraco.Cms.Api.Delivery.Indexing.Filters;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Querying.Filters;
|
||||
|
||||
@@ -15,15 +14,15 @@ public sealed class ContentTypeFilter : IFilterHandler
|
||||
/// <inheritdoc/>
|
||||
public FilterOption BuildFilterOption(string filter)
|
||||
{
|
||||
var alias = filter.Substring(ContentTypeSpecifier.Length);
|
||||
var filterValue = filter.Substring(ContentTypeSpecifier.Length);
|
||||
var negate = filterValue.StartsWith('!');
|
||||
var aliases = filterValue.TrimStart('!').Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return new FilterOption
|
||||
{
|
||||
FieldName = ContentTypeFilterIndexer.FieldName,
|
||||
Values = alias.IsNullOrWhiteSpace() == false
|
||||
? new[] { alias.TrimStart('!') }
|
||||
: Array.Empty<string>(),
|
||||
Operator = alias.StartsWith('!')
|
||||
Values = aliases,
|
||||
Operator = negate
|
||||
? FilterOperation.IsNot
|
||||
: FilterOperation.Is
|
||||
};
|
||||
|
||||
@@ -10,7 +10,8 @@ public abstract class QueryOptionBase
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IRequestRoutingService _requestRoutingService;
|
||||
|
||||
public QueryOptionBase(IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
public QueryOptionBase(
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IRequestRoutingService requestRoutingService)
|
||||
{
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Delivery.Indexing.Selectors;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Extensions;
|
||||
@@ -10,10 +12,21 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
|
||||
{
|
||||
private const string AncestorsSpecifier = "ancestors:";
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IRequestPreviewService _requestPreviewService;
|
||||
|
||||
public AncestorsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService)
|
||||
: base(publishedSnapshotAccessor, requestRoutingService) =>
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public AncestorsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IRequestRoutingService requestRoutingService)
|
||||
: this(publishedSnapshotAccessor, requestRoutingService, StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>())
|
||||
{
|
||||
}
|
||||
|
||||
public AncestorsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService, IRequestPreviewService requestPreviewService)
|
||||
: base(publishedSnapshotAccessor, requestRoutingService)
|
||||
{
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
_requestPreviewService = requestPreviewService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanHandle(string query)
|
||||
@@ -37,10 +50,20 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
|
||||
};
|
||||
}
|
||||
|
||||
IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
|
||||
IPublishedContentCache contentCache = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot()?.Content
|
||||
?? throw new InvalidOperationException("Could not obtain the content cache");
|
||||
|
||||
IPublishedContent contentItem = publishedSnapshot.Content?.GetById((Guid)id)
|
||||
?? throw new InvalidOperationException("Could not obtain the content cache");
|
||||
IPublishedContent? contentItem = contentCache.GetById(_requestPreviewService.IsPreview(), id.Value);
|
||||
|
||||
if (contentItem is null)
|
||||
{
|
||||
// no such content item, make sure the selector does not yield any results
|
||||
return new SelectorOption
|
||||
{
|
||||
FieldName = AncestorsSelectorIndexer.FieldName,
|
||||
Values = Array.Empty<string>()
|
||||
};
|
||||
}
|
||||
|
||||
var ancestorKeys = contentItem.Ancestors().Select(a => a.Key.ToString("D")).ToArray();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Examine;
|
||||
using Examine;
|
||||
using Examine.Search;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -27,6 +27,7 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider
|
||||
public ApiContentQueryProvider(
|
||||
IExamineManager examineManager,
|
||||
ContentIndexHandlerCollection indexHandlers,
|
||||
IApiContentQueryFactory apiContentQueryFactory,
|
||||
IOptions<DeliveryApiSettings> deliveryApiSettings,
|
||||
ILogger<ApiContentQueryProvider> logger)
|
||||
{
|
||||
@@ -41,7 +42,7 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider
|
||||
|
||||
// for the time being we're going to keep these as internal implementation details.
|
||||
// perhaps later on it will make sense to expose them through the DI.
|
||||
_selectorBuilder = new ApiContentQuerySelectorBuilder(deliveryApiSettings.Value);
|
||||
_selectorBuilder = new ApiContentQuerySelectorBuilder(deliveryApiSettings.Value, apiContentQueryFactory);
|
||||
_filterBuilder = new ApiContentQueryFilterBuilder(fieldTypes, _logger);
|
||||
_sortBuilder = new ApiContentQuerySortBuilder(fieldTypes, _logger);
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
// invalid filter
|
||||
_logger.LogInformation($"The \"{nameof(filters)}\" query option \"{filter}\" is not valid");
|
||||
_logger.LogInformation("An invalid filter option was encountered. Please ensure that supplied filter options are two-part, separated by ':'.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
break;
|
||||
default:
|
||||
// unknown filter
|
||||
_logger.LogInformation($"The \"{nameof(filters)}\" query option \"{filter}\" is not supported");
|
||||
_logger.LogInformation("An unsupported filter option was supplied for the query. Please use only valid filter options. See the documentation for details.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
// invalid sort
|
||||
_logger.LogInformation($"The \"{nameof(sorts)}\" query option \"{sort}\" is not valid");
|
||||
_logger.LogInformation("An invalid sort option was encountered. Please ensure that the supplied sort options are two-part, separated by ':'.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
break;
|
||||
default:
|
||||
// unknown sort
|
||||
_logger.LogInformation($"The \"{nameof(sorts)}\" query option \"{sort}\" is not supported");
|
||||
_logger.LogInformation("An unsupported sort option was supplied for the query. Please use only valid sort options. See the documentation for details.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using OpenIddict.Abstractions;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
// NOTE: this is public and unsealed to allow overriding the default claims with minimal effort.
|
||||
public class CurrentMemberClaimsProvider : ICurrentMemberClaimsProvider
|
||||
{
|
||||
private readonly IMemberManager _memberManager;
|
||||
|
||||
public CurrentMemberClaimsProvider(IMemberManager memberManager)
|
||||
=> _memberManager = memberManager;
|
||||
|
||||
public virtual async Task<Dictionary<string, object>> GetClaimsAsync()
|
||||
{
|
||||
MemberIdentityUser? memberIdentityUser = await _memberManager.GetCurrentMemberAsync();
|
||||
return memberIdentityUser is not null
|
||||
? await GetClaimsForMemberIdentityAsync(memberIdentityUser)
|
||||
: throw new InvalidOperationException("Could not retrieve the current member. This method should only ever be invoked when a member has been authorized.");
|
||||
}
|
||||
|
||||
protected virtual async Task<Dictionary<string, object>> GetClaimsForMemberIdentityAsync(MemberIdentityUser memberIdentityUser)
|
||||
{
|
||||
var claims = new Dictionary<string, object>
|
||||
{
|
||||
[OpenIddictConstants.Claims.Subject] = memberIdentityUser.Key
|
||||
};
|
||||
|
||||
if (memberIdentityUser.Name is not null)
|
||||
{
|
||||
claims[OpenIddictConstants.Claims.Name] = memberIdentityUser.Name;
|
||||
}
|
||||
|
||||
if (memberIdentityUser.Email is not null)
|
||||
{
|
||||
claims[OpenIddictConstants.Claims.Email] = memberIdentityUser.Email;
|
||||
}
|
||||
|
||||
claims[OpenIddictConstants.Claims.Role] = await _memberManager.GetRolesAsync(memberIdentityUser);
|
||||
|
||||
return claims;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Examine;
|
||||
using Examine.Lucene.Providers;
|
||||
using Examine.Lucene.Search;
|
||||
using Examine.Search;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services.QueryBuilders
|
||||
{
|
||||
internal sealed class ApiContentQueryFactory : IApiContentQueryFactory
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IQuery CreateApiContentQuery(IIndex index)
|
||||
{
|
||||
// Needed for enabling leading wildcards searches
|
||||
BaseLuceneSearcher searcher = index.Searcher as BaseLuceneSearcher ?? throw new InvalidOperationException($"Index searcher must be of type {nameof(BaseLuceneSearcher)}.");
|
||||
|
||||
IQuery query = searcher.CreateQuery(
|
||||
IndexTypes.Content,
|
||||
BooleanOperation.And,
|
||||
searcher.LuceneAnalyzer,
|
||||
new LuceneSearchOptions { AllowLeadingWildcard = true });
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-11
@@ -1,6 +1,4 @@
|
||||
using Examine;
|
||||
using Examine.Lucene.Providers;
|
||||
using Examine.Lucene.Search;
|
||||
using Examine.Search;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
@@ -13,11 +11,15 @@ namespace Umbraco.Cms.Api.Delivery.Services.QueryBuilders;
|
||||
internal sealed class ApiContentQuerySelectorBuilder
|
||||
{
|
||||
private readonly DeliveryApiSettings _deliveryApiSettings;
|
||||
private readonly IApiContentQueryFactory _queryFactory;
|
||||
private readonly string _fallbackGuidValue;
|
||||
|
||||
public ApiContentQuerySelectorBuilder(DeliveryApiSettings deliveryApiSettings)
|
||||
public ApiContentQuerySelectorBuilder(
|
||||
DeliveryApiSettings deliveryApiSettings,
|
||||
IApiContentQueryFactory queryFactory)
|
||||
{
|
||||
_deliveryApiSettings = deliveryApiSettings;
|
||||
_queryFactory = queryFactory;
|
||||
|
||||
// A fallback value is needed for Examine queries in case we don't have a value - we can't pass null or empty string
|
||||
// It is set to a random guid since this would be highly unlikely to yield any results
|
||||
@@ -26,14 +28,7 @@ internal sealed class ApiContentQuerySelectorBuilder
|
||||
|
||||
public IBooleanOperation Build(SelectorOption selectorOption, IIndex index, string culture, ProtectedAccess protectedAccess, bool preview)
|
||||
{
|
||||
// Needed for enabling leading wildcards searches
|
||||
BaseLuceneSearcher searcher = index.Searcher as BaseLuceneSearcher ?? throw new InvalidOperationException($"Index searcher must be of type {nameof(BaseLuceneSearcher)}.");
|
||||
|
||||
IQuery query = searcher.CreateQuery(
|
||||
IndexTypes.Content,
|
||||
BooleanOperation.And,
|
||||
searcher.LuceneAnalyzer,
|
||||
new LuceneSearchOptions { AllowLeadingWildcard = true });
|
||||
IQuery query = _queryFactory.CreateApiContentQuery(index);
|
||||
|
||||
IBooleanOperation selectorOperation = selectorOption.Values.Length == 1
|
||||
? query.Field(selectorOption.FieldName, selectorOption.Values.First())
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using Examine;
|
||||
using Examine.Search;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services.QueryBuilders
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to create an <see cref="IQuery"/> instance for content items for the content delivery api.
|
||||
/// </summary>
|
||||
public interface IApiContentQueryFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IQuery"/> for content items for the content delivery api.
|
||||
/// </summary>
|
||||
/// <param name="index">The <see cref="IIndex"/>.</param>
|
||||
/// <returns>An <see cref="IQuery"/> instance.</returns>
|
||||
IQuery CreateApiContentQuery(IIndex index);
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,5 @@ internal abstract class RequestHeaderHandler
|
||||
|
||||
protected RequestHeaderHandler(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;
|
||||
|
||||
protected string? GetHeaderValue(string headerName)
|
||||
{
|
||||
HttpContext httpContext = _httpContextAccessor.HttpContext ??
|
||||
throw new InvalidOperationException("Could not obtain an HTTP context");
|
||||
|
||||
return httpContext.Request.Headers[headerName];
|
||||
}
|
||||
protected string? GetHeaderValue(string headerName) => _httpContextAccessor.HttpContext?.Request.Headers[headerName];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
@@ -11,5 +11,5 @@ internal sealed class RequestPreviewService : RequestHeaderHandler, IRequestPrev
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsPreview() => GetHeaderValue("Preview") == "true";
|
||||
public bool IsPreview() => string.Equals(GetHeaderValue("Preview"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
@@ -42,31 +42,42 @@ internal sealed class RequestRedirectService : RoutingServiceBase, IRequestRedir
|
||||
{
|
||||
requestedPath = requestedPath.EnsureStartsWith("/");
|
||||
|
||||
IPublishedContent? startItem = GetStartItem();
|
||||
|
||||
// must append the root content url segment if it is not hidden by config, because
|
||||
// the URL tracking is based on the actual URL, including the root content url segment
|
||||
if (_globalSettings.HideTopLevelNodeFromPath == false)
|
||||
if (_globalSettings.HideTopLevelNodeFromPath == false && startItem?.UrlSegment != null)
|
||||
{
|
||||
IPublishedContent? startItem = GetStartItem();
|
||||
if (startItem?.UrlSegment != null)
|
||||
{
|
||||
requestedPath = $"{startItem.UrlSegment.EnsureStartsWith("/")}{requestedPath}";
|
||||
}
|
||||
requestedPath = $"{startItem.UrlSegment.EnsureStartsWith("/")}{requestedPath}";
|
||||
}
|
||||
|
||||
var culture = _requestCultureService.GetRequestedCulture();
|
||||
|
||||
// append the configured domain content ID to the path if we have a domain bound request,
|
||||
// because URL tracking registers the tracked url like "{domain content ID}/{content path}"
|
||||
Uri contentRoute = GetDefaultRequestUri(requestedPath);
|
||||
DomainAndUri? domainAndUri = GetDomainAndUriForRoute(contentRoute);
|
||||
if (domainAndUri != null)
|
||||
// important: redirect URLs are always tracked without trailing slashes
|
||||
requestedPath = requestedPath.TrimEnd("/");
|
||||
IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath, culture);
|
||||
|
||||
// if a redirect URL was not found, try by appending the start item ID because URL tracking might have tracked
|
||||
// a redirect with "{root content ID}/{content path}"
|
||||
if (redirectUrl is null && startItem is not null)
|
||||
{
|
||||
requestedPath = GetContentRoute(domainAndUri, contentRoute);
|
||||
culture ??= domainAndUri.Culture;
|
||||
redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl($"{startItem.Id}{requestedPath}", culture);
|
||||
}
|
||||
|
||||
// still no redirect URL found - try looking for a configured domain if we have a domain bound request,
|
||||
// because URL tracking might have tracked a redirect with "{domain content ID}/{content path}"
|
||||
if (redirectUrl is null)
|
||||
{
|
||||
Uri contentRoute = GetDefaultRequestUri(requestedPath);
|
||||
DomainAndUri? domainAndUri = GetDomainAndUriForRoute(contentRoute);
|
||||
if (domainAndUri is not null)
|
||||
{
|
||||
requestedPath = GetContentRoute(domainAndUri, contentRoute);
|
||||
culture ??= domainAndUri.Culture;
|
||||
redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath, culture);
|
||||
}
|
||||
}
|
||||
|
||||
// important: redirect URLs are always tracked without trailing slashes
|
||||
IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath.TrimEnd("/"), culture);
|
||||
IPublishedContent? content = redirectUrl != null
|
||||
? _apiPublishedContentCache.GetById(redirectUrl.ContentKey)
|
||||
: null;
|
||||
|
||||
@@ -36,7 +36,7 @@ internal abstract class RoutingServiceBase
|
||||
}
|
||||
|
||||
protected static string GetContentRoute(DomainAndUri domainAndUri, Uri contentRoute)
|
||||
=> $"{domainAndUri.ContentId}{DomainUtilities.PathRelativeToDomain(domainAndUri.Uri, contentRoute.AbsolutePath)}";
|
||||
=> $"{domainAndUri.ContentId}{DomainUtilities.PathRelativeToDomain(domainAndUri.Uri, contentRoute.LocalPath)}"; // Use LocalPath over AbsolutePath to keep the path decoded.
|
||||
|
||||
protected DomainAndUri? GetDomainAndUriForRoute(Uri contentUrl)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Umbraco.Tests.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Umbraco.Tests.Integration</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>DynamicProxyGenAssembly2</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Headers;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -48,16 +49,26 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions<Ima
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
int width = context.Parser.ParseValue<int>(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture);
|
||||
if (width <= 0 || width > _imagingSettings.Resize.MaxWidth)
|
||||
if (context.Commands.Contains(ResizeWebProcessor.Width))
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Width);
|
||||
if (!int.TryParse(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), NumberStyles.Integer,
|
||||
CultureInfo.InvariantCulture, out var width)
|
||||
|| width < 0
|
||||
|| width >= _imagingSettings.Resize.MaxWidth)
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Width);
|
||||
}
|
||||
}
|
||||
|
||||
int height = context.Parser.ParseValue<int>(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture);
|
||||
if (height <= 0 || height > _imagingSettings.Resize.MaxHeight)
|
||||
if (context.Commands.Contains(ResizeWebProcessor.Height))
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Height);
|
||||
if (!int.TryParse(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), NumberStyles.Integer,
|
||||
CultureInfo.InvariantCulture, out var height)
|
||||
|| height < 0
|
||||
|| height >= _imagingSettings.Resize.MaxHeight)
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Height);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.6, 3)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.10, 3)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" VersionOverride="[2.0.2, 3)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+8
@@ -7,7 +7,15 @@
|
||||
<ItemGroup>
|
||||
<!-- Take top-level depedendency on Azure.Identity, because Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version -->
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<!-- Both Azure.Identity, Microsoft.EntityFrameworkCore.SqlServer,NPoco.SqlServer, and more bring in a vulnerable version of System.Text.Json -->
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
<!-- Both Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer bring in a vulnerable version of Microsoft.Data.SqlClient -->
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Persistence.EFCore.Migrations;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Cms.Persistence.EFCore;
|
||||
|
||||
namespace Umbraco.Cms.Persistence.EFCore.Sqlite;
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Cms.Persistence.EFCore;
|
||||
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class ProviderNames
|
||||
{
|
||||
public const string SQLLite = "Microsoft.Data.Sqlite";
|
||||
|
||||
public const string SQLServer = "Microsoft.Data.SqlClient";
|
||||
}
|
||||
}
|
||||
+14
-14
@@ -16,8 +16,8 @@ namespace Umbraco.Cms.Persistence.EFCore.Locking;
|
||||
internal class SqlServerEFCoreDistributedLockingMechanism<T> : IDistributedLockingMechanism
|
||||
where T : DbContext
|
||||
{
|
||||
private readonly IOptionsMonitor<ConnectionStrings> _connectionStrings;
|
||||
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
|
||||
private ConnectionStrings _connectionStrings;
|
||||
private GlobalSettings _globalSettings;
|
||||
private readonly ILogger<SqlServerEFCoreDistributedLockingMechanism<T>> _logger;
|
||||
private readonly Lazy<IEFCoreScopeAccessor<T>> _scopeAccessor; // Hooray it's a circular dependency.
|
||||
|
||||
@@ -32,27 +32,29 @@ internal class SqlServerEFCoreDistributedLockingMechanism<T> : IDistributedLocki
|
||||
{
|
||||
_logger = logger;
|
||||
_scopeAccessor = scopeAccessor;
|
||||
_globalSettings = globalSettings;
|
||||
_connectionStrings = connectionStrings;
|
||||
_globalSettings = globalSettings.CurrentValue;
|
||||
_connectionStrings = connectionStrings.CurrentValue;
|
||||
globalSettings.OnChange(x=>_globalSettings = x);
|
||||
connectionStrings.OnChange(x=>_connectionStrings = x);
|
||||
}
|
||||
|
||||
public bool HasActiveRelatedScope => _scopeAccessor.Value.AmbientScope is not null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() &&
|
||||
string.Equals(_connectionStrings.CurrentValue.ProviderName, "Microsoft.Data.SqlClient", StringComparison.InvariantCultureIgnoreCase) && _scopeAccessor.Value.AmbientScope is not null;
|
||||
public bool Enabled => _connectionStrings.IsConnectionStringConfigured() &&
|
||||
string.Equals(_connectionStrings.ProviderName, "Microsoft.Data.SqlClient", StringComparison.InvariantCultureIgnoreCase) && _scopeAccessor.Value.AmbientScope is not null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null)
|
||||
{
|
||||
obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingReadLockDefaultTimeout;
|
||||
obtainLockTimeout ??= _globalSettings.DistributedLockingReadLockDefaultTimeout;
|
||||
return new SqlServerDistributedLock(this, lockId, DistributedLockType.ReadLock, obtainLockTimeout.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDistributedLock WriteLock(int lockId, TimeSpan? obtainLockTimeout = null)
|
||||
{
|
||||
obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingWriteLockDefaultTimeout;
|
||||
obtainLockTimeout ??= _globalSettings.DistributedLockingWriteLockDefaultTimeout;
|
||||
return new SqlServerDistributedLock(this, lockId, DistributedLockType.WriteLock, obtainLockTimeout.Value);
|
||||
}
|
||||
|
||||
@@ -136,9 +138,7 @@ internal class SqlServerEFCoreDistributedLockingMechanism<T> : IDistributedLocki
|
||||
"A transaction with minimum ReadCommitted isolation level is required.");
|
||||
}
|
||||
|
||||
await dbContext.Database.ExecuteSqlRawAsync($"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};");
|
||||
|
||||
var number = await dbContext.Database.ExecuteScalarAsync<int?>($"SELECT value FROM dbo.umbracoLock WITH (REPEATABLEREAD) WHERE id={LockId}");
|
||||
var number = await dbContext.Database.ExecuteScalarAsync<int?>($"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};SELECT value FROM dbo.umbracoLock WITH (REPEATABLEREAD) WHERE id={LockId}");
|
||||
|
||||
if (number == null)
|
||||
{
|
||||
@@ -170,9 +170,9 @@ internal class SqlServerEFCoreDistributedLockingMechanism<T> : IDistributedLocki
|
||||
"A transaction with minimum ReadCommitted isolation level is required.");
|
||||
}
|
||||
|
||||
await dbContext.Database.ExecuteSqlRawAsync($"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};");
|
||||
|
||||
var rowsAffected = await dbContext.Database.ExecuteSqlAsync(@$"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id={LockId}");
|
||||
#pragma warning disable EF1002
|
||||
var rowsAffected = await dbContext.Database.ExecuteSqlRawAsync(@$"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id={LockId}");
|
||||
#pragma warning restore EF1002
|
||||
|
||||
if (rowsAffected == 0)
|
||||
{
|
||||
|
||||
+10
-8
@@ -16,8 +16,8 @@ namespace Umbraco.Cms.Persistence.EFCore.Locking;
|
||||
internal class SqliteEFCoreDistributedLockingMechanism<T> : IDistributedLockingMechanism
|
||||
where T : DbContext
|
||||
{
|
||||
private readonly IOptionsMonitor<ConnectionStrings> _connectionStrings;
|
||||
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
|
||||
private ConnectionStrings _connectionStrings;
|
||||
private GlobalSettings _globalSettings;
|
||||
private readonly ILogger<SqliteEFCoreDistributedLockingMechanism<T>> _logger;
|
||||
private readonly Lazy<IEFCoreScopeAccessor<T>> _efCoreScopeAccessor;
|
||||
|
||||
@@ -29,27 +29,29 @@ internal class SqliteEFCoreDistributedLockingMechanism<T> : IDistributedLockingM
|
||||
{
|
||||
_logger = logger;
|
||||
_efCoreScopeAccessor = efCoreScopeAccessor;
|
||||
_connectionStrings = connectionStrings;
|
||||
_globalSettings = globalSettings;
|
||||
_globalSettings = globalSettings.CurrentValue;
|
||||
_connectionStrings = connectionStrings.CurrentValue;
|
||||
globalSettings.OnChange(x=>_globalSettings = x);
|
||||
connectionStrings.OnChange(x=>_connectionStrings = x);
|
||||
}
|
||||
|
||||
public bool HasActiveRelatedScope => _efCoreScopeAccessor.Value.AmbientScope is not null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() &&
|
||||
string.Equals(_connectionStrings.CurrentValue.ProviderName, "Microsoft.Data.Sqlite", StringComparison.InvariantCultureIgnoreCase) && _efCoreScopeAccessor.Value.AmbientScope is not null;
|
||||
public bool Enabled => _connectionStrings.IsConnectionStringConfigured() &&
|
||||
string.Equals(_connectionStrings.ProviderName, "Microsoft.Data.Sqlite", StringComparison.InvariantCultureIgnoreCase) && _efCoreScopeAccessor.Value.AmbientScope is not null;
|
||||
|
||||
// With journal_mode=wal we can always read a snapshot.
|
||||
public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null)
|
||||
{
|
||||
obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingReadLockDefaultTimeout;
|
||||
obtainLockTimeout ??= _globalSettings.DistributedLockingReadLockDefaultTimeout;
|
||||
return new SqliteDistributedLock(this, lockId, DistributedLockType.ReadLock, obtainLockTimeout.Value);
|
||||
}
|
||||
|
||||
// With journal_mode=wal only a single write transaction can exist at a time.
|
||||
public IDistributedLock WriteLock(int lockId, TimeSpan? obtainLockTimeout = null)
|
||||
{
|
||||
obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingWriteLockDefaultTimeout;
|
||||
obtainLockTimeout ??= _globalSettings.DistributedLockingWriteLockDefaultTimeout;
|
||||
return new SqliteDistributedLock(this, lockId, DistributedLockType.WriteLock, obtainLockTimeout.Value);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
<ItemGroup>
|
||||
<!-- Take top-level depedendency on Azure.Identity, because Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version -->
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
|
||||
<!-- Take top-level depedendency on Microsoft.Extensions.Caching.Memory, because Microsoft.EntityFrameworkCore.* depends on a vulnerable version -->
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
|
||||
<!-- Both Azure.Identity, Microsoft.EntityFrameworkCore.SqlServer,NPoco.SqlServer, and more bring in a vulnerable version of System.Text.Json -->
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
<!-- Both Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer bring in a vulnerable version of Microsoft.Data.SqlClient -->
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
<PackageReference Include="OpenIddict.EntityFrameworkCore" />
|
||||
|
||||
@@ -36,19 +36,14 @@ public class UmbracoDbContext : DbContext
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
public UmbracoDbContext(DbContextOptions<UmbracoDbContext> options)
|
||||
: base(ConfigureOptions(options, out IOptionsMonitor<ConnectionStrings>? connectionStringsOptionsMonitor))
|
||||
: base(ConfigureOptions(options))
|
||||
{
|
||||
connectionStringsOptionsMonitor.OnChange(c =>
|
||||
{
|
||||
ILogger<UmbracoDbContext> logger = StaticServiceProvider.Instance.GetRequiredService<ILogger<UmbracoDbContext>>();
|
||||
logger.LogWarning("Connection string changed, disposing context");
|
||||
Dispose();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static DbContextOptions<UmbracoDbContext> ConfigureOptions(DbContextOptions<UmbracoDbContext> options, out IOptionsMonitor<ConnectionStrings> connectionStringsOptionsMonitor)
|
||||
private static DbContextOptions<UmbracoDbContext> ConfigureOptions(DbContextOptions<UmbracoDbContext> options)
|
||||
{
|
||||
connectionStringsOptionsMonitor = StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<ConnectionStrings>>();
|
||||
IOptionsMonitor<ConnectionStrings> connectionStringsOptionsMonitor = StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<ConnectionStrings>>();
|
||||
|
||||
ConnectionStrings connectionStrings = connectionStringsOptionsMonitor.CurrentValue;
|
||||
|
||||
|
||||
+17
-12
@@ -17,8 +17,8 @@ namespace Umbraco.Cms.Persistence.SqlServer.Services;
|
||||
/// </summary>
|
||||
public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
|
||||
{
|
||||
private readonly IOptionsMonitor<ConnectionStrings> _connectionStrings;
|
||||
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
|
||||
private ConnectionStrings _connectionStrings;
|
||||
private GlobalSettings _globalSettings;
|
||||
private readonly ILogger<SqlServerDistributedLockingMechanism> _logger;
|
||||
private readonly Lazy<IScopeAccessor> _scopeAccessor; // Hooray it's a circular dependency.
|
||||
|
||||
@@ -33,25 +33,28 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
|
||||
{
|
||||
_logger = logger;
|
||||
_scopeAccessor = scopeAccessor;
|
||||
_globalSettings = globalSettings;
|
||||
_connectionStrings = connectionStrings;
|
||||
_globalSettings = globalSettings.CurrentValue;
|
||||
_connectionStrings = connectionStrings.CurrentValue;
|
||||
globalSettings.OnChange(x => _globalSettings = x);
|
||||
connectionStrings.OnChange(x => _connectionStrings = x);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() &&
|
||||
string.Equals(_connectionStrings.CurrentValue.ProviderName,Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase);
|
||||
public bool Enabled => _connectionStrings.IsConnectionStringConfigured() &&
|
||||
string.Equals(_connectionStrings.ProviderName,Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null)
|
||||
{
|
||||
obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingReadLockDefaultTimeout;
|
||||
obtainLockTimeout ??= _globalSettings.DistributedLockingReadLockDefaultTimeout;
|
||||
return new SqlServerDistributedLock(this, lockId, DistributedLockType.ReadLock, obtainLockTimeout.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDistributedLock WriteLock(int lockId, TimeSpan? obtainLockTimeout = null)
|
||||
{
|
||||
obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingWriteLockDefaultTimeout;
|
||||
obtainLockTimeout ??= _globalSettings.DistributedLockingWriteLockDefaultTimeout;
|
||||
return new SqlServerDistributedLock(this, lockId, DistributedLockType.WriteLock, obtainLockTimeout.Value);
|
||||
}
|
||||
|
||||
@@ -143,9 +146,10 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
|
||||
|
||||
const string query = "SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id";
|
||||
|
||||
db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");
|
||||
var lockTimeoutQuery = $"SET LOCK_TIMEOUT {_timeout.TotalMilliseconds}";
|
||||
|
||||
var i = db.ExecuteScalar<int?>(query, new { id = LockId });
|
||||
// execute the lock timeout query and the actual query in a single server roundtrip
|
||||
var i = db.ExecuteScalar<int?>($"{lockTimeoutQuery};{query}", new { id = LockId });
|
||||
|
||||
if (i == null)
|
||||
{
|
||||
@@ -178,9 +182,10 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
|
||||
const string query =
|
||||
@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id";
|
||||
|
||||
db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");
|
||||
var lockTimeoutQuery = $"SET LOCK_TIMEOUT {_timeout.TotalMilliseconds}";
|
||||
|
||||
var i = db.Execute(query, new { id = LockId });
|
||||
// execute the lock timeout query and the actual query in a single server roundtrip
|
||||
var i = db.Execute($"{lockTimeoutQuery};{query}", new { id = LockId });
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,15 @@
|
||||
<ItemGroup>
|
||||
<!-- Take top-level depedendency on Azure.Identity, because NPoco.SqlServer depends on a vulnerable version -->
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<!-- Both Azure.Identity, Microsoft.EntityFrameworkCore.SqlServer,NPoco.SqlServer, and more bring in a vulnerable version of System.Text.Json -->
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
<!-- Both Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer bring in a vulnerable version of Microsoft.Data.SqlClient -->
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
|
||||
<PackageReference Include="NPoco.SqlServer" />
|
||||
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace Umbraco.Cms.Persistence.Sqlite.Services;
|
||||
|
||||
public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
|
||||
{
|
||||
private readonly IOptionsMonitor<ConnectionStrings> _connectionStrings;
|
||||
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
|
||||
private ConnectionStrings _connectionStrings;
|
||||
private GlobalSettings _globalSettings;
|
||||
private readonly ILogger<SqliteDistributedLockingMechanism> _logger;
|
||||
private readonly Lazy<IScopeAccessor> _scopeAccessor;
|
||||
|
||||
@@ -29,25 +29,27 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
|
||||
{
|
||||
_logger = logger;
|
||||
_scopeAccessor = scopeAccessor;
|
||||
_connectionStrings = connectionStrings;
|
||||
_globalSettings = globalSettings;
|
||||
_connectionStrings = connectionStrings.CurrentValue;
|
||||
_globalSettings = globalSettings.CurrentValue;
|
||||
globalSettings.OnChange(x=>_globalSettings = x);
|
||||
connectionStrings.OnChange(x=>_connectionStrings = x);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() &&
|
||||
string.Equals(_connectionStrings.CurrentValue.ProviderName, Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase);
|
||||
public bool Enabled => _connectionStrings.IsConnectionStringConfigured() &&
|
||||
string.Equals(_connectionStrings.ProviderName, Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
// With journal_mode=wal we can always read a snapshot.
|
||||
public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null)
|
||||
{
|
||||
obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingReadLockDefaultTimeout;
|
||||
obtainLockTimeout ??= _globalSettings.DistributedLockingReadLockDefaultTimeout;
|
||||
return new SqliteDistributedLock(this, lockId, DistributedLockType.ReadLock, obtainLockTimeout.Value);
|
||||
}
|
||||
|
||||
// With journal_mode=wal only a single write transaction can exist at a time.
|
||||
public IDistributedLock WriteLock(int lockId, TimeSpan? obtainLockTimeout = null)
|
||||
{
|
||||
obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingWriteLockDefaultTimeout;
|
||||
obtainLockTimeout ??= _globalSettings.DistributedLockingWriteLockDefaultTimeout;
|
||||
return new SqliteDistributedLock(this, lockId, DistributedLockType.WriteLock, obtainLockTimeout.Value);
|
||||
}
|
||||
|
||||
@@ -162,7 +164,7 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
|
||||
|
||||
try
|
||||
{
|
||||
var i = command.ExecuteNonQuery();
|
||||
var i = db.ExecuteNonQuery(command);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
|
||||
@@ -12,34 +12,56 @@
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<BellePath>$(ProjectDir)wwwroot\umbraco</BellePath>
|
||||
<BasePath>$(ProjectDir)wwwroot\umbraco</BasePath>
|
||||
<BellePath>$(BasePath)\lib</BellePath>
|
||||
<LoginPath>$(BasePath)\login</LoginPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="BuildBellePreconditions" BeforeTargets="Build">
|
||||
<Target Name="BuildStaticAssetsPreconditions" BeforeTargets="Build">
|
||||
<Message Text="Skip BuildBelle target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
|
||||
<Message Text="Skip BuildBelle target because '$(BellePath)' already exists" Importance="high" Condition="Exists('$(BellePath)')" />
|
||||
<Message Text="Call BuildBelle target because UmbracoBuild is empty (this is Visual Studio) and '$(BellePath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BellePath)')" />
|
||||
<CallTarget Targets="BuildBelle" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BellePath)')" />
|
||||
|
||||
<Message Text="Skip BuildLogin target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
|
||||
<Message Text="Skip BuildLogin target because '$(LoginPath)' already exists" Importance="high" Condition="Exists('$(LoginPath)')" />
|
||||
<Message Text="Call BuildLogin target because UmbracoBuild is empty (this is Visual Studio) and '$(LoginPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginPath)')" />
|
||||
<CallTarget Targets="BuildLogin" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginPath)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildBelle">
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Login\" Command="npm ci --no-fund --no-audit --prefer-offline" />
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Login\" Command="npm run build" />
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Client\" Command="npm ci --no-fund --no-audit --prefer-offline" />
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Client\" Command="npm run build:skip-tests" />
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Client\" Command="npm ci --no-fund --no-audit --prefer-offline" Timeout="600000" />
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Client\" Command="npm run build:skip-tests" Timeout="600000" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanBellePreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
|
||||
<Target Name="BuildLogin">
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Login\" Command="npm ci --no-fund --no-audit --prefer-offline" Timeout="600000" />
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Login\" Command="npm run build" Timeout="600000" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
|
||||
<Message Text="Skip CleanBelle target because '$(BellePath)' doesn't exist" Importance="high" Condition="!Exists('$(BellePath)')" />
|
||||
<Message Text="Skip CleanBelle target because preserve.belle marker file exists" Importance="high" Condition="Exists('$(BellePath)') and Exists('$(SolutionDir)preserve.belle')" />
|
||||
<Message Text="Call CleanBelle target because '$(BellePath)' exists and preserve.belle marker file doesn't exist" Importance="high" Condition="Exists('$(BellePath)') and !Exists('$(SolutionDir)preserve.belle')" />
|
||||
<CallTarget Targets="CleanBelle" Condition="Exists('$(BellePath)') and !Exists('$(SolutionDir)preserve.belle')" />
|
||||
|
||||
<Message Text="Skip CleanLogin target because '$(LoginPath)' doesn't exist" Importance="high" Condition="!Exists('$(LoginPath)')" />
|
||||
<Message Text="Skip CleanLogin target because preserve.login marker file exists" Importance="high" Condition="Exists('$(LoginPath)') and Exists('$(SolutionDir)preserve.login')" />
|
||||
<Message Text="Call CleanLogin target because '$(LoginPath)' exists and preserve.login marker file doesn't exist" Importance="high" Condition="Exists('$(LoginPath)') and !Exists('$(SolutionDir)preserve.login')" />
|
||||
<CallTarget Targets="CleanLogin" Condition="Exists('$(LoginPath)') and !Exists('$(SolutionDir)preserve.login')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanBelle">
|
||||
<ItemGroup>
|
||||
<BelleLib Include="$(BellePath)" />
|
||||
<BelleDirectories Include="$(BasePath)/js;$(BasePath)/lib;$(BasePath)/assets;$(BasePath)/views;" />
|
||||
</ItemGroup>
|
||||
<RemoveDir Directories="@(BelleLib)" />
|
||||
<RemoveDir Directories="@(BelleDirectories)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanLogin">
|
||||
<ItemGroup>
|
||||
<LoginDirectories Include="$(LoginPath)" />
|
||||
</ItemGroup>
|
||||
<RemoveDir Directories="@(LoginDirectories)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -132,3 +132,4 @@
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
var disableLocalLogin = ExternalLogins.HasDenyLocalLogin();
|
||||
var externalLoginsUrl = LinkGenerator.GetPathByAction(nameof(BackOfficeController.ExternalLogin), ControllerExtensions.GetControllerName<BackOfficeController>(), new { area = Constants.Web.Mvc.BackOfficeArea });
|
||||
var externalLoginProviders = await ExternalLogins.GetBackOfficeProvidersAsync();
|
||||
var externalSignInErrors = ViewData.GetExternalSignInProviderErrors();
|
||||
}
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
||||
@@ -106,6 +107,15 @@
|
||||
custom-view="@provider.ExternalLoginProvider.Options.CustomBackOfficeView">
|
||||
</umb-external-login-provider>
|
||||
}
|
||||
@if (externalSignInErrors?.Errors?.Count() > 0)
|
||||
{
|
||||
<div slot="subheadline" style="color:var(--uui-color-danger-standalone)">
|
||||
@foreach (var error in externalSignInErrors.Errors)
|
||||
{
|
||||
<div>@error</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</umb-auth>
|
||||
</umb-backoffice-icon-registry>
|
||||
|
||||
|
||||
+1
-1
@@ -21,5 +21,5 @@
|
||||
|
||||
</style>
|
||||
|
||||
<div class="text" ng-click="block.edit()" ng-focus="block.focus" ng-bind-html="block.data.richText.markup" style="margin: 0 20px;">
|
||||
<div class="text" ng-click="block.edit()" ng-focus="block.focus" ng-bind-html="block.data.richText.markup | safe_html" style="margin: 0 20px;">
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<!-- Generate JSON schema on build (and before copying to project) -->
|
||||
<Target Name="GenerateAppsettingsSchema" BeforeTargets="Build;CopyUmbracoJsonSchemaFiles" Condition="!Exists('$(_UmbracoCmsJsonSchemaReference)')">
|
||||
<Message Text="Generating $(_UmbracoCmsJsonSchemaReference) because it doesn't exist" Importance="high" />
|
||||
<Exec WorkingDirectory="$(MSBuildThisFileDirectory)..\..\tools\Umbraco.JsonSchema" Command="dotnet run --configuration $(Configuration) -- --outputFile "$(MSBuildThisFileDirectory)$(_UmbracoCmsJsonSchemaReference)"" />
|
||||
<Exec WorkingDirectory="$(MSBuildThisFileDirectory)..\..\tools\Umbraco.JsonSchema" Command="dotnet run --configuration $(Configuration) -- --outputFile "$(MSBuildThisFileDirectory)$(_UmbracoCmsJsonSchemaReference)"" Timeout="600000" />
|
||||
</Target>
|
||||
|
||||
<!-- Remove generated JSON schema on clean -->
|
||||
|
||||
@@ -43,7 +43,7 @@ public static class AppCacheExtensions
|
||||
public static T? GetCacheItem<T>(this IAppCache provider, string cacheKey)
|
||||
{
|
||||
var result = provider.Get(cacheKey);
|
||||
if (result == null)
|
||||
if (IsRetrievedItemNull(result))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
@@ -54,11 +54,13 @@ public static class AppCacheExtensions
|
||||
public static T? GetCacheItem<T>(this IAppCache provider, string cacheKey, Func<T> getCacheItem)
|
||||
{
|
||||
var result = provider.Get(cacheKey, () => getCacheItem());
|
||||
if (result == null)
|
||||
if (IsRetrievedItemNull(result))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return result.TryConvertTo<T>().Result;
|
||||
}
|
||||
|
||||
private static bool IsRetrievedItemNull(object? result) => result is null or (object)Cms.Core.Constants.Cache.NullRepresentationInCache;
|
||||
}
|
||||
|
||||
@@ -22,4 +22,6 @@ public static class CacheKeys
|
||||
|
||||
public const string ContentRecycleBinCacheKey = "recycleBin_content";
|
||||
public const string MediaRecycleBinCacheKey = "recycleBin_media";
|
||||
|
||||
public const string MemberUserNameCachePrefix = "uRepo_userNameKey+";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Cache;
|
||||
|
||||
/// <summary>
|
||||
/// This cache is a temporary measure to reduce the amount of computational power required to deserialize and initialize <see cref="IDataType" /> when fetched from the main cache/database,
|
||||
/// because datatypes are fetched multiple times troughout a (backoffice content) request with a lot of content (or nested content) and each of these fetches initializes certain fields on the datatypes.
|
||||
/// </summary>
|
||||
internal sealed class DataTypeConfigurationCache : IDataTypeConfigurationCache
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
|
||||
public DataTypeConfigurationCache(IDataTypeService dataTypeService, IMemoryCache memoryCache, IIdKeyMap idKeyMap)
|
||||
{
|
||||
_dataTypeService = dataTypeService;
|
||||
_memoryCache = memoryCache;
|
||||
}
|
||||
|
||||
public T? GetConfigurationAs<T>(Guid key)
|
||||
where T : class
|
||||
{
|
||||
var cacheKey = GetCacheKey(key);
|
||||
if (_memoryCache.TryGetValue(cacheKey, out T? configuration) is false)
|
||||
{
|
||||
IDataType? dataType = _dataTypeService.GetDataType(key);
|
||||
configuration = dataType?.ConfigurationAs<T>();
|
||||
|
||||
// Only cache if data type was found (but still cache null configurations)
|
||||
if (dataType is not null)
|
||||
{
|
||||
_memoryCache.Set(cacheKey, configuration);
|
||||
}
|
||||
}
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void ClearCache(IEnumerable<Guid> keys)
|
||||
{
|
||||
foreach (Guid key in keys)
|
||||
{
|
||||
_memoryCache.Remove(GetCacheKey(key));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetCacheKey(Guid key) => $"DataTypeConfigurationCache_{key}";
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
|
||||
namespace Umbraco.Cms.Core.Cache;
|
||||
|
||||
internal sealed class DataTypeConfigurationCacheRefresher : INotificationHandler<DataTypeCacheRefresherNotification>
|
||||
{
|
||||
private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache;
|
||||
|
||||
public DataTypeConfigurationCacheRefresher(IDataTypeConfigurationCache dataTypeConfigurationCache)
|
||||
=> _dataTypeConfigurationCache = dataTypeConfigurationCache;
|
||||
|
||||
public void Handle(DataTypeCacheRefresherNotification notification)
|
||||
=> _dataTypeConfigurationCache.ClearCache(((DataTypeCacheRefresher.JsonPayload[])notification.MessageObject).Select(x => x.Key));
|
||||
}
|
||||
@@ -118,15 +118,37 @@ public static class DistributedCacheExtensions
|
||||
#region ContentCacheRefresher
|
||||
|
||||
public static void RefreshAllContentCache(this DistributedCache dc)
|
||||
{
|
||||
ContentCacheRefresher.JsonPayload[] payloads = new[]
|
||||
{
|
||||
new ContentCacheRefresher.JsonPayload()
|
||||
{
|
||||
ChangeTypes = TreeChangeTypes.RefreshAll
|
||||
}
|
||||
};
|
||||
|
||||
// note: refresh all content cache does refresh content types too
|
||||
=> dc.RefreshByPayload(ContentCacheRefresher.UniqueId, new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll).Yield());
|
||||
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
|
||||
}
|
||||
|
||||
[Obsolete("Use the overload accepting IEnumerable instead to avoid allocating arrays. This overload will be removed in Umbraco 13.")]
|
||||
public static void RefreshContentCache(this DistributedCache dc, TreeChange<IContent>[] changes)
|
||||
=> dc.RefreshContentCache(changes.AsEnumerable());
|
||||
|
||||
public static void RefreshContentCache(this DistributedCache dc, IEnumerable<TreeChange<IContent>> changes)
|
||||
=> dc.RefreshByPayload(ContentCacheRefresher.UniqueId, changes.DistinctBy(x => (x.Item.Id, x.Item.Key, x.ChangeTypes)).Select(x => new ContentCacheRefresher.JsonPayload(x.Item.Id, x.Item.Key, x.ChangeTypes)));
|
||||
{
|
||||
IEnumerable<ContentCacheRefresher.JsonPayload> payloads = changes.Select(x => new ContentCacheRefresher.JsonPayload()
|
||||
{
|
||||
Id = x.Item.Id,
|
||||
Key = x.Item.Key,
|
||||
ChangeTypes = x.ChangeTypes,
|
||||
Blueprint = x.Item.Blueprint,
|
||||
PublishedCultures = x.PublishedCultures?.ToArray(),
|
||||
UnpublishedCultures = x.UnpublishedCultures?.ToArray()
|
||||
});
|
||||
|
||||
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace Umbraco.Cms.Core.Cache;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a cache for <see cref="Umbraco.Cms.Core.Models.IDataType" /> configuration.
|
||||
/// </summary>
|
||||
public interface IDataTypeConfigurationCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the data type configuration.
|
||||
/// </summary>
|
||||
/// <param name="key">The data type key.</param>
|
||||
/// <returns>
|
||||
/// The data type configuration.
|
||||
/// </returns>
|
||||
object? GetConfiguration(Guid key) => GetConfigurationAs<object>(key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data type configuration as <typeparamref name="T" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The data type configuration type.</typeparam>
|
||||
/// <param name="key">The data type key.</param>
|
||||
/// <returns>
|
||||
/// The data type configuration as <typeparamref name="T" />.
|
||||
/// </returns>
|
||||
T? GetConfigurationAs<T>(Guid key)
|
||||
where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// Clears the cache for the specified keys.
|
||||
/// </summary>
|
||||
/// <param name="keys">The keys.</param>
|
||||
void ClearCache(IEnumerable<Guid> keys);
|
||||
}
|
||||
@@ -9,6 +9,9 @@ namespace Umbraco.Cms.Core.Cache;
|
||||
/// </summary>
|
||||
public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
private static readonly TimeSpan _readLockTimeout = TimeSpan.FromSeconds(5);
|
||||
private static readonly TimeSpan _writeLockTimeout = TimeSpan.FromSeconds(5);
|
||||
|
||||
private readonly ReaderWriterLockSlim _locker = new(LockRecursionPolicy.SupportsRecursion);
|
||||
private bool _disposedValue;
|
||||
|
||||
@@ -33,7 +36,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
Lazy<object?>? result;
|
||||
try
|
||||
{
|
||||
_locker.EnterReadLock();
|
||||
if (_locker.TryEnterReadLock(_readLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when getting item");
|
||||
}
|
||||
result = MemoryCache.Get(key) as Lazy<object?>; // null if key not found
|
||||
}
|
||||
finally
|
||||
@@ -195,7 +201,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when clearing item");
|
||||
}
|
||||
if (MemoryCache[key] == null)
|
||||
{
|
||||
return;
|
||||
@@ -223,8 +232,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
var isInterface = type.IsInterface;
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when clearing by type");
|
||||
}
|
||||
// ToArray required to remove
|
||||
foreach (var key in MemoryCache
|
||||
.Where(x =>
|
||||
@@ -259,7 +270,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when clearing by generic type");
|
||||
}
|
||||
Type typeOfT = typeof(T);
|
||||
var isInterface = typeOfT.IsInterface;
|
||||
|
||||
@@ -296,7 +310,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when clearing generic type with predicate");
|
||||
}
|
||||
Type typeOfT = typeof(T);
|
||||
var isInterface = typeOfT.IsInterface;
|
||||
|
||||
@@ -338,7 +355,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when clearing with prefix");
|
||||
}
|
||||
|
||||
// ToArray required to remove
|
||||
foreach (var key in MemoryCache
|
||||
@@ -365,7 +385,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cach when clearing by regex");
|
||||
}
|
||||
|
||||
// ToArray required to remove
|
||||
foreach (var key in MemoryCache
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
namespace Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators;
|
||||
|
||||
/// <summary>
|
||||
/// Defines behaviours for clearing of cached partials views that are configured to be cached individually by member.
|
||||
/// </summary>
|
||||
public interface IMemberPartialViewCacheInvalidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears the partial view cache items for the specified member ids.
|
||||
/// </summary>
|
||||
/// <param name="memberIds">The member Ids to clear the cache for.</param>
|
||||
/// <remarks>
|
||||
/// Called from the <see cref="MemberCacheRefresher"/> when a member is saved or deleted.
|
||||
/// </remarks>
|
||||
void ClearPartialViewCacheItems(IEnumerable<int> memberIds);
|
||||
}
|
||||
@@ -84,8 +84,8 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
isolatedCache.ClearOfType<IContent>((k, v) => v.Path?.Contains(pathid) ?? false);
|
||||
}
|
||||
|
||||
// if the item is being completely removed, we need to refresh the domains cache if any domain was assigned to the content
|
||||
if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove))
|
||||
// if the item is not a blueprint and is being completely removed, we need to refresh the domains cache if any domain was assigned to the content
|
||||
if (payload.Blueprint is false && payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove))
|
||||
{
|
||||
idsRemoved.Add(payload.Id);
|
||||
}
|
||||
@@ -120,7 +120,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
// should rename it, and then, this is only for Deploy, and then, ???
|
||||
// if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache)
|
||||
// ...
|
||||
NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads);
|
||||
if (payloads.Any(x => x.Blueprint is false))
|
||||
{
|
||||
// Only notify if the payload contains actual (non-blueprint) contents
|
||||
NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads);
|
||||
}
|
||||
|
||||
base.Refresh(payloads);
|
||||
}
|
||||
@@ -157,8 +161,13 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (V14): Change into a record
|
||||
public class JsonPayload
|
||||
{
|
||||
public JsonPayload()
|
||||
{ }
|
||||
|
||||
[Obsolete("Use the default constructor and property initializers.")]
|
||||
public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes)
|
||||
{
|
||||
Id = id;
|
||||
@@ -166,11 +175,17 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
ChangeTypes = changeTypes;
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
public int Id { get; init; }
|
||||
|
||||
public Guid? Key { get; }
|
||||
public Guid? Key { get; init; }
|
||||
|
||||
public TreeChangeTypes ChangeTypes { get; }
|
||||
public TreeChangeTypes ChangeTypes { get; init; }
|
||||
|
||||
public bool Blueprint { get; init; }
|
||||
|
||||
public string[]? PublishedCultures { get; init; }
|
||||
|
||||
public string[]? UnpublishedCultures { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -134,8 +134,14 @@ public sealed class LanguageCacheRefresher : PayloadCacheRefresherBase<LanguageC
|
||||
ContentCacheRefresher.RefreshContentTypes(AppCaches); // we need to evict all IContent items
|
||||
|
||||
// now refresh all nucache
|
||||
ContentCacheRefresher.JsonPayload[] clearContentPayload =
|
||||
new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) };
|
||||
ContentCacheRefresher.JsonPayload[] clearContentPayload = new[]
|
||||
{
|
||||
new ContentCacheRefresher.JsonPayload()
|
||||
{
|
||||
ChangeTypes = TreeChangeTypes.RefreshAll
|
||||
}
|
||||
};
|
||||
|
||||
ContentCacheRefresher.NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, clearContentPayload);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// using Newtonsoft.Json;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
@@ -15,10 +18,37 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase<MemberCache
|
||||
public static readonly Guid UniqueId = Guid.Parse("E285DF34-ACDC-4226-AE32-C0CB5CF388DA");
|
||||
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
private readonly IMemberPartialViewCacheInvalidator _memberPartialViewCacheInvalidator;
|
||||
|
||||
public MemberCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IIdKeyMap idKeyMap, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory)
|
||||
: base(appCaches, serializer, eventAggregator, factory) =>
|
||||
[Obsolete("Use the non obsoleted constructor instead. Scheduled for removal in v17")]
|
||||
public MemberCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
IIdKeyMap idKeyMap,
|
||||
IEventAggregator eventAggregator,
|
||||
ICacheRefresherNotificationFactory factory)
|
||||
: this(
|
||||
appCaches,
|
||||
serializer,
|
||||
idKeyMap,
|
||||
eventAggregator,
|
||||
factory,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IMemberPartialViewCacheInvalidator>())
|
||||
{
|
||||
}
|
||||
|
||||
public MemberCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
IIdKeyMap idKeyMap,
|
||||
IEventAggregator eventAggregator,
|
||||
ICacheRefresherNotificationFactory factory,
|
||||
IMemberPartialViewCacheInvalidator memberPartialViewCacheInvalidator)
|
||||
: base(appCaches, serializer, eventAggregator, factory)
|
||||
{
|
||||
_idKeyMap = idKeyMap;
|
||||
_memberPartialViewCacheInvalidator = memberPartialViewCacheInvalidator;
|
||||
}
|
||||
|
||||
#region Indirect
|
||||
|
||||
@@ -67,17 +97,30 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase<MemberCache
|
||||
|
||||
private void ClearCache(params JsonPayload[] payloads)
|
||||
{
|
||||
AppCaches.ClearPartialViewCache();
|
||||
// Clear the partial views cache for all partials that are cached by member, for the updates members.
|
||||
_memberPartialViewCacheInvalidator.ClearPartialViewCacheItems(payloads.Select(p => p.Id));
|
||||
|
||||
Attempt<IAppPolicyCache?> memberCache = AppCaches.IsolatedCaches.Get<IMember>();
|
||||
|
||||
foreach (JsonPayload p in payloads)
|
||||
{
|
||||
_idKeyMap.ClearCache(p.Id);
|
||||
if (memberCache.Success)
|
||||
if (memberCache.Success is false)
|
||||
{
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
|
||||
continue;
|
||||
}
|
||||
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
|
||||
|
||||
// This specific cache key was introduced to fix an issue where the member username could not be the same as the member id, because the cache keys collided.
|
||||
// This is done in a bit of a hacky way, because the cache key is created internally in the repository, but we need to clear it here.
|
||||
// Ideally, we want to use a shared way of generating the key between this and the repository.
|
||||
// Additionally, the RepositoryCacheKeys actually caches the string to avoid re-allocating memory; we would like to also use this in the repository
|
||||
// See:
|
||||
// https://github.com/umbraco/Umbraco-CMS/pull/17350
|
||||
// https://github.com/umbraco/Umbraco-CMS/pull/17815
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(CacheKeys.MemberUserNameCachePrefix + p.Username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ public class RepositoryCachePolicyOptions
|
||||
public RepositoryCachePolicyOptions(Func<int> performCount)
|
||||
{
|
||||
PerformCount = performCount;
|
||||
CacheNullValues = false;
|
||||
GetAllCacheValidateCount = true;
|
||||
GetAllCacheAllowZeroCount = false;
|
||||
}
|
||||
@@ -21,6 +22,7 @@ public class RepositoryCachePolicyOptions
|
||||
public RepositoryCachePolicyOptions()
|
||||
{
|
||||
PerformCount = null;
|
||||
CacheNullValues = false;
|
||||
GetAllCacheValidateCount = false;
|
||||
GetAllCacheAllowZeroCount = false;
|
||||
}
|
||||
@@ -30,6 +32,11 @@ public class RepositoryCachePolicyOptions
|
||||
/// </summary>
|
||||
public Func<int>? PerformCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the Get method will cache null results so that the db is not hit for repeated lookups
|
||||
/// </summary>
|
||||
public bool CacheNullValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True/false as to validate the total item count when all items are returned from cache, the default is true but this
|
||||
/// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Collections;
|
||||
namespace Umbraco.Cms.Core.Composing;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a base class for builder collections.
|
||||
/// Provides a base class for builder collections.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items.</typeparam>
|
||||
public abstract class BuilderCollectionBase<TItem> : IBuilderCollection<TItem>
|
||||
@@ -11,21 +11,18 @@ public abstract class BuilderCollectionBase<TItem> : IBuilderCollection<TItem>
|
||||
private readonly LazyReadOnlyCollection<TItem> _items;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BuilderCollectionBase{TItem}" /> with items.
|
||||
/// Initializes a new instance of the <see cref="BuilderCollectionBase{TItem}" /> with items.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public BuilderCollectionBase(Func<IEnumerable<TItem>> items) => _items = new LazyReadOnlyCollection<TItem>(items);
|
||||
public BuilderCollectionBase(Func<IEnumerable<TItem>> items)
|
||||
=> _items = new LazyReadOnlyCollection<TItem>(items);
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _items.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<TItem> GetEnumerator() => _items.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
namespace Umbraco.Cms.Core.Composing;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder collection, ie an immutable enumeration of items.
|
||||
/// Represents a builder collection, ie an immutable enumeration of items.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items.</typeparam>
|
||||
public interface IBuilderCollection<out TItem> : IEnumerable<TItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the number of items in the collection.
|
||||
/// Gets the number of items in the collection.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The count.
|
||||
/// </value>
|
||||
int Count { get; }
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public class TypeFinder : ITypeFinder
|
||||
"ServiceStack.", "SqlCE4Umbraco,", "Superpower,", // used by Serilog
|
||||
"System.", "TidyNet,", "TidyNet.", "WebDriver,", "itextsharp,", "mscorlib,", "NUnit,", "NUnit.", "NUnit3.",
|
||||
"Selenium.", "ImageProcessor", "MiniProfiler.", "Owin,", "SQLite",
|
||||
"ReSharperTestRunner32", "ReSharperTestRunner64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension
|
||||
"ReSharperTestRunner", "ReSharperTestRunner32", "ReSharperTestRunner64", "ReSharperTestRunnerArm32", "ReSharperTestRunnerArm64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension
|
||||
};
|
||||
|
||||
private static readonly ConcurrentDictionary<string, Type?> TypeNamesCache = new();
|
||||
|
||||
@@ -160,6 +160,7 @@ public class ContentSettings
|
||||
internal const bool StaticDisableUnpublishWhenReferenced = false;
|
||||
internal const bool StaticAllowEditInvariantFromNonDefault = false;
|
||||
internal const bool StaticShowDomainWarnings = true;
|
||||
internal const bool StaticShowUnroutableContentWarnings = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the content notification settings.
|
||||
@@ -285,4 +286,10 @@ public class ContentSettings
|
||||
/// </summary>
|
||||
[DefaultValue(StaticShowDomainWarnings)]
|
||||
public bool ShowDomainWarnings { get; set; } = StaticShowDomainWarnings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show unroutable content warnings.
|
||||
/// </summary>
|
||||
[DefaultValue(StaticShowUnroutableContentWarnings)]
|
||||
public bool ShowUnroutableContentWarnings { get; set; } = StaticShowUnroutableContentWarnings;
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ public class GlobalSettings
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to install the database when it is missing.
|
||||
/// </summary>
|
||||
[Obsolete("This option will be removed in V16.")]
|
||||
[DefaultValue(StaticInstallMissingDatabase)]
|
||||
public bool InstallMissingDatabase { get; set; } = StaticInstallMissingDatabase;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ public class ModelsBuilderSettings
|
||||
internal const string StaticModelsDirectory = "~/umbraco/models";
|
||||
internal const bool StaticAcceptUnsafeModelsDirectory = false;
|
||||
internal const int StaticDebugLevel = 0;
|
||||
internal const bool StaticIncludeVersionNumberInGeneratedModels = true;
|
||||
private bool _flagOutOfDateModels = true;
|
||||
|
||||
/// <summary>
|
||||
@@ -78,4 +79,16 @@ public class ModelsBuilderSettings
|
||||
/// <remarks>0 means minimal (safe on live site), anything else means more and more details (maybe not safe).</remarks>
|
||||
[DefaultValue(StaticDebugLevel)]
|
||||
public int DebugLevel { get; set; } = StaticDebugLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the version number should be included in generated models.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default this is written to the <see cref="System.CodeDom.Compiler.GeneratedCodeAttribute"/> output in
|
||||
/// generated code for each property of the model. This can be useful for debugging purposes but isn't essential,
|
||||
/// and it has the causes the generated code to change every time Umbraco is upgraded. In turn, this leads
|
||||
/// to unnecessary code file changes that need to be checked into source control. Default is <c>true</c>.
|
||||
/// </remarks>
|
||||
[DefaultValue(StaticIncludeVersionNumberInGeneratedModels)]
|
||||
public bool IncludeVersionNumberInGeneratedModels { get; set; } = StaticIncludeVersionNumberInGeneratedModels;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class RequestHandlerSettings
|
||||
{
|
||||
internal const bool StaticAddTrailingSlash = true;
|
||||
internal const string StaticConvertUrlsToAscii = "try";
|
||||
internal const string StaticConvertFileNamesToAscii = "false";
|
||||
internal const bool StaticEnableDefaultCharReplacements = true;
|
||||
|
||||
internal static readonly CharItem[] DefaultCharCollection =
|
||||
@@ -73,6 +74,22 @@ public class RequestHandlerSettings
|
||||
/// </summary>
|
||||
public bool ShouldTryConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("try");
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to convert file names to ASCII (valid values: "true", "try" or "false").
|
||||
/// </summary>
|
||||
[DefaultValue(StaticConvertFileNamesToAscii)]
|
||||
public string ConvertFileNamesToAscii { get; set; } = StaticConvertFileNamesToAscii;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether URLs should be converted to ASCII.
|
||||
/// </summary>
|
||||
public bool ShouldConvertFileNamesToAscii => ConvertFileNamesToAscii.InvariantEquals("true");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether URLs should be tried to be converted to ASCII.
|
||||
/// </summary>
|
||||
public bool ShouldTryConvertFileNamesToAscii => ConvertFileNamesToAscii.InvariantEquals("try");
|
||||
|
||||
/// <summary>
|
||||
/// Disable all default character replacements
|
||||
/// </summary>
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Umbraco.Cms.Core.Configuration.Models;
|
||||
public class RichTextEditorSettings
|
||||
{
|
||||
internal const string StaticValidElements =
|
||||
"+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-s[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*],figure,figcaption,cite,video[*],audio[*],picture[*],source[*],canvas[*]";
|
||||
"+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-s[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*],figure,figcaption,cite,video[*],audio[*],picture[*],source[*],canvas[*],code";
|
||||
|
||||
internal const string StaticInvalidElements = "font";
|
||||
|
||||
@@ -146,6 +146,11 @@ public class RichTextEditorSettings
|
||||
[DefaultValue(StaticInvalidElements)]
|
||||
public string InvalidElements { get; set; } = StaticInvalidElements;
|
||||
|
||||
/// <summary>
|
||||
/// Cloud API Key for TinyMCE. This is required to use TinyMCE premium plugins.
|
||||
/// </summary>
|
||||
public string? CloudApiKey { get; set; }
|
||||
|
||||
public class RichTextEditorCommand
|
||||
{
|
||||
[Required]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models;
|
||||
|
||||
@@ -19,12 +20,16 @@ public class SecuritySettings
|
||||
internal const bool StaticAllowEditInvariantFromNonDefault = false;
|
||||
internal const bool StaticAllowConcurrentLogins = false;
|
||||
internal const string StaticAuthCookieName = "UMB_UCONTEXT";
|
||||
internal const bool StaticUsernameIsEmail = true;
|
||||
internal const bool StaticMemberRequireUniqueEmail = true;
|
||||
|
||||
internal const string StaticAllowedUserNameCharacters =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+\\";
|
||||
|
||||
internal const int StaticMemberDefaultLockoutTimeInMinutes = 30 * 24 * 60;
|
||||
internal const int StaticUserDefaultLockoutTimeInMinutes = 30 * 24 * 60;
|
||||
internal const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000;
|
||||
internal const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to keep the user logged in.
|
||||
@@ -58,7 +63,14 @@ public class SecuritySettings
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the user's email address is to be considered as their username.
|
||||
/// </summary>
|
||||
public bool UsernameIsEmail { get; set; } = true;
|
||||
[DefaultValue(StaticUsernameIsEmail)]
|
||||
public bool UsernameIsEmail { get; set; } = StaticUsernameIsEmail;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the member's email address must be unique.
|
||||
/// </summary>
|
||||
[DefaultValue(StaticMemberRequireUniqueEmail)]
|
||||
public bool MemberRequireUniqueEmail { get; set; } = StaticMemberRequireUniqueEmail;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the set of allowed characters for a username
|
||||
@@ -116,4 +128,28 @@ public class SecuritySettings
|
||||
/// </summary>
|
||||
[DefaultValue(StaticAllowConcurrentLogins)]
|
||||
public bool AllowConcurrentLogins { get; set; } = StaticAllowConcurrentLogins;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default duration (in milliseconds) of failed login attempts.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The default duration (in milliseconds) of failed login attempts.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// The user login endpoint ensures that failed login attempts take at least as long as the average successful login.
|
||||
/// However, if no successful logins have occurred, this value is used as the default duration.
|
||||
/// </remarks>
|
||||
[Range(0, int.MaxValue)] // TODO (V17): Change property type to short and update maximum range to short.MaxValue
|
||||
[DefaultValue(StaticUserDefaultFailedLoginDurationInMilliseconds)]
|
||||
public long UserDefaultFailedLoginDurationInMilliseconds { get; set; } = StaticUserDefaultFailedLoginDurationInMilliseconds;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum duration (in milliseconds) of failed login attempts.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The minimum duration (in milliseconds) of failed login attempts.
|
||||
/// </value>
|
||||
[Range(0, int.MaxValue)] // TODO (V17): Change property type to short and update maximum range to short.MaxValue
|
||||
[DefaultValue(StaticUserMinimumFailedLoginDurationInMilliseconds)]
|
||||
public long UserMinimumFailedLoginDurationInMilliseconds { get; set; } = StaticUserMinimumFailedLoginDurationInMilliseconds;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Umbraco.Cms.Core;
|
||||
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the string used to represent a null value in the cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used in conjunction with the option to cache null values on the repository caches, so we
|
||||
/// can distinguish a true null "not found" value and a cached null value.</remarks>
|
||||
public const string NullRepresentationInCache = "*NULL*";
|
||||
}
|
||||
}
|
||||
@@ -32,5 +32,14 @@ public static partial class Constants
|
||||
public static string BackofficeExternalLoginProviderCount = "BackofficeExternalLoginProviderCount";
|
||||
public static string DeliverApiEnabled = "DeliverApiEnabled";
|
||||
public static string DeliveryApiPublicAccess = "DeliveryApiPublicAccess";
|
||||
public static string WebhookPrefix = "WebhookCount_";
|
||||
public static string WebhookTotal = $"{WebhookPrefix}Total";
|
||||
public static string WebhookCustomHeaders = $"{WebhookPrefix}CustomHeaders";
|
||||
public static string WebhookCustomEvent = $"{WebhookPrefix}CustomEvent";
|
||||
public static string RichTextEditorCount = "RichTextEditorCount";
|
||||
public static string RichTextBlockCount = "RichTextBlockCount";
|
||||
public static string TotalPropertyCount = "TotalPropertyCount";
|
||||
public static string HighestPropertyCount = "HighestPropertyCount";
|
||||
public static string TotalCompositions = "TotalCompositions";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ public static partial class Constants
|
||||
public const string ControllerToken = "controller";
|
||||
public const string ActionToken = "action";
|
||||
public const string AreaToken = "area";
|
||||
public const string DynamicRoutePattern = "/{**umbracoSlug}";
|
||||
}
|
||||
|
||||
public static class RoutePath
|
||||
|
||||
@@ -93,6 +93,316 @@ public static partial class Constants
|
||||
/// Webhook event alias for media save.
|
||||
/// </summary>
|
||||
public const string MediaSave = "Umbraco.MediaSave";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for document type changed.
|
||||
/// </summary>
|
||||
public const string DocumentTypeChanged = "documentTypeChanged";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for document type deleted.
|
||||
/// </summary>
|
||||
public const string DocumentTypeDeleted = "documentTypeDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for document type moved.
|
||||
/// </summary>
|
||||
public const string DocumentTypeMoved = "documentTypeMoved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for document type saved.
|
||||
/// </summary>
|
||||
public const string DocumentTypeSaved = "documentTypeSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for media type changed.
|
||||
/// </summary>
|
||||
public const string MediaTypeChanged = "mediaTypeChanged";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for media type deleted.
|
||||
/// </summary>
|
||||
public const string MediaTypeDeleted = "mediaTypeDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for media type moved.
|
||||
/// </summary>
|
||||
public const string MediaTypeMoved = "mediaTypeMoved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for media type saved.
|
||||
/// </summary>
|
||||
public const string MediaTypeSaved = "mediaTypeSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for member type changed.
|
||||
/// </summary>
|
||||
public const string MemberTypeChanged = "memberTypeChanged";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for member type deleted.
|
||||
/// </summary>
|
||||
public const string MemberTypeDeleted = "memberTypeDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for member type moved.
|
||||
/// </summary>
|
||||
public const string MemberTypeMoved = "memberTypeMoved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for member type saved.
|
||||
/// </summary>
|
||||
public const string MemberTypeSaved = "memberTypeSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for data type deleted.
|
||||
/// </summary>
|
||||
public const string DataTypeDeleted = "dataTypeDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for data type moved.
|
||||
/// </summary>
|
||||
public const string DataTypeMoved = "dataTypeMoved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for data type saved.
|
||||
/// </summary>
|
||||
public const string DataTypeSaved = "dataTypeSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for dictionary item deleted.
|
||||
/// </summary>
|
||||
public const string DictionaryItemDeleted = "dictionaryItemDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for dictionary item saved.
|
||||
/// </summary>
|
||||
public const string DictionaryItemSaved = "dictionaryItemSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for domain deleted.
|
||||
/// </summary>
|
||||
public const string DomainDeleted = "domainDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for domain saved.
|
||||
/// </summary>
|
||||
public const string DomainSaved = "domainSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for partial view deleted.
|
||||
/// </summary>
|
||||
public const string PartialViewDeleted = "partialViewDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for partial view saved.
|
||||
/// </summary>
|
||||
public const string PartialViewSaved = "partialViewSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for script deleted.
|
||||
/// </summary>
|
||||
public const string ScriptDeleted = "scriptDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for script saved.
|
||||
/// </summary>
|
||||
public const string ScriptSaved = "scriptSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for stylesheet deleted.
|
||||
/// </summary>
|
||||
public const string StylesheetDeleted = "stylesheetDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for stylesheet saved.
|
||||
/// </summary>
|
||||
public const string StylesheetSaved = "stylesheetSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for template deleted.
|
||||
/// </summary>
|
||||
public const string TemplateDeleted = "templateDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for template saved.
|
||||
/// </summary>
|
||||
public const string TemplateSaved = "templateSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for health check completed.
|
||||
/// </summary>
|
||||
public const string HealthCheckCompleted = "healthCheckCompleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for language deleted.
|
||||
/// </summary>
|
||||
public const string LanguageDeleted = "languageDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for language saved.
|
||||
/// </summary>
|
||||
public const string LanguageSaved = "languageSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for media emptied recycle bin.
|
||||
/// </summary>
|
||||
public const string MediaEmptiedRecycleBin = "mediaEmptiedRecycleBin";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for media moved to recycle bin.
|
||||
/// </summary>
|
||||
public const string MediaMovedToRecycleBin = "mediaMovedToRecycleBin";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for media moved.
|
||||
/// </summary>
|
||||
public const string MediaMoved = "mediaMoved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for assigned member roles.
|
||||
/// </summary>
|
||||
public const string AssignedMemberRoles = "assignedMemberRoles";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for exported member.
|
||||
/// </summary>
|
||||
public const string ExportedMember = "exportedMember";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for member deleted.
|
||||
/// </summary>
|
||||
public const string MemberDeleted = "memberDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for member group deleted.
|
||||
/// </summary>
|
||||
public const string MemberGroupDeleted = "memberGroupDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for member group saved.
|
||||
/// </summary>
|
||||
public const string MemberGroupSaved = "memberGroupSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for member saved.
|
||||
/// </summary>
|
||||
public const string MemberSaved = "memberSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for removed member roles.
|
||||
/// </summary>
|
||||
public const string RemovedMemberRoles = "removedMemberRoles";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for package imported.
|
||||
/// </summary>
|
||||
public const string PackageImported = "packageImported";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for public access entry deleted.
|
||||
/// </summary>
|
||||
public const string PublicAccessEntryDeleted = "publicAccessEntryDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for public access entry saved.
|
||||
/// </summary>
|
||||
public const string PublicAccessEntrySaved = "publicAccessEntrySaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for relation deleted.
|
||||
/// </summary>
|
||||
public const string RelationDeleted = "relationDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for relation saved.
|
||||
/// </summary>
|
||||
public const string RelationSaved = "relationSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for relation type deleted.
|
||||
/// </summary>
|
||||
public const string RelationTypeDeleted = "relationTypeDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for relation type saved.
|
||||
/// </summary>
|
||||
public const string RelationTypeSaved = "relationTypeSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for assigned user group permissions;
|
||||
/// </summary>
|
||||
public const string AssignedUserGroupPermissions = "assignedUserGroupPermissions";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user deleted.
|
||||
/// </summary>
|
||||
public const string UserDeleted = "userDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user forgot password requested.
|
||||
/// </summary>
|
||||
public const string UserForgotPasswordRequested = "userForgotPasswordRequested";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user group deleted.
|
||||
/// </summary>
|
||||
public const string UserGroupDeleted = "UserGroupDeleted";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user group saved.
|
||||
/// </summary>
|
||||
public const string UserGroupSaved = "userGroupSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user locked.
|
||||
/// </summary>
|
||||
public const string UserLocked = "userLocked";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user login failed.
|
||||
/// </summary>
|
||||
public const string UserLoginFailed = "userLoginFailed";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user login requires verification.
|
||||
/// </summary>
|
||||
public const string UserLoginRequiresVerification = "userLoginRequiresVerification";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user login success.
|
||||
/// </summary>
|
||||
public const string UserLoginSuccess = "userLoginSuccess";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user logout success.
|
||||
/// </summary>
|
||||
public const string UserLogoutSuccess = "userLogoutSuccess";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user password changed.
|
||||
/// </summary>
|
||||
public const string UserPasswordChanged = "userPasswordChanged";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user password reset.
|
||||
/// </summary>
|
||||
public const string UserPasswordReset = "userPasswordReset";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user saved.
|
||||
/// </summary>
|
||||
public const string UserSaved = "userSaved";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user two factor requested.
|
||||
/// </summary>
|
||||
public const string UserTwoFactorRequested = "userTwoFactorRequested";
|
||||
|
||||
/// <summary>
|
||||
/// Webhook event alias for user unlocked.
|
||||
/// </summary>
|
||||
public const string UserUnlocked = "userUnlocked";
|
||||
}
|
||||
|
||||
public static class Types
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
// NOTE: left unsealed on purpose so it is extendable.
|
||||
public class ApiContentPathProvider : IApiContentPathProvider
|
||||
{
|
||||
private readonly IPublishedUrlProvider _publishedUrlProvider;
|
||||
|
||||
public ApiContentPathProvider(IPublishedUrlProvider publishedUrlProvider)
|
||||
=> _publishedUrlProvider = publishedUrlProvider;
|
||||
|
||||
public virtual string? GetContentPath(IPublishedContent content, string? culture)
|
||||
=> _publishedUrlProvider.GetUrl(content, UrlMode.Relative, culture);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
// NOTE: left unsealed on purpose so it is extendable.
|
||||
public class ApiContentPathResolver : IApiContentPathResolver
|
||||
{
|
||||
private readonly IRequestRoutingService _requestRoutingService;
|
||||
private readonly IApiPublishedContentCache _apiPublishedContentCache;
|
||||
|
||||
public ApiContentPathResolver(IRequestRoutingService requestRoutingService, IApiPublishedContentCache apiPublishedContentCache)
|
||||
{
|
||||
_requestRoutingService = requestRoutingService;
|
||||
_apiPublishedContentCache = apiPublishedContentCache;
|
||||
}
|
||||
|
||||
[Obsolete("No longer used in V15. Scheduled for removal in V15.")]
|
||||
public virtual bool IsResolvablePath(string path)
|
||||
{
|
||||
// File requests will blow up with an downstream exception in GetRequiredPublishedSnapshot, which fails due to an UmbracoContext
|
||||
// not being available for what's considered a static file request.
|
||||
// See: https://github.com/umbraco/Umbraco-CMS/issues/19051
|
||||
// Given a URL segment and hence route can't contain a period, we can safely assume that if the last segment of the path contains
|
||||
// a period, it's a file request and should return null here.
|
||||
if (IsFileRequest(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsFileRequest(string path) => path
|
||||
.Split('/', StringSplitOptions.RemoveEmptyEntries)
|
||||
.LastOrDefault()?
|
||||
.Contains('.') is true;
|
||||
|
||||
public virtual IPublishedContent? ResolveContentPath(string path)
|
||||
{
|
||||
path = path.EnsureStartsWith("/");
|
||||
|
||||
var contentRoute = _requestRoutingService.GetContentRoute(path);
|
||||
IPublishedContent? contentItem = _apiPublishedContentCache.GetByRoute(contentRoute);
|
||||
return contentItem;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
using Umbraco.Cms.Core.Models.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public sealed class ApiContentResponseBuilder : ApiContentBuilderBase<IApiContentResponse>, IApiContentResponseBuilder
|
||||
public class ApiContentResponseBuilder : ApiContentBuilderBase<IApiContentResponse>, IApiContentResponseBuilder
|
||||
{
|
||||
private readonly IApiContentRouteBuilder _apiContentRouteBuilder;
|
||||
|
||||
@@ -14,6 +13,12 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase<IApiConten
|
||||
=> _apiContentRouteBuilder = apiContentRouteBuilder;
|
||||
|
||||
protected override IApiContentResponse Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary<string, object?> properties)
|
||||
{
|
||||
IDictionary<string, IApiContentRoute> cultures = GetCultures(content);
|
||||
return new ApiContentResponse(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties, cultures);
|
||||
}
|
||||
|
||||
protected virtual IDictionary<string, IApiContentRoute> GetCultures(IPublishedContent content)
|
||||
{
|
||||
var routesByCulture = new Dictionary<string, IApiContentRoute>();
|
||||
|
||||
@@ -35,6 +40,6 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase<IApiConten
|
||||
routesByCulture[publishedCultureInfo.Culture] = cultureRoute;
|
||||
}
|
||||
|
||||
return new ApiContentResponse(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties, routesByCulture);
|
||||
return routesByCulture;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
@@ -10,13 +12,14 @@ namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
{
|
||||
private readonly IPublishedUrlProvider _publishedUrlProvider;
|
||||
private readonly IApiContentPathProvider _apiContentPathProvider;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IRequestPreviewService _requestPreviewService;
|
||||
private RequestHandlerSettings _requestSettings;
|
||||
|
||||
[Obsolete($"Use the constructor that does not accept {nameof(IPublishedUrlProvider)}. Will be removed in V15.")]
|
||||
public ApiContentRouteBuilder(
|
||||
IPublishedUrlProvider publishedUrlProvider,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
@@ -24,8 +27,32 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings)
|
||||
: this(StaticServiceProvider.Instance.GetRequiredService<IApiContentPathProvider>(), globalSettings, variationContextAccessor, publishedSnapshotAccessor, requestPreviewService, requestSettings)
|
||||
{
|
||||
_publishedUrlProvider = publishedUrlProvider;
|
||||
}
|
||||
|
||||
[Obsolete($"Use the constructor that does not accept {nameof(IPublishedUrlProvider)}. Will be removed in V15.")]
|
||||
public ApiContentRouteBuilder(
|
||||
IPublishedUrlProvider publishedUrlProvider,
|
||||
IApiContentPathProvider apiContentPathProvider,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings)
|
||||
: this(apiContentPathProvider, globalSettings, variationContextAccessor, publishedSnapshotAccessor, requestPreviewService, requestSettings)
|
||||
{
|
||||
}
|
||||
|
||||
public ApiContentRouteBuilder(
|
||||
IApiContentPathProvider apiContentPathProvider,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings)
|
||||
{
|
||||
_apiContentPathProvider = apiContentPathProvider;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
_requestPreviewService = requestPreviewService;
|
||||
@@ -72,7 +99,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
}
|
||||
|
||||
// grab the content path from the URL provider
|
||||
var contentPath = _publishedUrlProvider.GetUrl(content, UrlMode.Relative, culture);
|
||||
var contentPath = _apiContentPathProvider.GetContentPath(content, culture);
|
||||
|
||||
// in some scenarios the published content is actually routable, but due to the built-in handling of i.e. lacking culture setup
|
||||
// the URL provider resolves the content URL as empty string or "#". since the Delivery API handles routing explicitly,
|
||||
@@ -96,7 +123,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
|
||||
private string ContentPreviewPath(IPublishedContent content) => $"{Constants.DeliveryApi.Routing.PreviewContentPathPrefix}{content.Key:D}{(_requestSettings.AddTrailingSlash ? "/" : string.Empty)}";
|
||||
|
||||
private static bool IsInvalidContentPath(string path) => path.IsNullOrWhiteSpace() || "#".Equals(path);
|
||||
private static bool IsInvalidContentPath(string? path) => path.IsNullOrWhiteSpace() || "#".Equals(path);
|
||||
|
||||
private IPublishedContent GetRoot(IPublishedContent content, bool isPreview)
|
||||
{
|
||||
|
||||
@@ -17,6 +17,6 @@ public sealed class ApiMediaUrlProvider : IApiMediaUrlProvider
|
||||
throw new ArgumentException("Media URLs can only be generated from Media items.", nameof(media));
|
||||
}
|
||||
|
||||
return _publishedUrlProvider.GetMediaUrl(media, UrlMode.Relative);
|
||||
return _publishedUrlProvider.GetMediaUrl(media);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public interface IApiContentPathProvider
|
||||
{
|
||||
string? GetContentPath(IPublishedContent content, string? culture);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public interface IApiContentPathResolver
|
||||
{
|
||||
[Obsolete("No longer used in V15. Scheduled for removal in V15.")]
|
||||
bool IsResolvablePath(string path) => true;
|
||||
|
||||
IPublishedContent? ResolveContentPath(string path);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public interface ICurrentMemberClaimsProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the claims for the currently logged in member.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used by the OIDC user info endpoint to supply "current user" info.
|
||||
/// </remarks>
|
||||
Task<Dictionary<string, object>> GetClaimsAsync();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public class NoopCurrentMemberClaimsProvider : ICurrentMemberClaimsProvider
|
||||
{
|
||||
public Task<Dictionary<string, object>> GetClaimsAsync() => Task.FromResult(new Dictionary<string, object>());
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user