Compare commits
390
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59f9bf290d | ||
|
|
b6b8f54c65 | ||
|
|
50139ee506 | ||
|
|
62947b2862 | ||
|
|
1748a08f7b | ||
|
|
3264d58f13 | ||
|
|
b6be468b3a | ||
|
|
3ec88aab65 | ||
|
|
da43086017 | ||
|
|
d62fe5e315 | ||
|
|
852192a5d2 | ||
|
|
f4498c3d05 | ||
|
|
b045b33049 | ||
|
|
32a4fbebd5 | ||
|
|
cc2f1df78e | ||
|
|
9b2fd1253b | ||
|
|
024697f3bf | ||
|
|
d8b76575ca | ||
|
|
5b00312158 | ||
|
|
d920e93d1e | ||
|
|
feba51a49d | ||
|
|
853c1acf7e | ||
|
|
3f10bd8c21 | ||
|
|
b13eb8aaf8 | ||
|
|
275478066e | ||
|
|
b01def0872 | ||
|
|
b106305167 | ||
|
|
103631de15 | ||
|
|
1b19ca7cdb | ||
|
|
0ad020f0ce | ||
|
|
fc1455e0d8 | ||
|
|
793534c627 | ||
|
|
15a8b4066a | ||
|
|
38f6e2b0da | ||
|
|
14f60a108a | ||
|
|
10518ed454 | ||
|
|
375a0b4388 | ||
|
|
6300ccc211 | ||
|
|
5b6f544d2a | ||
|
|
a1379f13a0 | ||
|
|
38e9781fc5 | ||
|
|
dfc6ead6d5 | ||
|
|
30a431f870 | ||
|
|
e932fa5404 | ||
|
|
3579c4ac9e | ||
|
|
671751b618 | ||
|
|
3b6e4a96f1 | ||
|
|
83e580c3a7 | ||
|
|
31ee35e721 | ||
|
|
cf0f3f1380 | ||
|
|
b4528cf963 | ||
|
|
cb1ec988ce | ||
|
|
31c3f5ae0c | ||
|
|
15643135ba | ||
|
|
297ceaf0aa | ||
|
|
8e87f2260c | ||
|
|
927e4d8cad | ||
|
|
cd71e5666a | ||
|
|
c38faec74b | ||
|
|
c86a6fa8e5 | ||
|
|
1e521760df | ||
|
|
1ccb5cc09e | ||
|
|
12ca71558a | ||
|
|
8b759cf3a4 | ||
|
|
25fffd4173 | ||
|
|
29c0151460 | ||
|
|
beb60065bb | ||
|
|
cc9c33bfe6 | ||
|
|
6edffd9f08 | ||
|
|
7e57396960 | ||
|
|
981f173a79 | ||
|
|
83fe87ca88 | ||
|
|
432dda8c47 | ||
|
|
ae078bf96b | ||
|
|
6455f96e51 | ||
|
|
a9fc88d7e6 | ||
|
|
76e2103551 | ||
|
|
340ae6c474 | ||
|
|
7495c3c7b2 | ||
|
|
449c94f08d | ||
|
|
83b92666ac | ||
|
|
b7bf30744a | ||
|
|
9e97f6028b | ||
|
|
f9496e8067 | ||
|
|
8ba3a4d926 | ||
|
|
7fe6971a61 | ||
|
|
7b10d39d66 | ||
|
|
9ef2b37be4 | ||
|
|
516d62a348 | ||
|
|
97b3023e14 | ||
|
|
ff30b4f686 | ||
|
|
6bca91a52f | ||
|
|
164b80af0e | ||
|
|
ba0028c03d | ||
|
|
6f38a57c8a | ||
|
|
a0e3ca601e | ||
|
|
1a9d8e3a5d | ||
|
|
76f27726c5 | ||
|
|
d9d66465f6 | ||
|
|
947afdbc1e | ||
|
|
134c8006c0 | ||
|
|
86e7343f99 | ||
|
|
10ecc99d74 | ||
|
|
84d0ae3ea2 | ||
|
|
aa76f782ef | ||
|
|
06a2a500b3 | ||
|
|
17a5218a58 | ||
|
|
4c9084238c | ||
|
|
9c03422222 | ||
|
|
b61b9dbd5e | ||
|
|
d9a31d337a | ||
|
|
822c8374ef | ||
|
|
4239f0fc06 | ||
|
|
db43799d0f | ||
|
|
2723e4f77c | ||
|
|
f926316751 | ||
|
|
b991976fd3 | ||
|
|
c0f820a05c | ||
|
|
f6d93ef0ba | ||
|
|
3d1e17b07f | ||
|
|
ae423decc3 | ||
|
|
1d9a031c1c | ||
|
|
4078e83634 | ||
|
|
4b016317f9 | ||
|
|
1f4c19d484 | ||
|
|
fd77074d57 | ||
|
|
bf74636f26 | ||
|
|
1aff4c69f0 | ||
|
|
c139015019 | ||
|
|
90a34e17ca | ||
|
|
806f10e52a | ||
|
|
788e5cd678 | ||
|
|
ebd5fb4d0c | ||
|
|
5f3ce28032 | ||
|
|
ad7053af36 | ||
|
|
6247f54976 | ||
|
|
3e6b9313e5 | ||
|
|
7d41791543 | ||
|
|
157b7d14eb | ||
|
|
f3658bf356 | ||
|
|
e4b3104399 | ||
|
|
9db62a2c0c | ||
|
|
3cd6fcfe67 | ||
|
|
3b736d8c73 | ||
|
|
bbfe40d733 | ||
|
|
9d30d5b11c | ||
|
|
11c19847cf | ||
|
|
8e0912cbf1 | ||
|
|
bf89eae07f | ||
|
|
19e873c7ad | ||
|
|
d4acd53fd5 | ||
|
|
85fc189ee8 | ||
|
|
a48a70595a | ||
|
|
0f539088fa | ||
|
|
80233a7850 | ||
|
|
1dd10eabd4 | ||
|
|
e7a52de097 | ||
|
|
40a2d75282 | ||
|
|
7fb9df9b19 | ||
|
|
1720692d3d | ||
|
|
b798925ddb | ||
|
|
f4caabaa98 | ||
|
|
b1dbd80a07 | ||
|
|
882a0427e4 | ||
|
|
d978a10b61 | ||
|
|
7200b7b623 | ||
|
|
f200da48d8 | ||
|
|
fc815db80b | ||
|
|
c5a3778ce7 | ||
|
|
8c1dfe5431 | ||
|
|
4cd43b264d | ||
|
|
3e835cd949 | ||
|
|
88b1b9de13 | ||
|
|
98e0615338 | ||
|
|
e18cdab185 | ||
|
|
26907f202f | ||
|
|
13d95f31f2 | ||
|
|
c422c82f21 | ||
|
|
ae87f9ccd4 | ||
|
|
3359562e32 | ||
|
|
0e3c8dab8f | ||
|
|
4d8406bb64 | ||
|
|
2bc549bbc4 | ||
|
|
e8b7d9daee | ||
|
|
48ac4b546e | ||
|
|
eb5c57fb94 | ||
|
|
468ca9ae94 | ||
|
|
95cd6797cc | ||
|
|
6f63837dbc | ||
|
|
6dc4eca607 | ||
|
|
aca908c549 | ||
|
|
685a05827e | ||
|
|
07080604b9 | ||
|
|
85176d1bf6 | ||
|
|
52bf6bc412 | ||
|
|
47e09efec6 | ||
|
|
9761ef899b | ||
|
|
164868f32a | ||
|
|
a71ebe1902 | ||
|
|
94f0add4d9 | ||
|
|
9c88326681 | ||
|
|
95e89f8481 | ||
|
|
b25bcdc436 | ||
|
|
028da4545e | ||
|
|
37035e6e7f | ||
|
|
f8c35bb7e9 | ||
|
|
056434fa27 | ||
|
|
e32eb06ddf | ||
|
|
76dd141d86 | ||
|
|
54601d42e2 | ||
|
|
619e025e7a | ||
|
|
609e9f43e1 | ||
|
|
ccc70fc786 | ||
|
|
b5bffdfada | ||
|
|
0dd4443b75 | ||
|
|
10f37494b6 | ||
|
|
2711ac07ac | ||
|
|
80e092069b | ||
|
|
c23e2eb6f2 | ||
|
|
25837e66fc | ||
|
|
3be766fe2a | ||
|
|
34649db5c9 | ||
|
|
4c679a5f4c | ||
|
|
0331cd154b | ||
|
|
36c66177fc | ||
|
|
ad443c7b13 | ||
|
|
0b6fe91fd3 | ||
|
|
ee496d2730 | ||
|
|
384b9bdb65 | ||
|
|
63113c4552 | ||
|
|
39cad5b2ea | ||
|
|
30633fe728 | ||
|
|
394210a8f7 | ||
|
|
45b0e43b89 | ||
|
|
c21da38c32 | ||
|
|
ef751ca699 | ||
|
|
52debc3010 | ||
|
|
a38a9b29a3 | ||
|
|
e346ac2bee | ||
|
|
425901636e | ||
|
|
d9f17b36c6 | ||
|
|
5fe5a16158 | ||
|
|
e999d3977c | ||
|
|
0f98b8342c | ||
|
|
b13f90046d | ||
|
|
304db8c28a | ||
|
|
723d3be905 | ||
|
|
5f37cd3d20 | ||
|
|
203cc0e380 | ||
|
|
442de9a803 | ||
|
|
eea6b9b2f1 | ||
|
|
b3b1ce2dcd | ||
|
|
69b69a5fc1 | ||
|
|
605cda9224 | ||
|
|
b65ad4e234 | ||
|
|
4e680c88bb | ||
|
|
e8f991f485 | ||
|
|
66891333a8 | ||
|
|
044c9d2c87 | ||
|
|
7b0c419ace | ||
|
|
6ec1b9f1e4 | ||
|
|
18069633b2 | ||
|
|
b24c29c647 | ||
|
|
f3f7fcc051 | ||
|
|
b6fa93edac | ||
|
|
b07a24ba55 | ||
|
|
42cb9b5c1e | ||
|
|
88657c1bb0 | ||
|
|
e91a25dcb6 | ||
|
|
81e63e7fa9 | ||
|
|
2f9d58fc68 | ||
|
|
e9ba06929f | ||
|
|
a5da305093 | ||
|
|
e1e5f527e4 | ||
|
|
f4e0e38824 | ||
|
|
fa09ce6214 | ||
|
|
dd8fb66300 | ||
|
|
a02db287ce | ||
|
|
e9c97f8c9b | ||
|
|
f016dbd223 | ||
|
|
da5669d9fc | ||
|
|
74aa3d9676 | ||
|
|
c671668e45 | ||
|
|
e337821d0e | ||
|
|
b2aa04ae1a | ||
|
|
d3c6c87378 | ||
|
|
b3e192916d | ||
|
|
dfe100d7fc | ||
|
|
3420748b35 | ||
|
|
e270adc50f | ||
|
|
ce9460161f | ||
|
|
fd5b8de533 | ||
|
|
e3d9b042c2 | ||
|
|
54931472f8 | ||
|
|
df6a88b4d5 | ||
|
|
2cc9d2cab0 | ||
|
|
c0b6317da8 | ||
|
|
b2f69691d6 | ||
|
|
b684bf0a69 | ||
|
|
434dd50021 | ||
|
|
863d3d11c6 | ||
|
|
d906fc346d | ||
|
|
ced3db8542 | ||
|
|
a48d8a4110 | ||
|
|
c1671f4e9b | ||
|
|
be4d4bef39 | ||
|
|
f4ae796b30 | ||
|
|
367d0386b6 | ||
|
|
1d48261b7b | ||
|
|
ebb6a580dc | ||
|
|
5b54bed406 | ||
|
|
bc488d78e4 | ||
|
|
d3b60277d7 | ||
|
|
a51cbd3ee9 | ||
|
|
458eb2e52f | ||
|
|
7ef11621dd | ||
|
|
0d066c4ccc | ||
|
|
4fe168f540 | ||
|
|
ead277ed3e | ||
|
|
c35d7d18a8 | ||
|
|
2ec6ff4ebc | ||
|
|
78a8d017a2 | ||
|
|
50c3756eb0 | ||
|
|
c757f9fcb0 | ||
|
|
c03b1612b6 | ||
|
|
a79095a869 | ||
|
|
95c892000d | ||
|
|
7b6cef6083 | ||
|
|
5bf63c2fa8 | ||
|
|
a939f8ee1c | ||
|
|
29b4056f68 | ||
|
|
45ea6a3cfc | ||
|
|
6d0dd82781 | ||
|
|
7b422598f9 | ||
|
|
52bb32205b | ||
|
|
8a68457421 | ||
|
|
84d5b7aa52 | ||
|
|
62690194cf | ||
|
|
900938edd0 | ||
|
|
00e7602b55 | ||
|
|
7a9e1b66d9 | ||
|
|
1a205d8727 | ||
|
|
867d405992 | ||
|
|
a99c581ab5 | ||
|
|
85883cee85 | ||
|
|
0f9c2332e0 | ||
|
|
a7f5b142e9 | ||
|
|
ca6da751f2 | ||
|
|
5358c0346e | ||
|
|
6d5b6a4553 | ||
|
|
396b5ea211 | ||
|
|
7e4dda7bab | ||
|
|
3cf9099f56 | ||
|
|
92cb355f4d | ||
|
|
fb11191fd1 | ||
|
|
30186e0bdd | ||
|
|
2b24b40a75 | ||
|
|
09bcede957 | ||
|
|
998b6f9296 | ||
|
|
2826678049 | ||
|
|
67ed38f6a2 | ||
|
|
fbe2cfacd8 | ||
|
|
e8ff21987b | ||
|
|
b9250c20e9 | ||
|
|
e9ef05057e | ||
|
|
8379cba15d | ||
|
|
51cce234f4 | ||
|
|
865a2cd83a | ||
|
|
33d83e1491 | ||
|
|
f6b4ddf598 | ||
|
|
2ebc6badd7 | ||
|
|
2bb894c038 | ||
|
|
3ef9d64f1a | ||
|
|
a20f11e48f | ||
|
|
4795609973 | ||
|
|
8e7809d37e | ||
|
|
96febee559 | ||
|
|
82eeb0737b | ||
|
|
451b5f96e7 | ||
|
|
15ebae5025 | ||
|
|
64024e418f | ||
|
|
a7a6eb8af6 | ||
|
|
f6ab6eea75 | ||
|
|
9809db4179 | ||
|
|
32bfbadb8e | ||
|
|
5ed09ebefa | ||
|
|
eaea7a67ba | ||
|
|
de2114b8c5 | ||
|
|
64607ddbaa | ||
|
|
ac3b17b390 |
@@ -61,7 +61,7 @@ This guide describes each step to make your first contribution:
|
||||
## Further contribution guides
|
||||
|
||||
- [Before you start](contributing-before-you-start.md)
|
||||
- [Finding your first issue: Up for grabs](contributing-before-you-start.md)
|
||||
- [Finding your first issue: Up for grabs](contributing-first-issue.md)
|
||||
- [Contributing to the new backoffice](https://docs.umbraco.com/umbraco-backoffice/)
|
||||
- [Unwanted changes](contributing-unwanted-changes.md)
|
||||
- [Other ways to contribute](contributing-other-ways-to-contribute.md)
|
||||
|
||||
@@ -6,8 +6,8 @@ body:
|
||||
- type: input
|
||||
id: "version"
|
||||
attributes:
|
||||
label: "Which Umbraco version are you using? (Please write the *exact* version, example: 10.1.0)"
|
||||
description: "Use the help icon in the Umbraco backoffice to find the version you're using"
|
||||
label: "Which Umbraco version are you using?"
|
||||
description: "Please write the *exact* version, example: `10.1.0`. Use the help icon in the Umbraco backoffice to find the version you're using"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -4,11 +4,11 @@ contact_links:
|
||||
url: https://github.com/umbraco/Umbraco-CMS/discussions/new?category=features-and-ideas
|
||||
about: Start a new discussion when you have ideas or feature requests, eventually discussions can turn into plans
|
||||
- name: ⁉️ Support Question
|
||||
url: https://our.umbraco.com
|
||||
url: https://forum.umbraco.com
|
||||
about: This issue tracker is NOT meant for support questions. If you have a question, please join us on the forum.
|
||||
- name: 📖 Documentation Issue
|
||||
url: https://github.com/umbraco/UmbracoDocs/issues
|
||||
about: Documentation issues should be reported on the Umbraco documentation repository.
|
||||
- name: 🔐 Security Issue
|
||||
url: https://umbraco.com/about-us/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/
|
||||
url: https://umbraco.com/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/
|
||||
about: Discovered a Security Issue in Umbraco?
|
||||
|
||||
+5
-4
@@ -1,11 +1,12 @@
|
||||
# [Umbraco CMS](https://umbraco.com)
|
||||
|
||||
[](../LICENSE.md)
|
||||
[](CONTRIBUTING.md)
|
||||

|
||||
[](https://discord.gg/umbraco)
|
||||
[](https://discord-chats.umbraco.com)
|
||||
[](https://www.nuget.org/packages/Umbraco.Cms)
|
||||
[](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=301)
|
||||
[](CONTRIBUTING.md)
|
||||
[](https://forum.umbraco.com)
|
||||
[](https://discord.gg/umbraco)
|
||||

|
||||
|
||||
|
||||
### Umbraco is a free and open source .NET content management system. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
../src/Umbraco.Web.UI.Client/.vscode/lit.code-snippets
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"unprovide"
|
||||
]
|
||||
}
|
||||
@@ -45,8 +45,8 @@
|
||||
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
|
||||
<PackageVersion Include="Examine" Version="3.6.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.6.0" />
|
||||
<PackageVersion Include="Examine" Version="3.7.1" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.7.1" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.74" />
|
||||
<PackageVersion Include="JsonPatch.Net" Version="3.1.1" />
|
||||
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
|
||||
@@ -100,4 +100,4 @@
|
||||
<!-- Examine.Lucene brings in a vulnerable version of Lucene.Net.Replicator -->
|
||||
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
+579
@@ -0,0 +1,579 @@
|
||||
Third-Party Notices
|
||||
===================
|
||||
|
||||
This file contains notices and attributions for third-party software used in the Umbraco CMS project.
|
||||
|
||||
It is not a license and does not grant any rights to use the third-party software.
|
||||
|
||||
Umbraco CMS is licensed under the MIT License, which can be found in the LICENSE file.
|
||||
|
||||
---
|
||||
|
||||
@openid/AppAuth-JS: An OpenID Connect and OAuth 2.0 client library for JavaScript
|
||||
|
||||
URL: https://github.com/openid/AppAuth-JS
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2017 Google Inc.
|
||||
|
||||
---
|
||||
|
||||
AutoFixture: Write maintainable unit tests, faster
|
||||
|
||||
URL: https://github.com/AutoFixture/AutoFixture
|
||||
License: MIT License
|
||||
Copyright: 2013 Mark Seemann
|
||||
|
||||
---
|
||||
|
||||
Asp.Versioning.Mvc: A library for ASP.NET Core versioning
|
||||
|
||||
URL: https://github.com/dotnet/aspnet-api-versioning
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and contributors
|
||||
|
||||
---
|
||||
|
||||
Babel: A JavaScript compiler
|
||||
|
||||
URL: https://babeljs.io/
|
||||
License: MIT License
|
||||
Copyright: 2014-present Sebastian McKenzie and other contributors
|
||||
|
||||
---
|
||||
|
||||
BenchmarkDotNet: Powerful .NET library for benchmarking
|
||||
|
||||
URL: https://github.com/dotnet/BenchmarkDotNet
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
Bogus: A simple and sane data generator for populating objects that supports different locales.
|
||||
|
||||
URL: https://github.com/bchavez/Bogus
|
||||
License: MIT License
|
||||
Copyright: 2015 Brian Chavez
|
||||
|
||||
---
|
||||
|
||||
CommandLineParser: Terse syntax C# command line parser for .NET
|
||||
|
||||
URL: https://github.com/commandlineparser/commandline
|
||||
License: MIT License
|
||||
Copyright: 2005-2015 Giacomo Stelluti Scala & Contributors
|
||||
|
||||
---
|
||||
|
||||
cross-env: A CLI tool to set environment variables across platforms
|
||||
|
||||
URL: https://github.com/kentcdodds/cross-env
|
||||
License: MIT License
|
||||
Copyright: 2017 Kent C. Dodds
|
||||
|
||||
---
|
||||
|
||||
Dazinator.Extensions.FileProviders: A library for file provider extensions
|
||||
|
||||
URL: https://github.com/dazinator/Dazinator.Extensions.FileProviders
|
||||
License: MIT License
|
||||
Copyright: 2016 Darrell
|
||||
|
||||
---
|
||||
|
||||
DOMPurify: A DOM-only XSS sanitizer for HTML, MathML and SVG
|
||||
|
||||
URL: https://github.com/cure53/DOMPurify
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2025 Dr.-Ing. Mario Heiderich, Cure53
|
||||
|
||||
---
|
||||
|
||||
Element Internals Polyfill: A polyfill for the Element Internals API
|
||||
|
||||
URL: https://github.com/calebdwilliams/element-internals-polyfill
|
||||
License: MIT License
|
||||
Copyright: 2021 Caleb Williams
|
||||
|
||||
---
|
||||
|
||||
Eslint: A tool for identifying and reporting on patterns in JavaScript
|
||||
|
||||
URL: https://eslint.org/
|
||||
License: MIT License
|
||||
Copyright: OpenJS Foundation and other contributors
|
||||
|
||||
---
|
||||
|
||||
Examine: A search and indexing library for .NET
|
||||
|
||||
URL: https://github.com/Shazwazza/Examine
|
||||
License: Microsoft Public License (Ms-PL)
|
||||
Copyright: 2023 Shannon Deminick
|
||||
|
||||
---
|
||||
|
||||
Glob: A library for matching file paths using glob patterns
|
||||
|
||||
URL: https://github.com/isaacs/node-glob
|
||||
License: ISC License
|
||||
Copyright: 2009-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
---
|
||||
|
||||
Globals: A library for managing global variables in JavaScript
|
||||
|
||||
URL: https://github.com/sindresorhus/globals
|
||||
License: MIT License
|
||||
Copyright: Sindre Sorhus
|
||||
|
||||
---
|
||||
|
||||
Html Agility Pack: An HTML parser for .NET
|
||||
|
||||
URL: https://html-agility-pack.net/
|
||||
License: MIT License
|
||||
Copyright: ZZZ Projects Inc.
|
||||
|
||||
---
|
||||
|
||||
ImageSharp: A cross-platform library for processing images in .NET
|
||||
|
||||
URL: https://github.com/SixLabors/ImageSharp
|
||||
License: Apache License, Version 2.0 under the Six Labors Split License
|
||||
Copyright: Six Labors
|
||||
|
||||
---
|
||||
|
||||
jsdiff: A JavaScript text differencing implementation
|
||||
|
||||
URL: https://github.com/kpdecker/jsdiff
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2009-2015 Kevin Decker <kpdecker@gmail.com>
|
||||
|
||||
---
|
||||
|
||||
JsonPatch.Net: A library for JSON Patch (RFC 6902) in .NET
|
||||
|
||||
URL: https://github.com/json-everything/json-everything
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
K4os.Compression.LZ4: A fast LZ4 compression library for .NET
|
||||
|
||||
URL: https://github.com/MiloszKrajewski/K4os.Compression.LZ4
|
||||
License: MIT License
|
||||
Copyright: 2017 Milosz Krajewski
|
||||
|
||||
---
|
||||
|
||||
Lit: A simple library for building fast, lightweight web components
|
||||
|
||||
URL: https://lit.dev
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2020 Google LLC. All rights reserved.
|
||||
|
||||
---
|
||||
|
||||
Lucide: Beautiful & consistent icons for the web
|
||||
|
||||
URL: https://lucide.dev/
|
||||
License: ISC License
|
||||
Copyright: 2013-2022 Cole Bemis
|
||||
Copyright: 2022 Lucide Contributors
|
||||
|
||||
---
|
||||
|
||||
Madge: A dependency graph generator for JavaScript
|
||||
|
||||
URL: https://github.com/pahen/madge
|
||||
License: MIT License
|
||||
Copyright: 2017 Patrik Henningsson
|
||||
|
||||
---
|
||||
|
||||
MailKit: A library for sending email in .NET
|
||||
|
||||
URL: https://github.com/jstedfast/MailKit
|
||||
License: MIT License
|
||||
Copyright: 2013-2024 .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
Markdown: A library for parsing and compiling Markdown
|
||||
|
||||
URL: https://github.com/hey-red/Markdown
|
||||
License: MIT License
|
||||
Copyright: 2018 red
|
||||
|
||||
---
|
||||
|
||||
marked: A markdown parser and compiler
|
||||
|
||||
URL: https://marked.js.org/
|
||||
License: MIT License
|
||||
Copyright: 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
|
||||
Copyright: 2018+, MarkedJS (https://github.com/markedjs/)
|
||||
|
||||
---
|
||||
|
||||
Message Pack: The extremely fast MessagePack serializer for C#
|
||||
|
||||
URL: https://github.com/MessagePack-CSharp/MessagePack-CSharp
|
||||
License: MIT License
|
||||
Copyright: 2017 Yoshifumi Kawai and contributors
|
||||
|
||||
---
|
||||
|
||||
Miniprofiler: A mini profiler for .NET
|
||||
|
||||
URL: https://github.com/MiniProfiler/dotnet
|
||||
License: MIT License
|
||||
Copyright: .NET MiniProfiler Contributors
|
||||
|
||||
---
|
||||
|
||||
Monaco Editor: A browser-based code editor
|
||||
|
||||
URL: https://microsoft.github.io/monaco-editor/
|
||||
License: MIT License
|
||||
Copyright: 2016-present Microsoft Corporation
|
||||
|
||||
---
|
||||
|
||||
Moq: A mocking library for .NET
|
||||
|
||||
URL: https://github.com/moq/moq
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2007 Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors.
|
||||
|
||||
---
|
||||
|
||||
Mock Service Worker (MSW): A library for mocking API requests in JavaScript
|
||||
|
||||
URL: https://mswjs.io/
|
||||
License: MIT License
|
||||
Copyright: 2018–present Artem Zakharchenko
|
||||
|
||||
---
|
||||
|
||||
NCrontab: A cron schedule parser for .NET
|
||||
|
||||
URL: https://github.com/atifaziz/NCrontab
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2001 The OpenSymphony Group
|
||||
Copyright: 2008 Atif Aziz
|
||||
|
||||
---
|
||||
|
||||
Nerdbank.GitVersioning: A library for versioning .NET projects
|
||||
|
||||
URL: https://github.com/dotnet/Nerdbank.GitVersioning
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
NJsonSchema: A JSON schema validator for .NET
|
||||
|
||||
URL: https://github.com/RicoSuter/NJsonSchema
|
||||
License: MIT License
|
||||
Copyright: 2022 Rico Suter
|
||||
|
||||
---
|
||||
|
||||
NPoco: A micro ORM for .NET
|
||||
|
||||
URL: https://github.com/schotime/NPoco
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Schotime
|
||||
|
||||
---
|
||||
|
||||
NUnit: A unit testing framework for .NET
|
||||
|
||||
URL: https://github.com/nunit/nunit
|
||||
License: MIT License
|
||||
Copyright: Charlie Poole, Rob Prouse and Contributors
|
||||
|
||||
---
|
||||
|
||||
Open Web Components: A set of standards and libraries for building web components
|
||||
|
||||
URL: https://open-wc.org/
|
||||
License: MIT License
|
||||
Copyright: 2018 open-wc
|
||||
|
||||
---
|
||||
|
||||
Openapi-ts: The OpenAPI to TypeScript codegen
|
||||
|
||||
URL: https://github.com/hey-api/openapi-ts
|
||||
License: MIT License
|
||||
Copyright: Hey API
|
||||
|
||||
---
|
||||
|
||||
OpenIddict: A simple and flexible OpenID Connect server for ASP.NET Core
|
||||
|
||||
URL: https://github.com/openiddict/openiddict-core
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Kévin Chalet
|
||||
|
||||
---
|
||||
|
||||
Playwright: A Node.js library to automate browser testing
|
||||
|
||||
URL: https://playwright.dev/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2025 Microsoft Corporation
|
||||
|
||||
---
|
||||
|
||||
Playwright-msw: A library to wrap Mock Service Worker with Playwright
|
||||
|
||||
URL: https://github.com/valendres/playwright-msw
|
||||
License: MIT License
|
||||
Copyright: 2022 Peter Weller
|
||||
|
||||
---
|
||||
|
||||
Prettier: An opinionated code formatter
|
||||
|
||||
URL: https://prettier.io/
|
||||
License: MIT License
|
||||
Copyright: James Long and contributors
|
||||
|
||||
---
|
||||
|
||||
Remark-gfm: A GitHub Flavored Markdown plugin for Remark
|
||||
|
||||
URL: https://github.com/remarkjs/remark-gfm
|
||||
License: MIT License
|
||||
Copyright: Titus Wormer
|
||||
|
||||
---
|
||||
|
||||
Rollup: A module bundler for JavaScript
|
||||
|
||||
URL: https://rollupjs.org/
|
||||
License: MIT License
|
||||
Copyright: 2015-present Rollup contributors
|
||||
|
||||
---
|
||||
|
||||
Rollup Plugins: A collection of Rollup plugins
|
||||
|
||||
URL: https://github.com/rollup/plugins
|
||||
License: MIT License
|
||||
Copyright: 2019-present Rollup Plugins contributors
|
||||
|
||||
---
|
||||
|
||||
Rollup-plugin-esbuild: A Rollup plugin for using esbuild
|
||||
|
||||
URL: https://github.com/egoist/rollup-plugin-esbuild
|
||||
License: MIT License
|
||||
Copyright: 2020 EGOIST
|
||||
|
||||
---
|
||||
|
||||
Rollup-plugin-import-css: A Rollup plugin for importing CSS files
|
||||
|
||||
URL: https://github.com/jleeson/rollup-plugin-import-css
|
||||
License: MIT License
|
||||
Copyright: 2020 Jacob Leeson
|
||||
|
||||
---
|
||||
|
||||
rxjs: Reactive Extensions for JavaScript
|
||||
|
||||
URL: https://rxjs.dev/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2015-present Ben Lesh <ben@benlesh.com>, Google, Inc., Netflix, Inc., Microsoft Corp., and contributors
|
||||
|
||||
---
|
||||
|
||||
Serilog: A diagnostic logging library for .NET
|
||||
|
||||
URL: https://github.com/serilog/serilog
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Serilog Contributors
|
||||
|
||||
---
|
||||
|
||||
Simple Icons: A set of SVG icons for popular brands
|
||||
|
||||
URL: https://simpleicons.org/
|
||||
License: CC0 1.0 Universal License
|
||||
Copyright: Simple Icons Contributors
|
||||
|
||||
---
|
||||
|
||||
Storybook: A UI component explorer for Web Components
|
||||
|
||||
URL: https://storybook.js.org/
|
||||
License: MIT License
|
||||
Copyright: 2024 Storybook
|
||||
|
||||
---
|
||||
|
||||
StyleCop.Analyzers: Analyzers for StyleCop
|
||||
|
||||
URL: https://github.com/DotNetAnalyzers/StyleCopAnalyzers
|
||||
License: MIT License
|
||||
Copyright: Tunnel Vision Laboratories, LLC
|
||||
|
||||
---
|
||||
|
||||
SVGO: A tool for optimizing SVG files
|
||||
|
||||
URL: https://svgo.dev/
|
||||
License: MIT License
|
||||
Copyright: Kir Belevich
|
||||
|
||||
---
|
||||
|
||||
Swashbuckle.AspNetCore: A library for generating Swagger documentation for ASP.NET Core APIs
|
||||
|
||||
URL: https://github.com/domaindrivendev/Swashbuckle.AspNetCore
|
||||
License: MIT License
|
||||
Copyright: 2016 Richard Morris
|
||||
|
||||
---
|
||||
|
||||
Tiny Glob: A tiny globbing library for Node.js
|
||||
|
||||
URL: https://github.com/terkelg/tiny-glob
|
||||
License: MIT License
|
||||
Copyright: 2018 Terkel
|
||||
|
||||
---
|
||||
|
||||
TinyMCE, version 6.x: A rich text editor for the web
|
||||
|
||||
URL: https://www.tiny.cloud/
|
||||
License: MIT License
|
||||
Copyright: 2022 Ephox Corporation DBA Tiny Technologies, Inc.
|
||||
|
||||
---
|
||||
|
||||
Tiptap: A renderless rich-text editor for the web
|
||||
|
||||
URL: https://tiptap.dev/
|
||||
License: MIT License
|
||||
Copyright: 2025 Tiptap GmbH
|
||||
|
||||
---
|
||||
|
||||
Tsc-alias: A TypeScript compiler plugin for aliasing module paths
|
||||
|
||||
URL: https://github.com/justkey007/tsc-alias
|
||||
License: MIT License
|
||||
Copyright: 2018 Justkey
|
||||
|
||||
---
|
||||
|
||||
Typedoc: A documentation generator for TypeScript projects
|
||||
|
||||
URL: https://typedoc.org/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Gerrit Birkeland and Contributors
|
||||
|
||||
---
|
||||
|
||||
Typescript: A typed superset of JavaScript that compiles to plain JavaScript
|
||||
|
||||
URL: https://www.typescriptlang.org/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2012-present Microsoft Corporation
|
||||
|
||||
---
|
||||
|
||||
Typescript-eslint: A set of tools for linting TypeScript code
|
||||
|
||||
URL: https://github.com/typescript-eslint/typescript-eslint
|
||||
License: MIT License
|
||||
Copyright: 2019 typescript-eslint and other contributors
|
||||
|
||||
---
|
||||
|
||||
Typescript-json-schema: A library for generating JSON schema from TypeScript types
|
||||
|
||||
URL: https://github.com/YousefED/typescript-json-schema
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2016 typescript-json-schema contributors
|
||||
|
||||
---
|
||||
|
||||
Umbraco.Code: Provides code-level tools for Umbraco
|
||||
|
||||
URL: https://github.com/umbraco/Umbraco-Code
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
Umbraco.GitVersioning.Extensions: Utilities for Nerdbank.GitVersioning
|
||||
|
||||
URL: https://github.com/umbraco/Umbraco.GitVersioning.Extensions
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
Umbraco.JsonSchema.Extensions: Utilities for JSON schema generation
|
||||
|
||||
URL: https://github.com/umbraco/Umbraco.JsonSchema.Extensions
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
Umbraco UI Library: A set of UI components for building web applications
|
||||
|
||||
URL: https://uui.umbraco.com/
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
uuid: A library for generating unique identifiers
|
||||
|
||||
URL: https://github.com/uuidjs/uuid
|
||||
License: MIT License
|
||||
Copyright: 2010-2020 Robert Kieffer and other contributors
|
||||
|
||||
---
|
||||
|
||||
Vite: A fast build tool and development server for modern web projects
|
||||
|
||||
URL: https://vite.dev/
|
||||
License: MIT License
|
||||
Copyright: 2019-present VoidZero Inc. and Vite contributors
|
||||
|
||||
---
|
||||
|
||||
Vite-plugin-static-copy: A Vite plugin for copying static files
|
||||
|
||||
URL: https://github.com/sapphi-red/vite-plugin-static-copy
|
||||
License: MIT License
|
||||
Copyright: 2021 sapphi-red
|
||||
|
||||
---
|
||||
|
||||
Vite-tsconfig-paths: A Vite plugin for resolving TypeScript paths
|
||||
|
||||
URL: https://github.com/aleclarson/vite-tsconfig-paths
|
||||
License: MIT License
|
||||
Copyright: Alec Larson
|
||||
|
||||
---
|
||||
|
||||
Web Component Analyzer: A tool for analyzing web components
|
||||
|
||||
URL: https://github.com/runem/web-component-analyzer
|
||||
License: MIT License
|
||||
Copyright: 2019 Rune Mehlsen
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "src/Umbraco.Web.UI.Client"
|
||||
},
|
||||
{
|
||||
"path": "src/Umbraco.Web.UI.Login"
|
||||
}
|
||||
]
|
||||
}
|
||||
+105
-232
@@ -313,8 +313,8 @@ stages:
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart3Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
macOSPart1Of3:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
@@ -325,8 +325,8 @@ stages:
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
macOSPart3Of3:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
variables:
|
||||
@@ -389,8 +389,8 @@ stages:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
LinuxPart1Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: UmbracoIntegration123!
|
||||
@@ -410,8 +410,8 @@ stages:
|
||||
SA_PASSWORD: UmbracoIntegration123!
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
@@ -432,6 +432,33 @@ stages:
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- powershell: |
|
||||
$maxAttempts = 12
|
||||
$attempt = 0
|
||||
$status = ""
|
||||
|
||||
while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
|
||||
Start-Sleep -Seconds 5
|
||||
# We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
|
||||
$status = docker inspect -f '{{.State.Status}}' mssql
|
||||
|
||||
if ($status -ne 'running') {
|
||||
Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
|
||||
$attempt++
|
||||
}
|
||||
}
|
||||
|
||||
if ($status -eq 'running') {
|
||||
Write-Host "SQL Server container is running"
|
||||
docker ps -a
|
||||
} else {
|
||||
Write-Host "SQL Server did not become ready in time. Last known status: $status"
|
||||
docker logs mssql
|
||||
exit 1
|
||||
}
|
||||
displayName: Wait for SQL Server to be ready (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
@@ -495,306 +522,148 @@ stages:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
|
||||
DatabaseType: SQLite
|
||||
strategy:
|
||||
matrix:
|
||||
LinuxPart1Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=1/3"
|
||||
LinuxPart2Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=2/3"
|
||||
LinuxPart3Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=3/3"
|
||||
WindowsPart1Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=1/3"
|
||||
WindowsPart2Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=2/3"
|
||||
WindowsPart3Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=3/3"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
retryCountOnTaskFailure: 3
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
# Setup test environment Template
|
||||
- template: nightly-E2E-setup-template.yml
|
||||
parameters:
|
||||
nodeVersion: ${{ variables.nodeVersion }}
|
||||
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
|
||||
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
npm_config_cache: ${{ variables.npm_config_cache }}
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
displayName: Restore project
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
condition: succeeded()
|
||||
|
||||
# Run application
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
# Run application Template
|
||||
- template: nightly-E2E-run-application-template.yml
|
||||
parameters:
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
# Run tests Template
|
||||
- template: nightly-E2E-run-tests-template.yml
|
||||
parameters:
|
||||
testCommand: $(testCommand)
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
DatabaseType: SQLServer
|
||||
strategy:
|
||||
matrix:
|
||||
${{ if eq(parameters.sqlServerLinuxAcceptanceTests, True) }}:
|
||||
LinuxPart1Of3:
|
||||
testCommand: "npm run smokeTest -- --shard=1/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
testFolder: "DefaultConfig"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
LinuxPart2Of3:
|
||||
testCommand: "npm run smokeTest -- --shard=2/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
testFolder: "DefaultConfig"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
LinuxPart3Of3:
|
||||
testCommand: "npm run smokeTest -- --shard=3/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
testFolder: "DefaultConfig"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
WindowsPart1Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTest -- --shard=1/3"
|
||||
WindowsPart2Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTest -- --shard=2/3"
|
||||
WindowsPart3Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTest -- --shard=3/3"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
# Setup test environment Template
|
||||
- template: nightly-E2E-setup-template.yml
|
||||
parameters:
|
||||
nodeVersion: ${{ variables.nodeVersion }}
|
||||
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
|
||||
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
npm_config_cache: ${{ variables.npm_config_cache }}
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
displayName: Restore project
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
condition: succeeded()
|
||||
|
||||
# Start SQL Server
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
# Run application Template
|
||||
- template: nightly-E2E-run-application-template.yml
|
||||
parameters:
|
||||
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
# Run tests Template
|
||||
- template: nightly-E2E-run-tests-template.yml
|
||||
parameters:
|
||||
testCommand: $(testCommand)
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
# Run application
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: eq(variables['Agent.OS'], 'Linux')
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
###############################################
|
||||
## Release
|
||||
###############################################
|
||||
@@ -807,6 +676,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
|
||||
@@ -868,6 +739,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
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
parameters:
|
||||
- name: testFolder
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: buildConfiguration
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: additionalEnvironmentVariables
|
||||
type: boolean
|
||||
default: False
|
||||
|
||||
steps:
|
||||
- pwsh: |
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
displayName: Restore project
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Update application to use necessary app settings
|
||||
- pwsh: |
|
||||
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
|
||||
$destinationPath = "UmbracoProject"
|
||||
$jsonFiles = Get-ChildItem -Path $sourcePath -Filter "*.json"
|
||||
if ($jsonFiles) {
|
||||
$jsonFiles | ForEach-Object {
|
||||
Write-Host "Copying: $($_.FullName)"
|
||||
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
|
||||
}
|
||||
} else {
|
||||
Write-Host "No JSON files found."
|
||||
}
|
||||
displayName: Update application to use necessary app settings
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Update application to use necessary App_Plugins
|
||||
- pwsh: |
|
||||
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
|
||||
$destinationPath = "UmbracoProject"
|
||||
$appPluginsFolders = Get-ChildItem -Path $sourcePath -Directory -Filter "App_Plugins"
|
||||
if ($appPluginsFolders) {
|
||||
foreach ($folder in $appPluginsFolders) {
|
||||
Write-Host "Copying folder: $($folder.FullName)"
|
||||
Copy-Item -Path $folder.FullName -Destination $destinationPath -Recurse -Force
|
||||
}
|
||||
} else {
|
||||
Write-Host "No App_Plugins found."
|
||||
}
|
||||
displayName: Update application to use necessary app plugins
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Update application to use necessary classes
|
||||
- pwsh: |
|
||||
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
|
||||
$destinationPath = "UmbracoProject"
|
||||
$csharpFiles = Get-ChildItem -Path $sourcePath -Filter "*.cs"
|
||||
if ($csharpFiles) {
|
||||
$csharpFiles | ForEach-Object {
|
||||
Write-Host "Copying: $($_.FullName)"
|
||||
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
|
||||
}
|
||||
} else {
|
||||
Write-Host "No C# files found."
|
||||
}
|
||||
displayName: Update application to use necessary classes
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- ${{ if eq(parameters.additionalEnvironmentVariables, False) }}:
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
condition: succeeded()
|
||||
@@ -0,0 +1,47 @@
|
||||
parameters:
|
||||
- name: SA_PASSWORD
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: buildConfiguration
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: additionalEnvironmentVariables
|
||||
type: boolean
|
||||
default: False
|
||||
|
||||
- name: DatabaseType
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
steps:
|
||||
# Skips the SQLServer setup if the databaseType does not match
|
||||
- ${{ if eq(parameters.DatabaseType, 'SQLServer') }}:
|
||||
# Start SQL Server Linux
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=${{ parameters.SA_PASSWORD }}" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
# Start SQL Server LocalDB Windows
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# If we want to add additional environment variables to the run step, then we will skip these
|
||||
- ${{ if eq(parameters.additionalEnvironmentVariables, False) }}:
|
||||
# Run application for Linux
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Run application for Windows
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
@@ -0,0 +1,105 @@
|
||||
parameters:
|
||||
- name: ASPNETCORE_URLS
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: testCommand
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: port
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: AZUREB2CTESTUSEREMAIL
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: AZUREB2CTESTUSERPASSWORD
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: DatabaseType
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
steps:
|
||||
# Ensures we have the package wait-on installed
|
||||
- pwsh: npm install wait-on
|
||||
displayName: Install wait-on package
|
||||
|
||||
# Wait for either the port of the aspnetcore url
|
||||
- pwsh: |
|
||||
$Port = "${{ parameters.port }}"
|
||||
$Url = "${{ parameters.ASPNETCORE_URLS }}"
|
||||
|
||||
if ($Port -ne "") {
|
||||
Write-Host "Waiting on TCP port $Port"
|
||||
npx wait-on -v --interval 1000 --timeout 120000 "tcp:$Port"
|
||||
} else {
|
||||
Write-Host "Waiting on URL $Url"
|
||||
npx wait-on -v --interval 1000 --timeout 120000 "$Url"
|
||||
}
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: ${{ parameters.testCommand }}
|
||||
displayName: Run Playwright tests
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
AZUREADB2CTESTUSEREMAIL: ${{ parameters.AZUREB2CTESTUSEREMAIL }}
|
||||
AZUREADB2CTESTUSERPASSWORD: ${{ parameters.AZUREB2CTESTUSERPASSWORD }}
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(succeededOrFailed(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(succeededOrFailed(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
- ${{ if eq(parameters.DatabaseType, 'SQLServer') }}:
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: and(succeededOrFailed(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: and(succeededOrFailed(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
@@ -0,0 +1,69 @@
|
||||
parameters:
|
||||
- name: nodeVersion
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: PlaywrightUserEmail
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: PlaywrightPassword
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: ASPNETCORE_URLS
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: npm_config_cache
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=${{ parameters.PlaywrightUserEmail }}
|
||||
UMBRACO_USER_PASSWORD=${{ parameters.PlaywrightPassword }}
|
||||
URL=${{ parameters.ASPNETCORE_URLS }}
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: ${{ parameters.npm_config_cache }}
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Install Template
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
displayName: Install Template
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
@@ -3,13 +3,29 @@ name: Nightly_E2E_Test_$(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName
|
||||
pr: none
|
||||
trigger: none
|
||||
|
||||
schedules:
|
||||
- cron: '0 0 * * *'
|
||||
displayName: Daily midnight build
|
||||
branches:
|
||||
include:
|
||||
- v14/dev
|
||||
- v15/dev
|
||||
# schedules:
|
||||
# - cron: '0 0 * * *'
|
||||
# displayName: Daily midnight build
|
||||
# branches:
|
||||
# include:
|
||||
# - v14/dev
|
||||
# - v15/dev
|
||||
|
||||
parameters:
|
||||
- name: differentAppSettingsAcceptanceTests
|
||||
displayName: Run acceptance tests with different app settings
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
- name: skipDefaultConfigAcceptanceTests
|
||||
displayName: Skip tests with DefaultConfig
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
- name: skipIntegrationTests
|
||||
displayName: Skip integration tests
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
variables:
|
||||
nodeVersion: 20
|
||||
@@ -32,25 +48,17 @@ stages:
|
||||
- job: A
|
||||
displayName: Build Umbraco CMS
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
vmImage: "windows-latest"
|
||||
steps:
|
||||
- checkout: self
|
||||
fetchDepth: 0
|
||||
submodules: true
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 500
|
||||
- template: templates/backoffice-install.yml
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
- template: templates/backoffice-install.yml
|
||||
- script: npm run build:for:cms
|
||||
displayName: Run build (Bellissima)
|
||||
workingDirectory: src/Umbraco.Web.UI.Client
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
displayName: Run npm ci (Login)
|
||||
workingDirectory: src/Umbraco.Web.UI.Login
|
||||
- script: npm run build
|
||||
displayName: Run npm build (Login)
|
||||
workingDirectory: src/Umbraco.Web.UI.Login
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet restore
|
||||
inputs:
|
||||
@@ -62,7 +70,7 @@ stages:
|
||||
inputs:
|
||||
command: build
|
||||
projects: $(solution)
|
||||
arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg'
|
||||
arguments: "--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg"
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish nupkg
|
||||
inputs:
|
||||
@@ -74,8 +82,237 @@ stages:
|
||||
targetPath: $(Build.SourcesDirectory)
|
||||
artifactName: build_output
|
||||
|
||||
- stage: E2E
|
||||
displayName: E2E Tests
|
||||
- job: B
|
||||
displayName: Build Bellissima Package
|
||||
pool:
|
||||
vmImage: "ubuntu-latest"
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 500
|
||||
- template: templates/backoffice-install.yml
|
||||
- script: npm run build:for:npm
|
||||
displayName: Run build:for:npm
|
||||
workingDirectory: src/Umbraco.Web.UI.Client
|
||||
- bash: |
|
||||
echo "##[command]Running npm pack"
|
||||
echo "##[debug]Output directory: $(Build.ArtifactStagingDirectory)"
|
||||
mkdir $(Build.ArtifactStagingDirectory)/npm
|
||||
npm pack --pack-destination $(Build.ArtifactStagingDirectory)/npm
|
||||
mv .npmrc $(Build.ArtifactStagingDirectory)/npm/
|
||||
displayName: Run npm pack
|
||||
workingDirectory: src/Umbraco.Web.UI.Client
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish Bellissima npm artifact
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)/npm
|
||||
artifactName: npm
|
||||
- stage: Integration
|
||||
displayName: Integration Tests
|
||||
dependsOn: Build
|
||||
condition: ${{ eq(parameters.skipIntegrationTests, false) }}
|
||||
jobs:
|
||||
# Integration Tests (SQLite)
|
||||
- job:
|
||||
timeoutInMinutes: 180
|
||||
displayName: Integration Tests (SQLite)
|
||||
strategy:
|
||||
matrix:
|
||||
# Windows:
|
||||
# vmImage: 'windows-latest'
|
||||
# We split the tests into 4 parts for each OS to reduce the time it takes to run them on the pipeline
|
||||
LinuxPart1Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart2Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart3Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
LinuxPart4Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are part of the ManagementApi namespace
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
macOSPart1Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
macOSPart2Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
macOSPart3Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
macOSPart4Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
variables:
|
||||
Tests__Database__DatabaseType: "Sqlite"
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 1
|
||||
fetchFilter: tree:0
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download build artifacts
|
||||
inputs:
|
||||
artifact: build_output
|
||||
path: $(Build.SourcesDirectory)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
|
||||
# Test
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test
|
||||
inputs:
|
||||
command: test
|
||||
projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
|
||||
testRunTitle: Integration Tests SQLite - $(Agent.OS)
|
||||
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build'
|
||||
|
||||
# Integration Tests (SQL Server)
|
||||
- job:
|
||||
timeoutInMinutes: 180
|
||||
displayName: Integration Tests (SQL Server)
|
||||
variables:
|
||||
SA_PASSWORD: UmbracoAcceptance123!
|
||||
strategy:
|
||||
matrix:
|
||||
# We split the tests into 4 parts for each OS to reduce the time it takes to run them on the pipeline
|
||||
WindowsPart1Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
WindowsPart2Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
WindowsPart3Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
WindowsPart4Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are part of the ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
LinuxPart1Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart2Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart3Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
LinuxPart4Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are part of the ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download build artifacts
|
||||
inputs:
|
||||
artifact: build_output
|
||||
path: $(Build.SourcesDirectory)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
|
||||
# Start SQL Server
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- powershell: |
|
||||
$maxAttempts = 12
|
||||
$attempt = 0
|
||||
$status = ""
|
||||
|
||||
while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
|
||||
Start-Sleep -Seconds 5
|
||||
# We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
|
||||
$status = docker inspect -f '{{.State.Status}}' mssql
|
||||
|
||||
if ($status -ne 'running') {
|
||||
Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
|
||||
$attempt++
|
||||
}
|
||||
}
|
||||
|
||||
if ($status -eq 'running') {
|
||||
Write-Host "SQL Server container is running"
|
||||
docker ps -a
|
||||
} else {
|
||||
Write-Host "SQL Server did not become ready in time. Last known status: $status"
|
||||
docker logs mssql
|
||||
exit 1
|
||||
}
|
||||
displayName: Wait for SQL Server to be ready (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Test
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test
|
||||
inputs:
|
||||
command: test
|
||||
projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
|
||||
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
|
||||
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build'
|
||||
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
- stage: DefaultConfigE2E
|
||||
displayName: Default Config E2E Tests
|
||||
dependsOn: Build
|
||||
variables:
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
|
||||
@@ -103,318 +340,290 @@ stages:
|
||||
- job:
|
||||
displayName: E2E Tests (SQLite)
|
||||
timeoutInMinutes: 180
|
||||
condition: ${{ eq(parameters.skipDefaultConfigAcceptanceTests, false) }}
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
|
||||
DatabaseType: SQLite
|
||||
strategy:
|
||||
matrix:
|
||||
LinuxPart1Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testCommand: "npm run test -- --shard=1/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
LinuxPart2Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testCommand: "npm run test -- --shard=2/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
LinuxPart3Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testCommand: "npm run test -- --shard=3/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
WindowsPart1Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run test -- --shard=1/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
WindowsPart2Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run test -- --shard=2/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
WindowsPart3Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run test -- --shard=3/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
retryCountOnTaskFailure: 3
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
# Setup test environment Template
|
||||
- template: nightly-E2E-setup-template.yml
|
||||
parameters:
|
||||
nodeVersion: ${{ variables.nodeVersion }}
|
||||
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
|
||||
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
npm_config_cache: ${{ variables.npm_config_cache }}
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
displayName: Restore project
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
condition: succeeded()
|
||||
|
||||
# Run application
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
# Run application Template
|
||||
- template: nightly-E2E-run-application-template.yml
|
||||
parameters:
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Ensures we have the package wait-on installed
|
||||
- pwsh: npm install wait-on
|
||||
displayName: Install wait-on package
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
# Run tests Template
|
||||
- template: nightly-E2E-run-tests-template.yml
|
||||
parameters:
|
||||
testCommand: $(testCommand)
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
timeoutInMinutes: 180
|
||||
condition: ${{ eq(parameters.skipDefaultConfigAcceptanceTests, false) }}
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
DatabaseType: SQLServer
|
||||
SA_PASSWORD: UmbracoAcceptance123!
|
||||
strategy:
|
||||
matrix:
|
||||
LinuxPart1Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
testCommand: "npm run test -- --shard=1/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
LinuxPart2Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
testCommand: "npm run test -- --shard=2/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
LinuxPart3Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
testCommand: "npm run test -- --shard=3/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
WindowsPart1Of3:
|
||||
testCommand: "npm run test -- --shard=1/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
WindowsPart2Of3:
|
||||
testCommand: "npm run test -- --shard=2/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
WindowsPart3Of3:
|
||||
testCommand: "npm run test -- --shard=3/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
# Setup test environment Template
|
||||
- template: nightly-E2E-setup-template.yml
|
||||
parameters:
|
||||
nodeVersion: ${{ variables.nodeVersion }}
|
||||
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
|
||||
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
npm_config_cache: ${{ variables.npm_config_cache }}
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
displayName: Restore project
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
condition: succeeded()
|
||||
|
||||
# Start SQL Server
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
# Run application Template
|
||||
- template: nightly-E2E-run-application-template.yml
|
||||
parameters:
|
||||
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
# Run tests Template
|
||||
- template: nightly-E2E-run-tests-template.yml
|
||||
parameters:
|
||||
testCommand: $(testCommand)
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
# Run application
|
||||
- stage: AdditionalConfigE2E
|
||||
displayName: Additional Config E2E Tests
|
||||
dependsOn: Build
|
||||
variables:
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
|
||||
ASPNETCORE_URLS: https://localhost:44331
|
||||
PlaywrightPassword: UmbracoAcceptance123!
|
||||
PlaywrightUserEmail: playwright@umbraco.com
|
||||
jobs:
|
||||
- job:
|
||||
displayName: E2E Tests with Different App settings (SQL Server)
|
||||
condition: ${{ eq(parameters.differentAppSettingsAcceptanceTests, true) }}
|
||||
timeoutInMinutes: 180
|
||||
variables:
|
||||
SA_PASSWORD: UmbracoAcceptance123!
|
||||
DatabaseType: SQLServer
|
||||
strategy:
|
||||
matrix:
|
||||
# UnattendedInstallConfig
|
||||
WindowsUnattendedInstallConfig:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "UnattendedInstallConfig"
|
||||
testCommand: "npx playwright test --project=unattendedInstallConfig --grep=InstallSQLServer"
|
||||
port: 44331
|
||||
additionalEnvironmentVariables: false
|
||||
# DeliveryApiConfig
|
||||
WindowsDeliveryApiConfig:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DeliveryApi"
|
||||
port: ''
|
||||
testCommand: "npx playwright test --project=deliveryApi"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
additionalEnvironmentVariables: false
|
||||
LinuxDeliveryApiConfig:
|
||||
vmImage: "ubuntu-latest"
|
||||
testFolder: "DeliveryApi"
|
||||
port: ''
|
||||
testCommand: "npx playwright test --project=deliveryApi"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
additionalEnvironmentVariables: false
|
||||
# ExternalLogin AzureADB2C
|
||||
WindowsExternalLoginAzureADB2C:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "ExternalLogin\\AzureADB2C"
|
||||
testCommand: "npx playwright test --project=externalLoginAzureADB2C"
|
||||
port: 44331
|
||||
packageName: "Microsoft.AspNetCore.Authentication.OpenIdConnect"
|
||||
packageVersion: "9.0.8"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
additionalEnvironmentVariables: true
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment Template
|
||||
- template: nightly-E2E-setup-template.yml
|
||||
parameters:
|
||||
nodeVersion: ${{ variables.nodeVersion }}
|
||||
PlaywrightUserEmail: ${{ variables.PlaywrightUserEmail }}
|
||||
PlaywrightPassword: ${{ variables.PlaywrightPassword }}
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
npm_config_cache: ${{ variables.npm_config_cache }}
|
||||
|
||||
# Install NuGet package if specified in the matrix
|
||||
- pwsh: |
|
||||
Write-Host "Installing package $(packageName) version $(packageVersion)"
|
||||
dotnet add package $(packageName) --version $(packageVersion)
|
||||
displayName: "Install NuGet package: $(packageName)"
|
||||
workingDirectory: $(Agent.BuildDirectory)/app/UmbracoProject
|
||||
condition: and(succeeded(), ne(variables['packageName'], ''), ne(variables['packageVersion'], ''))
|
||||
|
||||
# Build application Template
|
||||
- template: nightly-E2E-build-template.yml
|
||||
parameters:
|
||||
testFolder: $(testFolder)
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
additionalEnvironmentVariables: ${{ eq(variables['additionalEnvironmentVariables'], true) }}
|
||||
|
||||
# Build application for AzureADB2C
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application for AzureADB2C
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
env:
|
||||
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
|
||||
AZUREADB2CTENANT: $(AZUREB2CTENANT)
|
||||
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
|
||||
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
|
||||
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
|
||||
condition: and(succeeded(), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
|
||||
|
||||
# Run application Template
|
||||
- template: nightly-E2E-run-application-template.yml
|
||||
parameters:
|
||||
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
|
||||
additionalEnvironmentVariables: ${{ eq(variables['additionalEnvironmentVariables'], true ) }}
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
# Run application for Linux with additional Environment Variables for Azure AD
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
nohup dotnet run --project UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
env:
|
||||
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
|
||||
AZUREADB2CTENANT: $(AZUREB2CTENANT)
|
||||
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
|
||||
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
|
||||
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
|
||||
|
||||
# Run application for Windows with additional Environment Variables for Azure AD
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Ensures we have the package wait-on installed
|
||||
- pwsh: npm install wait-on
|
||||
displayName: Install wait-on package
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
|
||||
AZUREADB2CTENANT: $(AZUREB2CTENANT)
|
||||
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
|
||||
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
|
||||
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
# Run tests Template
|
||||
- template: nightly-E2E-run-tests-template.yml
|
||||
parameters:
|
||||
testCommand: $(testCommand)
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
port: $(port)
|
||||
AZUREB2CTESTUSEREMAIL: $(AZUREB2CTESTUSEREMAIL)
|
||||
AZUREB2CTESTUSERPASSWORD: $(AZUREB2CTESTUSERPASSWORD)
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
@@ -9,9 +9,10 @@ schedules:
|
||||
branches:
|
||||
include:
|
||||
- v10/dev
|
||||
- v12/dev
|
||||
- v13/dev
|
||||
- v14/dev
|
||||
- v15/dev
|
||||
- v16/dev
|
||||
|
||||
steps:
|
||||
- checkout: none
|
||||
|
||||
@@ -18,7 +18,12 @@ internal sealed class DeliveryApiOutputCachePolicy : IOutputCachePolicy
|
||||
.RequestServices
|
||||
.GetRequiredService<IRequestPreviewService>();
|
||||
|
||||
context.EnableOutputCaching = requestPreviewService.IsPreview() is false;
|
||||
IApiAccessService apiAccessService = context
|
||||
.HttpContext
|
||||
.RequestServices
|
||||
.GetRequiredService<IApiAccessService>();
|
||||
|
||||
context.EnableOutputCaching = requestPreviewService.IsPreview() is false && apiAccessService.HasPublicAccess();
|
||||
context.ResponseExpirationTimeSpan = _duration;
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
|
||||
Id = AuthSchemeName,
|
||||
}
|
||||
},
|
||||
new string[] { }
|
||||
[]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -185,7 +185,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
}
|
||||
|
||||
|
||||
private Attempt<PagedModel<Guid>, ApiMediaQueryOperationStatus> PagedResult(IEnumerable<IPublishedContent> children, int skip, int take)
|
||||
private static Attempt<PagedModel<Guid>, ApiMediaQueryOperationStatus> PagedResult(IEnumerable<IPublishedContent> children, int skip, int take)
|
||||
{
|
||||
IPublishedContent[] childrenAsArray = children as IPublishedContent[] ?? children.ToArray();
|
||||
var result = new PagedModel<Guid>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DataType.References;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ReferencedByDataTypeController : DataTypeControllerBase
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
|
||||
|
||||
public ReferencedByDataTypeController(IDataTypeService dataTypeService, IRelationTypePresentationFactory relationTypePresentationFactory)
|
||||
{
|
||||
_dataTypeService = dataTypeService;
|
||||
_relationTypePresentationFactory = relationTypePresentationFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a paged list of references for the current data type, so you can see where it is being used.
|
||||
/// </summary>
|
||||
[HttpGet("{id:guid}/referenced-by")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> ReferencedBy(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
int skip = 0,
|
||||
int take = 20)
|
||||
{
|
||||
PagedModel<RelationItemModel> relationItems = await _dataTypeService.GetPagedRelationsAsync(id, skip, take);
|
||||
|
||||
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
|
||||
{
|
||||
Total = relationItems.Total,
|
||||
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
|
||||
};
|
||||
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Obsolete("Please use ReferencedByDataTypeController and the referenced-by endpoint. Scheduled for removal in Umbraco 17.")]
|
||||
public class ReferencesDataTypeController : DataTypeControllerBase
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Controllers.Content;
|
||||
@@ -140,6 +140,10 @@ public abstract class DocumentControllerBase : ContentControllerBase
|
||||
.WithDetail(
|
||||
"An unspecified error occurred while (un)publishing. Please check the logs for additional information.")
|
||||
.Build()),
|
||||
ContentPublishingOperationStatus.TaskResultNotFound => NotFound(problemDetailsBuilder
|
||||
.WithTitle("The result of the submitted task could not be found")
|
||||
.Build()),
|
||||
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown content operation status."),
|
||||
});
|
||||
|
||||
|
||||
+22
-3
@@ -30,14 +30,33 @@ public class SearchDocumentItemController : DocumentItemControllerBase
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
|
||||
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, skip, take, parentId);
|
||||
=> await SearchWithTrashed(cancellationToken, query, null, skip, take, parentId);
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> SearchFromParentWithAllowedTypes(
|
||||
CancellationToken cancellationToken,
|
||||
string query,
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
Guid? parentId = null,
|
||||
[FromQuery] IEnumerable<Guid>? allowedDocumentTypes = null) =>
|
||||
await SearchWithTrashed(cancellationToken, query, null, skip, take, parentId, allowedDocumentTypes);
|
||||
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedDocumentTypes = null)
|
||||
public async Task<IActionResult> SearchWithTrashed(
|
||||
CancellationToken cancellationToken,
|
||||
string query,
|
||||
bool? trashed = null,
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
Guid? parentId = null,
|
||||
[FromQuery] IEnumerable<Guid>? allowedDocumentTypes = null)
|
||||
{
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, skip, take);
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, trashed, skip, take);
|
||||
var result = new PagedModel<DocumentItemResponseModel>
|
||||
{
|
||||
Items = searchResult.Items.OfType<IDocumentEntitySlim>().Select(_documentPresentationFactory.CreateItemResponseModel),
|
||||
|
||||
+9
-4
@@ -35,7 +35,7 @@ public class PublishDocumentWithDescendantsController : DocumentControllerBase
|
||||
|
||||
[HttpPut("{id:guid}/publish-with-descendants")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(PublishWithDescendantsResultModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> PublishWithDescendants(CancellationToken cancellationToken, Guid id, PublishDocumentWithDescendantsRequestModel requestModel)
|
||||
@@ -54,10 +54,15 @@ public class PublishDocumentWithDescendantsController : DocumentControllerBase
|
||||
id,
|
||||
requestModel.Cultures,
|
||||
BuildPublishBranchFilter(requestModel),
|
||||
CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
CurrentUserKey(_backOfficeSecurityAccessor),
|
||||
true);
|
||||
|
||||
return attempt.Success
|
||||
? Ok()
|
||||
return attempt.Success && attempt.Result.AcceptedTaskId.HasValue
|
||||
? Ok(new PublishWithDescendantsResultModel
|
||||
{
|
||||
TaskId = attempt.Result.AcceptedTaskId.Value,
|
||||
IsComplete = false
|
||||
})
|
||||
: DocumentPublishingOperationStatusResult(attempt.Status, failedBranchItems: attempt.Result.FailedItems);
|
||||
}
|
||||
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.Models.ContentPublishing;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class PublishDocumentWithDescendantsResultController : DocumentControllerBase
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IContentPublishingService _contentPublishingService;
|
||||
|
||||
public PublishDocumentWithDescendantsResultController(
|
||||
IAuthorizationService authorizationService,
|
||||
IContentPublishingService contentPublishingService)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_contentPublishingService = contentPublishingService;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/publish-with-descendants/result/{taskId:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PublishWithDescendantsResultModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> PublishWithDescendantsResult(CancellationToken cancellationToken, Guid id, Guid taskId)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.Branch(ActionPublish.ActionLetter, id),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
// Check if the publishing task has completed, if not, return the status.
|
||||
var isPublishing = await _contentPublishingService.IsPublishingBranchAsync(taskId);
|
||||
if (isPublishing)
|
||||
{
|
||||
return Ok(new PublishWithDescendantsResultModel
|
||||
{
|
||||
TaskId = taskId,
|
||||
IsComplete = false
|
||||
});
|
||||
};
|
||||
|
||||
// If completed, get the result and return the status.
|
||||
Attempt<ContentPublishingBranchResult, ContentPublishingOperationStatus> attempt = await _contentPublishingService.GetPublishBranchResultAsync(taskId);
|
||||
return attempt.Success
|
||||
? Ok(new PublishWithDescendantsResultModel
|
||||
{
|
||||
TaskId = taskId,
|
||||
IsComplete = true
|
||||
})
|
||||
: DocumentPublishingOperationStatusResult(attempt.Status, failedBranchItems: attempt.Result.FailedItems);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document.RecycleBin;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ReferencedByDocumentRecycleBinController : DocumentRecycleBinControllerBase
|
||||
{
|
||||
private readonly ITrackedReferencesService _trackedReferencesService;
|
||||
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
|
||||
|
||||
public ReferencedByDocumentRecycleBinController(
|
||||
IEntityService entityService,
|
||||
IDocumentPresentationFactory documentPresentationFactory,
|
||||
ITrackedReferencesService trackedReferencesService,
|
||||
IRelationTypePresentationFactory relationTypePresentationFactory)
|
||||
: base(entityService, documentPresentationFactory)
|
||||
{
|
||||
_trackedReferencesService = trackedReferencesService;
|
||||
_relationTypePresentationFactory = relationTypePresentationFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a paged list of tracked references for all items in the document recycle bin, so you can see where an item is being used.
|
||||
/// </summary>
|
||||
[HttpGet("referenced-by")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> ReferencedBy(
|
||||
CancellationToken cancellationToken,
|
||||
int skip = 0,
|
||||
int take = 20)
|
||||
{
|
||||
PagedModel<RelationItemModel> relationItems = await _trackedReferencesService.GetPagedRelationsForRecycleBinAsync(UmbracoObjectTypes.Document, skip, take, true);
|
||||
|
||||
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
|
||||
{
|
||||
Total = relationItems.Total,
|
||||
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
|
||||
};
|
||||
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
@@ -45,6 +45,6 @@ public class AreReferencedDocumentController : DocumentControllerBase
|
||||
Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items),
|
||||
};
|
||||
|
||||
return await Task.FromResult(pagedViewModel);
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,6 +45,6 @@ public class ReferencedByDocumentController : DocumentControllerBase
|
||||
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
|
||||
};
|
||||
|
||||
return await Task.FromResult(pagedViewModel);
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
@@ -45,6 +45,6 @@ public class ReferencedDescendantsDocumentController : DocumentControllerBase
|
||||
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItems.Items),
|
||||
};
|
||||
|
||||
return await Task.FromResult(pagedViewModel);
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -4,6 +4,7 @@ using Umbraco.Cms.Api.Management.Controllers.Tree;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.Services.Entities;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
@@ -33,7 +34,7 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IDocumentPresentationFactory documentPresentationFactory)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService)
|
||||
{
|
||||
_publicAccessService = publicAccessService;
|
||||
_appCaches = appCaches;
|
||||
@@ -52,6 +53,8 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
|
||||
if (entity is IDocumentEntitySlim documentEntitySlim)
|
||||
{
|
||||
responseModel.IsProtected = _publicAccessService.IsProtected(entity.Path);
|
||||
responseModel.Ancestors = EntityService.GetPathKeys(entity, omitSelf: true)
|
||||
.Select(x => new ReferenceByIdModel(x));
|
||||
responseModel.IsTrashed = entity.Trashed;
|
||||
responseModel.Id = entity.Key;
|
||||
responseModel.CreateDate = entity.CreateDate;
|
||||
|
||||
+25
-2
@@ -1,11 +1,14 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
@@ -16,15 +19,32 @@ public class ValidateCreateDocumentController : CreateDocumentControllerBase
|
||||
{
|
||||
private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory;
|
||||
private readonly IContentEditingService _contentEditingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public ValidateCreateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
|
||||
IContentEditingService contentEditingService)
|
||||
: this(
|
||||
authorizationService,
|
||||
documentEditingPresentationFactory,
|
||||
contentEditingService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ValidateCreateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
|
||||
IContentEditingService contentEditingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
: base(authorizationService)
|
||||
{
|
||||
_documentEditingPresentationFactory = documentEditingPresentationFactory;
|
||||
_contentEditingService = contentEditingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPost("validate")]
|
||||
@@ -36,7 +56,10 @@ public class ValidateCreateDocumentController : CreateDocumentControllerBase
|
||||
=> await HandleRequest(requestModel, async () =>
|
||||
{
|
||||
ContentCreateModel model = _documentEditingPresentationFactory.MapCreateModel(requestModel);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateCreateAsync(model);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
|
||||
await _contentEditingService.ValidateCreateAsync(
|
||||
model,
|
||||
CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
|
||||
+26
-2
@@ -1,11 +1,14 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
@@ -17,15 +20,32 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
|
||||
{
|
||||
private readonly IContentEditingService _contentEditingService;
|
||||
private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public ValidateUpdateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IContentEditingService contentEditingService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory)
|
||||
: this(
|
||||
authorizationService,
|
||||
contentEditingService,
|
||||
documentEditingPresentationFactory,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ValidateUpdateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IContentEditingService contentEditingService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
: base(authorizationService)
|
||||
{
|
||||
_contentEditingService = contentEditingService;
|
||||
_documentEditingPresentationFactory = documentEditingPresentationFactory;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/validate")]
|
||||
@@ -62,7 +82,11 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
|
||||
=> await HandleRequest(id, requestModel, async () =>
|
||||
{
|
||||
ValidateContentUpdateModel model = _documentEditingPresentationFactory.MapValidateUpdateModel(requestModel);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateUpdateAsync(id, model);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
|
||||
await _contentEditingService.ValidateUpdateAsync(
|
||||
id,
|
||||
model,
|
||||
CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentBlueprint;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
|
||||
public class ScaffoldDocumentBlueprintController : DocumentBlueprintControllerBase
|
||||
{
|
||||
private readonly IContentBlueprintEditingService _contentBlueprintEditingService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public ScaffoldDocumentBlueprintController(IContentBlueprintEditingService contentBlueprintEditingService, IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_contentBlueprintEditingService = contentBlueprintEditingService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/scaffold")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(DocumentBlueprintResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Scaffold(CancellationToken cancellationToken, Guid id)
|
||||
{
|
||||
IContent? blueprint = await _contentBlueprintEditingService.GetScaffoldedAsync(id);
|
||||
return blueprint is not null
|
||||
? Ok(_umbracoMapper.Map<DocumentBlueprintResponseModel>(blueprint))
|
||||
: DocumentBlueprintNotFound();
|
||||
}
|
||||
}
|
||||
+12
-4
@@ -1,5 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
@@ -9,7 +8,6 @@ using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
|
||||
@@ -25,6 +23,15 @@ public class AllowedChildrenDocumentTypeController : DocumentTypeControllerBase
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Use the non obsoleted method instead. Scheduled to be removed in v16")]
|
||||
public async Task<IActionResult> AllowedChildrenByKey(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
=> await AllowedChildrenByKey(cancellationToken, id, null, skip, take);
|
||||
|
||||
[HttpGet("{id:guid}/allowed-children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<AllowedDocumentType>), StatusCodes.Status200OK)]
|
||||
@@ -32,10 +39,11 @@ public class AllowedChildrenDocumentTypeController : DocumentTypeControllerBase
|
||||
public async Task<IActionResult> AllowedChildrenByKey(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
Guid? parentContentKey = null,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
{
|
||||
Attempt<PagedModel<IContentType>?, ContentTypeOperationStatus> attempt = await _contentTypeService.GetAllowedChildrenAsync(id, skip, take);
|
||||
Attempt<PagedModel<IContentType>?, ContentTypeOperationStatus> attempt = await _contentTypeService.GetAllowedChildrenAsync(id, parentContentKey, skip, take);
|
||||
if (attempt.Success is false)
|
||||
{
|
||||
return OperationStatusResult(attempt.Status);
|
||||
|
||||
+32
-17
@@ -1,45 +1,60 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType.Item;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Item;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class SearchDocumentTypeItemController : DocumentTypeItemControllerBase
|
||||
{
|
||||
private readonly IEntitySearchService _entitySearchService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
private readonly IContentTypeSearchService _contentTypeSearchService;
|
||||
|
||||
[Obsolete("Please use ctor that only accepts IUmbracoMapper & IContentTypeSearchService, scheduled for removal in v17")]
|
||||
public SearchDocumentTypeItemController(IEntitySearchService entitySearchService, IContentTypeService contentTypeService, IUmbracoMapper mapper)
|
||||
: this(mapper, StaticServiceProvider.Instance.GetRequiredService<IContentTypeSearchService>())
|
||||
{
|
||||
_entitySearchService = entitySearchService;
|
||||
_contentTypeService = contentTypeService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[Obsolete("Please use ctor that only accepts IUmbracoMapper & IContentTypeSearchService, scheduled for removal in v17")]
|
||||
// We need to have this constructor, or else we get ambiguous constructor error
|
||||
public SearchDocumentTypeItemController(
|
||||
IEntitySearchService entitySearchService,
|
||||
IContentTypeService contentTypeService,
|
||||
IUmbracoMapper mapper,
|
||||
IContentTypeSearchService contentTypeSearchService)
|
||||
: this(mapper, contentTypeSearchService)
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public SearchDocumentTypeItemController(IUmbracoMapper mapper, IContentTypeSearchService contentTypeSearchService)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_contentTypeSearchService = contentTypeSearchService;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
|
||||
=> await SearchDocumentType(cancellationToken, query, null, skip, take);
|
||||
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<DocumentTypeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
|
||||
public async Task<IActionResult> SearchDocumentType(CancellationToken cancellationToken, string query, bool? isElement = null, int skip = 0, int take = 100)
|
||||
{
|
||||
PagedModel<IEntitySlim> searchResult = _entitySearchService.Search(UmbracoObjectTypes.DocumentType, query, skip, take);
|
||||
if (searchResult.Items.Any() is false)
|
||||
{
|
||||
return await Task.FromResult(Ok(new PagedModel<DocumentTypeItemResponseModel> { Total = searchResult.Total }));
|
||||
}
|
||||
|
||||
IEnumerable<IContentType> contentTypes = _contentTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
|
||||
PagedModel<IContentType> contentTypes = await _contentTypeSearchService.SearchAsync(query, isElement, cancellationToken, skip, take);
|
||||
var result = new PagedModel<DocumentTypeItemResponseModel>
|
||||
{
|
||||
Items = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes),
|
||||
Total = searchResult.Total
|
||||
Items = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes.Items),
|
||||
Total = contentTypes.Total
|
||||
};
|
||||
|
||||
return Ok(result);
|
||||
|
||||
@@ -32,12 +32,17 @@ public class SearchMediaItemController : MediaItemControllerBase
|
||||
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
|
||||
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, skip, take, parentId);
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
|
||||
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, null, skip, take, parentId);
|
||||
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<MediaItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
|
||||
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, bool? trashed = null, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
|
||||
{
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, skip, take);
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, trashed, skip, take);
|
||||
var result = new PagedModel<MediaItemResponseModel>
|
||||
{
|
||||
Items = searchResult.Items.OfType<IMediaEntitySlim>().Select(_mediaPresentationFactory.CreateItemResponseModel),
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Media.RecycleBin;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ReferencedByMediaRecycleBinController : MediaRecycleBinControllerBase
|
||||
{
|
||||
private readonly ITrackedReferencesService _trackedReferencesService;
|
||||
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
|
||||
|
||||
public ReferencedByMediaRecycleBinController(
|
||||
IEntityService entityService,
|
||||
IMediaPresentationFactory mediaPresentationFactory,
|
||||
ITrackedReferencesService trackedReferencesService,
|
||||
IRelationTypePresentationFactory relationTypePresentationFactory)
|
||||
: base(entityService, mediaPresentationFactory)
|
||||
{
|
||||
_trackedReferencesService = trackedReferencesService;
|
||||
_relationTypePresentationFactory = relationTypePresentationFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a paged list of tracked references for all items in the media recycle bin, so you can see where an item is being used.
|
||||
/// </summary>
|
||||
[HttpGet("referenced-by")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> ReferencedBy(
|
||||
CancellationToken cancellationToken,
|
||||
int skip = 0,
|
||||
int take = 20)
|
||||
{
|
||||
PagedModel<RelationItemModel> relationItems = await _trackedReferencesService.GetPagedRelationsForRecycleBinAsync(UmbracoObjectTypes.Media, skip, take, true);
|
||||
|
||||
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
|
||||
{
|
||||
Total = relationItems.Total,
|
||||
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
|
||||
};
|
||||
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
@@ -46,6 +46,6 @@ public class AreReferencedMediaController : MediaControllerBase
|
||||
Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items),
|
||||
};
|
||||
|
||||
return await Task.FromResult(pagedViewModel);
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,6 +45,6 @@ public class ReferencedByMediaController : MediaControllerBase
|
||||
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
|
||||
};
|
||||
|
||||
return await Task.FromResult(pagedViewModel);
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
@@ -45,6 +45,6 @@ public class ReferencedDescendantsMediaController : MediaControllerBase
|
||||
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItems.Items),
|
||||
};
|
||||
|
||||
return await Task.FromResult(pagedViewModel);
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-2
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
@@ -23,6 +23,15 @@ public class AllowedChildrenMediaTypeController : MediaTypeControllerBase
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Use the non obsoleted method instead. Scheduled for removal in Umbraco 16.")]
|
||||
public async Task<IActionResult> AllowedChildrenByKey(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
=> await AllowedChildrenByKey(cancellationToken, id, null, skip, take);
|
||||
|
||||
[HttpGet("{id:guid}/allowed-children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<AllowedMediaType>), StatusCodes.Status200OK)]
|
||||
@@ -30,10 +39,11 @@ public class AllowedChildrenMediaTypeController : MediaTypeControllerBase
|
||||
public async Task<IActionResult> AllowedChildrenByKey(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
Guid? parentContentKey = null,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
{
|
||||
Attempt<PagedModel<IMediaType>?, ContentTypeOperationStatus> attempt = await _mediaTypeService.GetAllowedChildrenAsync(id, skip, take);
|
||||
Attempt<PagedModel<IMediaType>?, ContentTypeOperationStatus> attempt = await _mediaTypeService.GetAllowedChildrenAsync(id, parentContentKey, skip, take);
|
||||
if (attempt.Success is false)
|
||||
{
|
||||
return OperationStatusResult(attempt.Status);
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Member.References;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AreReferencedMemberController : MemberControllerBase
|
||||
{
|
||||
private readonly ITrackedReferencesService _trackedReferencesSkipTakeService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public AreReferencedMemberController(ITrackedReferencesService trackedReferencesSkipTakeService, IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_trackedReferencesSkipTakeService = trackedReferencesSkipTakeService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a page list of the items used in any kind of relation from selected keys.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used when bulk deleting content/media and bulk unpublishing content (delete and unpublish on List view).
|
||||
/// This is basically finding children of relations.
|
||||
/// </remarks>
|
||||
// [HttpGet("item")]
|
||||
[HttpGet("are-referenced")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<ReferenceByIdModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> GetPagedReferencedItems(
|
||||
CancellationToken cancellationToken,
|
||||
[FromQuery(Name="id")] HashSet<Guid> ids,
|
||||
int skip = 0,
|
||||
int take = 20)
|
||||
{
|
||||
PagedModel<Guid> distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedKeysWithDependentReferencesAsync(ids, Constants.ObjectTypes.Member, skip, take);
|
||||
var pagedViewModel = new PagedViewModel<ReferenceByIdModel>
|
||||
{
|
||||
Total = distinctByKeyItemsWithReferencedRelations.Total,
|
||||
Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items),
|
||||
};
|
||||
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Member.References;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ReferencedByMemberController : MemberControllerBase
|
||||
{
|
||||
private readonly ITrackedReferencesService _trackedReferencesService;
|
||||
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
|
||||
|
||||
public ReferencedByMemberController(ITrackedReferencesService trackedReferencesService, IRelationTypePresentationFactory relationTypePresentationFactory)
|
||||
{
|
||||
_trackedReferencesService = trackedReferencesService;
|
||||
_relationTypePresentationFactory = relationTypePresentationFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a page list of tracked references for the current item, so you can see where an item is being used.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used by info tabs on content, media etc. and for the delete and unpublish of single items.
|
||||
/// This is basically finding parents of relations.
|
||||
/// </remarks>
|
||||
[HttpGet("{id:guid}/referenced-by")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> ReferencedBy(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
int skip = 0,
|
||||
int take = 20)
|
||||
{
|
||||
PagedModel<RelationItemModel> relationItems = await _trackedReferencesService.GetPagedRelationsForItemAsync(id, skip, take, true);
|
||||
|
||||
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
|
||||
{
|
||||
Total = relationItems.Total,
|
||||
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
|
||||
};
|
||||
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Member.References;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ReferencedDescendantsMemberController : MemberControllerBase
|
||||
{
|
||||
private readonly ITrackedReferencesService _trackedReferencesSkipTakeService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public ReferencedDescendantsMemberController(ITrackedReferencesService trackedReferencesSkipTakeService, IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_trackedReferencesSkipTakeService = trackedReferencesSkipTakeService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a page list of the child nodes of the current item used in any kind of relation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used when deleting and unpublishing a single item to check if this item has any descending items that are in any
|
||||
/// kind of relation.
|
||||
/// This is basically finding the descending items which are children in relations.
|
||||
/// </remarks>
|
||||
[HttpGet("{id:guid}/referenced-descendants")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<ReferenceByIdModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> ReferencedDescendants(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
int skip = 0,
|
||||
int take = 20)
|
||||
{
|
||||
PagedModel<RelationItemModel> relationItems = await _trackedReferencesSkipTakeService.GetPagedDescendantsInReferencesAsync(id, skip, take, true);
|
||||
var pagedViewModel = new PagedViewModel<ReferenceByIdModel>
|
||||
{
|
||||
Total = relationItems.Total,
|
||||
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItems.Items),
|
||||
};
|
||||
|
||||
return pagedViewModel;
|
||||
}
|
||||
}
|
||||
+15
-2
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
@@ -17,7 +17,20 @@ public class RebuildPublishedCacheController : PublishedCacheControllerBase
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Rebuild(CancellationToken cancellationToken)
|
||||
{
|
||||
_databaseCacheRebuilder.Rebuild();
|
||||
if (_databaseCacheRebuilder.IsRebuilding())
|
||||
{
|
||||
var problemDetails = new ProblemDetails
|
||||
{
|
||||
Title = "Database cache cannot be rebuilt",
|
||||
Detail = $"The database cache is in the process of rebuilding.",
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Type = "Error",
|
||||
};
|
||||
|
||||
return await Task.FromResult(Conflict(problemDetails));
|
||||
}
|
||||
|
||||
_databaseCacheRebuilder.Rebuild(true);
|
||||
return await Task.FromResult(Ok());
|
||||
}
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.PublishedCache;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class RebuildPublishedCacheStatusController : PublishedCacheControllerBase
|
||||
{
|
||||
private readonly IDatabaseCacheRebuilder _databaseCacheRebuilder;
|
||||
|
||||
public RebuildPublishedCacheStatusController(IDatabaseCacheRebuilder databaseCacheRebuilder) => _databaseCacheRebuilder = databaseCacheRebuilder;
|
||||
|
||||
[HttpGet("rebuild/status")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(RebuildStatusModel), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Status(CancellationToken cancellationToken)
|
||||
{
|
||||
var isRebuilding = _databaseCacheRebuilder.IsRebuilding();
|
||||
return Task.FromResult((IActionResult)Ok(new RebuildStatusModel
|
||||
{
|
||||
IsRebuilding = isRebuilding
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Security;
|
||||
public class BackOfficeGraphicsController : Controller
|
||||
{
|
||||
public const string LogoRouteName = nameof(BackOfficeGraphicsController) + "." + nameof(Logo);
|
||||
public const string LogoAlternativeRouteName = nameof(BackOfficeGraphicsController) + "." + nameof(LogoAlternative);
|
||||
public const string LoginBackGroundRouteName = nameof(BackOfficeGraphicsController) + "." + nameof(LoginBackground);
|
||||
public const string LoginLogoRouteName = nameof(BackOfficeGraphicsController) + "." + nameof(LoginLogo);
|
||||
public const string LoginLogoAlternativeRouteName = nameof(BackOfficeGraphicsController) + "." + nameof(LoginLogoAlternative);
|
||||
@@ -44,6 +45,11 @@ public class BackOfficeGraphicsController : Controller
|
||||
[MapToApiVersion("1.0")]
|
||||
public IActionResult Logo() => HandleFileRequest(_contentSettings.Value.BackOfficeLogo);
|
||||
|
||||
[HttpGet("logo-alternative", Name = LogoAlternativeRouteName)]
|
||||
[AllowAnonymous]
|
||||
[MapToApiVersion("1.0")]
|
||||
public IActionResult LogoAlternative() => HandleFileRequest(_contentSettings.Value.BackOfficeLogoAlternative);
|
||||
|
||||
[HttpGet("login-logo", Name = LoginLogoRouteName)]
|
||||
[AllowAnonymous]
|
||||
[MapToApiVersion("1.0")]
|
||||
|
||||
@@ -21,6 +21,10 @@ public abstract class SecurityControllerBase : ManagementApiControllerBase
|
||||
.WithTitle("The password reset token was invalid")
|
||||
.WithDetail("The specified password reset token was either used already or wrong.")
|
||||
.Build()),
|
||||
UserOperationStatus.CancelledByNotification => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Cancelled by notification")
|
||||
.WithDetail("A notification handler prevented the user operation.")
|
||||
.Build()),
|
||||
UserOperationStatus.UnknownFailure => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Unknown failure")
|
||||
.WithDetail(errorMessageResult?.Error?.ErrorMessage ?? "The error was unknown")
|
||||
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.Builders;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
@@ -17,6 +17,9 @@ public abstract class TemporaryFileControllerBase : ManagementApiControllerBase
|
||||
.WithTitle("File extension not allowed")
|
||||
.WithDetail("The file extension is not allowed.")
|
||||
.Build()),
|
||||
TemporaryFileOperationStatus.InvalidFileName => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("The provided file name is not valid")
|
||||
.Build()),
|
||||
TemporaryFileOperationStatus.KeyAlreadyUsed => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Key already used")
|
||||
.WithDetail("The specified key is already used.")
|
||||
|
||||
@@ -42,11 +42,21 @@ public abstract class UserStartNodeTreeControllerBase<TItem> : EntityTreeControl
|
||||
|
||||
protected override IEntitySlim[] GetPagedChildEntities(Guid parentKey, int skip, int take, out long totalItems)
|
||||
{
|
||||
IEntitySlim[] children = base.GetPagedChildEntities(parentKey, skip, take, out totalItems);
|
||||
return UserHasRootAccess() || IgnoreUserStartNodes()
|
||||
? children
|
||||
// Keeping the correct totalItems amount from GetPagedChildEntities
|
||||
: CalculateAccessMap(() => _userStartNodeEntitiesService.ChildUserAccessEntities(children, UserStartNodePaths), out _);
|
||||
if (UserHasRootAccess() || IgnoreUserStartNodes())
|
||||
{
|
||||
return base.GetPagedChildEntities(parentKey, skip, take, out totalItems);
|
||||
}
|
||||
|
||||
IEnumerable<UserAccessEntity> userAccessEntities = _userStartNodeEntitiesService.ChildUserAccessEntities(
|
||||
ItemObjectType,
|
||||
UserStartNodePaths,
|
||||
parentKey,
|
||||
skip,
|
||||
take,
|
||||
ItemOrdering,
|
||||
out totalItems);
|
||||
|
||||
return CalculateAccessMap(() => userAccessEntities, out _);
|
||||
}
|
||||
|
||||
protected override TItem[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Security.OperationStatus;
|
||||
|
||||
@@ -20,7 +20,7 @@ public abstract class ClientCredentialsUserControllerBase : UserControllerBase
|
||||
.Build()),
|
||||
BackOfficeUserClientCredentialsOperationStatus.InvalidClientId => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Invalid client ID")
|
||||
.WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~]. Furthermore, including the prefix it cannot be longer than 255 characters.")
|
||||
.WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~]. Furthermore, including the prefix it cannot be longer than 100 characters.")
|
||||
.Build()),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder
|
||||
.WithTitle("Unknown client credentials operation status.")
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.RequireAdminAccess)]
|
||||
public class ConfigurationUserController : UserControllerBase
|
||||
{
|
||||
private readonly IUserPresentationFactory _userPresentationFactory;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -5,11 +6,13 @@ using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Webhook.Logs;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Webhook.Logs;
|
||||
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Webhook}")]
|
||||
[ApiExplorerSettings(GroupName = "Webhook")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public class WebhookLogControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
protected PagedViewModel<WebhookLogResponseModel> CreatePagedWebhookLogResponseModel(PagedModel<WebhookLog> logs, IWebhookPresentationFactory webhookPresentationFactory)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document.Collection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -9,11 +12,23 @@ namespace Umbraco.Cms.Api.Management.Factories;
|
||||
public class DocumentCollectionPresentationFactory : ContentCollectionPresentationFactory<IContent, DocumentCollectionResponseModel, DocumentValueResponseModel, DocumentVariantResponseModel>, IDocumentCollectionPresentationFactory
|
||||
{
|
||||
private readonly IPublicAccessService _publicAccessService;
|
||||
private readonly IEntityService _entityService;
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in V17.")]
|
||||
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService)
|
||||
: base(mapper)
|
||||
: this(
|
||||
mapper,
|
||||
publicAccessService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IEntityService>())
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService, IEntityService entityService)
|
||||
: base(mapper)
|
||||
{
|
||||
_publicAccessService = publicAccessService;
|
||||
_entityService = entityService;
|
||||
}
|
||||
|
||||
protected override Task SetUnmappedProperties(ListViewPagedModel<IContent> contentCollection, List<DocumentCollectionResponseModel> collectionResponseModels)
|
||||
@@ -27,6 +42,8 @@ public class DocumentCollectionPresentationFactory : ContentCollectionPresentati
|
||||
}
|
||||
|
||||
item.IsProtected = _publicAccessService.IsProtected(matchingContentItem).Success;
|
||||
item.Ancestors = _entityService.GetPathKeys(matchingContentItem, omitSelf: true)
|
||||
.Select(x => new ReferenceByIdModel(x));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -26,7 +26,7 @@ internal sealed class DocumentVersionPresentationFactory : IDocumentVersionPrese
|
||||
new ReferenceByIdModel(_entityService.GetKey(contentVersion.ContentTypeId, UmbracoObjectTypes.DocumentType)
|
||||
.Result),
|
||||
new ReferenceByIdModel(await _userIdKeyResolver.GetAsync(contentVersion.UserId)),
|
||||
new DateTimeOffset(contentVersion.VersionDate, TimeSpan.Zero), // todo align with datetime offset rework
|
||||
new DateTimeOffset(contentVersion.VersionDate),
|
||||
contentVersion.CurrentPublishedVersion,
|
||||
contentVersion.CurrentDraftVersion,
|
||||
contentVersion.PreventCleanup);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.RedirectUrlManagement;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
@@ -22,6 +22,12 @@ public class RedirectUrlPresentationFactory : IRedirectUrlPresentationFactory
|
||||
|
||||
var originalUrl = _publishedUrlProvider.GetUrlFromRoute(source.ContentId, source.Url, source.Culture);
|
||||
|
||||
// Even if the URL could not be extracted from the route, if we have a path as a the route for the original URL, we should display it.
|
||||
if (originalUrl == "#" && source.Url.StartsWith('/'))
|
||||
{
|
||||
originalUrl = source.Url;
|
||||
}
|
||||
|
||||
return new RedirectUrlResponseModel
|
||||
{
|
||||
OriginalUrl = originalUrl,
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Infrastructure.Scoping;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -9,21 +14,73 @@ namespace Umbraco.Cms.Api.Management.Factories;
|
||||
public class RelationTypePresentationFactory : IRelationTypePresentationFactory
|
||||
{
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
private readonly IEntityRepository _entityRepository;
|
||||
private readonly IDocumentPresentationFactory _documentPresentationFactory;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
|
||||
[Obsolete("Please use the non obsoleted constructor. Scheduled for removal in v17")]
|
||||
public RelationTypePresentationFactory(IUmbracoMapper umbracoMapper)
|
||||
: this(
|
||||
umbracoMapper,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IEntityRepository>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IDocumentPresentationFactory>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IScopeProvider>())
|
||||
{
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IReferenceResponseModel>> CreateReferenceResponseModelsAsync(IEnumerable<RelationItemModel> relationItemModels)
|
||||
public RelationTypePresentationFactory(
|
||||
IUmbracoMapper umbracoMapper,
|
||||
IEntityRepository entityRepository,
|
||||
IDocumentPresentationFactory documentPresentationFactory,
|
||||
IScopeProvider scopeProvider)
|
||||
{
|
||||
IReferenceResponseModel[] result = relationItemModels.Select(relationItemModel => relationItemModel.NodeType switch
|
||||
{
|
||||
Constants.UdiEntityType.Document => _umbracoMapper.Map<DocumentReferenceResponseModel>(relationItemModel),
|
||||
Constants.UdiEntityType.Media => _umbracoMapper.Map<MediaReferenceResponseModel>(relationItemModel),
|
||||
_ => _umbracoMapper.Map<DefaultReferenceResponseModel>(relationItemModel) as IReferenceResponseModel,
|
||||
}).WhereNotNull().ToArray();
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_entityRepository = entityRepository;
|
||||
_documentPresentationFactory = documentPresentationFactory;
|
||||
_scopeProvider = scopeProvider;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IReferenceResponseModel>> CreateReferenceResponseModelsAsync(
|
||||
IEnumerable<RelationItemModel> relationItemModels)
|
||||
{
|
||||
IReadOnlyCollection<RelationItemModel> relationItemModelsCollection = relationItemModels.ToArray();
|
||||
|
||||
Guid[] documentKeys = relationItemModelsCollection
|
||||
.Where(item => item.NodeType is Constants.UdiEntityType.Document)
|
||||
.Select(item => item.NodeKey).Distinct().ToArray();
|
||||
|
||||
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
|
||||
var slimEntities = _entityRepository.GetAll(Constants.ObjectTypes.Document, documentKeys).ToList();
|
||||
|
||||
IReferenceResponseModel[] result = relationItemModelsCollection.Select(relationItemModel =>
|
||||
relationItemModel.NodeType switch
|
||||
{
|
||||
Constants.ReferenceType.Document => MapDocumentReference(relationItemModel, slimEntities),
|
||||
Constants.ReferenceType.Media => _umbracoMapper.Map<MediaReferenceResponseModel>(relationItemModel),
|
||||
Constants.ReferenceType.Member => _umbracoMapper.Map<MemberReferenceResponseModel>(relationItemModel),
|
||||
Constants.ReferenceType.DocumentTypePropertyType => _umbracoMapper.Map<DocumentTypePropertyTypeReferenceResponseModel>(relationItemModel),
|
||||
Constants.ReferenceType.MediaTypePropertyType => _umbracoMapper.Map<MediaTypePropertyTypeReferenceResponseModel>(relationItemModel),
|
||||
Constants.ReferenceType.MemberTypePropertyType => _umbracoMapper.Map<MemberTypePropertyTypeReferenceResponseModel>(relationItemModel),
|
||||
_ => _umbracoMapper.Map<DefaultReferenceResponseModel>(relationItemModel),
|
||||
}).WhereNotNull().ToArray();
|
||||
|
||||
return await Task.FromResult(result);
|
||||
}
|
||||
|
||||
private IReferenceResponseModel? MapDocumentReference(RelationItemModel relationItemModel,
|
||||
List<IEntitySlim> slimEntities)
|
||||
{
|
||||
DocumentReferenceResponseModel? documentReferenceResponseModel =
|
||||
_umbracoMapper.Map<DocumentReferenceResponseModel>(relationItemModel);
|
||||
if (documentReferenceResponseModel is not null
|
||||
&& slimEntities.FirstOrDefault(e => e.Key == relationItemModel.NodeKey) is DocumentEntitySlim
|
||||
matchingSlimDocument)
|
||||
{
|
||||
documentReferenceResponseModel.Variants =
|
||||
_documentPresentationFactory.CreateVariantsItemResponseModels(matchingSlimDocument);
|
||||
}
|
||||
|
||||
return documentReferenceResponseModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.Security;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User.Current;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User.Item;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.UserGroup;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Mail;
|
||||
using Umbraco.Cms.Core.Media;
|
||||
@@ -20,7 +24,6 @@ namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
public class UserPresentationFactory : IUserPresentationFactory
|
||||
{
|
||||
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly MediaFileManager _mediaFileManager;
|
||||
@@ -31,7 +34,10 @@ public class UserPresentationFactory : IUserPresentationFactory
|
||||
private readonly IPasswordConfigurationPresentationFactory _passwordConfigurationPresentationFactory;
|
||||
private readonly IBackOfficeExternalLoginProviders _externalLoginProviders;
|
||||
private readonly SecuritySettings _securitySettings;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IContentService _contentService;
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public UserPresentationFactory(
|
||||
IEntityService entityService,
|
||||
AppCaches appCaches,
|
||||
@@ -43,6 +49,35 @@ public class UserPresentationFactory : IUserPresentationFactory
|
||||
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
|
||||
IOptionsSnapshot<SecuritySettings> securitySettings,
|
||||
IBackOfficeExternalLoginProviders externalLoginProviders)
|
||||
: this(
|
||||
entityService,
|
||||
appCaches,
|
||||
mediaFileManager,
|
||||
imageUrlGenerator,
|
||||
userGroupPresentationFactory,
|
||||
absoluteUrlBuilder,
|
||||
emailSender,
|
||||
passwordConfigurationPresentationFactory,
|
||||
securitySettings,
|
||||
externalLoginProviders,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IUserService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentService>())
|
||||
{
|
||||
}
|
||||
|
||||
public UserPresentationFactory(
|
||||
IEntityService entityService,
|
||||
AppCaches appCaches,
|
||||
MediaFileManager mediaFileManager,
|
||||
IImageUrlGenerator imageUrlGenerator,
|
||||
IUserGroupPresentationFactory userGroupPresentationFactory,
|
||||
IAbsoluteUrlBuilder absoluteUrlBuilder,
|
||||
IEmailSender emailSender,
|
||||
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
|
||||
IOptionsSnapshot<SecuritySettings> securitySettings,
|
||||
IBackOfficeExternalLoginProviders externalLoginProviders,
|
||||
IUserService userService,
|
||||
IContentService contentService)
|
||||
{
|
||||
_entityService = entityService;
|
||||
_appCaches = appCaches;
|
||||
@@ -54,6 +89,8 @@ public class UserPresentationFactory : IUserPresentationFactory
|
||||
_externalLoginProviders = externalLoginProviders;
|
||||
_securitySettings = securitySettings.Value;
|
||||
_absoluteUrlBuilder = absoluteUrlBuilder;
|
||||
_userService = userService;
|
||||
_contentService = contentService;
|
||||
}
|
||||
|
||||
public UserResponseModel CreateResponseModel(IUser user)
|
||||
@@ -195,7 +232,7 @@ public class UserPresentationFactory : IUserPresentationFactory
|
||||
var contentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches);
|
||||
var documentStartNodeKeys = GetKeysFromIds(contentStartNodeIds, UmbracoObjectTypes.Document);
|
||||
|
||||
var permissions = presentationGroups.SelectMany(x => x.Permissions).ToHashSet();
|
||||
var permissions = GetAggregatedGranularPermissions(user, presentationGroups);
|
||||
var fallbackPermissions = presentationGroups.SelectMany(x => x.FallbackPermissions).ToHashSet();
|
||||
|
||||
var hasAccessToAllLanguages = presentationGroups.Any(x => x.HasAccessToAllLanguages);
|
||||
@@ -225,6 +262,42 @@ public class UserPresentationFactory : IUserPresentationFactory
|
||||
});
|
||||
}
|
||||
|
||||
private HashSet<IPermissionPresentationModel> GetAggregatedGranularPermissions(IUser user, IEnumerable<UserGroupResponseModel> presentationGroups)
|
||||
{
|
||||
var permissions = presentationGroups.SelectMany(x => x.Permissions).ToHashSet();
|
||||
|
||||
// The raw permission data consists of several permissions for each document. We want to aggregate this server-side so
|
||||
// we return one set of aggregate permissions per document that the client will use.
|
||||
|
||||
// Get the unique document keys that have granular permissions.
|
||||
IEnumerable<Guid> documentKeysWithGranularPermissions = permissions
|
||||
.Where(x => x is DocumentPermissionPresentationModel)
|
||||
.Cast<DocumentPermissionPresentationModel>()
|
||||
.Select(x => x.Document.Id)
|
||||
.Distinct();
|
||||
|
||||
var aggregatedPermissions = new HashSet<IPermissionPresentationModel>();
|
||||
foreach (Guid documentKey in documentKeysWithGranularPermissions)
|
||||
{
|
||||
// Retrieve the path of the document.
|
||||
var path = _contentService.GetById(documentKey)?.Path;
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// With the path we can call the same logic as used server-side for authorizing access to resources.
|
||||
EntityPermissionSet permissionsForPath = _userService.GetPermissionsForPath(user, path);
|
||||
aggregatedPermissions.Add(new DocumentPermissionPresentationModel
|
||||
{
|
||||
Document = new ReferenceByIdModel(documentKey),
|
||||
Verbs = permissionsForPath.GetAllPermissions()
|
||||
});
|
||||
}
|
||||
|
||||
return aggregatedPermissions;
|
||||
}
|
||||
|
||||
public async Task<CalculatedUserStartNodesResponseModel> CreateCalculatedUserStartNodesResponseModelAsync(IUser user)
|
||||
{
|
||||
var mediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches);
|
||||
|
||||
@@ -67,7 +67,7 @@ public class DocumentMapDefinition : ContentMapDefinition<IContent, DocumentValu
|
||||
target.IsTrashed = source.Trashed;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -IsProtected
|
||||
// Umbraco.Code.MapAll -IsProtected -Ancestors
|
||||
private void Map(IContent source, DocumentCollectionResponseModel target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Key;
|
||||
|
||||
+67
-2
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
@@ -11,12 +11,16 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
|
||||
{
|
||||
mapper.Define<RelationItemModel, DocumentReferenceResponseModel>((source, context) => new DocumentReferenceResponseModel(), Map);
|
||||
mapper.Define<RelationItemModel, MediaReferenceResponseModel>((source, context) => new MediaReferenceResponseModel(), Map);
|
||||
mapper.Define<RelationItemModel, MemberReferenceResponseModel>((source, context) => new MemberReferenceResponseModel(), Map);
|
||||
mapper.Define<RelationItemModel, DocumentTypePropertyTypeReferenceResponseModel>((source, context) => new DocumentTypePropertyTypeReferenceResponseModel(), Map);
|
||||
mapper.Define<RelationItemModel, MediaTypePropertyTypeReferenceResponseModel>((source, context) => new MediaTypePropertyTypeReferenceResponseModel(), Map);
|
||||
mapper.Define<RelationItemModel, MemberTypePropertyTypeReferenceResponseModel>((source, context) => new MemberTypePropertyTypeReferenceResponseModel(), Map);
|
||||
mapper.Define<RelationItemModel, DefaultReferenceResponseModel>((source, context) => new DefaultReferenceResponseModel(), Map);
|
||||
mapper.Define<RelationItemModel, ReferenceByIdModel>((source, context) => new ReferenceByIdModel(), Map);
|
||||
mapper.Define<Guid, ReferenceByIdModel>((source, context) => new ReferenceByIdModel(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
// Umbraco.Code.MapAll -Variants
|
||||
private void Map(RelationItemModel source, DocumentReferenceResponseModel target, MapperContext context)
|
||||
{
|
||||
target.Id = source.NodeKey;
|
||||
@@ -24,6 +28,7 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
|
||||
target.Published = source.NodePublished;
|
||||
target.DocumentType = new TrackedReferenceDocumentType
|
||||
{
|
||||
Id = source.ContentTypeKey,
|
||||
Alias = source.ContentTypeAlias,
|
||||
Icon = source.ContentTypeIcon,
|
||||
Name = source.ContentTypeName,
|
||||
@@ -37,6 +42,66 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
|
||||
target.Name = source.NodeName;
|
||||
target.MediaType = new TrackedReferenceMediaType
|
||||
{
|
||||
Id = source.ContentTypeKey,
|
||||
Alias = source.ContentTypeAlias,
|
||||
Icon = source.ContentTypeIcon,
|
||||
Name = source.ContentTypeName,
|
||||
};
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(RelationItemModel source, MemberReferenceResponseModel target, MapperContext context)
|
||||
{
|
||||
target.Id = source.NodeKey;
|
||||
target.Name = source.NodeName;
|
||||
target.MemberType = new TrackedReferenceMemberType
|
||||
{
|
||||
Id = source.ContentTypeKey,
|
||||
Alias = source.ContentTypeAlias,
|
||||
Icon = source.ContentTypeIcon,
|
||||
Name = source.ContentTypeName,
|
||||
};
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(RelationItemModel source, DocumentTypePropertyTypeReferenceResponseModel target, MapperContext context)
|
||||
{
|
||||
target.Id = source.NodeKey;
|
||||
target.Name = source.NodeName;
|
||||
target.Alias = source.NodeAlias;
|
||||
target.DocumentType = new TrackedReferenceDocumentType
|
||||
{
|
||||
Id = source.ContentTypeKey,
|
||||
Alias = source.ContentTypeAlias,
|
||||
Icon = source.ContentTypeIcon,
|
||||
Name = source.ContentTypeName,
|
||||
};
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(RelationItemModel source, MediaTypePropertyTypeReferenceResponseModel target, MapperContext context)
|
||||
{
|
||||
target.Id = source.NodeKey;
|
||||
target.Name = source.NodeName;
|
||||
target.Alias = source.NodeAlias;
|
||||
target.MediaType = new TrackedReferenceMediaType
|
||||
{
|
||||
Id = source.ContentTypeKey,
|
||||
Alias = source.ContentTypeAlias,
|
||||
Icon = source.ContentTypeIcon,
|
||||
Name = source.ContentTypeName,
|
||||
};
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(RelationItemModel source, MemberTypePropertyTypeReferenceResponseModel target, MapperContext context)
|
||||
{
|
||||
target.Id = source.NodeKey;
|
||||
target.Name = source.NodeName;
|
||||
target.Alias = source.NodeAlias;
|
||||
target.MemberType = new TrackedReferenceMemberType
|
||||
{
|
||||
Id = source.ContentTypeKey,
|
||||
Alias = source.ContentTypeAlias,
|
||||
Icon = source.ContentTypeIcon,
|
||||
Name = source.ContentTypeName,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
@@ -38,7 +38,7 @@ public abstract class BackOfficeSecurityRequirementsOperationFilterBase : IOpera
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = ManagementApiConfiguration.ApiSecurityName
|
||||
}
|
||||
}, new string[] { }
|
||||
}, []
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -59,7 +59,7 @@ public abstract class BackOfficeSecurityRequirementsOperationFilterBase : IOpera
|
||||
{
|
||||
operation.Responses.Add(StatusCodes.Status403Forbidden.ToString(), new OpenApiResponse()
|
||||
{
|
||||
Description = "The authenticated user do not have access to this resource"
|
||||
Description = "The authenticated user does not have access to this resource"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,5 +36,7 @@ public class ConfigureUmbracoBackofficeJsonOptions : IConfigureNamedOptions<Json
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonObjectConverter());
|
||||
|
||||
options.JsonSerializerOptions.TypeInfoResolver = _umbracoJsonTypeInfoResolver;
|
||||
|
||||
options.JsonSerializerOptions.MaxDepth = 64; // Ensures the maximum possible value is used, in particular to support handling as best we can levels of nested blocks.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,41 @@ public interface IUserStartNodeEntitiesService
|
||||
/// <remarks>
|
||||
/// The returned entities may include entities that outside of the user start node scope, but are needed to
|
||||
/// for browsing to the actual user start nodes. These entities will be marked as "no access" entities.
|
||||
///
|
||||
/// This method does not support pagination, because it must load all entities explicitly in order to calculate
|
||||
/// the correct result, given that user start nodes can be descendants of root nodes. Consumers need to apply
|
||||
/// pagination to the result if applicable.
|
||||
/// </remarks>
|
||||
IEnumerable<UserAccessEntity> RootUserAccessEntities(UmbracoObjectTypes umbracoObjectType, int[] userStartNodeIds);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the applicable child entities for a given object type for users without root access.
|
||||
/// </summary>
|
||||
/// <param name="umbracoObjectType">The object type.</param>
|
||||
/// <param name="userStartNodePaths">The calculated start node paths for the user.</param>
|
||||
/// <param name="parentKey">The key of the parent.</param>
|
||||
/// <param name="skip">The number of applicable children to skip.</param>
|
||||
/// <param name="take">The number of applicable children to take.</param>
|
||||
/// <param name="ordering">The ordering to apply when fetching and paginating the children.</param>
|
||||
/// <param name="totalItems">The total number of applicable children available.</param>
|
||||
/// <returns>A list of child entities applicable for the user.</returns>
|
||||
/// <remarks>
|
||||
/// The returned entities may include entities that outside of the user start node scope, but are needed to
|
||||
/// for browsing to the actual user start nodes. These entities will be marked as "no access" entities.
|
||||
/// </remarks>
|
||||
IEnumerable<UserAccessEntity> ChildUserAccessEntities(
|
||||
UmbracoObjectTypes umbracoObjectType,
|
||||
string[] userStartNodePaths,
|
||||
Guid parentKey,
|
||||
int skip,
|
||||
int take,
|
||||
Ordering ordering,
|
||||
out long totalItems)
|
||||
{
|
||||
totalItems = 0;
|
||||
return [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the applicable child entities from a list of candidate child entities for users without root access.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using Umbraco.Cms.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Api.Management.Models.Entities;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Services.Entities;
|
||||
@@ -10,8 +14,24 @@ namespace Umbraco.Cms.Api.Management.Services.Entities;
|
||||
public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
|
||||
{
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly ICoreScopeProvider _scopeProvider;
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
|
||||
public UserStartNodeEntitiesService(IEntityService entityService) => _entityService = entityService;
|
||||
[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in V17.")]
|
||||
public UserStartNodeEntitiesService(IEntityService entityService)
|
||||
: this(
|
||||
entityService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ICoreScopeProvider>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IIdKeyMap>())
|
||||
{
|
||||
}
|
||||
|
||||
public UserStartNodeEntitiesService(IEntityService entityService, ICoreScopeProvider scopeProvider, IIdKeyMap idKeyMap)
|
||||
{
|
||||
_entityService = entityService;
|
||||
_scopeProvider = scopeProvider;
|
||||
_idKeyMap = idKeyMap;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<UserAccessEntity> RootUserAccessEntities(UmbracoObjectTypes umbracoObjectType, int[] userStartNodeIds)
|
||||
@@ -43,6 +63,54 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<UserAccessEntity> ChildUserAccessEntities(UmbracoObjectTypes umbracoObjectType, string[] userStartNodePaths, Guid parentKey, int skip, int take, Ordering ordering, out long totalItems)
|
||||
{
|
||||
Attempt<int> parentIdAttempt = _idKeyMap.GetIdForKey(parentKey, umbracoObjectType);
|
||||
if (parentIdAttempt.Success is false)
|
||||
{
|
||||
totalItems = 0;
|
||||
return [];
|
||||
}
|
||||
|
||||
var parentId = parentIdAttempt.Result;
|
||||
IEntitySlim? parent = _entityService.Get(parentId);
|
||||
if (parent is null)
|
||||
{
|
||||
totalItems = 0;
|
||||
return [];
|
||||
}
|
||||
|
||||
IEntitySlim[] children;
|
||||
if (userStartNodePaths.Any(path => $"{parent.Path},".StartsWith($"{path},")))
|
||||
{
|
||||
// the requested parent is one of the user start nodes (or a descendant of one), all children are by definition allowed
|
||||
children = _entityService.GetPagedChildren(parentKey, umbracoObjectType, skip, take, out totalItems, ordering: ordering).ToArray();
|
||||
return ChildUserAccessEntities(children, userStartNodePaths);
|
||||
}
|
||||
|
||||
// if one or more of the user start nodes are descendants of the requested parent, find the "next child IDs" in those user start node paths
|
||||
// - e.g. given the user start node path "-1,2,3,4,5", if the requested parent ID is 3, the "next child ID" is 4.
|
||||
var userStartNodePathIds = userStartNodePaths.Select(path => path.Split(Constants.CharArrays.Comma).Select(int.Parse).ToArray()).ToArray();
|
||||
var allowedChildIds = userStartNodePathIds
|
||||
.Where(ids => ids.Contains(parentId))
|
||||
// given the previous checks, the parent ID can never be the last in the user start node path, so this is safe
|
||||
.Select(ids => ids[ids.IndexOf(parentId) + 1])
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
totalItems = allowedChildIds.Length;
|
||||
if (allowedChildIds.Length == 0)
|
||||
{
|
||||
// the requested parent is outside the scope of any user start nodes
|
||||
return [];
|
||||
}
|
||||
|
||||
// even though we know the IDs of the allowed child entities to fetch, we still use a Query to yield correctly sorted children
|
||||
IQuery<IUmbracoEntity> query = _scopeProvider.CreateQuery<IUmbracoEntity>().Where(x => allowedChildIds.Contains(x.Id));
|
||||
children = _entityService.GetPagedChildren(parentKey, umbracoObjectType, skip, take, out totalItems, query, ordering).ToArray();
|
||||
return ChildUserAccessEntities(children, userStartNodePaths);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<UserAccessEntity> ChildUserAccessEntities(IEnumerable<IEntitySlim> candidateChildren, string[] userStartNodePaths)
|
||||
// child entities for users without root access should include:
|
||||
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary;
|
||||
|
||||
@@ -7,6 +7,5 @@ public class DictionaryItemTranslationModel
|
||||
[Required]
|
||||
public string IsoCode { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string Translation { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
+2
@@ -11,5 +11,7 @@ public class DocumentCollectionResponseModel : ContentCollectionResponseModelBas
|
||||
|
||||
public bool IsProtected { get; set; }
|
||||
|
||||
public IEnumerable<ReferenceByIdModel> Ancestors { get; set; } = [];
|
||||
|
||||
public string? Updater { get; set; }
|
||||
}
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
public class PublishWithDescendantsResultModel
|
||||
{
|
||||
public Guid TaskId { get; set; }
|
||||
|
||||
public bool IsComplete { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.PublishedCache;
|
||||
|
||||
public class RebuildStatusModel
|
||||
{
|
||||
public bool IsRebuilding { get; set; }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public abstract class ContentTypePropertyTypeReferenceResponseModel : ReferenceResponseModel
|
||||
{
|
||||
public string? Alias { get; set; }
|
||||
}
|
||||
+2
-6
@@ -1,11 +1,7 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public class DefaultReferenceResponseModel : IReferenceResponseModel
|
||||
public class DefaultReferenceResponseModel : ReferenceResponseModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string? Type { get; set; }
|
||||
|
||||
public string? Icon { get; set; }
|
||||
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
public class DocumentReferenceResponseModel : IReferenceResponseModel
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public class DocumentReferenceResponseModel : ReferenceResponseModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public bool? Published { get; set; }
|
||||
|
||||
public TrackedReferenceDocumentType DocumentType { get; set; } = new();
|
||||
|
||||
public IEnumerable<DocumentVariantItemResponseModel> Variants { get; set; } = Enumerable.Empty<DocumentVariantItemResponseModel>();
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public class DocumentTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
|
||||
{
|
||||
public TrackedReferenceDocumentType DocumentType { get; set; } = new();
|
||||
}
|
||||
+2
-6
@@ -1,10 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public class MediaReferenceResponseModel : IReferenceResponseModel
|
||||
public class MediaReferenceResponseModel : ReferenceResponseModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public TrackedReferenceMediaType MediaType { get; set; } = new();
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public class MediaTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
|
||||
{
|
||||
public TrackedReferenceMediaType MediaType { get; set; } = new();
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public class MemberReferenceResponseModel : ReferenceResponseModel
|
||||
{
|
||||
public TrackedReferenceMemberType MemberType { get; set; } = new();
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public class MemberTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
|
||||
{
|
||||
public TrackedReferenceMemberType MemberType { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public abstract class ReferenceResponseModel : IReferenceResponseModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public abstract class TrackedReferenceContentType
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Icon { get; set; }
|
||||
|
||||
public string? Alias { get; set; }
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
|
||||
|
||||
public class TrackedReferenceMemberType : TrackedReferenceContentType
|
||||
{
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
|
||||
@@ -8,6 +7,8 @@ public class DocumentTreeItemResponseModel : ContentTreeItemResponseModel
|
||||
{
|
||||
public bool IsProtected { get; set; }
|
||||
|
||||
public IEnumerable<ReferenceByIdModel> Ancestors { get; set; } = [];
|
||||
|
||||
public DocumentTypeReferenceResponseModel DocumentType { get; set; } = new();
|
||||
|
||||
public IEnumerable<DocumentVariantItemResponseModel> Variants { get; set; } = Enumerable.Empty<DocumentVariantItemResponseModel>();
|
||||
|
||||
@@ -127,10 +127,16 @@ internal class EFCoreScope<TDbContext> : CoreScope, IEfCoreScope<TDbContext>
|
||||
|
||||
Locks.ClearLocks(InstanceId);
|
||||
|
||||
if (ParentScope is null)
|
||||
// Since we can nest EFCoreScopes in other scopes derived from CoreScope, we should check whether our ParentScope OR the base ParentScope exists.
|
||||
// Only if neither do do we take responsibility for ensuring the locks are cleared.
|
||||
// Eventually the highest parent will clear the locks.
|
||||
// Further, these locks are a reference to the locks of the highest parent anyway (see the constructor of CoreScope).
|
||||
#pragma warning disable SA1100 // Do not prefix calls with base unless local implementation exists (justification: provides additional clarify here that this is defined on the base class).
|
||||
if (ParentScope is null && base.HasParentScope is false)
|
||||
{
|
||||
Locks.EnsureLocksCleared(InstanceId);
|
||||
}
|
||||
#pragma warning restore SA1100 // Do not prefix calls with base unless local implementation exists
|
||||
|
||||
_efCoreScopeProvider.PopAmbientScope();
|
||||
|
||||
|
||||
@@ -26,23 +26,36 @@
|
||||
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Restore and build backoffice project -->
|
||||
<!-- General ignored files -->
|
||||
<ItemGroup>
|
||||
<Content Remove="wwwroot\umbraco\assets\README.md" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
<!-- BEGIN: Restore and build backoffice project -->
|
||||
<PropertyGroup>
|
||||
<BackofficeProjectDirectory Condition="'$(BackofficeProjectDirectory)' == ''">..\Umbraco.Web.UI.Client\</BackofficeProjectDirectory>
|
||||
<BackofficeAssetsPath>wwwroot\umbraco\backoffice</BackofficeAssetsPath>
|
||||
<BackofficeAssetsPath>$(ProjectDir)wwwroot\umbraco\backoffice</BackofficeAssetsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<BackofficeAssetsInputs Include="$(BackofficeProjectDirectory)package.json;$(BackofficeProjectDirectory)package-lock.json;$(BackofficeProjectDirectory)src\**" Exclude="$(DefaultItemExcludes)" />
|
||||
<Content Remove="$(BackofficeAssetsPath)\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="RestoreBackoffice" Inputs="$(BackofficeProjectDirectory)package-lock.json" Outputs="$(BackofficeProjectDirectory)node_modules\.package-lock.json">
|
||||
<Message Importance="high" Text="Restoring Backoffice NPM packages..." />
|
||||
<Exec Command="npm ci --no-fund --no-audit --prefer-offline" WorkingDirectory="$(BackofficeProjectDirectory)" />
|
||||
<Target Name="BuildStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
|
||||
<Message Text="Skip BuildBackoffice target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
|
||||
<Message Text="Skip BuildBackoffice target because '$(BackofficeAssetsPath)' already exists" Importance="high" Condition="Exists('$(BackofficeAssetsPath)')" />
|
||||
<Message Text="Call BuildBackoffice target because UmbracoBuild is empty (this is Visual Studio) and '$(BackofficeAssetsPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficeAssetsPath)')" />
|
||||
<CallTarget Targets="BuildBackoffice" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficeAssetsPath)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildBackoffice" DependsOnTargets="RestoreBackoffice" BeforeTargets="AssignTargetPaths" Inputs="@(BackofficeAssetsInputs)" Outputs="$(IntermediateOutputPath)backoffice.complete.txt">
|
||||
<Target Name="RestoreBackoffice" Inputs="$(BackofficeProjectDirectory)package-lock.json" Outputs="$(BackofficeProjectDirectory)node_modules\.package-lock.json">
|
||||
<Message Importance="high" Text="Restoring Backoffice NPM packages..." />
|
||||
<Exec Command="npm i --no-fund --no-audit" WorkingDirectory="$(BackofficeProjectDirectory)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildBackoffice" DependsOnTargets="RestoreBackoffice">
|
||||
<Message Importance="high" Text="Executing Backoffice NPM build script..." />
|
||||
<Exec Command="npm run build:for:cms" WorkingDirectory="$(BackofficeProjectDirectory)" />
|
||||
<ItemGroup>
|
||||
@@ -61,27 +74,46 @@
|
||||
</DefineStaticWebAssets>
|
||||
</Target>
|
||||
|
||||
<!-- Restore and build login project -->
|
||||
<Target Name="CleanStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
|
||||
<Message Text="Skip CleanBackoffice target because '$(BackofficeAssetsPath)' doesn't exist" Importance="high" Condition="!Exists('$(BackofficeAssetsPath)')" />
|
||||
<Message Text="Skip CleanBackoffice target because preserve.backoffice marker file exists" Importance="high" Condition="Exists('$(BackofficeAssetsPath)') and Exists('$(SolutionDir)preserve.backoffice')" />
|
||||
<Message Text="Call CleanBackoffice target because '$(BackofficeAssetsPath)' exists and preserve.backoffice marker file doesn't exist" Importance="high" Condition="Exists('$(BackofficeAssetsPath)') and !Exists('$(SolutionDir)preserve.backoffice')" />
|
||||
<CallTarget Targets="CleanBackoffice" Condition="Exists('$(BackofficeAssetsPath)') and !Exists('$(SolutionDir)preserve.backoffice')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanBackoffice">
|
||||
<ItemGroup>
|
||||
<BackofficeDirectories Include="$(BackofficeAssetsPath)" />
|
||||
</ItemGroup>
|
||||
<RemoveDir Directories="@(BackofficeDirectories)" />
|
||||
</Target>
|
||||
<!-- END: Restore and build backoffice project -->
|
||||
|
||||
|
||||
|
||||
<!-- BEGIN: Restore and build login project -->
|
||||
<PropertyGroup>
|
||||
<LoginProjectDirectory Condition="'$(LoginProjectDirectory)' == ''">..\Umbraco.Web.UI.Login\</LoginProjectDirectory>
|
||||
<LoginAssetsPath>wwwroot\umbraco\login</LoginAssetsPath>
|
||||
<LoginAssetsPath>$(ProjectDir)wwwroot\umbraco\login</LoginAssetsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<LoginAssetsInputs Include="$(LoginProjectDirectory)**" Exclude="$(DefaultItemExcludes)" />
|
||||
<Content Remove="$(LoginAssetsPath)\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="wwwroot\umbraco\assets\README.md" />
|
||||
</ItemGroup>
|
||||
<Target Name="BuildLoginStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
|
||||
<Message Text="Skip BuildLogin target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
|
||||
<Message Text="Skip BuildLogin target because '$(LoginAssetsPath)' already exists" Importance="high" Condition="Exists('$(LoginAssetsPath)')" />
|
||||
<Message Text="Call BuildLogin target because UmbracoBuild is empty (this is Visual Studio) and '$(LoginAssetsPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginAssetsPath)')" />
|
||||
<CallTarget Targets="BuildLogin" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginAssetsPath)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="RestoreLogin" Inputs="$(LoginProjectDirectory)package-lock.json" Outputs="$(LoginProjectDirectory)node_modules/.package-lock.json">
|
||||
<Message Importance="high" Text="Restoring Login NPM packages..." />
|
||||
<Exec Command="npm ci --no-fund --no-audit --prefer-offline" WorkingDirectory="$(LoginProjectDirectory)" />
|
||||
<Exec Command="npm i --no-fund --no-audit" WorkingDirectory="$(LoginProjectDirectory)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildLogin" DependsOnTargets="RestoreLogin" BeforeTargets="AssignTargetPaths" Inputs="@(LoginAssetsInputs)" Outputs="$(IntermediateOutputPath)login.complete.txt">
|
||||
<Target Name="BuildLogin" DependsOnTargets="RestoreLogin">
|
||||
<Message Importance="high" Text="Executing Login NPM build script..." />
|
||||
<Exec Command="npm run build" WorkingDirectory="$(LoginProjectDirectory)" />
|
||||
<ItemGroup>
|
||||
@@ -99,4 +131,19 @@
|
||||
<Output TaskParameter="Assets" ItemName="StaticWebAsset" />
|
||||
</DefineStaticWebAssets>
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanLoginStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
|
||||
<Message Text="Skip CleanLogin target because '$(LoginAssetsPath)' doesn't exist" Importance="high" Condition="!Exists('$(LoginAssetsPath)')" />
|
||||
<Message Text="Skip CleanLogin target because preserve.login marker file exists" Importance="high" Condition="Exists('$(LoginAssetsPath)') and Exists('$(SolutionDir)preserve.login')" />
|
||||
<Message Text="Call CleanLogin target because '$(LoginAssetsPath)' exists and preserve.login marker file doesn't exist" Importance="high" Condition="Exists('$(LoginAssetsPath)') and !Exists('$(SolutionDir)preserve.login')" />
|
||||
<CallTarget Targets="CleanLogin" Condition="Exists('$(LoginAssetsPath)') and !Exists('$(SolutionDir)preserve.login')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanLogin">
|
||||
<ItemGroup>
|
||||
<LoginDirectories Include="$(LoginAssetsPath)" />
|
||||
</ItemGroup>
|
||||
<RemoveDir Directories="@(LoginDirectories)" />
|
||||
</Target>
|
||||
<!-- END: Restore and build login project -->
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 468"><path d="M5.5 233.7C5.7 107.5 108.1 5.3 234.3 5.5s228.4 102.6 228.2 228.8c-.2 126.1-102.4 228.2-228.5 228.2C107.7 462.4 5.4 360 5.5 233.7zM229.4 312c-17.7.5-35.4-1.1-52.8-4.8-12.8-2.6-23.6-11.2-28.9-23.1-5.2-11.9-7.7-30.3-7.5-55.1.1-13 .9-25.9 2.4-38.8 1.5-12.6 2.9-22.9 4.4-31.1l1.6-8.1v-.7c0-2.3-1.6-4.2-3.8-4.6l-29.5-4.6h-.7c-2.2 0-4 1.5-4.5 3.6-.5 1.9-.8 3.3-1.7 7.8-1.7 8.7-3.2 17.1-5 29.5-1.8 13.4-2.9 26.9-3.3 40.4-.6 9.4-.6 18.9 0 28.3.7 25 5 45 12.9 59.9s21.2 25.7 40.1 32.3c18.8 6.6 45.1 9.8 78.7 9.6h4.2c33.7.1 59.9-3.1 78.7-9.6 18.8-6.6 32.2-17.3 40.1-32.3 7.9-15 12.2-34.9 12.9-59.9.6-9.4.6-18.9 0-28.3-.4-13.5-1.5-27-3.3-40.4-1.7-12.2-3.3-20.6-5-29.5-.9-4.5-1.2-5.9-1.7-7.8-.5-2.1-2.3-3.6-4.5-3.6h-.8l-29.5 4.6c-2.2.4-3.9 2.3-3.9 4.6v.7l1.6 8.1c1.5 8.2 3 18.5 4.5 31 1.5 12.9 2.3 25.9 2.4 38.8.2 24.8-2.3 43.2-7.6 55.1-5.2 11.9-15.9 20.5-28.6 23.2-17.3 3.7-35.1 5.3-52.8 4.8h-9.1z" fill="#283a97"/></svg>
|
||||
|
After Width: | Height: | Size: 980 B |
@@ -1,51 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.8.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_2_00000159465072208785227250000008040336475015305350_"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1256.6 344.8"
|
||||
style="enable-background:new 0 0 1256.6 344.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#283A97;}
|
||||
</style>
|
||||
<g id="Layer_1-2">
|
||||
<g>
|
||||
<path class="st0" d="M5,172.2C5.1,79.7,80.2,4.9,172.6,5s167.3,75.2,167.2,167.6c-0.1,92.4-75,167.2-167.4,167.2
|
||||
C79.9,339.7,5,264.7,5,172.2L5,172.2L5,172.2z M169,229.5c-13,0.4-26-0.8-38.7-3.5c-9.4-1.9-17.3-8.2-21.2-17
|
||||
c-3.8-8.7-5.6-22.2-5.5-40.4c0.1-9.5,0.7-19,1.8-28.4c1.1-9.2,2.2-16.8,3.2-22.8l1.1-5.9c0-0.2,0-0.3,0-0.5c0-1.7-1.2-3.1-2.8-3.4
|
||||
l-21.6-3.4H85c-1.6,0-2.9,1.1-3.3,2.6c-0.4,1.4-0.6,2.4-1.2,5.7c-1.2,6.4-2.4,12.6-3.6,21.6c-1.3,9.8-2.1,19.7-2.4,29.6
|
||||
c-0.5,6.9-0.5,13.8,0,20.7c0.5,18.3,3.7,33,9.4,43.9s15.6,18.8,29.4,23.6c13.8,4.8,33,7.2,57.7,7.1h3.1
|
||||
c24.7,0.1,43.9-2.2,57.7-7.1c13.8-4.8,23.6-12.7,29.4-23.6c5.8-11,8.9-25.6,9.4-43.9c0.5-6.9,0.5-13.8,0-20.7
|
||||
c-0.3-9.9-1.1-19.8-2.4-29.6c-1.3-9-2.4-15.1-3.6-21.6c-0.6-3.3-0.9-4.3-1.2-5.7c-0.4-1.5-1.7-2.6-3.3-2.6h-0.6l-21.6,3.4
|
||||
c-1.6,0.3-2.8,1.7-2.8,3.4c0,0.2,0,0.3,0,0.5l1.2,5.9c1.1,6,2.2,13.6,3.3,22.7c1.1,9.4,1.7,18.9,1.8,28.4
|
||||
c0.2,18.2-1.7,31.6-5.5,40.4c-3.8,8.7-11.6,15-20.9,17c-12.7,2.7-25.7,3.9-38.7,3.5L169,229.5L169,229.5z"/>
|
||||
<g>
|
||||
<path class="st0" d="M1155.2,186.1c0-33.4,9.6-56.9,48.2-56.9s48.2,23.5,48.2,56.9s-9.6,56.9-48.2,56.9
|
||||
S1155.2,219.5,1155.2,186.1z M1227.1,186.1c0-23.2-3-36.5-23.6-36.5s-23.6,13.3-23.6,36.5s3.1,36.5,23.6,36.5
|
||||
S1227.1,209.2,1227.1,186.1z"/>
|
||||
<path class="st0" d="M487.2,238.7c0.6,1,1.6,1.6,2.8,1.6h9c1.8,0,3.2-1.4,3.2-3.2l0,0V135c0-1.8-1.4-3.2-3.2-3.2h-18
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v81c-7.7,4.3-16.4,6.5-25.2,6.3c-11.5,0-17.2-5-17.2-16.1V135c0-1.8-1.4-3.2-3.2-3.2h-17.9
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v73.4c0,20.8,9.8,34.5,37.3,34.5c12.6-0.1,24.9-4.1,35.2-11.3l3.2,7.2L487.2,238.7L487.2,238.7z"/>
|
||||
<path class="st0" d="M689.8,163.7c0-20.6-10.2-34.5-35.9-34.5c-12.4,0-24.5,3.8-34.8,10.7c-4.6-6.7-13.3-10.7-27.8-10.7
|
||||
c-11.8,0.2-23.3,4.2-32.8,11.3l-3.2-7.2l0,0c-0.6-1-1.6-1.7-2.8-1.7h-9c-1.8,0-3.2,1.4-3.2,3.2v102.1c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-80.8c6.9-4.1,14.8-6.3,22.8-6.3c9.8,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-81.1c6.8-4.2,14.8-6.4,22.8-6.4c9.6,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2l0,0L689.8,163.7L689.8,163.7z"/>
|
||||
<path class="st0" d="M745.4,231.7c10.2,7.5,22.5,11.5,35.2,11.3c31.7,0,43.4-21.3,43.4-56.9s-11.7-56.9-43.4-56.9
|
||||
c-10.3,0.1-20.4,3-29.1,8.5v-32.8c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8,0-3.2,1.4-3.2,3.2v132.3c0,1.8,1.4,3.2,3.2,3.2h9
|
||||
c1.2,0,2.2-0.6,2.8-1.6l0,0L745.4,231.7L745.4,231.7z M774.9,222.3c-8.2,0-16.3-2.1-23.4-6.3v-59.9c7.1-4.1,15.2-6.3,23.4-6.3
|
||||
c21.3,0,24.5,16.3,24.5,36.2S796.2,222.3,774.9,222.3L774.9,222.3L774.9,222.3z"/>
|
||||
<path class="st0" d="M913.9,150.5c-2.8-0.4-5.6-0.6-8.5-0.5c-9.7-0.4-19.3,1.9-27.8,6.5v80.6c0,1.8-1.4,3.2-3.2,3.2h-18
|
||||
c-1.8,0-3.2-1.4-3.2-3.2V135c0-1.8,1.4-3.2,3.2-3.2h9c1.2,0,2.2,0.6,2.8,1.6l0,0l3.2,7.2c9.9-7.5,22-11.5,34.4-11.3
|
||||
c2.8,0,5.7,0.2,8.5,0.7l0,0c1.7,0,3,2.7,3,4.4v13c0,1.8-1.4,3.2-3.2,3.2h-0.2"/>
|
||||
<path class="st0" d="M976.1,190.2c-10.7,1.3-17.2,5.4-17.2,16.7c0,8.3,3.6,16.1,16.7,16.1c8.3,0.1,16.4-2.4,23.2-7.2v-28.1
|
||||
L976.1,190.2L976.1,190.2z M1004.8,231.7C995.5,239,984,243,972.1,243c-28,0-37.3-17.4-37.3-34.8c0-23.5,15.2-33.4,39.7-35.4
|
||||
l24.4-1.9v-5.4c0-11.1-5.2-15.4-21.3-15.4c-10.1,0-20.1,1.7-29.6,4.9c-0.3,0.1-0.7,0.1-1,0c-1.8,0-3.2-1.4-3.2-3.2v-14.4
|
||||
c0-1.4,0.8-2.6,2.1-3.1l0,0c10.8-3.6,22.1-5.4,33.5-5.4c35.6,0,43.8,15.6,43.8,38.7v69.3c0,1.8-1.4,3.2-3.2,3.2h-9
|
||||
c-1.2,0-2.2-0.6-2.8-1.6l0,0L1004.8,231.7L1004.8,231.7z"/>
|
||||
<path class="st0" d="M1128.6,218c0.3,0,0.7,0,1,0c1.8,0,3.2,1.4,3.2,3.2v14.4c0,1.3-0.8,2.5-2,3l0,0c-8.9,3.2-18.4,4.8-27.9,4.6
|
||||
c-38.5,0-50.3-23-50.3-56.9s11.8-56.9,50.3-56.9c9.4-0.2,18.9,1.2,27.8,4.4l0,0c1.2,0.5,2,1.7,2,3v14.5c0,1.8-1.4,3.2-3.2,3.2
|
||||
c-0.4,0.1-0.7,0.1-1.1,0l0,0c-7.8-2.5-16-3.7-24.2-3.6c-21.1,0-27.2,14.4-27.2,35.4s6.1,35.4,27.2,35.4
|
||||
c8.2,0.1,16.4-1.1,24.2-3.6"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg version="1.1" id="prefix__Layer_2_00000159465072208785227250000008040336475015305350_" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 1256.6 344.8" xml:space="preserve"><style>.prefix__st0{fill:#283a97}</style><g id="prefix__Layer_1-2"><path class="prefix__st0" d="M5 172.2C5.1 79.7 80.2 4.9 172.6 5s167.3 75.2 167.2 167.6c-.1 92.4-75 167.2-167.4 167.2C79.9 339.7 5 264.7 5 172.2zm164 57.3c-13 .4-26-.8-38.7-3.5-9.4-1.9-17.3-8.2-21.2-17-3.8-8.7-5.6-22.2-5.5-40.4.1-9.5.7-19 1.8-28.4 1.1-9.2 2.2-16.8 3.2-22.8l1.1-5.9v-.5c0-1.7-1.2-3.1-2.8-3.4l-21.6-3.4H85c-1.6 0-2.9 1.1-3.3 2.6-.4 1.4-.6 2.4-1.2 5.7-1.2 6.4-2.4 12.6-3.6 21.6-1.3 9.8-2.1 19.7-2.4 29.6-.5 6.9-.5 13.8 0 20.7.5 18.3 3.7 33 9.4 43.9s15.6 18.8 29.4 23.6c13.8 4.8 33 7.2 57.7 7.1h3.1c24.7.1 43.9-2.2 57.7-7.1 13.8-4.8 23.6-12.7 29.4-23.6 5.8-11 8.9-25.6 9.4-43.9.5-6.9.5-13.8 0-20.7-.3-9.9-1.1-19.8-2.4-29.6-1.3-9-2.4-15.1-3.6-21.6-.6-3.3-.9-4.3-1.2-5.7-.4-1.5-1.7-2.6-3.3-2.6h-.6l-21.6 3.4c-1.6.3-2.8 1.7-2.8 3.4v.5l1.2 5.9c1.1 6 2.2 13.6 3.3 22.7 1.1 9.4 1.7 18.9 1.8 28.4.2 18.2-1.7 31.6-5.5 40.4-3.8 8.7-11.6 15-20.9 17-12.7 2.7-25.7 3.9-38.7 3.5l-7.3.1zM1155.2 186.1c0-33.4 9.6-56.9 48.2-56.9s48.2 23.5 48.2 56.9-9.6 56.9-48.2 56.9-48.2-23.5-48.2-56.9zm71.9 0c0-23.2-3-36.5-23.6-36.5s-23.6 13.3-23.6 36.5 3.1 36.5 23.6 36.5 23.6-13.4 23.6-36.5zM487.2 238.7c.6 1 1.6 1.6 2.8 1.6h9c1.8 0 3.2-1.4 3.2-3.2V135c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8 0-3.2 1.4-3.2 3.2v81c-7.7 4.3-16.4 6.5-25.2 6.3-11.5 0-17.2-5-17.2-16.1V135c0-1.8-1.4-3.2-3.2-3.2h-17.9c-1.8 0-3.2 1.4-3.2 3.2v73.4c0 20.8 9.8 34.5 37.3 34.5 12.6-.1 24.9-4.1 35.2-11.3l3.2 7.2.4-.1zM689.8 163.7c0-20.6-10.2-34.5-35.9-34.5-12.4 0-24.5 3.8-34.8 10.7-4.6-6.7-13.3-10.7-27.8-10.7-11.8.2-23.3 4.2-32.8 11.3l-3.2-7.2c-.6-1-1.6-1.7-2.8-1.7h-9c-1.8 0-3.2 1.4-3.2 3.2v102.1c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2v-80.8c6.9-4.1 14.8-6.3 22.8-6.3 9.8 0 15.4 3.6 15.4 14v73.4c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2v-81.1c6.8-4.2 14.8-6.4 22.8-6.4 9.6 0 15.4 3.6 15.4 14v73.4c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2l-.1-73.4zM745.4 231.7c10.2 7.5 22.5 11.5 35.2 11.3 31.7 0 43.4-21.3 43.4-56.9s-11.7-56.9-43.4-56.9c-10.3.1-20.4 3-29.1 8.5v-32.8c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8 0-3.2 1.4-3.2 3.2v132.3c0 1.8 1.4 3.2 3.2 3.2h9c1.2 0 2.2-.6 2.8-1.6l3.3-7.1zm29.5-9.4c-8.2 0-16.3-2.1-23.4-6.3v-59.9c7.1-4.1 15.2-6.3 23.4-6.3 21.3 0 24.5 16.3 24.5 36.2s-3.2 36.3-24.5 36.3zM913.9 150.5c-2.8-.4-5.6-.6-8.5-.5-9.7-.4-19.3 1.9-27.8 6.5v80.6c0 1.8-1.4 3.2-3.2 3.2h-18c-1.8 0-3.2-1.4-3.2-3.2V135c0-1.8 1.4-3.2 3.2-3.2h9c1.2 0 2.2.6 2.8 1.6l3.2 7.2c9.9-7.5 22-11.5 34.4-11.3 2.8 0 5.7.2 8.5.7 1.7 0 3 2.7 3 4.4v13c0 1.8-1.4 3.2-3.2 3.2h-.2M976.1 190.2c-10.7 1.3-17.2 5.4-17.2 16.7 0 8.3 3.6 16.1 16.7 16.1 8.3.1 16.4-2.4 23.2-7.2v-28.1l-22.7 2.5zm28.7 41.5C995.5 239 984 243 972.1 243c-28 0-37.3-17.4-37.3-34.8 0-23.5 15.2-33.4 39.7-35.4l24.4-1.9v-5.4c0-11.1-5.2-15.4-21.3-15.4-10.1 0-20.1 1.7-29.6 4.9-.3.1-.7.1-1 0-1.8 0-3.2-1.4-3.2-3.2v-14.4c0-1.4.8-2.6 2.1-3.1 10.8-3.6 22.1-5.4 33.5-5.4 35.6 0 43.8 15.6 43.8 38.7v69.3c0 1.8-1.4 3.2-3.2 3.2h-9c-1.2 0-2.2-.6-2.8-1.6l-3.4-6.8zM1128.6 218h1c1.8 0 3.2 1.4 3.2 3.2v14.4c0 1.3-.8 2.5-2 3-8.9 3.2-18.4 4.8-27.9 4.6-38.5 0-50.3-23-50.3-56.9s11.8-56.9 50.3-56.9c9.4-.2 18.9 1.2 27.8 4.4 1.2.5 2 1.7 2 3v14.5c0 1.8-1.4 3.2-3.2 3.2-.4.1-.7.1-1.1 0-7.8-2.5-16-3.7-24.2-3.6-21.1 0-27.2 14.4-27.2 35.4s6.1 35.4 27.2 35.4c8.2.1 16.4-1.1 24.2-3.6"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -1,51 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.8.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_2_00000109717856507224707600000004572449649498936508_"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1256.6 344.8"
|
||||
style="enable-background:new 0 0 1256.6 344.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_1-2">
|
||||
<g>
|
||||
<path class="st0" d="M5,172.2C5.1,79.7,80.2,4.9,172.6,5s167.3,75.2,167.2,167.6c-0.1,92.4-75,167.2-167.4,167.2
|
||||
C79.9,339.7,4.9,264.7,5,172.2L5,172.2L5,172.2z M169,229.5c-13,0.4-26-0.8-38.7-3.5c-9.4-1.9-17.3-8.2-21.2-17
|
||||
c-3.8-8.7-5.6-22.2-5.5-40.4c0.1-9.5,0.7-19,1.8-28.4c1.1-9.2,2.2-16.8,3.2-22.8l1.1-5.9c0-0.2,0-0.3,0-0.5c0-1.7-1.2-3.1-2.8-3.4
|
||||
l-21.6-3.4H85c-1.6,0-2.9,1.1-3.3,2.6c-0.4,1.4-0.6,2.4-1.2,5.7c-1.2,6.4-2.4,12.6-3.6,21.6c-1.3,9.8-2.1,19.7-2.4,29.6
|
||||
c-0.5,6.9-0.5,13.8,0,20.7c0.5,18.3,3.7,33,9.4,43.9s15.6,18.8,29.4,23.6c13.8,4.8,33,7.2,57.7,7.1h3.1
|
||||
c24.7,0.1,43.9-2.2,57.7-7.1c13.8-4.8,23.6-12.7,29.4-23.6c5.8-11,8.9-25.6,9.4-43.9c0.5-6.9,0.5-13.8,0-20.7
|
||||
c-0.3-9.9-1.1-19.8-2.4-29.6c-1.3-8.9-2.4-15.1-3.6-21.6c-0.6-3.3-0.9-4.3-1.2-5.7c-0.4-1.5-1.7-2.6-3.3-2.6h-0.6l-21.6,3.4
|
||||
c-1.6,0.3-2.8,1.7-2.8,3.4c0,0.2,0,0.3,0,0.5l1.2,5.9c1.1,6,2.2,13.6,3.3,22.7c1.1,9.4,1.7,18.9,1.8,28.4
|
||||
c0.2,18.2-1.7,31.6-5.5,40.4c-3.8,8.7-11.6,15-20.9,17c-12.7,2.7-25.7,3.9-38.7,3.5L169,229.5L169,229.5z"/>
|
||||
<g>
|
||||
<path class="st0" d="M1155.2,186.1c0-33.4,9.6-56.9,48.2-56.9s48.2,23.5,48.2,56.9s-9.6,56.9-48.2,56.9
|
||||
S1155.2,219.5,1155.2,186.1z M1227.1,186.1c0-23.2-3-36.5-23.6-36.5s-23.6,13.3-23.6,36.5s3.1,36.5,23.6,36.5
|
||||
S1227.1,209.2,1227.1,186.1z"/>
|
||||
<path class="st0" d="M487.2,238.7c0.6,1,1.6,1.6,2.8,1.6h9c1.8,0,3.2-1.4,3.2-3.2l0,0V135c0-1.8-1.4-3.2-3.2-3.2h-18
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v81c-7.7,4.3-16.4,6.5-25.2,6.3c-11.5,0-17.2-5-17.2-16.1V135c0-1.8-1.4-3.2-3.2-3.2h-17.9
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v73.4c0,20.8,9.8,34.5,37.3,34.5c12.6-0.1,24.9-4.1,35.2-11.3l3.2,7.2L487.2,238.7L487.2,238.7z"/>
|
||||
<path class="st0" d="M689.8,163.7c0-20.6-10.2-34.5-35.9-34.5c-12.4,0-24.5,3.8-34.8,10.7c-4.6-6.7-13.3-10.7-27.8-10.7
|
||||
c-11.8,0.2-23.3,4.2-32.8,11.3l-3.2-7.2l0,0c-0.6-1-1.6-1.7-2.8-1.7h-9c-1.8,0-3.2,1.4-3.2,3.2v102.1c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-80.8c6.9-4.1,14.8-6.3,22.8-6.3c9.8,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-81.1c6.8-4.2,14.8-6.4,22.8-6.4c9.6,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2l0,0L689.8,163.7L689.8,163.7z"/>
|
||||
<path class="st0" d="M745.4,231.7c10.2,7.5,22.5,11.5,35.2,11.3c31.7,0,43.4-21.3,43.4-56.9s-11.7-56.9-43.4-56.9
|
||||
c-10.3,0.1-20.4,3-29.1,8.5v-32.9c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8,0-3.2,1.4-3.2,3.2v132.3c0,1.8,1.4,3.2,3.2,3.2h9
|
||||
c1.2,0,2.2-0.6,2.8-1.6l0,0L745.4,231.7L745.4,231.7z M774.9,222.3c-8.2,0-16.3-2.1-23.4-6.3v-59.9c7.1-4.1,15.2-6.3,23.4-6.3
|
||||
c21.3,0,24.5,16.3,24.5,36.2S796.2,222.3,774.9,222.3L774.9,222.3L774.9,222.3z"/>
|
||||
<path class="st0" d="M913.9,150.5c-2.8-0.4-5.6-0.6-8.5-0.5c-9.7-0.4-19.3,1.9-27.8,6.5v80.6c0,1.8-1.4,3.2-3.2,3.2h-18
|
||||
c-1.8,0-3.2-1.4-3.2-3.2V135c0-1.8,1.4-3.2,3.2-3.2h9c1.2,0,2.2,0.6,2.8,1.6l0,0l3.2,7.2c9.9-7.5,22-11.5,34.4-11.3
|
||||
c2.8,0,5.7,0.2,8.5,0.7l0,0c1.7,0,3,2.7,3,4.4v13c0,1.8-1.4,3.2-3.2,3.2L913.9,150.5"/>
|
||||
<path class="st0" d="M976.1,190.2c-10.7,1.3-17.2,5.4-17.2,16.7c0,8.3,3.6,16.1,16.7,16.1c8.3,0.1,16.4-2.4,23.2-7.2v-28.1
|
||||
L976.1,190.2L976.1,190.2z M1004.8,231.7C995.5,239,984,243,972.1,243c-28,0-37.3-17.4-37.3-34.8c0-23.5,15.2-33.4,39.7-35.4
|
||||
l24.4-1.9v-5.4c0-11.1-5.2-15.4-21.3-15.4c-10.1,0-20.1,1.7-29.6,4.9c-0.3,0.1-0.7,0.1-1,0c-1.8,0-3.2-1.4-3.2-3.2v-14.4
|
||||
c0-1.4,0.8-2.6,2.1-3.1l0,0c10.8-3.6,22.1-5.4,33.5-5.4c35.6,0,43.8,15.6,43.8,38.7v69.3c0,1.8-1.4,3.2-3.2,3.2h-9
|
||||
c-1.2,0-2.2-0.6-2.8-1.6l0,0L1004.8,231.7L1004.8,231.7z"/>
|
||||
<path class="st0" d="M1128.6,218c0.3,0,0.7,0,1,0c1.8,0,3.2,1.4,3.2,3.2v14.4c0,1.3-0.8,2.5-2,3l0,0c-8.9,3.2-18.4,4.8-27.9,4.6
|
||||
c-38.5,0-50.3-23-50.3-56.9s11.8-56.9,50.3-56.9c9.4-0.2,18.9,1.2,27.8,4.4l0,0c1.2,0.5,2,1.7,2,3v14.5c0,1.8-1.4,3.2-3.2,3.2
|
||||
c-0.4,0.1-0.7,0.1-1.1,0l0,0c-7.8-2.5-16-3.7-24.2-3.6c-21.1,0-27.2,14.4-27.2,35.4s6.1,35.4,27.2,35.4
|
||||
c8.2,0.1,16.4-1.1,24.2-3.6"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg version="1.1" id="prefix__Layer_2_00000109717856507224707600000004572449649498936508_" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 1256.6 344.8" xml:space="preserve"><style>.prefix__st0{fill:#fff}</style><g id="prefix__Layer_1-2"><path class="prefix__st0" d="M5 172.2C5.1 79.7 80.2 4.9 172.6 5s167.3 75.2 167.2 167.6c-.1 92.4-75 167.2-167.4 167.2C79.9 339.7 4.9 264.7 5 172.2zm164 57.3c-13 .4-26-.8-38.7-3.5-9.4-1.9-17.3-8.2-21.2-17-3.8-8.7-5.6-22.2-5.5-40.4.1-9.5.7-19 1.8-28.4 1.1-9.2 2.2-16.8 3.2-22.8l1.1-5.9v-.5c0-1.7-1.2-3.1-2.8-3.4l-21.6-3.4H85c-1.6 0-2.9 1.1-3.3 2.6-.4 1.4-.6 2.4-1.2 5.7-1.2 6.4-2.4 12.6-3.6 21.6-1.3 9.8-2.1 19.7-2.4 29.6-.5 6.9-.5 13.8 0 20.7.5 18.3 3.7 33 9.4 43.9s15.6 18.8 29.4 23.6c13.8 4.8 33 7.2 57.7 7.1h3.1c24.7.1 43.9-2.2 57.7-7.1 13.8-4.8 23.6-12.7 29.4-23.6 5.8-11 8.9-25.6 9.4-43.9.5-6.9.5-13.8 0-20.7-.3-9.9-1.1-19.8-2.4-29.6-1.3-8.9-2.4-15.1-3.6-21.6-.6-3.3-.9-4.3-1.2-5.7-.4-1.5-1.7-2.6-3.3-2.6h-.6l-21.6 3.4c-1.6.3-2.8 1.7-2.8 3.4v.5l1.2 5.9c1.1 6 2.2 13.6 3.3 22.7 1.1 9.4 1.7 18.9 1.8 28.4.2 18.2-1.7 31.6-5.5 40.4-3.8 8.7-11.6 15-20.9 17-12.7 2.7-25.7 3.9-38.7 3.5l-7.3.1zM1155.2 186.1c0-33.4 9.6-56.9 48.2-56.9s48.2 23.5 48.2 56.9-9.6 56.9-48.2 56.9-48.2-23.5-48.2-56.9zm71.9 0c0-23.2-3-36.5-23.6-36.5s-23.6 13.3-23.6 36.5 3.1 36.5 23.6 36.5 23.6-13.4 23.6-36.5zM487.2 238.7c.6 1 1.6 1.6 2.8 1.6h9c1.8 0 3.2-1.4 3.2-3.2V135c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8 0-3.2 1.4-3.2 3.2v81c-7.7 4.3-16.4 6.5-25.2 6.3-11.5 0-17.2-5-17.2-16.1V135c0-1.8-1.4-3.2-3.2-3.2h-17.9c-1.8 0-3.2 1.4-3.2 3.2v73.4c0 20.8 9.8 34.5 37.3 34.5 12.6-.1 24.9-4.1 35.2-11.3l3.2 7.2.4-.1zM689.8 163.7c0-20.6-10.2-34.5-35.9-34.5-12.4 0-24.5 3.8-34.8 10.7-4.6-6.7-13.3-10.7-27.8-10.7-11.8.2-23.3 4.2-32.8 11.3l-3.2-7.2c-.6-1-1.6-1.7-2.8-1.7h-9c-1.8 0-3.2 1.4-3.2 3.2v102.1c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2v-80.8c6.9-4.1 14.8-6.3 22.8-6.3 9.8 0 15.4 3.6 15.4 14v73.4c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2v-81.1c6.8-4.2 14.8-6.4 22.8-6.4 9.6 0 15.4 3.6 15.4 14v73.4c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2l-.1-73.4zM745.4 231.7c10.2 7.5 22.5 11.5 35.2 11.3 31.7 0 43.4-21.3 43.4-56.9s-11.7-56.9-43.4-56.9c-10.3.1-20.4 3-29.1 8.5v-32.9c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8 0-3.2 1.4-3.2 3.2v132.3c0 1.8 1.4 3.2 3.2 3.2h9c1.2 0 2.2-.6 2.8-1.6l3.3-7zm29.5-9.4c-8.2 0-16.3-2.1-23.4-6.3v-59.9c7.1-4.1 15.2-6.3 23.4-6.3 21.3 0 24.5 16.3 24.5 36.2s-3.2 36.3-24.5 36.3zM913.9 150.5c-2.8-.4-5.6-.6-8.5-.5-9.7-.4-19.3 1.9-27.8 6.5v80.6c0 1.8-1.4 3.2-3.2 3.2h-18c-1.8 0-3.2-1.4-3.2-3.2V135c0-1.8 1.4-3.2 3.2-3.2h9c1.2 0 2.2.6 2.8 1.6l3.2 7.2c9.9-7.5 22-11.5 34.4-11.3 2.8 0 5.7.2 8.5.7 1.7 0 3 2.7 3 4.4v13c0 1.8-1.4 3.2-3.2 3.2l-.2-.1M976.1 190.2c-10.7 1.3-17.2 5.4-17.2 16.7 0 8.3 3.6 16.1 16.7 16.1 8.3.1 16.4-2.4 23.2-7.2v-28.1l-22.7 2.5zm28.7 41.5C995.5 239 984 243 972.1 243c-28 0-37.3-17.4-37.3-34.8 0-23.5 15.2-33.4 39.7-35.4l24.4-1.9v-5.4c0-11.1-5.2-15.4-21.3-15.4-10.1 0-20.1 1.7-29.6 4.9-.3.1-.7.1-1 0-1.8 0-3.2-1.4-3.2-3.2v-14.4c0-1.4.8-2.6 2.1-3.1 10.8-3.6 22.1-5.4 33.5-5.4 35.6 0 43.8 15.6 43.8 38.7v69.3c0 1.8-1.4 3.2-3.2 3.2h-9c-1.2 0-2.2-.6-2.8-1.6l-3.4-6.8zM1128.6 218h1c1.8 0 3.2 1.4 3.2 3.2v14.4c0 1.3-.8 2.5-2 3-8.9 3.2-18.4 4.8-27.9 4.6-38.5 0-50.3-23-50.3-56.9s11.8-56.9 50.3-56.9c9.4-.2 18.9 1.2 27.8 4.4 1.2.5 2 1.7 2 3v14.5c0 1.8-1.4 3.2-3.2 3.2-.4.1-.7.1-1.1 0-7.8-2.5-16-3.7-24.2-3.6-21.1 0-27.2 14.4-27.2 35.4s6.1 35.4 27.2 35.4c8.2.1 16.4-1.1 24.2-3.6"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -37,6 +37,7 @@ public interface IAction : IDiscoverable
|
||||
/// <summary>
|
||||
/// Gets the icon to display for this action
|
||||
/// </summary>
|
||||
[Obsolete("No longer used. Scheduled for removal in V17.")]
|
||||
string Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -51,5 +52,6 @@ public interface IAction : IDiscoverable
|
||||
/// <remarks>
|
||||
/// Used in the UI when assigning permissions
|
||||
/// </remarks>
|
||||
[Obsolete("No longer used. Scheduled for removal in V17.")]
|
||||
string? Category { get; }
|
||||
}
|
||||
|
||||
@@ -22,4 +22,6 @@ public static class CacheKeys
|
||||
|
||||
public const string PreviewPropertyCacheKeyPrefix = "Cache.Property.CacheValues[D:";
|
||||
public const string PropertyCacheKeyPrefix = "Cache.Property.CacheValues[P:";
|
||||
|
||||
public const string MemberUserNameCachePrefix = "uRepo_userNameKey+";
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
@@ -21,9 +23,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
private readonly IDocumentNavigationManagementService _documentNavigationManagementService;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IDocumentCacheService _documentCacheService;
|
||||
private readonly ICacheManager _cacheManager;
|
||||
private readonly IPublishStatusManagementService _publishStatusManagementService;
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
|
||||
[Obsolete("Use the constructor with ICacheManager instead, scheduled for removal in V17.")]
|
||||
public ContentCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
@@ -38,6 +42,39 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
IContentService contentService,
|
||||
IPublishStatusManagementService publishStatusManagementService,
|
||||
IDocumentCacheService documentCacheService)
|
||||
: this(
|
||||
appCaches,
|
||||
serializer,
|
||||
idKeyMap,
|
||||
domainService,
|
||||
eventAggregator,
|
||||
factory,
|
||||
documentUrlService,
|
||||
domainCacheService,
|
||||
documentNavigationQueryService,
|
||||
documentNavigationManagementService,
|
||||
contentService,
|
||||
publishStatusManagementService,
|
||||
documentCacheService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ICacheManager>())
|
||||
{
|
||||
}
|
||||
|
||||
public ContentCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
IIdKeyMap idKeyMap,
|
||||
IDomainService domainService,
|
||||
IEventAggregator eventAggregator,
|
||||
ICacheRefresherNotificationFactory factory,
|
||||
IDocumentUrlService documentUrlService,
|
||||
IDomainCacheService domainCacheService,
|
||||
IDocumentNavigationQueryService documentNavigationQueryService,
|
||||
IDocumentNavigationManagementService documentNavigationManagementService,
|
||||
IContentService contentService,
|
||||
IPublishStatusManagementService publishStatusManagementService,
|
||||
IDocumentCacheService documentCacheService,
|
||||
ICacheManager cacheManager)
|
||||
: base(appCaches, serializer, eventAggregator, factory)
|
||||
{
|
||||
_idKeyMap = idKeyMap;
|
||||
@@ -49,6 +86,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
_contentService = contentService;
|
||||
_documentCacheService = documentCacheService;
|
||||
_publishStatusManagementService = publishStatusManagementService;
|
||||
|
||||
// TODO: Ideally we should inject IElementsCache
|
||||
// this interface is in infrastructure, and changing this is very breaking
|
||||
// so as long as we have the cache manager, which casts the IElementsCache to a simple AppCache we might as well use that.
|
||||
_cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
#region Indirect
|
||||
@@ -83,6 +125,13 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
AppCaches.RuntimeCache.ClearOfType<PublicAccessEntry>();
|
||||
AppCaches.RuntimeCache.ClearByKey(CacheKeys.ContentRecycleBinCacheKey);
|
||||
|
||||
// Ideally, we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.
|
||||
// The reason for this is that we have no way to know which elements are affected by the changes or what their keys are.
|
||||
// This is because currently published elements live exclusively in a JSON blob in the umbracoPropertyData table.
|
||||
// This means that the only way to resolve these keys is to actually parse this data with a specific value converter, and for all cultures, which is not possible.
|
||||
// If published elements become their own entities with relations, instead of just property data, we can revisit this.
|
||||
_cacheManager.ElementsCache.Clear();
|
||||
|
||||
var idsRemoved = new HashSet<int>();
|
||||
IAppPolicyCache isolatedCache = AppCaches.IsolatedCaches.GetOrCreate<IContent>();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
@@ -18,7 +20,9 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
|
||||
private readonly IMediaNavigationManagementService _mediaNavigationManagementService;
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IMediaCacheService _mediaCacheService;
|
||||
private readonly ICacheManager _cacheManager;
|
||||
|
||||
[Obsolete("Use the constructor with ICacheManager instead, scheduled for removal in V17.")]
|
||||
public MediaCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
@@ -29,6 +33,31 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
|
||||
IMediaNavigationManagementService mediaNavigationManagementService,
|
||||
IMediaService mediaService,
|
||||
IMediaCacheService mediaCacheService)
|
||||
: this(
|
||||
appCaches,
|
||||
serializer,
|
||||
idKeyMap,
|
||||
eventAggregator,
|
||||
factory,
|
||||
mediaNavigationQueryService,
|
||||
mediaNavigationManagementService,
|
||||
mediaService,
|
||||
mediaCacheService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ICacheManager>())
|
||||
{
|
||||
}
|
||||
|
||||
public MediaCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
IIdKeyMap idKeyMap,
|
||||
IEventAggregator eventAggregator,
|
||||
ICacheRefresherNotificationFactory factory,
|
||||
IMediaNavigationQueryService mediaNavigationQueryService,
|
||||
IMediaNavigationManagementService mediaNavigationManagementService,
|
||||
IMediaService mediaService,
|
||||
IMediaCacheService mediaCacheService,
|
||||
ICacheManager cacheManager)
|
||||
: base(appCaches, serializer, eventAggregator, factory)
|
||||
{
|
||||
_idKeyMap = idKeyMap;
|
||||
@@ -36,6 +65,9 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
|
||||
_mediaNavigationManagementService = mediaNavigationManagementService;
|
||||
_mediaService = mediaService;
|
||||
_mediaCacheService = mediaCacheService;
|
||||
|
||||
// TODO: Use IElementsCache instead of ICacheManager, see ContentCacheRefresher for more information.
|
||||
_cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
#region Indirect
|
||||
@@ -87,6 +119,13 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
|
||||
AppCaches.RuntimeCache.ClearByKey(CacheKeys.MediaRecycleBinCacheKey);
|
||||
Attempt<IAppPolicyCache?> mediaCache = AppCaches.IsolatedCaches.Get<IMedia>();
|
||||
|
||||
// Ideally, we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.
|
||||
// The reason for this is that we have no way to know which elements are affected by the changes or what their keys are.
|
||||
// This is because currently published elements live exclusively in a JSON blob in the umbracoPropertyData table.
|
||||
// This means that the only way to resolve these keys is to actually parse this data with a specific value converter, and for all cultures, which is not possible.
|
||||
// If published elements become their own entities with relations, instead of just property data, we can revisit this.
|
||||
_cacheManager.ElementsCache.Clear();
|
||||
|
||||
foreach (JsonPayload payload in payloads)
|
||||
{
|
||||
if (payload.ChangeTypes == TreeChangeTypes.Remove)
|
||||
|
||||
@@ -71,11 +71,22 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase<MemberCache
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,9 +160,9 @@ public class ObservableDictionary<TKey, TValue> : ObservableCollection<TValue>,
|
||||
|
||||
if (index != Count)
|
||||
{
|
||||
foreach (TKey k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList())
|
||||
foreach (KeyValuePair<TKey, int> largerOrEqualToIndex in Indecies.Where(kvp => kvp.Value >= index))
|
||||
{
|
||||
Indecies[k]++;
|
||||
Indecies[largerOrEqualToIndex.Key] = largerOrEqualToIndex.Value + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,9 +185,9 @@ public class ObservableDictionary<TKey, TValue> : ObservableCollection<TValue>,
|
||||
|
||||
Indecies.Remove(key);
|
||||
|
||||
foreach (TKey k in Indecies.Keys.Where(k => Indecies[k] > index).ToList())
|
||||
foreach (KeyValuePair<TKey, int> largerThanIndex in Indecies.Where(kvp => kvp.Value > index))
|
||||
{
|
||||
Indecies[k]--;
|
||||
Indecies[largerThanIndex.Key] = largerThanIndex.Value - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -233,10 +233,7 @@ public class TypeFinder : ITypeFinder
|
||||
excludeFromResults = new HashSet<Assembly>();
|
||||
}
|
||||
|
||||
if (exclusionFilter == null)
|
||||
{
|
||||
exclusionFilter = new string[] { };
|
||||
}
|
||||
exclusionFilter ??= [];
|
||||
|
||||
return GetAllAssemblies()
|
||||
.Where(x => excludeFromResults.Contains(x) == false
|
||||
|
||||
@@ -5,14 +5,17 @@ namespace Umbraco.Extensions;
|
||||
public static class ContentSettingsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if file extension is allowed for upload based on (optional) white list and black list
|
||||
/// held in settings.
|
||||
/// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted.
|
||||
/// Determines if file extension is allowed for upload based on (optional) allow list and deny list held in settings.
|
||||
/// Disallowed file extensions are only considered if there are no allowed file extensions.
|
||||
/// </summary>
|
||||
public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension) =>
|
||||
contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) ||
|
||||
(contentSettings.AllowedUploadedFileExtensions.Any() == false &&
|
||||
contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) == false);
|
||||
/// <param name="contentSettings">The content settings.</param>
|
||||
/// <param name="extension">The file extension.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the file extension is allowed for upload; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension)
|
||||
=> contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) ||
|
||||
(contentSettings.AllowedUploadedFileExtensions.Any() == false && contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) == false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the auto-fill configuration for a specified property alias.
|
||||
|
||||
@@ -23,6 +23,7 @@ public class ContentSettings
|
||||
internal const string StaticLoginLogoImage = "assets/logo_light.svg";
|
||||
internal const string StaticLoginLogoImageAlternative = "assets/logo_dark.svg";
|
||||
internal const string StaticBackOfficeLogo = "assets/logo.svg";
|
||||
internal const string StaticBackOfficeLogoAlternative = "assets/logo_blue.svg";
|
||||
internal const bool StaticHideBackOfficeLogo = false;
|
||||
internal const bool StaticDisableDeleteWhenReferenced = false;
|
||||
internal const bool StaticDisableUnpublishWhenReferenced = false;
|
||||
@@ -88,9 +89,18 @@ public class ContentSettings
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the path to the backoffice logo.
|
||||
/// </summary>
|
||||
/// <remarks>The alternative version of this logo can be found at <see cref="BackOfficeLogoAlternative"/>.</remarks>
|
||||
[DefaultValue(StaticBackOfficeLogo)]
|
||||
public string BackOfficeLogo { get; set; } = StaticBackOfficeLogo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the path to the alternative backoffice logo, which can be shown
|
||||
/// on top of a light background.
|
||||
/// </summary>
|
||||
/// <remarks>This is the alternative version to the regular logo found at <see cref="BackOfficeLogo"/>.</remarks>
|
||||
[DefaultValue(StaticBackOfficeLogoAlternative)]
|
||||
public string BackOfficeLogoAlternative { get; set; } = StaticBackOfficeLogoAlternative;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to hide the backoffice umbraco logo or not.
|
||||
/// </summary>
|
||||
|
||||
@@ -233,17 +233,27 @@ public static partial class Constants
|
||||
public const string RelatedMediaName = "Related Media";
|
||||
|
||||
/// <summary>
|
||||
/// Alias for default relation type "Related Media"
|
||||
/// Alias for default relation type "Related Media".
|
||||
/// </summary>
|
||||
public const string RelatedMediaAlias = "umbMedia";
|
||||
|
||||
/// <summary>
|
||||
/// Name for default relation type "Related Member".
|
||||
/// </summary>
|
||||
public const string RelatedMemberName = "Related Member";
|
||||
|
||||
/// <summary>
|
||||
/// Alias for default relation type "Related Member".
|
||||
/// </summary>
|
||||
public const string RelatedMemberAlias = "umbMember";
|
||||
|
||||
/// <summary>
|
||||
/// Name for default relation type "Related Document".
|
||||
/// </summary>
|
||||
public const string RelatedDocumentName = "Related Document";
|
||||
|
||||
/// <summary>
|
||||
/// Alias for default relation type "Related Document"
|
||||
/// Alias for default relation type "Related Document".
|
||||
/// </summary>
|
||||
public const string RelatedDocumentAlias = "umbDocument";
|
||||
|
||||
@@ -284,7 +294,7 @@ public static partial class Constants
|
||||
/// Developers should not manually use these relation types since they will all be cleared whenever an entity
|
||||
/// (content, media or member) is saved since they are auto-populated based on property values.
|
||||
/// </remarks>
|
||||
public static string[] AutomaticRelationTypes { get; } = { RelatedMediaAlias, RelatedDocumentAlias };
|
||||
public static string[] AutomaticRelationTypes { get; } = { RelatedMediaAlias, RelatedMemberAlias, RelatedDocumentAlias };
|
||||
|
||||
// TODO: return a list of built in types so we can use that to prevent deletion in the UI
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Umbraco.Cms.Core;
|
||||
|
||||
public static partial class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines reference types.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reference types are used to identify the type of entity that is being referenced when exposing references
|
||||
/// between Umbraco entities.
|
||||
/// These are used in the management API and backoffice to indicate and warn editors when working with an entity,
|
||||
/// as to what other entities depend on it.
|
||||
/// These consist of references managed by Umbraco relations (e.g. document, media and member).
|
||||
/// But also references that come from schema (e.g. data type usage on content types).
|
||||
/// </remarks>
|
||||
public static class ReferenceType
|
||||
{
|
||||
public const string Document = UdiEntityType.Document;
|
||||
public const string Media = UdiEntityType.Media;
|
||||
public const string Member = UdiEntityType.Member;
|
||||
public const string DocumentTypePropertyType = "document-type-property-type";
|
||||
public const string MediaTypePropertyType = "media-type-property-type";
|
||||
public const string MemberTypePropertyType = "member-type-property-type";
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -19,6 +20,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
private readonly IPublishedContentCache _contentCache;
|
||||
private readonly IDocumentNavigationQueryService _navigationQueryService;
|
||||
private readonly IPublishStatusQueryService _publishStatusQueryService;
|
||||
private readonly IDocumentUrlService _documentUrlService;
|
||||
private RequestHandlerSettings _requestSettings;
|
||||
|
||||
public ApiContentRouteBuilder(
|
||||
@@ -29,7 +31,8 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings,
|
||||
IPublishedContentCache contentCache,
|
||||
IDocumentNavigationQueryService navigationQueryService,
|
||||
IPublishStatusQueryService publishStatusQueryService)
|
||||
IPublishStatusQueryService publishStatusQueryService,
|
||||
IDocumentUrlService documentUrlService)
|
||||
{
|
||||
_apiContentPathProvider = apiContentPathProvider;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
@@ -37,11 +40,35 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
_contentCache = contentCache;
|
||||
_navigationQueryService = navigationQueryService;
|
||||
_publishStatusQueryService = publishStatusQueryService;
|
||||
_documentUrlService = documentUrlService;
|
||||
_globalSettings = globalSettings.Value;
|
||||
_requestSettings = requestSettings.CurrentValue;
|
||||
requestSettings.OnChange(settings => _requestSettings = settings);
|
||||
}
|
||||
|
||||
[Obsolete("Use the non-obsolete constructor, scheduled for removal in v17")]
|
||||
public ApiContentRouteBuilder(
|
||||
IApiContentPathProvider apiContentPathProvider,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings,
|
||||
IPublishedContentCache contentCache,
|
||||
IDocumentNavigationQueryService navigationQueryService,
|
||||
IPublishStatusQueryService publishStatusQueryService)
|
||||
: this(
|
||||
apiContentPathProvider,
|
||||
globalSettings,
|
||||
variationContextAccessor,
|
||||
requestPreviewService,
|
||||
requestSettings,
|
||||
contentCache,
|
||||
navigationQueryService,
|
||||
publishStatusQueryService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IDocumentUrlService>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Use the non-obsolete constructor, scheduled for removal in v17")]
|
||||
public ApiContentRouteBuilder(
|
||||
IApiContentPathProvider apiContentPathProvider,
|
||||
@@ -59,7 +86,8 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
requestSettings,
|
||||
contentCache,
|
||||
navigationQueryService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
|
||||
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IDocumentUrlService>())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -113,7 +141,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
// we can perform fallback to the content route.
|
||||
if (IsInvalidContentPath(contentPath))
|
||||
{
|
||||
contentPath = _contentCache.GetRouteById(content.Id, culture) ?? contentPath;
|
||||
contentPath = _documentUrlService.GetLegacyRouteFormat(content.Key, culture ?? _variationContextAccessor.VariationContext?.Culture, isPreview);
|
||||
}
|
||||
|
||||
// if the content path has still not been resolved as a valid path, the content is un-routable in this culture
|
||||
@@ -125,7 +153,9 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
: null;
|
||||
}
|
||||
|
||||
return contentPath;
|
||||
return _requestSettings.AddTrailingSlash
|
||||
? contentPath?.EnsureEndsWith('/')
|
||||
: contentPath?.TrimEnd('/');
|
||||
}
|
||||
|
||||
private string ContentPreviewPath(IPublishedContent content) => $"{Constants.DeliveryApi.Routing.PreviewContentPathPrefix}{content.Key:D}{(_requestSettings.AddTrailingSlash ? "/" : string.Empty)}";
|
||||
|
||||
@@ -86,10 +86,35 @@ public sealed class ApiPublishedContentCache : IApiPublishedContentCache
|
||||
_variationContextAccessor.VariationContext?.Culture,
|
||||
_requestPreviewService.IsPreview());
|
||||
|
||||
// in multi-root settings, we've historically resolved all but the first root by their ID + URL segment,
|
||||
// e.g. "1234/second-root-url-segment". in V15+, IDocumentUrlService won't resolve this anymore; it will
|
||||
// however resolve "1234/" correctly, so to remain backwards compatible, we need to perform this extra step.
|
||||
var verifyUrlSegment = false;
|
||||
if (documentKey is null && route.TrimEnd('/').CountOccurrences("/") is 1)
|
||||
{
|
||||
documentKey = _apiDocumentUrlService.GetDocumentKeyByRoute(
|
||||
route[..(route.IndexOf('/') + 1)],
|
||||
_variationContextAccessor.VariationContext?.Culture,
|
||||
_requestPreviewService.IsPreview());
|
||||
verifyUrlSegment = true;
|
||||
}
|
||||
|
||||
IPublishedContent? content = documentKey.HasValue
|
||||
? _publishedContentCache.GetById(isPreviewMode, documentKey.Value)
|
||||
: null;
|
||||
|
||||
// the additional look-up above can result in false positives; if attempting to request a non-existing child to
|
||||
// the currently contextualized request root (either by start item or by domain), the root content key might
|
||||
// get resolved. to counter for this, we compare the requested URL segment with the resolved content URL segment.
|
||||
if (content is not null && verifyUrlSegment)
|
||||
{
|
||||
var expectedUrlSegment = route[(route.IndexOf('/') + 1)..];
|
||||
if (content.UrlSegment != expectedUrlSegment)
|
||||
{
|
||||
content = null;
|
||||
}
|
||||
}
|
||||
|
||||
return ContentOrNullIfDisallowed(content);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user