Compare commits
365
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 | ||
|
|
d8df405db4 | ||
|
|
ab32bac5d9 | ||
|
|
ba9ddd11da | ||
|
|
cfcdc9c98d | ||
|
|
23d0a6b9b2 | ||
|
|
fee222daff | ||
|
|
edb516f71a | ||
|
|
cae106bfe8 | ||
|
|
119fde2033 | ||
|
|
0980350172 | ||
|
|
a27a4dcd84 | ||
|
|
adc2435073 | ||
|
|
096991ac79 | ||
|
|
2bb56f1b81 | ||
|
|
e993c23d5c | ||
|
|
71e16d20ef | ||
|
|
682b7656bc | ||
|
|
fdea8c28c8 | ||
|
|
ce372abf8a | ||
|
|
52c21b0fca |
@@ -35,7 +35,7 @@
|
||||
<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>
|
||||
|
||||
+42
-32
@@ -5,35 +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.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1" />
|
||||
<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.1" />
|
||||
<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.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.1" />
|
||||
<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>
|
||||
@@ -45,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.2.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.2.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.57" />
|
||||
<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" />
|
||||
@@ -62,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.1" />
|
||||
<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.3" />
|
||||
<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>
|
||||
|
||||
+100
-24
@@ -71,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
|
||||
@@ -198,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
|
||||
@@ -249,6 +258,11 @@ stages:
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 1
|
||||
fetchFilter: tree:0
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download build artifacts
|
||||
inputs:
|
||||
@@ -288,6 +302,11 @@ stages:
|
||||
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
|
||||
@@ -319,7 +338,9 @@ stages:
|
||||
# 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:
|
||||
@@ -415,10 +436,24 @@ stages:
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: 'ubuntu-latest'
|
||||
Windows:
|
||||
vmImage: 'windows-latest'
|
||||
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:
|
||||
@@ -496,12 +531,12 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: npm run test --ignore-certificate-errors
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
@@ -522,18 +557,28 @@ stages:
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results $(Build.ArtifactStagingDirectory) -Recurse
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
# Publish test artifacts
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'Acceptance Tests - $(Agent.JobName) - 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)
|
||||
@@ -544,12 +589,30 @@ stages:
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: 'ubuntu-latest'
|
||||
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'
|
||||
Windows:
|
||||
vmImage: 'windows-latest'
|
||||
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:
|
||||
@@ -635,14 +698,13 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: npm run test --ignore-certificate-errors
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
@@ -670,18 +732,28 @@ stages:
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results $(Build.ArtifactStagingDirectory) -Recurse
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
# Publish test artifacts
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'Acceptance Tests - $(Agent.JobName) - 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)"
|
||||
|
||||
###############################################
|
||||
## Release
|
||||
@@ -695,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
|
||||
@@ -721,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -145,6 +145,11 @@ public class ByRouteContentApiController : ContentApiItemControllerBase
|
||||
path = DecodePath(path);
|
||||
path = path.Length == 0 ? "/" : path;
|
||||
|
||||
if (_apiContentPathResolver.IsResolvablePath(path) is false)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
IPublishedContent? contentItem = GetContent(path);
|
||||
if (contentItem is not 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);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ public static class UmbracoBuilderExtensions
|
||||
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;
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,10 +5,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.7, 3)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.10, 3)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" VersionOverride="[2.0.2, 3)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.7" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="2.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<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>
|
||||
|
||||
+13
-11
@@ -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);
|
||||
}
|
||||
|
||||
@@ -168,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" />
|
||||
|
||||
+11
-8
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildBelle">
|
||||
<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="BuildLogin">
|
||||
<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.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)' == ''">
|
||||
|
||||
@@ -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+";
|
||||
}
|
||||
|
||||
@@ -142,7 +142,9 @@ public static class DistributedCacheExtensions
|
||||
Id = x.Item.Id,
|
||||
Key = x.Item.Key,
|
||||
ChangeTypes = x.ChangeTypes,
|
||||
Blueprint = x.Item.Blueprint
|
||||
Blueprint = x.Item.Blueprint,
|
||||
PublishedCultures = x.PublishedCultures?.ToArray(),
|
||||
UnpublishedCultures = x.UnpublishedCultures?.ToArray()
|
||||
});
|
||||
|
||||
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
|
||||
|
||||
+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);
|
||||
}
|
||||
@@ -182,6 +182,10 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
public TreeChangeTypes ChangeTypes { get; init; }
|
||||
|
||||
public bool Blueprint { get; init; }
|
||||
|
||||
public string[]? PublishedCultures { get; init; }
|
||||
|
||||
public string[]? UnpublishedCultures { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
"ReSharperTestRunner", "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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,27 @@ public class ApiContentPathResolver : IApiContentPathResolver
|
||||
_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("/");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@ 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>());
|
||||
}
|
||||
@@ -126,7 +126,6 @@ public static partial class UmbracoBuilderExtensions
|
||||
// register OEmbed providers - no type scanning - all explicit opt-in of adding types, IEmbedProvider is not IDiscoverable
|
||||
builder.EmbedProviders()
|
||||
.Append<YouTube>()
|
||||
.Append<Twitter>()
|
||||
.Append<Vimeo>()
|
||||
.Append<DailyMotion>()
|
||||
.Append<Flickr>()
|
||||
@@ -138,7 +137,8 @@ public static partial class UmbracoBuilderExtensions
|
||||
.Append<Issuu>()
|
||||
.Append<Hulu>()
|
||||
.Append<Giphy>()
|
||||
.Append<LottieFiles>();
|
||||
.Append<LottieFiles>()
|
||||
.Append<X>();
|
||||
builder.SearchableTrees().Add(() => builder.TypeLoader.GetTypes<ISearchableTree>());
|
||||
builder.BackOfficeAssets();
|
||||
builder.SelectorHandlers().Add(() => builder.TypeLoader.GetTypes<ISelectorHandler>());
|
||||
|
||||
@@ -27,7 +27,9 @@ public class RootDynamicRootOriginFinder : IDynamicRootOriginFinder
|
||||
return null;
|
||||
}
|
||||
|
||||
var entity = _entityService.Get(query.Context.ParentKey);
|
||||
// when creating new content, CurrentKey will be null - fallback to using ParentKey
|
||||
Guid entityKey = query.Context.CurrentKey ?? query.Context.ParentKey;
|
||||
var entity = _entityService.Get(entityKey);
|
||||
|
||||
if (entity is null || _allowedObjectTypes.Contains(entity.NodeObjectType) is false)
|
||||
{
|
||||
|
||||
@@ -20,12 +20,14 @@ public class SiteDynamicRootOriginFinder : RootDynamicRootOriginFinder
|
||||
|
||||
public override Guid? FindOriginKey(DynamicRootNodeQuery query)
|
||||
{
|
||||
if (query.OriginAlias != SupportedOriginType || query.Context.CurrentKey.HasValue is false)
|
||||
if (query.OriginAlias != SupportedOriginType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
IEntitySlim? entity = _entityService.Get(query.Context.CurrentKey.Value);
|
||||
// when creating new content, CurrentKey will be null - fallback to using ParentKey
|
||||
Guid entityKey = query.Context.CurrentKey ?? query.Context.ParentKey;
|
||||
IEntitySlim? entity = _entityService.Get(entityKey);
|
||||
if (entity is null || entity.NodeObjectType != Constants.ObjectTypes.Document)
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -1469,6 +1469,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="publishWithMissingDomain">Der er ikke noget domæne konfigureret for %0%, kontakt vensligst en
|
||||
administrator, se loggen for mere information
|
||||
</key>
|
||||
<key alias="publishWithNoUrl">Dokumentet har ikke nogen URL, muligvis grundet en kollision med et andet dokuments navn. Flere detaljer kan ses under Info.</key>
|
||||
<key alias="copySuccessMessage">Dit systems information er blevet kopieret til udklipsholderen</key>
|
||||
<key alias="cannotCopyInformation">Kunne desværre ikke kopiere dit systems information til udklipsholderen</key>
|
||||
<key alias="webhookSaved">Webhook gemt</key>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -167,6 +167,7 @@
|
||||
<key alias="move">Media moved</key>
|
||||
<key alias="copy">Media copied</key>
|
||||
<key alias="save">Media saved</key>
|
||||
<key alias="new">Media created</key>
|
||||
</area>
|
||||
<area alias="auditTrails">
|
||||
<key alias="atViewingFor">Viewing for</key>
|
||||
@@ -190,6 +191,7 @@
|
||||
<key alias="smallPublishVariant">Publish</key>
|
||||
<key alias="smallMove">Move</key>
|
||||
<key alias="smallSave">Save</key>
|
||||
<key alias="smallNew">New</key>
|
||||
<key alias="smallSaveVariant">Save</key>
|
||||
<key alias="smallDelete">Delete</key>
|
||||
<key alias="smallUnpublish">Unpublish</key>
|
||||
@@ -292,6 +294,7 @@
|
||||
Content.
|
||||
</key>
|
||||
<key alias="nestedContentDeleteAllItems">Are you sure you want to delete all items?</key>
|
||||
<key alias="nestedContentDeleteItem">Are you sure you want to delete this item?</key>
|
||||
<key alias="nestedContentNoContentTypes">No Content Types are configured for this property.</key>
|
||||
<key alias="nestedContentAddElementType">Add Element Type</key>
|
||||
<key alias="nestedContentSelectElementTypeModalTitle">Select Element Type</key>
|
||||
@@ -329,6 +332,7 @@
|
||||
<key alias="schedulePublishHelp">Select the date and time to publish and/or unpublish the content item.</key>
|
||||
<key alias="createEmpty">Create new</key>
|
||||
<key alias="createFromClipboard">Paste from clipboard</key>
|
||||
<key alias="removeItem">Remove item</key>
|
||||
<key alias="nodeIsInTrash">This item is in the Recycle Bin</key>
|
||||
<key alias="noProperties">No content can be added for this item</key>
|
||||
<key alias="variantSaveNotAllowed">Save is not allowed</key>
|
||||
@@ -1700,6 +1704,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="publishWithMissingDomain">There is no domain configured for %0%, please contact an administrator, see
|
||||
log for more information
|
||||
</key>
|
||||
<key alias="publishWithNoUrl">The document does not have a URL, possibly due to a naming collision with another document. More details can be found under Info.</key>
|
||||
<key alias="copySuccessMessage">Your system information has successfully been copied to the clipboard</key>
|
||||
<key alias="cannotCopyInformation">Could not copy your system information to the clipboard</key>
|
||||
<key alias="webhookSaved">Webhook saved</key>
|
||||
@@ -3077,7 +3082,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="detailedLevelDescription"><![CDATA[We will send:
|
||||
<ul>
|
||||
<li>Anonymized site ID, Umbraco version, and packages installed.</li>
|
||||
<li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, and Property Editors in use.</li>
|
||||
<li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Property Types, Compositions, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, Webhooks, rich text datatypes, blocks used in rich text datatypes, and Property Editors in use.</li>
|
||||
<li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>
|
||||
<li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode.</li>
|
||||
</ul>
|
||||
|
||||
@@ -165,6 +165,7 @@
|
||||
<key alias="move">Media moved</key>
|
||||
<key alias="copy">Media copied</key>
|
||||
<key alias="save">Media saved</key>
|
||||
<key alias="new">Media created</key>
|
||||
</area>
|
||||
<area alias="auditTrails">
|
||||
<key alias="atViewingFor">Viewing for</key>
|
||||
@@ -189,6 +190,7 @@
|
||||
<key alias="smallPublishVariant">Publish</key>
|
||||
<key alias="smallMove">Move</key>
|
||||
<key alias="smallSave">Save</key>
|
||||
<key alias="smallNew">New</key>
|
||||
<key alias="smallSaveVariant">Save</key>
|
||||
<key alias="smallDelete">Delete</key>
|
||||
<key alias="smallUnpublish">Unpublish</key>
|
||||
@@ -288,6 +290,7 @@
|
||||
<![CDATA[<a href="https://docs.umbraco.com/umbraco-cms/fundamentals/data/scheduled-publishing#timezones" target="_blank" rel="noopener">What does this mean?</a>]]></key>
|
||||
<key alias="nestedContentDeleteItem">Are you sure you want to delete this item?</key>
|
||||
<key alias="nestedContentDeleteAllItems">Are you sure you want to delete all items?</key>
|
||||
<key alias="nestedContentDeleteItem">Are you sure you want to delete this item?</key>
|
||||
<key alias="nestedContentEditorNotSupported">Property %0% uses editor %1% which is not supported by Nested
|
||||
Content.
|
||||
</key>
|
||||
@@ -328,6 +331,7 @@
|
||||
<key alias="schedulePublishHelp">Select the date and time to publish and/or unpublish the content item.</key>
|
||||
<key alias="createEmpty">Create new</key>
|
||||
<key alias="createFromClipboard">Paste from clipboard</key>
|
||||
<key alias="removeItem">Remove item</key>
|
||||
<key alias="nodeIsInTrash">This item is in the Recycle Bin</key>
|
||||
<key alias="variantSaveNotAllowed">Save is not allowed</key>
|
||||
<key alias="variantPublishNotAllowed">Publish is not allowed</key>
|
||||
@@ -1732,6 +1736,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="publishWithMissingDomain">There is no domain configured for %0%, please contact an administrator, see
|
||||
log for more information
|
||||
</key>
|
||||
<key alias="publishWithNoUrl">The document does not have a URL, possibly due to a naming collision with another document. More details can be found under Info.</key>
|
||||
<key alias="preventCleanupEnableError">An error occurred while enabling version cleanup for %0%</key>
|
||||
<key alias="preventCleanupDisableError">An error occurred while disabling version cleanup for %0%</key>
|
||||
<key alias="copySuccessMessage">Your system information has successfully been copied to the clipboard</key>
|
||||
@@ -2035,7 +2040,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="webhookKey">Webhook key</key>
|
||||
<key alias="retryCount">Retry count</key>
|
||||
<key alias="toggleDebug">Toggle debug mode for more information.</key>
|
||||
<key alias="statusNotOk">Not OK status code</key>
|
||||
<key alias="statusNotOk">Not OK status code</key>
|
||||
<key alias="urlDescription">The url to call when the webhook is triggered.</key>
|
||||
<key alias="eventDescription">The events for which the webhook should be triggered.</key>
|
||||
<key alias="contentTypeDescription">Only trigger the webhook for a specific content type.</key>
|
||||
@@ -3096,7 +3101,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
We will send:
|
||||
<ul>
|
||||
<li>Anonymized site ID, Umbraco version, and packages installed.</li>
|
||||
<li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, and Property Editors in use.</li>
|
||||
<li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Property Types, Compositions, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, Webhooks, rich text datatypes, blocks used in rich text datatypes, and Property Editors in use.</li>
|
||||
<li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>
|
||||
<li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode.</li>
|
||||
</ul>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,9 +4,6 @@
|
||||
<name>The Umbraco community</name>
|
||||
<link>https://docs.umbraco.com/umbraco-cms/extending/language-files</link>
|
||||
</creator>
|
||||
<area alias="apps">
|
||||
<key alias="umbContent">Innehåll</key>
|
||||
</area>
|
||||
<area alias="actions">
|
||||
<key alias="assigndomain">Hantera domännamn</key>
|
||||
<key alias="auditTrail">Hantera versioner</key>
|
||||
@@ -14,13 +11,16 @@
|
||||
<key alias="changeDocType">Ändra dokumenttyp</key>
|
||||
<key alias="copy">Kopiera</key>
|
||||
<key alias="create">Skapa</key>
|
||||
<key alias="export">Exportera</key>
|
||||
<key alias="createGroup">Skapa grupp</key>
|
||||
<key alias="createPackage">Skapa paket</key>
|
||||
<key alias="createblueprint">Skapa innehållsmall</key>
|
||||
<key alias="defaultValue">Standardvärde</key>
|
||||
<key alias="delete">Ta bort</key>
|
||||
<key alias="disable">Avaktivera</key>
|
||||
<key alias="editSettings">Ändra inställningar</key>
|
||||
<key alias="emptyrecyclebin">Töm papperskorgen</key>
|
||||
<key alias="enable">Aktivera</key>
|
||||
<key alias="exportDocumentType">Exportera dokumenttyp</key>
|
||||
<key alias="importdocumenttype">Importera dokumenttyp</key>
|
||||
<key alias="importPackage">Importera paket</key>
|
||||
@@ -40,6 +40,13 @@
|
||||
<key alias="translate">Översätt</key>
|
||||
<key alias="unpublish">Avpublicera</key>
|
||||
<key alias="update">Uppdatera</key>
|
||||
<key alias="setPermissions">Ändra rättigheter</key>
|
||||
<key alias="unlock">Lås upp</key>
|
||||
<key alias="createblueprint">Skapa innehållsmall</key>
|
||||
<key alias="resendInvite">Skicka inbjudan igen</key>
|
||||
<key alias="toggleHideUnavailable">Dölj otillgängliga alternativ</key>
|
||||
<key alias="changeDataType">Ändra datatyp</key>
|
||||
<key alias="editContent">Redigera innehåll</key>
|
||||
</area>
|
||||
<area alias="actionCategories">
|
||||
<key alias="administration">Administration</key>
|
||||
@@ -47,6 +54,10 @@
|
||||
<key alias="other">Övrigt</key>
|
||||
<key alias="content">Innehåll</key>
|
||||
</area>
|
||||
<area alias="apps">
|
||||
<key alias="umbContent">Innehåll</key>
|
||||
<key alias="umbInfo">Info</key>
|
||||
</area>
|
||||
<area alias="assignDomain">
|
||||
<key alias="addNew">Lägg till nytt domännamn</key>
|
||||
<key alias="domain">Domännamn</key>
|
||||
@@ -113,8 +124,21 @@
|
||||
<key alias="areaValidationEntriesExceed"><![CDATA[<strong>%0%</strong> måste maximalt finnas <strong>%3%</strong> time(s).]]></key>
|
||||
</area>
|
||||
<area alias="validation">
|
||||
<key alias="invalidEmail">Ogiltig e-postadress</key>
|
||||
<key alias="invalidNull">Värde får inte vara 'null'</key>
|
||||
<key alias="invalidEmpty">Värde får inte vara tomt</key>
|
||||
<key alias="invalidPattern">Värdet är ogiltigt och matchar inte det korrekta formatet</key>
|
||||
<key alias="entriesShort"><![CDATA[Minsta %0% poster, kräver <strong>%1%</strong> mer.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Max %0% poster, <strong>%1%</strong> för många.]]></key>
|
||||
<key alias="entriesAreasMismatch">Innehållskraven har ej godkänts på ett eller flera områden.</key>
|
||||
<key alias="invalidMemberGroupName">Ogiltigt namn för medlemsgrupp</key>
|
||||
<key alias="invalidUserGroupName">Ogiltigt namn för användargrupp</key>
|
||||
<key alias="invalidToken">Ogiltig token</key>
|
||||
<key alias="invalidUsername">Ogiltigt användarnamn</key>
|
||||
<key alias="duplicateEmail">E-postadressen '%0%' är redan tagen</key>
|
||||
<key alias="duplicateUserGroupName">Namnet '%0%' är redan taget</key>
|
||||
<key alias="duplicateMemberGroupName">Namnet '%0%' är redan taget</key>
|
||||
<key alias="duplicateUsername">Användarnamnet '%0%' är redan taget</key>
|
||||
</area>
|
||||
<area alias="blueprints">
|
||||
<key alias="createBlueprintFrom"><![CDATA[Skapa en ny innehållsmall för <em>%0%</em>]]></key>
|
||||
@@ -176,6 +200,7 @@
|
||||
<key alias="clickToEdit">Klicka för att redigera detta objekt</key>
|
||||
<key alias="createBy">Skapad av</key>
|
||||
<key alias="createByDesc">Ursprunglig författare</key>
|
||||
<key alias="updatedBy" version="7.0">Uppdaterad av</key>
|
||||
<key alias="createDate">Skapad</key>
|
||||
<key alias="createDateDesc">Datum/tid som dokumentet skapades</key>
|
||||
<key alias="documentType">Dokumenttyp</key>
|
||||
@@ -559,6 +584,29 @@
|
||||
<key alias="retrieve">Hämta</key>
|
||||
<key alias="selected">valda</key>
|
||||
</area>
|
||||
<area alias="colors">
|
||||
<key alias="blue">Blå</key>
|
||||
</area>
|
||||
<area alias="shortcuts">
|
||||
<key alias="addGroup">Lägg till grupp</key>
|
||||
<key alias="addProperty">Lägg till egenskap</key>
|
||||
<key alias="addEditor">Lägg till redigerare</key>
|
||||
<key alias="addTemplate">Lägg till mall</key>
|
||||
<key alias="editDataType">Ändra datatyp</key>
|
||||
<key alias="shortcut">Genvägar</key>
|
||||
<key alias="showShortcuts">visa genvägar</key>
|
||||
<key alias="toggleListView">Toggle list view</key>
|
||||
<key alias="toggleAllowAsRoot">Toggle allow as root</key>
|
||||
<key alias="commentLine">Kommentera bort/återställ rader</key>
|
||||
<key alias="removeLine">Radera rad</key>
|
||||
<key alias="copyLineUp">Kopiera upp rader</key>
|
||||
<key alias="copyLineDown">Kopiera ned rader</key>
|
||||
<key alias="moveLineUp">Flytta upp rader</key>
|
||||
<key alias="moveLineDown">Flytta ned rader</key>
|
||||
<key alias="generalHeader">Allmänt</key>
|
||||
<key alias="editorHeader">Redigerare</key>
|
||||
<key alias="addTab">Lägg till tabb</key>
|
||||
</area>
|
||||
<area alias="graphicheadline">
|
||||
<key alias="backgroundcolor">Bakgrundsfärg</key>
|
||||
<key alias="bold">Fetstil</key>
|
||||
@@ -661,6 +709,11 @@
|
||||
<key alias="createNewMember">Skapa en ny medlem</key>
|
||||
<key alias="allMembers">Alla medlemmar</key>
|
||||
<key alias="memberGroupNoProperties">Medlemsgrupper har inga extra egenskaper för redigering.</key>
|
||||
<key alias="duplicateMemberLogin">En medlem med detta inlogg existerar redan</key>
|
||||
<key alias="memberHasGroup">Medlem finns redan i gruppen '%0%'</key>
|
||||
<key alias="memberHasPassword">Medlem har redan ett lösenord</key>
|
||||
<key alias="memberLockoutNotEnabled">Avstängning är inte aktiverat för denna medlem</key>
|
||||
<key alias="memberNotInGroup">Medlem är inte med i gruppen '%0%'</key>
|
||||
</area>
|
||||
<area alias="moveOrCopy">
|
||||
<key alias="choose">Välj sida ovan...</key>
|
||||
@@ -1087,9 +1140,45 @@
|
||||
<key alias="sortCreateDateAscending">Äldst</key>
|
||||
<key alias="sortCreateDateDescending">Nyast</key>
|
||||
<key alias="sortLastLoginDateDescending">Senaste login</key>
|
||||
<key alias="duplicateLogin">En användare med detta inlogg existerar redan</key>
|
||||
<key alias="passwordRequiresDigit">Lösenordet måste innehålla minst en siffra ('0'-'9')</key>
|
||||
<key alias="passwordRequiresLower">Lösenordet måste innehålla minst en liten bokstav ('a'-'z')</key>
|
||||
<key alias="passwordRequiresNonAlphanumeric">Lösenordet måste innehålla minst ett specialtecken</key>
|
||||
<key alias="passwordRequiresUniqueChars">Lösenordet måste innehålla minst %0% unika tecken</key>
|
||||
<key alias="passwordRequiresUpper">Lösenordet måste innehålla minst en stor bokstav ('A'-'Z')</key>
|
||||
<key alias="passwordTooShort">Lösenordet måste vara minst %0% tecken</key>
|
||||
<key alias="userHasPassword">Användaren har redan ett lösenord</key>
|
||||
<key alias="userHasGroup">Användaren finns redan i gruppen '%0%'</key>
|
||||
<key alias="userLockoutNotEnabled">Avstängning är inte aktiverat för denna användaren</key>
|
||||
<key alias="userNotInGroup">Användaren finns inte i gruppen '%0%'</key>
|
||||
</area>
|
||||
<area alias="logViewer">
|
||||
<key alias="selectAllLogLevelFilters">Välj alla</key>
|
||||
<key alias="deselectAllLogLevelFilters">Avmarkera alla</key>
|
||||
</area>
|
||||
<area alias="preview">
|
||||
<key alias="endLabel">Avsluta</key>
|
||||
<key alias="endTitle">Avsluta förhandsvisning</key>
|
||||
<key alias="openWebsiteLabel">Förhandsvisa webbplats</key>
|
||||
<key alias="openWebsiteTitle">Öppna webbplats i förhandsvisningsläge</key>
|
||||
<key alias="returnToPreviewHeadline">Förhandsvisa webbplats?</key>
|
||||
<key alias="returnToPreviewDescription">Du har avslutat förhandsvisningsläge, vill du aktivera det igen för att se senast sparade version av webbplatsen?</key>
|
||||
<key alias="returnToPreviewAcceptButton">Förhandsvisa senaste version</key>
|
||||
<key alias="returnToPreviewDeclineButton">Visa publicerad version</key>
|
||||
<key alias="viewPublishedContentHeadline">Visa publicerad version?</key>
|
||||
<key alias="viewPublishedContentDescription">Du visar webbplats i förhandsvisningsläge, vill du avsluta förhandsvisning och visa senaste publicerad version av webbplatsen?
|
||||
</key>
|
||||
<key alias="viewPublishedContentAcceptButton">Visa publicerad version</key>
|
||||
<key alias="viewPublishedContentDeclineButton">Fortsätt förhandsvisning</key>
|
||||
</area>
|
||||
<area alias="permissions">
|
||||
<key alias="FolderCreation">Skapa kataloger</key>
|
||||
<key alias="FileWritingForPackages">Skriva filer till paket</key>
|
||||
<key alias="FileWriting">Skriva filer</key>
|
||||
<key alias="MediaFolderCreation">Skapa media-kataloger</key>
|
||||
</area>
|
||||
<area alias="analytics">
|
||||
<key alias="minimalLevelDescription">Vi kommer endast att skicka ett anonymt Id så att vi informeras om att webbplatsens finns.</key>
|
||||
<key alias="basicLevelDescription">Vi kommer att skicka ett anonymt Id, Umbraco-version och installerade paket</key>
|
||||
</area>
|
||||
</language>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@using Umbraco.Extensions
|
||||
|
||||
@{
|
||||
var isLoggedIn = Context.User?.Identity?.IsAuthenticated ?? false;
|
||||
var isLoggedIn = Context.User.GetMemberIdentity()?.IsAuthenticated ?? false;
|
||||
var logoutModel = new PostRedirectModel();
|
||||
// You can modify this to redirect to a different URL instead of the current one
|
||||
logoutModel.RedirectUrl = null;
|
||||
@@ -15,7 +15,7 @@
|
||||
{
|
||||
<div class="login-status">
|
||||
|
||||
<p>Welcome back <strong>@Context?.User?.Identity?.Name</strong>!</p>
|
||||
<p>Welcome back <strong>@Context.User?.GetMemberIdentity()?.Name</strong>!</p>
|
||||
|
||||
@using (Html.BeginUmbracoForm<UmbLoginStatusController>("HandleLogout", new { RedirectUrl = logoutModel.RedirectUrl }))
|
||||
{
|
||||
|
||||
@@ -330,7 +330,10 @@ public static class ClaimsIdentityExtensions
|
||||
var firstValue = identity.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (firstValue is not null)
|
||||
{
|
||||
return int.Parse(firstValue, CultureInfo.InvariantCulture);
|
||||
if (int.TryParse(firstValue, CultureInfo.InvariantCulture, out var id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
public static class IntExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Does something 'x' amount of times
|
||||
/// Does something 'x' amount of times.
|
||||
/// </summary>
|
||||
/// <param name="n"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="n">Number of times to execute the action.</param>
|
||||
/// <param name="action">The action to execute.</param>
|
||||
public static void Times(this int n, Action<int> action)
|
||||
{
|
||||
for (var i = 0; i < n; i++)
|
||||
@@ -19,11 +21,11 @@ public static class IntExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Guid based on an integer value
|
||||
/// Creates a Guid based on an integer value.
|
||||
/// </summary>
|
||||
/// <param name="value"><see cref="int" /> value to convert</param>
|
||||
/// <param name="value">The <see cref="int" /> value to convert.</param>
|
||||
/// <returns>
|
||||
/// <see cref="Guid" />
|
||||
/// The converted <see cref="Guid" />.
|
||||
/// </returns>
|
||||
public static Guid ToGuid(this int value)
|
||||
{
|
||||
@@ -31,4 +33,28 @@ public static class IntExtensions
|
||||
BitConverter.GetBytes(value).CopyTo(bytes, 0);
|
||||
return new Guid(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores a GUID previously created from an integer value using <see cref="ToGuid" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="Guid" /> value to convert.</param>
|
||||
/// <param name="result">The converted <see cref="int" />.</param>
|
||||
/// <returns>
|
||||
/// True if the <see cref="int" /> value could be created, otherwise false.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This is used with Umbraco entities that only have integer references in the database (e.g. users).
|
||||
/// </remarks>
|
||||
public static bool TryParseFromGuid(Guid value, [NotNullWhen(true)] out int? result)
|
||||
{
|
||||
if (value.ToString().EndsWith("-0000-0000-0000-000000000000") is false)
|
||||
{
|
||||
// We have a proper GUID, not one converted from an integer.
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
result = BitConverter.ToInt32(value.ToByteArray());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ namespace Umbraco.Cms.Core.IO
|
||||
|
||||
// nothing prevents us to reach the file, security-wise, yet it is outside
|
||||
// this filesystem's root - throw
|
||||
throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root.");
|
||||
throw new UnauthorizedAccessException($"Requested path {originalPath} is outside this filesystem's root.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders;
|
||||
/// <summary>
|
||||
/// Embed Provider for Twitter the popular online service for microblogging and social networking.
|
||||
/// </summary>
|
||||
[Obsolete("Please use X instead, scheduled for removal in v16")]
|
||||
public class Twitter : OEmbedProviderBase
|
||||
{
|
||||
public Twitter(IJsonSerializer jsonSerializer)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Core.Media.EmbedProviders;
|
||||
|
||||
/// <summary>
|
||||
/// Embed Provider for X the popular online service for microblogging and social networking.
|
||||
/// </summary>
|
||||
public class X : OEmbedProviderBase
|
||||
{
|
||||
public X(IJsonSerializer jsonSerializer)
|
||||
: base(jsonSerializer)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ApiEndpoint => "http://publish.twitter.com/oembed";
|
||||
|
||||
public override string[] UrlSchemeRegex => new[] { @"(https?:\/\/(www\.)?)(twitter|x)\.com\/.*\/status\/.*" };
|
||||
|
||||
public override Dictionary<string, string> RequestParams => new();
|
||||
|
||||
public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0)
|
||||
{
|
||||
var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight);
|
||||
OEmbedResponse? oembed = base.GetJsonResponse<OEmbedResponse>(requestUrl);
|
||||
|
||||
return oembed?.GetHtml();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,21 @@ public sealed class AuditItem : EntityBase, IAuditItem
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuditItem" /> class.
|
||||
/// </summary>
|
||||
public AuditItem(int objectId, AuditType type, int userId, string? entityType, DateTime createDate, string? comment = null, string? parameters = null)
|
||||
{
|
||||
DisableChangeTracking();
|
||||
|
||||
Id = objectId;
|
||||
Comment = comment;
|
||||
AuditType = type;
|
||||
UserId = userId;
|
||||
EntityType = entityType;
|
||||
Parameters = parameters;
|
||||
CreateDate = createDate;
|
||||
|
||||
EnableChangeTracking();
|
||||
}
|
||||
|
||||
public AuditItem(int objectId, AuditType type, int userId, string? entityType, string? comment = null, string? parameters = null)
|
||||
{
|
||||
DisableChangeTracking();
|
||||
|
||||
@@ -194,6 +194,7 @@ public class ContentItemDisplay<TVariant> :
|
||||
/// This is not used for outgoing model information.
|
||||
/// </remarks>
|
||||
[IgnoreDataMember]
|
||||
[Obsolete("No longer used. Will be removed in V15.")]
|
||||
public ContentPropertyCollectionDto? ContentDto { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
namespace Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
/// <summary>
|
||||
/// The action associated with saving a content item
|
||||
/// The action associated with saving a content item.
|
||||
/// </summary>
|
||||
public enum ContentSaveAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves the content item, no publish
|
||||
/// Saves the content item, no publish.
|
||||
/// </summary>
|
||||
Save = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new content item
|
||||
/// Creates a new content item.
|
||||
/// </summary>
|
||||
SaveNew = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Saves and publishes the content item
|
||||
/// Saves and publishes the content item.
|
||||
/// </summary>
|
||||
Publish = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Creates and publishes a new content item
|
||||
/// Creates and publishes a new content item.
|
||||
/// </summary>
|
||||
PublishNew = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Saves and sends publish notification
|
||||
/// Saves and sends publish notification.
|
||||
/// </summary>
|
||||
SendPublish = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Creates and sends publish notification
|
||||
/// Creates and sends publish notification.
|
||||
/// </summary>
|
||||
SendPublishNew = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Saves and schedules publishing
|
||||
/// Saves and schedules publishing.
|
||||
/// </summary>
|
||||
Schedule = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Creates and schedules publishing
|
||||
/// Creates and schedules publishing.
|
||||
/// </summary>
|
||||
ScheduleNew = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Saves and publishes the content item including all descendants that have a published version
|
||||
/// Saves and publishes the content item including all descendants that have a published version.
|
||||
/// </summary>
|
||||
PublishWithDescendants = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Creates and publishes the content item including all descendants that have a published version
|
||||
/// Creates and publishes the new content item including all descendants that have a published version.
|
||||
/// </summary>
|
||||
PublishWithDescendantsNew = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Saves and publishes the content item including all descendants regardless of whether they have a published version
|
||||
/// or not
|
||||
/// or not.
|
||||
/// </summary>
|
||||
PublishWithDescendantsForce = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Creates and publishes the content item including all descendants regardless of whether they have a published
|
||||
/// version or not
|
||||
/// Creates and publishes the new content item including all descendants regardless of whether they have a published
|
||||
/// version or not.
|
||||
/// </summary>
|
||||
PublishWithDescendantsForceNew = 11,
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public class PropertyTypeBasic
|
||||
|
||||
[Required]
|
||||
[RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")]
|
||||
[MaxLength(255, ErrorMessage = "Alias is too long")]
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias { get; set; } = null!;
|
||||
|
||||
|
||||
@@ -174,15 +174,17 @@ public static class ContentRepositoryExtensions
|
||||
foreach (IProperty property in content.Properties)
|
||||
{
|
||||
// each property type may or may not support the variation
|
||||
if (!property.PropertyType?.SupportsVariation(culture, "*", true) ?? false)
|
||||
if ((!property.PropertyType?.SupportsVariation(culture, "*", true) ?? false) &&
|
||||
!(property.PropertyType?.Variations == ContentVariation.Nothing))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (IPropertyValue pvalue in property.Values)
|
||||
{
|
||||
if ((property.PropertyType?.SupportsVariation(pvalue.Culture, pvalue.Segment, true) ?? false) &&
|
||||
(culture == "*" || (pvalue.Culture?.InvariantEquals(culture) ?? false)))
|
||||
if (((property.PropertyType?.SupportsVariation(pvalue.Culture, pvalue.Segment, true) ?? false) &&
|
||||
(culture == "*" || (pvalue.Culture?.InvariantEquals(culture) ?? false))) ||
|
||||
property.PropertyType?.Variations == ContentVariation.Nothing)
|
||||
{
|
||||
property.SetValue(null, pvalue.Culture, pvalue.Segment);
|
||||
}
|
||||
@@ -193,7 +195,8 @@ public static class ContentRepositoryExtensions
|
||||
IPropertyCollection otherProperties = other.Properties;
|
||||
foreach (IProperty otherProperty in otherProperties)
|
||||
{
|
||||
if (!otherProperty?.PropertyType?.SupportsVariation(culture, "*", true) ?? true)
|
||||
if ((!otherProperty?.PropertyType?.SupportsVariation(culture, "*", true) ?? true) &&
|
||||
!(otherProperty?.PropertyType?.Variations == ContentVariation.Nothing))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -203,8 +206,9 @@ public static class ContentRepositoryExtensions
|
||||
{
|
||||
foreach (IPropertyValue pvalue in otherProperty.Values)
|
||||
{
|
||||
if (otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, true) &&
|
||||
(culture == "*" || (pvalue.Culture?.InvariantEquals(culture) ?? false)))
|
||||
if (((otherProperty?.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, true) ?? false) &&
|
||||
(culture == "*" ||(pvalue.Culture?.InvariantEquals(culture) ?? false))) ||
|
||||
otherProperty?.PropertyType?.Variations == ContentVariation.Nothing)
|
||||
{
|
||||
var value = published ? pvalue.PublishedValue : pvalue.EditedValue;
|
||||
content.SetValue(alias, value, pvalue.Culture, pvalue.Segment);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Umbraco.Cms.Core.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the options available with publishing a content branch for force publishing.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum PublishBranchFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The default behavior is to publish only the published content that has changed.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// For publishing a branch, publish all changed content, including content that is not published.
|
||||
/// </summary>
|
||||
IncludeUnpublished = 1,
|
||||
|
||||
/// <summary>
|
||||
/// For publishing a branch, force republishing of all published content, including content that has not changed.
|
||||
/// </summary>
|
||||
ForceRepublish = 2,
|
||||
|
||||
/// <summary>
|
||||
/// For publishing a branch, publish all content, including content that is not published and content that has not changed.
|
||||
/// </summary>
|
||||
All = IncludeUnpublished | ForceRepublish,
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Umbraco.Cms.Core.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies options for publishing notifcations when saving.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum PublishNotificationSaveOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not publish any notifications.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Only publish the saving notification.
|
||||
/// </summary>
|
||||
Saving = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Only publish the saved notification.
|
||||
/// </summary>
|
||||
Saved = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Publish all the notifications.
|
||||
/// </summary>
|
||||
All = Saving | Saved,
|
||||
}
|
||||
@@ -27,4 +27,6 @@ public class WebhookLog
|
||||
public string ResponseBody { get; set; } = string.Empty;
|
||||
|
||||
public bool ExceptionOccured { get; set; }
|
||||
|
||||
public bool IsSuccessStatusCode { get; set; }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Umbraco.Cms.Core;
|
||||
/// Provides an equivalent to the c# lock statement, to be used in a using block.
|
||||
/// </summary>
|
||||
/// <remarks>Ie replace <c>lock (o) {...}</c> by <c>using (new MonitorLock(o)) { ... }</c></remarks>
|
||||
[Obsolete("Use System.Threading.Lock instead. This will be removed in a future version.")]
|
||||
public class MonitorLock : IDisposable
|
||||
{
|
||||
private readonly bool _entered;
|
||||
|
||||
@@ -32,4 +32,14 @@ public class ContentTreeChangeNotification : TreeChangeNotification<IContent>
|
||||
: base(new TreeChange<IContent>(target, changeTypes), messages)
|
||||
{
|
||||
}
|
||||
|
||||
public ContentTreeChangeNotification(
|
||||
IContent target,
|
||||
TreeChangeTypes changeTypes,
|
||||
IEnumerable<string>? publishedCultures,
|
||||
IEnumerable<string>? unpublishedCultures,
|
||||
EventMessages messages)
|
||||
: base(new TreeChange<IContent>(target, changeTypes, publishedCultures, unpublishedCultures), messages)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,29 @@ using Umbraco.Cms.Core.Security;
|
||||
namespace Umbraco.Cms.Core.Persistence.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for external logins with Guid as key, so it can be shared for members and users
|
||||
/// Repository for external logins with Guid as key, so it can be shared for members and users.
|
||||
/// </summary>
|
||||
public interface IExternalLoginWithKeyRepository : IReadWriteQueryRepository<int, IIdentityUserLogin>,
|
||||
IQueryRepository<IIdentityUserToken>
|
||||
{
|
||||
/// <summary>
|
||||
/// Replaces all external login providers for the user/member key
|
||||
/// Replaces all external login providers for the user/member key.
|
||||
/// </summary>
|
||||
void Save(Guid userOrMemberKey, IEnumerable<IExternalLogin> logins);
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all external login provider tokens for the providers specified for the user/member key
|
||||
/// Replaces all external login provider tokens for the providers specified for the user/member key.
|
||||
/// </summary>
|
||||
void Save(Guid userOrMemberKey, IEnumerable<IExternalLoginToken> tokens);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all external logins for the specified the user/member key
|
||||
/// Deletes all external logins for the specified the user/member key.
|
||||
/// </summary>
|
||||
void DeleteUserLogins(Guid userOrMemberKey);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes external logins that aren't associated with the current collection of providers.
|
||||
/// </summary>
|
||||
/// <param name="currentLoginProviders">The names of the currently configured providers.</param>
|
||||
void DeleteUserLoginsForRemovedProviders(IEnumerable<string> currentLoginProviders) { }
|
||||
}
|
||||
|
||||
@@ -38,4 +38,11 @@ public interface IMemberRepository : IContentRepository<int, IMember>
|
||||
/// <param name="query"></param>
|
||||
/// <returns></returns>
|
||||
int GetCountByQuery(IQuery<IMember>? query);
|
||||
|
||||
/// <summary>
|
||||
/// Saves only the properties related to login for the member, using an optimized, non-locking update.
|
||||
/// </summary>
|
||||
/// <param name="member">The member to update.</param>
|
||||
/// <returns>Used to avoid the full save of the member object after a login operation.</returns>
|
||||
Task UpdateLoginPropertiesAsync(IMember member) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Linq.Expressions;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
|
||||
@@ -109,5 +109,26 @@ public interface IUserRepository : IReadWriteQueryRepository<int, IUser>
|
||||
|
||||
void ClearLoginSession(Guid sessionId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a page of users, ordered by Id and starting from the provided Id.
|
||||
/// </summary>
|
||||
/// <param name="id">The user Id to start retrieving users from.</param>
|
||||
/// <param name="count">The number of users to return.</param>
|
||||
/// <returns>A page of <see cref="IUser"/> instances.</returns>
|
||||
[Obsolete("No longer used in Umbraco. Scheduled for removal in Umbraco 18.")]
|
||||
IEnumerable<IUser> GetNextUsers(int id, int count);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a page of approved users, ordered by Id and starting from the provided Id.
|
||||
/// </summary>
|
||||
/// <param name="id">The user Id to start retrieving users from.</param>
|
||||
/// <param name="count">The number of users to return.</param>
|
||||
/// <returns>A page of <see cref="IUser"/> instances.</returns>
|
||||
IEnumerable<IUser> GetNextApprovedUsers(int id, int count) => Enumerable.Empty<IUser>();
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates sessions for users that aren't associated with the current collection of providers.
|
||||
/// </summary>
|
||||
/// <param name="currentProviderKeys">The keys for the currently configured providers.</param>
|
||||
void InvalidateSessionsForRemovedProviders(IEnumerable<string> currentProviderKeys) { }
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
/// <summary>
|
||||
/// A validator that validates that the value is not null or empty (if it is a string)
|
||||
/// </summary>
|
||||
public sealed class RequiredValidator : IValueRequiredValidator, IManifestValueValidator
|
||||
public class RequiredValidator : IValueRequiredValidator, IManifestValueValidator
|
||||
{
|
||||
private const string ValueCannotBeNull = "Value cannot be null";
|
||||
private const string ValueCannotBeEmpty = "Value cannot be empty";
|
||||
@@ -23,7 +23,7 @@ public sealed class RequiredValidator : IValueRequiredValidator, IManifestValueV
|
||||
ValidateRequired(value, valueType);
|
||||
|
||||
/// <inheritdoc cref="IValueRequiredValidator.ValidateRequired" />
|
||||
public IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType)
|
||||
public virtual IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
|
||||
@@ -31,6 +31,12 @@ public interface IPublishedSnapshotService : IDisposable
|
||||
/// </remarks>
|
||||
IPublishedSnapshot CreatePublishedSnapshot(string? previewToken);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the database cache is in the process of being rebuilt.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool IsRebuilding() => false;
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds internal database caches (but does not reload).
|
||||
/// </summary>
|
||||
@@ -61,6 +67,38 @@ public interface IPublishedSnapshotService : IDisposable
|
||||
IReadOnlyCollection<int>? mediaTypeIds = null,
|
||||
IReadOnlyCollection<int>? memberTypeIds = null);
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds internal database caches (but does not reload).
|
||||
/// </summary>
|
||||
/// <param name="contentTypeIds">
|
||||
/// If not null will process content for the matching content types, if empty will process all
|
||||
/// content
|
||||
/// </param>
|
||||
/// <param name="mediaTypeIds">
|
||||
/// If not null will process content for the matching media types, if empty will process all
|
||||
/// media
|
||||
/// </param>
|
||||
/// <param name="memberTypeIds">
|
||||
/// If not null will process content for the matching members types, if empty will process all
|
||||
/// members
|
||||
/// </param>
|
||||
/// <param name="useBackgroundThread">Flag indicating whether to use a background thread for the operation and immediately return to the caller.</param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Forces the snapshot service to rebuild its internal database caches. For instance, some caches
|
||||
/// may rely on a database table to store pre-serialized version of documents.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This does *not* reload the caches. Caches need to be reloaded, for instance via
|
||||
/// <see cref="DistributedCache" /> RefreshAllPublishedSnapshot method.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
void Rebuild(
|
||||
bool useBackgroundThread,
|
||||
IReadOnlyCollection<int>? contentTypeIds = null,
|
||||
IReadOnlyCollection<int>? mediaTypeIds = null,
|
||||
IReadOnlyCollection<int>? memberTypeIds = null) => Rebuild(contentTypeIds, mediaTypeIds, memberTypeIds);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds all internal database caches (but does not reload).
|
||||
@@ -77,6 +115,22 @@ public interface IPublishedSnapshotService : IDisposable
|
||||
/// </remarks>
|
||||
void RebuildAll() => Rebuild(Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>());
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds all internal database caches (but does not reload).
|
||||
/// </summary>
|
||||
/// <param name="useBackgroundThread">Flag indicating whether to use a background thread for the operation and immediately return to the caller.</param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Forces the snapshot service to rebuild its internal database caches. For instance, some caches
|
||||
/// may rely on a database table to store pre-serialized version of documents.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This does *not* reload the caches. Caches need to be reloaded, for instance via
|
||||
/// <see cref="DistributedCache" /> RefreshAllPublishedSnapshot method.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
void RebuildAll(bool useBackgroundThread) => Rebuild(useBackgroundThread, Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>());
|
||||
|
||||
/* An IPublishedCachesService implementation can rely on transaction-level events to update
|
||||
* its internal, database-level data, as these events are purely internal. However, it cannot
|
||||
* rely on cache refreshers CacheUpdated events to update itself, as these events are external
|
||||
|
||||
@@ -33,7 +33,7 @@ public interface ITagQuery
|
||||
/// <summary>
|
||||
/// Gets all document tags.
|
||||
/// </summary>
|
||||
/// /// <remarks>
|
||||
/// <remarks>
|
||||
/// If no culture is specified, it retrieves tags with an invariant culture.
|
||||
/// If a culture is specified, it only retrieves tags for that culture.
|
||||
/// Use "*" to retrieve tags for all cultures.
|
||||
|
||||
@@ -47,4 +47,37 @@ public interface IPublishedRouter
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
Task<IPublishedRequest> UpdateRequestAsync(IPublishedRequest request, IPublishedContent? publishedContent);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest and VariationContext accordingly.
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is used for VirtualPage routing.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// In this case we do not want to run the entire routing pipeline since ContentFinders are not needed here.
|
||||
/// However, we do want to set the culture on VariationContext and PublishedRequest to the values specified by the domains.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="request">The request to update the culture on domain on</param>
|
||||
/// <returns>True if a domain was found otherwise false.</returns>
|
||||
bool RouteDomain(IPublishedRequestBuilder request) => false;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the site root (if any) matching the http request, and updates the VariationContext accordingly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is used for VirtualPage routing.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is required to set the culture on VariationContext to the values specified by the domains, before the FindContent method is called.
|
||||
/// In order to allow the FindContent implementer to correctly find content based off the culture. Before the PublishedRequest is built.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="uri">The URI to resolve the domain from.</param>
|
||||
/// <returns>True if a domain was found, otherwise false.</returns>
|
||||
bool UpdateVariationContext(Uri uri) => false;
|
||||
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public class PublishedRouter : IPublishedRouter
|
||||
// find domain
|
||||
if (builder.Domain == null)
|
||||
{
|
||||
FindDomain(builder);
|
||||
FindAndSetDomain(builder);
|
||||
}
|
||||
|
||||
await RouteRequestInternalAsync(builder);
|
||||
@@ -185,7 +185,7 @@ public class PublishedRouter : IPublishedRouter
|
||||
|
||||
private async Task<IPublishedRequest> TryRouteRequest(IPublishedRequestBuilder request)
|
||||
{
|
||||
FindDomain(request);
|
||||
FindAndSetDomain(request);
|
||||
|
||||
if (request.IsRedirect())
|
||||
{
|
||||
@@ -270,18 +270,31 @@ public class PublishedRouter : IPublishedRouter
|
||||
// to find out the appropriate template
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether a domain was found.</returns>
|
||||
internal bool FindDomain(IPublishedRequestBuilder request)
|
||||
/// <inheritdoc />
|
||||
public bool RouteDomain(IPublishedRequestBuilder request)
|
||||
{
|
||||
var found = FindAndSetDomain(request);
|
||||
HandleWildcardDomains(request);
|
||||
SetVariationContext(request.Culture);
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UpdateVariationContext(Uri uri)
|
||||
{
|
||||
DomainAndUri? domain = FindDomain(uri, out _);
|
||||
SetVariationContext(domain?.Culture);
|
||||
return domain?.Culture is not null;
|
||||
}
|
||||
|
||||
private DomainAndUri? FindDomain(Uri uri, out string? defaultCulture)
|
||||
{
|
||||
const string tracePrefix = "FindDomain: ";
|
||||
|
||||
// note - we are not handling schemes nor ports here.
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri);
|
||||
_logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, uri);
|
||||
}
|
||||
|
||||
IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
@@ -315,10 +328,20 @@ public class PublishedRouter : IPublishedRouter
|
||||
|
||||
domains = domains?.Where(IsPublishedContentDomain).ToList();
|
||||
|
||||
var defaultCulture = domainsCache?.DefaultCulture;
|
||||
defaultCulture = domainsCache?.DefaultCulture;
|
||||
|
||||
return DomainUtilities.SelectDomain(domains, uri, defaultCulture: defaultCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether a domain was found.</returns>
|
||||
internal bool FindAndSetDomain(IPublishedRequestBuilder request)
|
||||
{
|
||||
const string tracePrefix = "FindDomain: ";
|
||||
// try to find a domain matching the current request
|
||||
DomainAndUri? domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture);
|
||||
DomainAndUri? domainAndUri = FindDomain(request.Uri, out var defaultCulture);
|
||||
|
||||
// handle domain - always has a contentId and a culture
|
||||
if (domainAndUri != null)
|
||||
|
||||
@@ -148,7 +148,10 @@ public class UmbracoRequestPaths
|
||||
/// <summary>
|
||||
/// Checks if the current uri is an install request
|
||||
/// </summary>
|
||||
public bool IsInstallerRequest(string absPath) => absPath.InvariantStartsWith(_installPath);
|
||||
public bool IsInstallerRequest(string absPath) =>
|
||||
absPath.InvariantEquals(_installPath)
|
||||
|| absPath.InvariantStartsWith(_installPath.EnsureEndsWith('/'))
|
||||
|| absPath.InvariantStartsWith(_installPath.EnsureEndsWith('?'));
|
||||
|
||||
/// <summary>
|
||||
/// Rudimentary check to see if it's not a server side request
|
||||
|
||||
@@ -111,6 +111,12 @@ public sealed class UriUtility
|
||||
if (path != "/")
|
||||
{
|
||||
path = path.TrimEnd(Constants.CharArrays.ForwardSlash);
|
||||
|
||||
// perform fallback to root if the path was all slashes (i.e. https://some.where//////)
|
||||
if (path == string.Empty)
|
||||
{
|
||||
path = "/";
|
||||
}
|
||||
}
|
||||
|
||||
return uri.Rewrite(path);
|
||||
|
||||
@@ -8,10 +8,22 @@ public class TreeChange<TItem>
|
||||
ChangeTypes = changeTypes;
|
||||
}
|
||||
|
||||
public TreeChange(TItem changedItem, TreeChangeTypes changeTypes, IEnumerable<string>? publishedCultures, IEnumerable<string>? unpublishedCultures)
|
||||
{
|
||||
Item = changedItem;
|
||||
ChangeTypes = changeTypes;
|
||||
PublishedCultures = publishedCultures;
|
||||
UnpublishedCultures = unpublishedCultures;
|
||||
}
|
||||
|
||||
public TItem Item { get; }
|
||||
|
||||
public TreeChangeTypes ChangeTypes { get; }
|
||||
|
||||
public IEnumerable<string>? PublishedCultures { get; }
|
||||
|
||||
public IEnumerable<string>? UnpublishedCultures { get; }
|
||||
|
||||
public EventArgs ToEventArgs() => new EventArgs(this);
|
||||
|
||||
public class EventArgs : System.EventArgs
|
||||
|
||||
@@ -1487,6 +1487,14 @@ public class ContentService : RepositoryService, IContentService
|
||||
notificationState);
|
||||
if (publishResult.Success)
|
||||
{
|
||||
// raise Publishing notification
|
||||
if (scope.Notifications.PublishCancelable(
|
||||
new ContentPublishingNotification(content, eventMessages).WithState(notificationState)))
|
||||
{
|
||||
_logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled");
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, eventMessages, content);
|
||||
}
|
||||
|
||||
// note: StrategyPublish flips the PublishedState to Publishing!
|
||||
publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, eventMessages);
|
||||
|
||||
@@ -1587,7 +1595,12 @@ public class ContentService : RepositoryService, IContentService
|
||||
// events and audit
|
||||
scope.Notifications.Publish(
|
||||
new ContentUnpublishedNotification(content, eventMessages).WithState(notificationState));
|
||||
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));
|
||||
scope.Notifications.Publish(new ContentTreeChangeNotification(
|
||||
content,
|
||||
TreeChangeTypes.RefreshBranch,
|
||||
variesByCulture ? culturesPublishing.IsCollectionEmpty() ? null : culturesPublishing : null,
|
||||
variesByCulture ? culturesUnpublishing.IsCollectionEmpty() ? null : culturesUnpublishing : ["*"],
|
||||
eventMessages));
|
||||
|
||||
if (culturesUnpublishing != null)
|
||||
{
|
||||
@@ -1646,7 +1659,12 @@ public class ContentService : RepositoryService, IContentService
|
||||
if (!branchOne)
|
||||
{
|
||||
scope.Notifications.Publish(
|
||||
new ContentTreeChangeNotification(content, changeType, eventMessages));
|
||||
new ContentTreeChangeNotification(
|
||||
content,
|
||||
changeType,
|
||||
variesByCulture ? culturesPublishing.IsCollectionEmpty() ? null : culturesPublishing : ["*"],
|
||||
variesByCulture ? culturesUnpublishing.IsCollectionEmpty() ? null : culturesUnpublishing : null,
|
||||
eventMessages));
|
||||
scope.Notifications.Publish(
|
||||
new ContentPublishedNotification(content, eventMessages).WithState(notificationState));
|
||||
}
|
||||
@@ -1945,17 +1963,14 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// utility 'ShouldPublish' func used by SaveAndPublishBranch
|
||||
private HashSet<string>? SaveAndPublishBranch_ShouldPublish(ref HashSet<string>? cultures, string c, bool published, bool edited, bool isRoot, bool force)
|
||||
private HashSet<string>? SaveAndPublishBranch_ShouldPublish(ref HashSet<string>? cultures, string c, bool published, bool edited, bool isRoot, PublishBranchFilter publishBranchFilter)
|
||||
{
|
||||
// if published, republish
|
||||
if (published)
|
||||
{
|
||||
if (cultures == null)
|
||||
{
|
||||
cultures = new HashSet<string>(); // empty means 'already published'
|
||||
}
|
||||
cultures ??= []; // empty means 'already published'
|
||||
|
||||
if (edited)
|
||||
if (edited || publishBranchFilter.HasFlag(PublishBranchFilter.ForceRepublish))
|
||||
{
|
||||
cultures.Add(c); // <culture> means 'republish this culture'
|
||||
}
|
||||
@@ -1964,15 +1979,12 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// if not published, publish if force/root else do nothing
|
||||
if (!force && !isRoot)
|
||||
if (!publishBranchFilter.HasFlag(PublishBranchFilter.IncludeUnpublished) && !isRoot)
|
||||
{
|
||||
return cultures; // null means 'nothing to do'
|
||||
}
|
||||
|
||||
if (cultures == null)
|
||||
{
|
||||
cultures = new HashSet<string>();
|
||||
}
|
||||
cultures ??= [];
|
||||
|
||||
cultures.Add(c); // <culture> means 'publish this culture'
|
||||
return cultures;
|
||||
@@ -1980,6 +1992,10 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Constants.Security.SuperUserId)
|
||||
=> SaveAndPublishBranch(content, force ? PublishBranchFilter.IncludeUnpublished : PublishBranchFilter.Default, culture, userId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, PublishBranchFilter publishBranchFilter, string culture = "*", int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
// note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
|
||||
// and not to == them, else we would be comparing references, and that is a bad thing
|
||||
@@ -1998,13 +2014,13 @@ public class ContentService : RepositoryService, IContentService
|
||||
// invariant content type
|
||||
if (!c.ContentType.VariesByCulture())
|
||||
{
|
||||
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
|
||||
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, publishBranchFilter);
|
||||
}
|
||||
|
||||
// variant content type, specific culture
|
||||
if (culture != "*")
|
||||
{
|
||||
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force);
|
||||
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, publishBranchFilter);
|
||||
}
|
||||
|
||||
// variant content type, all cultures
|
||||
@@ -2014,23 +2030,27 @@ public class ContentService : RepositoryService, IContentService
|
||||
// others will have to 'republish this culture'
|
||||
foreach (var x in c.AvailableCultures)
|
||||
{
|
||||
SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
|
||||
SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, publishBranchFilter);
|
||||
}
|
||||
|
||||
return culturesToPublish;
|
||||
}
|
||||
|
||||
// if not published, publish if force/root else do nothing
|
||||
return force || isRoot
|
||||
// if not published, publish if forcing unpublished/root else do nothing
|
||||
return publishBranchFilter.HasFlag(PublishBranchFilter.IncludeUnpublished) || isRoot
|
||||
? new HashSet<string> { "*" } // "*" means 'publish all'
|
||||
: null; // null means 'nothing to do'
|
||||
}
|
||||
|
||||
return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
|
||||
return SaveAndPublishBranch(content, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId)
|
||||
=> SaveAndPublishBranch(content, force ? PublishBranchFilter.IncludeUnpublished : PublishBranchFilter.Default, cultures, userId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, PublishBranchFilter publishBranchFilter, string[] cultures, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
// note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
|
||||
// and not to == them, else we would be comparing references, and that is a bad thing
|
||||
@@ -2046,7 +2066,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// invariant content type
|
||||
if (!c.ContentType.VariesByCulture())
|
||||
{
|
||||
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
|
||||
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, publishBranchFilter);
|
||||
}
|
||||
|
||||
// variant content type, specific cultures
|
||||
@@ -2056,24 +2076,23 @@ public class ContentService : RepositoryService, IContentService
|
||||
// others will have to 'republish this culture'
|
||||
foreach (var x in cultures)
|
||||
{
|
||||
SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
|
||||
SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, publishBranchFilter);
|
||||
}
|
||||
|
||||
return culturesToPublish;
|
||||
}
|
||||
|
||||
// if not published, publish if force/root else do nothing
|
||||
return force || isRoot
|
||||
// if not published, publish if forcing unpublished/root else do nothing
|
||||
return publishBranchFilter.HasFlag(PublishBranchFilter.IncludeUnpublished) || isRoot
|
||||
? new HashSet<string>(cultures) // means 'publish specified cultures'
|
||||
: null; // null means 'nothing to do'
|
||||
}
|
||||
|
||||
return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
|
||||
return SaveAndPublishBranch(content, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
|
||||
}
|
||||
|
||||
internal IEnumerable<PublishResult> SaveAndPublishBranch(
|
||||
IContent document,
|
||||
bool force,
|
||||
Func<IContent, HashSet<string>?> shouldPublish,
|
||||
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
|
||||
int userId = Constants.Security.SuperUserId)
|
||||
@@ -2110,7 +2129,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// deal with the branch root - if it fails, abort
|
||||
PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs, out IDictionary<string, object?> notificationState);
|
||||
HashSet<string>? culturesToPublish = shouldPublish(document);
|
||||
PublishResult? result = SaveAndPublishBranchItem(scope, document, culturesToPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs, out IDictionary<string, object?> notificationState);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
@@ -2120,6 +2140,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<string> culturesPublished = culturesToPublish ?? [];
|
||||
|
||||
// deal with descendants
|
||||
// if one fails, abort its branch
|
||||
var exclude = new HashSet<int>();
|
||||
@@ -2145,12 +2167,14 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// no need to check path here, parent has to be published here
|
||||
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs, out _);
|
||||
culturesToPublish = shouldPublish(d);
|
||||
result = SaveAndPublishBranchItem(scope, d, culturesToPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs, out _);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
if (result.Success)
|
||||
{
|
||||
culturesPublished.UnionWith(culturesToPublish ?? []);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2167,8 +2191,14 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
// trigger events for the entire branch
|
||||
// (SaveAndPublishBranchOne does *not* do it)
|
||||
var variesByCulture = document.ContentType.VariesByCulture();
|
||||
scope.Notifications.Publish(
|
||||
new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages));
|
||||
new ContentTreeChangeNotification(
|
||||
document,
|
||||
TreeChangeTypes.RefreshBranch,
|
||||
variesByCulture ? culturesPublished.IsCollectionEmpty() ? null : culturesPublished : ["*"],
|
||||
null,
|
||||
eventMessages));
|
||||
scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages).WithState(notificationState));
|
||||
|
||||
scope.Complete();
|
||||
@@ -2183,7 +2213,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
private PublishResult? SaveAndPublishBranchItem(
|
||||
ICoreScope scope,
|
||||
IContent document,
|
||||
Func<IContent, HashSet<string>?> shouldPublish,
|
||||
HashSet<string>? culturesToPublish,
|
||||
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>,
|
||||
bool> publishCultures,
|
||||
bool isRoot,
|
||||
@@ -2194,7 +2224,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
out IDictionary<string, object?> notificationState)
|
||||
{
|
||||
notificationState = new Dictionary<string, object?>();
|
||||
HashSet<string>? culturesToPublish = shouldPublish(document);
|
||||
|
||||
// null = do not include
|
||||
if (culturesToPublish == null)
|
||||
@@ -2440,22 +2469,26 @@ public class ContentService : RepositoryService, IContentService
|
||||
/// <param name="content">The <see cref="IContent" /> to move</param>
|
||||
/// <param name="parentId">Id of the Content's new Parent</param>
|
||||
/// <param name="userId">Optional Id of the User moving the Content</param>
|
||||
public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId)
|
||||
public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId) =>
|
||||
AttemptMove(content, parentId, userId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Adds return type to Move method. Will be removed in V14, as the original method will be adjusted.")]
|
||||
public OperationResult AttemptMove(IContent content, int parentId, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
EventMessages eventMessages = EventMessagesFactory.Get();
|
||||
|
||||
if (content.ParentId == parentId)
|
||||
{
|
||||
return;
|
||||
return OperationResult.Succeed(eventMessages);
|
||||
}
|
||||
|
||||
// if moving to the recycle bin then use the proper method
|
||||
if (parentId == Constants.System.RecycleBinContent)
|
||||
{
|
||||
MoveToRecycleBin(content, userId);
|
||||
return;
|
||||
return MoveToRecycleBin(content, userId);
|
||||
}
|
||||
|
||||
EventMessages eventMessages = EventMessagesFactory.Get();
|
||||
|
||||
var moves = new List<(IContent, string)>();
|
||||
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
@@ -2474,7 +2507,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
if (scope.Notifications.PublishCancelable(movingNotification))
|
||||
{
|
||||
scope.Complete();
|
||||
return; // causes rollback
|
||||
return OperationResult.Cancel(eventMessages);// causes rollback
|
||||
}
|
||||
|
||||
// if content was trashed, and since we're not moving to the recycle bin,
|
||||
@@ -2509,6 +2542,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
return OperationResult.Succeed(eventMessages);
|
||||
}
|
||||
|
||||
// MUST be called from within WriteLock
|
||||
@@ -2746,6 +2781,9 @@ public class ContentService : RepositoryService, IContentService
|
||||
descendantCopy.CreatorId = userId;
|
||||
descendantCopy.WriterId = userId;
|
||||
|
||||
// since the repository relies on the dirty state to figure out whether it needs to update the sort order, we mark it dirty here
|
||||
descendantCopy.SortOrder = descendantCopy.SortOrder;
|
||||
|
||||
// save and flush (see above)
|
||||
_documentRepository.Save(descendantCopy);
|
||||
|
||||
@@ -3065,14 +3103,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
IReadOnlyCollection<ILanguage> allLangs,
|
||||
IDictionary<string, object?>? notificationState)
|
||||
{
|
||||
// raise Publishing notification
|
||||
if (scope.Notifications.PublishCancelable(
|
||||
new ContentPublishingNotification(content, evtMsgs).WithState(notificationState)))
|
||||
{
|
||||
_logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled");
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
}
|
||||
|
||||
var variesByCulture = content.ContentType.VariesByCulture();
|
||||
|
||||
// If it's null it's invariant
|
||||
@@ -3597,6 +3627,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
private static readonly string?[] ArrayOfOneNullString = { null };
|
||||
|
||||
/// <inheritdoc />
|
||||
public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
if (blueprint == null)
|
||||
|
||||
@@ -108,7 +108,7 @@ public class DomainService : RepositoryService, IDomainService
|
||||
EventMessages eventMessages = EventMessagesFactory.Get();
|
||||
|
||||
IDomain[] domains = items.ToArray();
|
||||
if (domains.Length == 0)
|
||||
if (domains.Length == 0 || AreDomainsAlreadySorted(domains))
|
||||
{
|
||||
return OperationResult.Attempt.NoOperation(eventMessages);
|
||||
}
|
||||
@@ -144,4 +144,18 @@ public class DomainService : RepositoryService, IDomainService
|
||||
|
||||
return OperationResult.Attempt.Succeed(eventMessages);
|
||||
}
|
||||
|
||||
private static bool AreDomainsAlreadySorted(IDomain[] domains)
|
||||
{
|
||||
// Check if the domains are already sorted by comparing the current sort order with what we'll set to be the new sort order.
|
||||
for (int i = 0; i < domains.Length; i++)
|
||||
{
|
||||
if (domains[i].SortOrder != i)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,4 +80,14 @@ public class ExternalLoginService : RepositoryService, IExternalLoginWithKeyServ
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DeleteUserLoginsForRemovedProviders(IEnumerable<string> currentLoginProviders)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
_externalLoginRepository.DeleteUserLoginsForRemovedProviders(currentLoginProviders);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
@@ -55,6 +56,9 @@ public interface IContentService : IContentServiceBase<IContent>
|
||||
/// <summary>
|
||||
/// Creates a new content item from a blueprint.
|
||||
/// </summary>
|
||||
/// <remarks>Warning: If you intend to save the resulting <c>IContent</c> as a content node, you must trigger a
|
||||
/// <see cref="Notifications.ContentScaffoldedNotification"/> notification to ensure that the block ids are regenerated.
|
||||
/// Failing to do so could lead to caching issues.</remarks>
|
||||
IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId);
|
||||
|
||||
/// <summary>
|
||||
@@ -315,6 +319,21 @@ public interface IContentService : IContentServiceBase<IContent>
|
||||
/// </summary>
|
||||
void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to move the <see cref="IContent"/> <paramref name="content"/> to under the node with id <paramref name="parentId"/>.
|
||||
/// </summary>
|
||||
/// <param name="content">The <see cref="IContent"/> that shall be moved.</param>
|
||||
/// <param name="parentId">The id of the new parent node.</param>
|
||||
/// <param name="userId">Id of the user attempting to move <paramref name="content"/>.</param>
|
||||
/// <returns>Success if moving succeeded, otherwise Failed.</returns>
|
||||
[Obsolete("Adds return type to Move method. Will be removed in V14, as the original method will be adjusted.")]
|
||||
OperationResult
|
||||
AttemptMove(IContent content, int parentId, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
Move(content, parentId, userId);
|
||||
return OperationResult.Succeed(new EventMessages());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a document.
|
||||
/// </summary>
|
||||
@@ -415,6 +434,7 @@ public interface IContentService : IContentServiceBase<IContent>
|
||||
/// published. The root of the branch is always published, regardless of <paramref name="force" />.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Obsolete("This method is not longer used as the 'force' parameter has been extended into options for publishing unpublished and re-publishing changed content. Please use the overload containing the parameter for those options instead.")]
|
||||
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Constants.Security.SuperUserId);
|
||||
|
||||
/// <summary>
|
||||
@@ -431,8 +451,47 @@ public interface IContentService : IContentServiceBase<IContent>
|
||||
/// published. The root of the branch is always published, regardless of <paramref name="force" />.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Obsolete("This method is not longer used as the 'force' parameter has been extended into options for publishing unpublished and re-publishing changed content. Please use the overload containing the parameter for those options instead.")]
|
||||
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId);
|
||||
|
||||
/// <summary>
|
||||
/// Saves and publishes a document branch.
|
||||
/// </summary>
|
||||
/// <param name="content">The root document.</param>
|
||||
/// <param name="publishBranchFilter">A value indicating options for force publishing unpublished or re-publishing unchanged content.</param>
|
||||
/// <param name="culture">A culture, or "*" for all cultures.</param>
|
||||
/// <param name="userId">The identifier of the user performing the operation.</param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Unless specified, all cultures are re-published. Otherwise, one culture can be specified. To act on more
|
||||
/// than one culture, see the other overloads of this method.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The root of the branch is always published, regardless of <paramref name="publishBranchFilter" />.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, PublishBranchFilter publishBranchFilter, string culture = "*", int userId = Constants.Security.SuperUserId)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> SaveAndPublishBranch(content, publishBranchFilter.HasFlag(PublishBranchFilter.IncludeUnpublished), culture, userId);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
/// <summary>
|
||||
/// Saves and publishes a document branch.
|
||||
/// </summary>
|
||||
/// <param name="content">The root document.</param>
|
||||
/// <param name="publishBranchFilter">A value indicating options for force publishing unpublished or re-publishing unchanged content.</param>
|
||||
/// <param name="cultures">The cultures to publish.</param>
|
||||
/// <param name="userId">The identifier of the user performing the operation.</param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The root of the branch is always published, regardless of <paramref name="publishBranchFilter" />.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, PublishBranchFilter publishBranchFilter, string[] cultures, int userId = Constants.Security.SuperUserId)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> SaveAndPublishBranch(content, publishBranchFilter.HasFlag(PublishBranchFilter.IncludeUnpublished), cultures, userId);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
///// <summary>
|
||||
///// Saves and publishes a document branch.
|
||||
///// </summary>
|
||||
|
||||
@@ -5,47 +5,53 @@ namespace Umbraco.Cms.Core.Services;
|
||||
public interface IExternalLoginWithKeyService : IService
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all user logins assigned
|
||||
/// Returns all user logins assigned.
|
||||
/// </summary>
|
||||
IEnumerable<IIdentityUserLogin> GetExternalLogins(Guid userOrMemberKey);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all user login tokens assigned
|
||||
/// Returns all user login tokens assigned.
|
||||
/// </summary>
|
||||
IEnumerable<IIdentityUserToken> GetExternalLoginTokens(Guid userOrMemberKey);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all logins matching the login info - generally there should only be one but in some cases
|
||||
/// there might be more than one depending on if an administrator has been editing/removing members
|
||||
/// there might be more than one depending on if an administrator has been editing/removing members.
|
||||
/// </summary>
|
||||
IEnumerable<IIdentityUserLogin> Find(string loginProvider, string providerKey);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the external logins associated with the user
|
||||
/// Saves the external logins associated with the user.
|
||||
/// </summary>
|
||||
/// <param name="userOrMemberKey">
|
||||
/// The user or member key associated with the logins
|
||||
/// The user or member key associated with the logins.
|
||||
/// </param>
|
||||
/// <param name="logins"></param>
|
||||
/// <remarks>
|
||||
/// This will replace all external login provider information for the user
|
||||
/// This will replace all external login provider information for the user.
|
||||
/// </remarks>
|
||||
void Save(Guid userOrMemberKey, IEnumerable<IExternalLogin> logins);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the external login tokens associated with the user
|
||||
/// Saves the external login tokens associated with the user.
|
||||
/// </summary>
|
||||
/// <param name="userOrMemberKey">
|
||||
/// The user or member key associated with the logins
|
||||
/// The user or member key associated with the logins.
|
||||
/// </param>
|
||||
/// <param name="tokens"></param>
|
||||
/// <remarks>
|
||||
/// This will replace all external login tokens for the user
|
||||
/// This will replace all external login tokens for the user.
|
||||
/// </remarks>
|
||||
void Save(Guid userOrMemberKey, IEnumerable<IExternalLoginToken> tokens);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all user logins - normally used when a member is deleted
|
||||
/// Deletes all user logins - normally used when a member is deleted.
|
||||
/// </summary>
|
||||
void DeleteUserLogins(Guid userOrMemberKey);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes external logins that aren't associated with the current collection of providers.
|
||||
/// </summary>
|
||||
/// <param name="currentLoginProviders">The names of the currently configured providers.</param>
|
||||
void DeleteUserLoginsForRemovedProviders(IEnumerable<string> currentLoginProviders) { }
|
||||
}
|
||||
|
||||
@@ -210,6 +210,21 @@ public interface IMemberService : IMembershipMemberService
|
||||
/// </returns>
|
||||
IMember? GetById(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Get an list of <see cref="IMember"/> for all members with the specified email.
|
||||
/// </summary>
|
||||
//// <param name="email">Email to use for retrieval</param>
|
||||
/// <returns>
|
||||
/// <see cref="IEnumerable{IMember}" />
|
||||
/// </returns>
|
||||
IEnumerable<IMember> GetMembersByEmail(string email)
|
||||
=>
|
||||
// TODO (V16): Remove this default implementation.
|
||||
// The following is very inefficient, but will return the correct data, so probably better than throwing a NotImplementedException
|
||||
// in the default implentation here, for, presumably rare, cases where a custom IMemberService implementation has been registered and
|
||||
// does not override this method.
|
||||
GetAllMembers().Where(x => x.Email.Equals(email));
|
||||
|
||||
/// <summary>
|
||||
/// Gets all Members for the specified MemberType alias
|
||||
/// </summary>
|
||||
@@ -328,4 +343,11 @@ public interface IMemberService : IMembershipMemberService
|
||||
/// <see cref="IEnumerable{IMember}" />
|
||||
/// </returns>
|
||||
IEnumerable<IMember>? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact);
|
||||
|
||||
/// <summary>
|
||||
/// Saves only the properties related to login for the member, using an optimized, non-locking update.
|
||||
/// </summary>
|
||||
/// <param name="member">The member to update.</param>
|
||||
/// <returns>Used to avoid the full save of the member object after a login operation.</returns>
|
||||
Task UpdateLoginPropertiesAsync(IMember member) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -135,6 +135,14 @@ public interface IMembershipMemberService<T> : IService
|
||||
/// <param name="entity"><see cref="IMember" /> or <see cref="IUser" /> to Save</param>
|
||||
void Save(T entity);
|
||||
|
||||
/// <summary>
|
||||
/// Saves an <see cref="IMembershipUser" />
|
||||
/// </summary>
|
||||
/// <remarks>An <see cref="IMembershipUser" /> can be of type <see cref="IMember" /> or <see cref="IUser" /></remarks>
|
||||
/// <param name="entity"><see cref="IMember" /> or <see cref="IUser" /> to Save</param>
|
||||
/// <param name="publishNotificationSaveOptions"> Enum for deciding which notifications to publish.</param>
|
||||
void Save(T entity, PublishNotificationSaveOptions publishNotificationSaveOptions) => Save(entity);
|
||||
|
||||
/// <summary>
|
||||
/// Saves a list of <see cref="IMembershipUser" /> objects
|
||||
/// </summary>
|
||||
|
||||
@@ -231,8 +231,29 @@ public interface IUserService : IMembershipUserService
|
||||
/// </returns>
|
||||
IEnumerable<IUser> GetAllNotInGroup(int groupId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a page of users, ordered by Id and starting from the provided Id.
|
||||
/// </summary>
|
||||
/// <param name="id">The user Id to start retrieving users from.</param>
|
||||
/// <param name="count">The number of users to return.</param>
|
||||
/// <returns>A page of <see cref="IUser"/> instances.</returns>
|
||||
[Obsolete("No longer used in Umbraco. Scheduled for removal in Umbraco 18.")]
|
||||
IEnumerable<IUser> GetNextUsers(int id, int count);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a page of approved users, ordered by Id and starting from the provided Id.
|
||||
/// </summary>
|
||||
/// <param name="id">The user Id to start retrieving users from.</param>
|
||||
/// <param name="count">The number of users to return.</param>
|
||||
/// <returns>A page of <see cref="IUser"/> instances.</returns>
|
||||
IEnumerable<IUser> GetNextApprovedUsers(int id, int count) => Enumerable.Empty<IUser>();
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates sessions for users that aren't associated with the current collection of providers.
|
||||
/// </summary>
|
||||
/// <param name="currentLoginProviders">The keys for the currently configured providers.</param>
|
||||
void InvalidateSessionsForRemovedProviders(IEnumerable<string> currentLoginProviders) { }
|
||||
|
||||
#region User groups
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -350,7 +350,13 @@ public class LocalizedTextService : ILocalizedTextService
|
||||
IEnumerable<XElement> areas = xmlSource[cult].Value.XPathSelectElements("//area");
|
||||
foreach (XElement area in areas)
|
||||
{
|
||||
var result = new Dictionary<string, string>(StringComparer.InvariantCulture);
|
||||
var areaAlias = area.Attribute("alias")!.Value;
|
||||
|
||||
if (!overallResult.TryGetValue(areaAlias, out IDictionary<string, string>? result))
|
||||
{
|
||||
result = new Dictionary<string, string>(StringComparer.InvariantCulture);
|
||||
}
|
||||
|
||||
IEnumerable<XElement> keys = area.XPathSelectElements("./key");
|
||||
foreach (XElement key in keys)
|
||||
{
|
||||
@@ -364,7 +370,10 @@ public class LocalizedTextService : ILocalizedTextService
|
||||
}
|
||||
}
|
||||
|
||||
overallResult.Add(area.Attribute("alias")!.Value, result);
|
||||
if (!overallResult.ContainsKey(areaAlias))
|
||||
{
|
||||
overallResult.Add(areaAlias, result);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge English Dictionary
|
||||
@@ -374,11 +383,11 @@ public class LocalizedTextService : ILocalizedTextService
|
||||
IEnumerable<XElement> enUS = xmlSource[englishCulture].Value.XPathSelectElements("//area");
|
||||
foreach (XElement area in enUS)
|
||||
{
|
||||
IDictionary<string, string>
|
||||
result = new Dictionary<string, string>(StringComparer.InvariantCulture);
|
||||
if (overallResult.ContainsKey(area.Attribute("alias")!.Value))
|
||||
var areaAlias = area.Attribute("alias")!.Value;
|
||||
|
||||
if (!overallResult.TryGetValue(areaAlias, out IDictionary<string, string>? result))
|
||||
{
|
||||
result = overallResult[area.Attribute("alias")!.Value];
|
||||
result = new Dictionary<string, string>(StringComparer.InvariantCulture);
|
||||
}
|
||||
|
||||
IEnumerable<XElement> keys = area.XPathSelectElements("./key");
|
||||
@@ -394,9 +403,9 @@ public class LocalizedTextService : ILocalizedTextService
|
||||
}
|
||||
}
|
||||
|
||||
if (!overallResult.ContainsKey(area.Attribute("alias")!.Value))
|
||||
if (!overallResult.ContainsKey(areaAlias))
|
||||
{
|
||||
overallResult.Add(area.Attribute("alias")!.Value, result);
|
||||
overallResult.Add(areaAlias, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ namespace Umbraco.Cms.Core.Services
|
||||
}
|
||||
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
|
||||
scope.ReadLock(Constants.Locks.ContentTree);
|
||||
scope.ReadLock(Constants.Locks.MediaTree);
|
||||
return _mediaRepository.GetPage(Query<IMedia>()?.Where(x => x.ContentTypeId == contentTypeId), pageIndex, pageSize, out totalRecords, filter, ordering);
|
||||
}
|
||||
|
||||
@@ -441,7 +441,7 @@ namespace Umbraco.Cms.Core.Services
|
||||
}
|
||||
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
|
||||
scope.ReadLock(Constants.Locks.ContentTree);
|
||||
scope.ReadLock(Constants.Locks.MediaTree);
|
||||
return _mediaRepository.GetPage(
|
||||
Query<IMedia>()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)), pageIndex, pageSize, out totalRecords, filter, ordering);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user