Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
011634e9ba | ||
|
|
1afac0c247 | ||
|
|
2031298001 | ||
|
|
04b44af3f9 | ||
|
|
74b1f42909 | ||
|
|
064c85f47a | ||
|
|
5ccbf08365 | ||
|
|
85b3df6672 | ||
|
|
d8b5006b30 | ||
|
|
1504b5c0d3 | ||
|
|
d482d9161c | ||
|
|
57d6bd75e3 | ||
|
|
e5168dee4a | ||
|
|
254e81823f |
@@ -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-first-issue.md)
|
||||
- [Finding your first issue: Up for grabs](contributing-before-you-start.md)
|
||||
- [Contributing to the new backoffice](https://docs.umbraco.com/umbraco-backoffice/)
|
||||
- [Unwanted changes](contributing-unwanted-changes.md)
|
||||
- [Other ways to contribute](contributing-other-ways-to-contribute.md)
|
||||
|
||||
@@ -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://forum.umbraco.com
|
||||
url: https://our.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/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/
|
||||
url: https://umbraco.com/about-us/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/
|
||||
about: Discovered a Security Issue in Umbraco?
|
||||
|
||||
+1
-1
@@ -4,8 +4,8 @@
|
||||
[](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)
|
||||
[](https://discord-chats.umbraco.com)
|
||||

|
||||
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
<!-- Package Validation -->
|
||||
<PropertyGroup>
|
||||
<GenerateCompatibilitySuppressionFile>false</GenerateCompatibilitySuppressionFile>
|
||||
<EnablePackageValidation>false</EnablePackageValidation> <!-- TODO: Update to true once 16.0.0 is released to NuGet. -->
|
||||
<PackageValidationBaselineVersion>16.0.0</PackageValidationBaselineVersion>
|
||||
<EnablePackageValidation>false</EnablePackageValidation>
|
||||
<PackageValidationBaselineVersion>15.0.0</PackageValidationBaselineVersion>
|
||||
<EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
|
||||
<EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms>
|
||||
</PropertyGroup>
|
||||
|
||||
+47
-50
@@ -5,102 +5,99 @@
|
||||
</PropertyGroup>
|
||||
<!-- Global packages (private, build-time packages for all projects) -->
|
||||
<ItemGroup>
|
||||
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.7.115" />
|
||||
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.6.146" />
|
||||
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<GlobalPackageReference Include="Umbraco.Code" Version="2.3.0" />
|
||||
<GlobalPackageReference Include="Umbraco.Code" Version="2.2.0" />
|
||||
<GlobalPackageReference Include="Umbraco.GitVersioning.Extensions" Version="0.2.0" />
|
||||
</ItemGroup>
|
||||
<!-- Microsoft packages -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.13.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.13.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.13.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.13.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.4.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0-preview.9.24556.5" />
|
||||
</ItemGroup>
|
||||
<!-- Umbraco packages -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Umbraco.JsonSchema.Extensions" Version="0.3.0" />
|
||||
<PackageVersion Include="Umbraco.CSharpTest.Net.Collections" Version="15.0.0" />
|
||||
</ItemGroup>
|
||||
<!-- Third-party packages -->
|
||||
<ItemGroup>
|
||||
<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.7.1" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.7.1" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.12.1" />
|
||||
<PackageVersion Include="JsonPatch.Net" Version="3.3.0" />
|
||||
<PackageVersion Include="Examine" Version="3.7.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.7.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.74" />
|
||||
<PackageVersion Include="JsonPatch.Net" Version="3.1.1" />
|
||||
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
|
||||
<PackageVersion Include="MailKit" Version="4.11.0" />
|
||||
<PackageVersion Include="MailKit" Version="4.10.0" />
|
||||
<PackageVersion Include="Markdown" Version="2.2.1" />
|
||||
<PackageVersion Include="MessagePack" Version="3.1.3" />
|
||||
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.4" />
|
||||
<PackageVersion Include="MessagePack" Version="2.5.192" />
|
||||
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.3.8" />
|
||||
<PackageVersion Include="MiniProfiler.Shared" Version="4.5.4" />
|
||||
<PackageVersion Include="ncrontab" Version="3.3.3" />
|
||||
<PackageVersion Include="NPoco" Version="5.7.1" />
|
||||
<PackageVersion Include="NPoco.SqlServer" Version="5.7.1" />
|
||||
<PackageVersion Include="OpenIddict.Abstractions" Version="6.2.1" />
|
||||
<PackageVersion Include="OpenIddict.AspNetCore" Version="6.2.1" />
|
||||
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="6.2.1" />
|
||||
<PackageVersion Include="OpenIddict.Abstractions" Version="6.1.1" />
|
||||
<PackageVersion Include="OpenIddict.AspNetCore" Version="6.1.1" />
|
||||
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="6.1.1" />
|
||||
<PackageVersion Include="Serilog" Version="4.2.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Process" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Extensions.Hosting" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="4.0.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.4" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Map" Version="2.0.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.4" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.3" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.1.0" />
|
||||
</ItemGroup>
|
||||
<!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) -->
|
||||
<ItemGroup>
|
||||
<!-- Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer brings in a vulnerable version of Azure.Identity -->
|
||||
<!-- Take top-level depedendency on Azure.Identity, because Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version -->
|
||||
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.13.1" />
|
||||
<!-- Microsoft.EntityFrameworkCore.SqlServer brings in a vulnerable version of System.Runtime.Caching -->
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="9.0.0" />
|
||||
<!-- Dazinator.Extensions.FileProviders brings in a vulnerable version of System.Net.Http -->
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<!-- Examine brings in a vulnerable version of System.Security.Cryptography.Xml -->
|
||||
<PackageVersion Include="System.Security.Cryptography.Xml" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Security.Cryptography.Xml" Version="9.0.0" />
|
||||
<!-- Dazinator.Extensions.FileProviders and MiniProfiler.AspNetCore.Mvc brings in a vulnerable version of System.Text.RegularExpressions -->
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<!-- OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer brings in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<!-- Take top-level depedendency on Microsoft.IdentityModel.JsonWebTokens, because OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version -->
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.8.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" />
|
||||
<!-- Azure.Identity, Microsoft.EntityFrameworkCore.SqlServer and Dazinator.Extensions.FileProviders brings in a legacy version of System.Text.Encodings.Web -->
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.0" />
|
||||
<!-- NPoco.SqlServer brings in a vulnerable version of Microsoft.Data.SqlClient -->
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
<!-- Examine.Lucene brings in a vulnerable version of Lucene.Net.Replicator -->
|
||||
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
-571
@@ -1,571 +0,0 @@
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
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
|
||||
@@ -432,33 +432,6 @@ 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'))
|
||||
@@ -856,8 +829,6 @@ 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
|
||||
@@ -919,8 +890,6 @@ 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
|
||||
|
||||
@@ -10,7 +10,7 @@ schedules:
|
||||
include:
|
||||
- v14/dev
|
||||
- v15/dev
|
||||
- main
|
||||
- v16/dev
|
||||
|
||||
variables:
|
||||
nodeVersion: 20
|
||||
@@ -33,17 +33,25 @@ stages:
|
||||
- job: A
|
||||
displayName: Build Umbraco CMS
|
||||
pool:
|
||||
vmImage: "windows-latest"
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 500
|
||||
- template: templates/backoffice-install.yml
|
||||
fetchDepth: 0
|
||||
submodules: true
|
||||
- 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:
|
||||
@@ -55,7 +63,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:
|
||||
@@ -67,33 +75,6 @@ stages:
|
||||
targetPath: $(Build.SourcesDirectory)
|
||||
artifactName: build_output
|
||||
|
||||
- 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: E2E
|
||||
displayName: E2E Tests
|
||||
dependsOn: Build
|
||||
@@ -228,9 +209,9 @@ stages:
|
||||
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
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
@@ -291,17 +272,17 @@ stages:
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
LinuxPart2Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
LinuxPart3Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
WindowsPart1Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
@@ -401,8 +382,8 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Content;
|
||||
[DeliveryApiAccess]
|
||||
[VersionedDeliveryApiRoute("content")]
|
||||
[ApiExplorerSettings(GroupName = "Content")]
|
||||
[ContextualizeFromAcceptHeaders]
|
||||
[LocalizeFromAcceptLanguageHeader]
|
||||
[ValidateStartItem]
|
||||
[AddVaryHeader]
|
||||
[OutputCache(PolicyName = Constants.DeliveryApi.OutputCache.ContentCachePolicy)]
|
||||
|
||||
@@ -49,7 +49,6 @@ public static class UmbracoBuilderExtensions
|
||||
: provider.GetRequiredService<RequestContextOutputExpansionStrategyV2>();
|
||||
});
|
||||
builder.Services.AddSingleton<IRequestCultureService, RequestCultureService>();
|
||||
builder.Services.AddSingleton<IRequestSegmmentService, RequestSegmentService>();
|
||||
builder.Services.AddSingleton<IRequestRoutingService, RequestRoutingService>();
|
||||
builder.Services.AddSingleton<IRequestRedirectService, RequestRedirectService>();
|
||||
builder.Services.AddSingleton<IRequestPreviewService, RequestPreviewService>();
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Umbraco.Cms.Api.Delivery.Filters;
|
||||
|
||||
public sealed class AddVaryHeaderAttribute : ActionFilterAttribute
|
||||
{
|
||||
private const string Vary = "Accept-Language, Accept-Segment, Preview, Start-Item";
|
||||
private const string Vary = "Accept-Language, Preview, Start-Item";
|
||||
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
=> context.HttpContext.Response.Headers.Vary = context.HttpContext.Response.Headers.Vary.Count > 0
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Filters;
|
||||
|
||||
internal sealed class ContextualizeFromAcceptHeadersAttribute : TypeFilterAttribute
|
||||
{
|
||||
public ContextualizeFromAcceptHeadersAttribute()
|
||||
: base(typeof(LocalizeFromAcceptLanguageHeaderAttributeFilter))
|
||||
{
|
||||
}
|
||||
|
||||
private class LocalizeFromAcceptLanguageHeaderAttributeFilter : IActionFilter
|
||||
{
|
||||
private readonly IRequestCultureService _requestCultureService;
|
||||
private readonly IRequestSegmmentService _requestSegmentService;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
public LocalizeFromAcceptLanguageHeaderAttributeFilter(
|
||||
IRequestCultureService requestCultureService,
|
||||
IRequestSegmmentService requestSegmentService,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
{
|
||||
_requestCultureService = requestCultureService;
|
||||
_requestSegmentService = requestSegmentService;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
}
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
var requestedCulture = _requestCultureService.GetRequestedCulture().NullOrWhiteSpaceAsNull();
|
||||
var requestedSegment = _requestSegmentService.GetRequestedSegment().NullOrWhiteSpaceAsNull();
|
||||
if (requestedCulture.IsNullOrWhiteSpace() && requestedSegment.IsNullOrWhiteSpace())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// contextualize the request
|
||||
// NOTE: request culture or segment can be null here (but not both), so make sure to retain any existing
|
||||
// context by means of fallback to current variation context (if available)
|
||||
_variationContextAccessor.VariationContext = new VariationContext(
|
||||
requestedCulture ?? _variationContextAccessor.VariationContext?.Culture,
|
||||
requestedSegment ?? _variationContextAccessor.VariationContext?.Segment);
|
||||
}
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Filters;
|
||||
|
||||
internal sealed class LocalizeFromAcceptLanguageHeaderAttribute : TypeFilterAttribute
|
||||
{
|
||||
public LocalizeFromAcceptLanguageHeaderAttribute()
|
||||
: base(typeof(LocalizeFromAcceptLanguageHeaderAttributeFilter))
|
||||
{
|
||||
}
|
||||
|
||||
private class LocalizeFromAcceptLanguageHeaderAttributeFilter : IActionFilter
|
||||
{
|
||||
private readonly IRequestCultureService _requestCultureService;
|
||||
|
||||
public LocalizeFromAcceptLanguageHeaderAttributeFilter(IRequestCultureService requestCultureService)
|
||||
=> _requestCultureService = requestCultureService;
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
var requestedCulture = _requestCultureService.GetRequestedCulture();
|
||||
if (requestedCulture.IsNullOrWhiteSpace())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_requestCultureService.SetRequestCulture(requestedCulture);
|
||||
}
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,20 +33,6 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi
|
||||
}
|
||||
});
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = "Accept-Segment",
|
||||
In = ParameterLocation.Header,
|
||||
Required = false,
|
||||
Description = "Defines the segment to return. Use this when querying segment variant content items.",
|
||||
Schema = new OpenApiSchema { Type = "string" },
|
||||
Examples = new Dictionary<string, OpenApiExample>
|
||||
{
|
||||
{ "Default", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{ "Segment One", new OpenApiExample { Value = new OpenApiString("segment-one") } }
|
||||
}
|
||||
});
|
||||
|
||||
AddApiKey(operation);
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
internal sealed partial class RequestCultureService : RequestHeaderHandler, IRequestCultureService
|
||||
{
|
||||
public RequestCultureService(IHttpContextAccessor httpContextAccessor)
|
||||
: base(httpContextAccessor)
|
||||
{
|
||||
}
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
public RequestCultureService(IHttpContextAccessor httpContextAccessor, IVariationContextAccessor variationContextAccessor)
|
||||
: base(httpContextAccessor) =>
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetRequestedCulture()
|
||||
@@ -19,10 +21,15 @@ internal sealed partial class RequestCultureService : RequestHeaderHandler, IReq
|
||||
return ValidLanguageHeaderRegex().IsMatch(acceptLanguage) ? acceptLanguage : null;
|
||||
}
|
||||
|
||||
[Obsolete("Use IVariationContextAccessor to manipulate the variation context. Scheduled for removal in V17.")]
|
||||
/// <inheritdoc />
|
||||
public void SetRequestCulture(string culture)
|
||||
{
|
||||
// no-op
|
||||
if (_variationContextAccessor.VariationContext?.Culture == culture)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_variationContextAccessor.VariationContext = new VariationContext(culture);
|
||||
}
|
||||
|
||||
// at the time of writing we're introducing this to get rid of accept-language header values like "en-GB,en-US;q=0.9,en;q=0.8",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
@@ -10,19 +11,14 @@ namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
internal sealed class RequestRoutingService : RoutingServiceBase, IRequestRoutingService
|
||||
{
|
||||
private readonly IRequestCultureService _requestCultureService;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
public RequestRoutingService(
|
||||
IDomainCache domainCache,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IRequestStartItemProviderAccessor requestStartItemProviderAccessor,
|
||||
IRequestCultureService requestCultureService,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
: base(domainCache, httpContextAccessor, requestStartItemProviderAccessor)
|
||||
{
|
||||
IRequestCultureService requestCultureService)
|
||||
: base(domainCache, httpContextAccessor, requestStartItemProviderAccessor) =>
|
||||
_requestCultureService = requestCultureService;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetContentRoute(string requestedPath)
|
||||
@@ -54,14 +50,9 @@ internal sealed class RequestRoutingService : RoutingServiceBase, IRequestRoutin
|
||||
}
|
||||
|
||||
// the Accept-Language header takes precedence over configured domain culture
|
||||
if (domainAndUri.Culture != null
|
||||
&& _requestCultureService.GetRequestedCulture().IsNullOrWhiteSpace()
|
||||
&& _variationContextAccessor.VariationContext?.Culture != domainAndUri.Culture)
|
||||
if (domainAndUri.Culture != null && _requestCultureService.GetRequestedCulture().IsNullOrWhiteSpace())
|
||||
{
|
||||
// update the variation context to match the requested domain culture while retaining any contextualized segment
|
||||
_variationContextAccessor.VariationContext = new VariationContext(
|
||||
culture: domainAndUri.Culture,
|
||||
segment: _variationContextAccessor.VariationContext?.Segment);
|
||||
_requestCultureService.SetRequestCulture(domainAndUri.Culture);
|
||||
}
|
||||
|
||||
// when resolving content from a configured domain, the content cache expects the content route
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
internal sealed class RequestSegmentService : RequestHeaderHandler, IRequestSegmmentService
|
||||
{
|
||||
public RequestSegmentService(IHttpContextAccessor httpContextAccessor)
|
||||
: base(httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetRequestedSegment()
|
||||
=> GetHeaderValue("Accept-Segment");
|
||||
}
|
||||
@@ -27,10 +27,6 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
|
||||
.WithTitle("Content type culture variance mismatch")
|
||||
.WithDetail("The content type variance did not match that of the passed content data.")
|
||||
.Build()),
|
||||
ContentEditingOperationStatus.ContentTypeSegmentVarianceMismatch => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Content type segment variance mismatch")
|
||||
.WithDetail("The content type variance did not match that of the passed content data.")
|
||||
.Build()),
|
||||
ContentEditingOperationStatus.NotFound => NotFound(problemDetailsBuilder
|
||||
.WithTitle("The content could not be found")
|
||||
.Build()),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -42,16 +42,12 @@ public class CopyDocumentController : DocumentControllerBase
|
||||
Guid id,
|
||||
CopyDocumentRequestModel copyDocumentRequestModel)
|
||||
{
|
||||
AuthorizationResult sourceAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionCopy.ActionLetter, [id]),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
AuthorizationResult destinationAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionNew.ActionLetter, [copyDocumentRequestModel.Target?.Id]),
|
||||
ContentPermissionResource.WithKeys(ActionCopy.ActionLetter, new[] { copyDocumentRequestModel.Target?.Id, id }),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
|
||||
if (sourceAuthorizationResult.Succeeded is false || destinationAuthorizationResult.Succeeded is false)
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
+3
-4
@@ -24,23 +24,22 @@ public class SearchDocumentItemController : DocumentItemControllerBase
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> SearchWithTrashed(
|
||||
public Task<IActionResult> SearchWithTrashed(
|
||||
CancellationToken cancellationToken,
|
||||
string query,
|
||||
bool? trashed = null,
|
||||
string? culture = null,
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
Guid? parentId = null,
|
||||
[FromQuery] IEnumerable<Guid>? allowedDocumentTypes = null)
|
||||
{
|
||||
PagedModel<IEntitySlim> searchResult = await _indexedEntitySearchService.SearchAsync(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, trashed, culture, 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),
|
||||
Total = searchResult.Total,
|
||||
};
|
||||
|
||||
return Ok(result);
|
||||
return Task.FromResult<IActionResult>(Ok(result));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -39,20 +39,6 @@ public class MoveDocumentController : DocumentControllerBase
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Move(CancellationToken cancellationToken, Guid id, MoveDocumentRequestModel moveDocumentRequestModel)
|
||||
{
|
||||
AuthorizationResult sourceAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionMove.ActionLetter, [id]),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
AuthorizationResult destinationAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionNew.ActionLetter, [moveDocumentRequestModel.Target?.Id]),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
|
||||
if (sourceAuthorizationResult.Succeeded is false || destinationAuthorizationResult.Succeeded is false)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionMove.ActionLetter, new[] { moveDocumentRequestModel.Target?.Id, id }),
|
||||
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -21,26 +21,23 @@ public class SearchMediaItemController : MediaItemControllerBase
|
||||
_mediaPresentationFactory = mediaPresentationFactory;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
public Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
|
||||
=> 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,
|
||||
bool? trashed = null,
|
||||
string? culture = null,
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
Guid? parentId = null,
|
||||
[FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
|
||||
public 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 = await _indexedEntitySearchService.SearchAsync(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, trashed, culture, 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),
|
||||
Total = searchResult.Total,
|
||||
};
|
||||
|
||||
return Ok(result);
|
||||
return Task.FromResult<IActionResult>(Ok(result));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using Umbraco.Cms.Web.Common.Authorization;
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Segment;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)]
|
||||
public class AllSegmentController : SegmentControllerBase
|
||||
{
|
||||
private readonly ISegmentService _segmentService;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -6,13 +5,11 @@ 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)
|
||||
|
||||
@@ -35,17 +35,11 @@ internal static class ApplicationBuilderExtensions
|
||||
return;
|
||||
}
|
||||
|
||||
var statusCode = (exception as BadHttpRequestException)?.StatusCode;
|
||||
if (statusCode.HasValue)
|
||||
{
|
||||
context.Response.StatusCode = statusCode.Value;
|
||||
}
|
||||
|
||||
var response = new ProblemDetails
|
||||
{
|
||||
Title = exception.Message,
|
||||
Detail = isDebug ? exception.StackTrace : null,
|
||||
Status = statusCode ?? StatusCodes.Status500InternalServerError,
|
||||
Status = StatusCodes.Status500InternalServerError,
|
||||
Instance = isDebug ? exception.GetType().Name : null,
|
||||
Type = "Error"
|
||||
};
|
||||
|
||||
-5
@@ -3,7 +3,6 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Api.Management.NotificationHandlers;
|
||||
using Umbraco.Cms.Api.Management.Security;
|
||||
using Umbraco.Cms.Api.Management.Services;
|
||||
using Umbraco.Cms.Api.Management.Telemetry;
|
||||
@@ -13,7 +12,6 @@ using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Net;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
@@ -76,9 +74,6 @@ public static partial class UmbracoBuilderExtensions
|
||||
|
||||
services.AddScoped<IBackOfficeExternalLoginService, BackOfficeExternalLoginService>();
|
||||
|
||||
// Register a notification handler to interrogate the registered external login providers at startup.
|
||||
builder.AddNotificationHandler<UmbracoApplicationStartingNotification, ExternalLoginProviderStartupHandler>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Api.Management.Extensions;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
@@ -6,22 +8,32 @@ internal abstract class ContentEditingPresentationFactory<TValueModel, TVariantM
|
||||
where TValueModel : ValueModelBase
|
||||
where TVariantModel : VariantModelBase
|
||||
{
|
||||
protected TContentEditingModel MapContentEditingModel<TContentEditingModel>(
|
||||
ContentModelBase<TValueModel, TVariantModel> contentModel)
|
||||
protected TContentEditingModel MapContentEditingModel<TContentEditingModel>(ContentModelBase<TValueModel, TVariantModel> contentModel)
|
||||
where TContentEditingModel : ContentEditingModelBase, new()
|
||||
=> new()
|
||||
{
|
||||
TVariantModel? invariantVariant = contentModel.Variants.FirstOrDefault(variant => variant.DoesNotVaryByCulture() && variant.DoesNotVaryBySegment());
|
||||
TValueModel[] invariantProperties = contentModel.Values.Where(value => value.DoesNotVaryByCulture() && value.DoesNotVaryBySegment()).ToArray();
|
||||
|
||||
PropertyValueModel ToPropertyValueModel(TValueModel valueModel)
|
||||
=> new() { Alias = valueModel.Alias, Value = valueModel.Value };
|
||||
|
||||
return new TContentEditingModel
|
||||
{
|
||||
Properties = contentModel
|
||||
.Values
|
||||
.Select(value => new PropertyValueModel
|
||||
{
|
||||
Alias = value.Alias, Value = value.Value, Culture = value.Culture, Segment = value.Segment
|
||||
}).ToArray(),
|
||||
InvariantName = invariantVariant?.Name,
|
||||
InvariantProperties = invariantProperties.Select(ToPropertyValueModel).ToArray(),
|
||||
Variants = contentModel
|
||||
.Variants
|
||||
.Where(variant => variant.VariesByCulture() || variant.VariesBySegment())
|
||||
.Select(variant => new VariantModel
|
||||
{
|
||||
Culture = variant.Culture, Segment = variant.Segment, Name = variant.Name
|
||||
Culture = variant.Culture,
|
||||
Segment = variant.Segment,
|
||||
Name = variant.Name,
|
||||
Properties = contentModel
|
||||
.Values
|
||||
.Where(value => value.Culture == variant.Culture && value.Segment == variant.Segment)
|
||||
.Select(ToPropertyValueModel).ToArray()
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ internal sealed class DocumentPresentationFactory : IDocumentPresentationFactory
|
||||
{
|
||||
DocumentResponseModel responseModel = _umbracoMapper.Map<DocumentResponseModel>(content)!;
|
||||
|
||||
responseModel.Urls = await _documentUrlFactory.CreateUrlsAsync(content);
|
||||
|
||||
Guid? templateKey = content.TemplateId.HasValue
|
||||
? _templateService.GetAsync(content.TemplateId.Value).Result?.Key
|
||||
: null;
|
||||
@@ -60,6 +62,8 @@ internal sealed class DocumentPresentationFactory : IDocumentPresentationFactory
|
||||
{
|
||||
PublishedDocumentResponseModel responseModel = _umbracoMapper.Map<PublishedDocumentResponseModel>(content)!;
|
||||
|
||||
responseModel.Urls = await _documentUrlFactory.CreateUrlsAsync(content);
|
||||
|
||||
Guid? templateKey = content.PublishTemplateId.HasValue
|
||||
? _templateService.GetAsync(content.PublishTemplateId.Value).Result?.Key
|
||||
: null;
|
||||
@@ -76,6 +80,8 @@ internal sealed class DocumentPresentationFactory : IDocumentPresentationFactory
|
||||
DocumentResponseModel responseModel = _umbracoMapper.Map<DocumentResponseModel>(content)!;
|
||||
_umbracoMapper.Map(schedule, responseModel);
|
||||
|
||||
responseModel.Urls = await _documentUrlFactory.CreateUrlsAsync(content);
|
||||
|
||||
Guid? templateKey = content.TemplateId.HasValue
|
||||
? _templateService.GetAsync(content.TemplateId.Value).Result?.Key
|
||||
: null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Media;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Media.Item;
|
||||
@@ -14,42 +14,56 @@ namespace Umbraco.Cms.Api.Management.Factories;
|
||||
internal sealed class MediaPresentationFactory : IMediaPresentationFactory
|
||||
{
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
private readonly IMediaUrlFactory _mediaUrlFactory;
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
|
||||
public MediaPresentationFactory(
|
||||
IUmbracoMapper umbracoMapper,
|
||||
IMediaUrlFactory mediaUrlFactory,
|
||||
IIdKeyMap idKeyMap)
|
||||
{
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_mediaUrlFactory = mediaUrlFactory;
|
||||
_idKeyMap = idKeyMap;
|
||||
}
|
||||
|
||||
public MediaResponseModel CreateResponseModel(IMedia media) => _umbracoMapper.Map<MediaResponseModel>(media)!;
|
||||
public MediaResponseModel CreateResponseModel(IMedia media)
|
||||
{
|
||||
MediaResponseModel responseModel = _umbracoMapper.Map<MediaResponseModel>(media)!;
|
||||
|
||||
responseModel.Urls = _mediaUrlFactory.CreateUrls(media);
|
||||
|
||||
return responseModel;
|
||||
}
|
||||
|
||||
public MediaItemResponseModel CreateItemResponseModel(IMediaEntitySlim entity)
|
||||
{
|
||||
Attempt<Guid> parentKeyAttempt = _idKeyMap.GetKeyForId(entity.ParentId, UmbracoObjectTypes.Media);
|
||||
|
||||
return new MediaItemResponseModel
|
||||
var responseModel = new MediaItemResponseModel
|
||||
{
|
||||
Id = entity.Key,
|
||||
IsTrashed = entity.Trashed,
|
||||
Parent = parentKeyAttempt.Success ? new ReferenceByIdModel { Id = parentKeyAttempt.Result } : null,
|
||||
HasChildren = entity.HasChildren,
|
||||
MediaType = _umbracoMapper.Map<MediaTypeReferenceResponseModel>(entity)!,
|
||||
Variants = CreateVariantsItemResponseModels(entity)
|
||||
};
|
||||
|
||||
responseModel.MediaType = _umbracoMapper.Map<MediaTypeReferenceResponseModel>(entity)!;
|
||||
|
||||
responseModel.Variants = CreateVariantsItemResponseModels(entity);
|
||||
|
||||
return responseModel;
|
||||
}
|
||||
|
||||
public IEnumerable<VariantItemResponseModel> CreateVariantsItemResponseModels(IMediaEntitySlim entity)
|
||||
=>
|
||||
[
|
||||
=> new[]
|
||||
{
|
||||
new VariantItemResponseModel
|
||||
{
|
||||
Name = entity.Name ?? string.Empty,
|
||||
Culture = null
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
public MediaTypeReferenceResponseModel CreateMediaTypeReferenceResponseModel(IMediaEntitySlim entity)
|
||||
=> _umbracoMapper.Map<MediaTypeReferenceResponseModel>(entity)!;
|
||||
|
||||
@@ -45,7 +45,6 @@ internal sealed class MediaTypeEditingPresentationFactory : ContentTypeEditingPr
|
||||
|
||||
updateModel.AllowedContentTypes = MapAllowedContentTypes(requestModel.AllowedMediaTypes);
|
||||
updateModel.Compositions = MapCompositions(CompositionTypesByKey(requestModel.Compositions));
|
||||
|
||||
updateModel.ListView = requestModel.Collection?.Id;
|
||||
|
||||
return updateModel;
|
||||
|
||||
@@ -56,8 +56,7 @@ public abstract class ContentMapDefinition<TContent, TValueViewModel, TVariantVi
|
||||
{
|
||||
IPropertyValue[] propertyValues = source.Properties.SelectMany(propertyCollection => propertyCollection.Values).ToArray();
|
||||
var cultures = source.AvailableCultures.DefaultIfEmpty(null).ToArray();
|
||||
// the default segment (null) must always be included in the view model - both for variant and invariant documents
|
||||
var segments = propertyValues.Select(property => property.Segment).Union([null]).Distinct().ToArray();
|
||||
var segments = propertyValues.Select(property => property.Segment).Distinct().DefaultIfEmpty(null).ToArray();
|
||||
|
||||
return cultures
|
||||
.SelectMany(culture => segments.Select(segment =>
|
||||
|
||||
@@ -84,8 +84,9 @@ public abstract class ContentTypeMapDefinition<TContentType, TPropertyTypeModel,
|
||||
: CompositionType.Composition;
|
||||
|
||||
protected static IEnumerable<T> MapCompositions<T>(IEnumerable<IContentTypeComposition> directCompositions, int contentTypeParentId, Func<ReferenceByIdModel, CompositionType, T> contentTypeCompositionFactory)
|
||||
=> directCompositions
|
||||
.Select(composition => contentTypeCompositionFactory(
|
||||
new ReferenceByIdModel(composition.Key),
|
||||
CalculateCompositionType(contentTypeParentId, composition))).ToArray();
|
||||
=> directCompositions
|
||||
.Select(composition => contentTypeCompositionFactory(
|
||||
new ReferenceByIdModel(composition.Key),
|
||||
CalculateCompositionType(contentTypeParentId, composition)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
+1
-9
@@ -65,21 +65,13 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware
|
||||
return;
|
||||
}
|
||||
|
||||
var host = $"{context.Request.Scheme}://{context.Request.Host}";
|
||||
if (_knownHosts.Contains(host))
|
||||
if (_knownHosts.Add($"{context.Request.Scheme}://{context.Request.Host}") is false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _firstBackOfficeRequestLocker.WaitAsync();
|
||||
|
||||
// NOTE: _knownHosts is not thread safe; check again after entering the semaphore
|
||||
if (_knownHosts.Add(host) is false)
|
||||
{
|
||||
_firstBackOfficeRequestLocker.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure we explicitly add UmbracoApplicationUrl if configured (https://github.com/umbraco/Umbraco-CMS/issues/16179)
|
||||
if (_webRoutingSettings.UmbracoApplicationUrl.IsNullOrWhiteSpace() is false)
|
||||
{
|
||||
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
using Umbraco.Cms.Api.Management.Security;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Sync;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.NotificationHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates backoffice sessions and clears external logins for removed providers if the external login
|
||||
/// provider setup has changed.
|
||||
/// </summary>
|
||||
internal sealed class ExternalLoginProviderStartupHandler : INotificationHandler<UmbracoApplicationStartingNotification>
|
||||
{
|
||||
private readonly IBackOfficeExternalLoginProviders _backOfficeExternalLoginProviders;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly IServerRoleAccessor _serverRoleAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExternalLoginProviderStartupHandler"/> class.
|
||||
/// </summary>
|
||||
public ExternalLoginProviderStartupHandler(
|
||||
IBackOfficeExternalLoginProviders backOfficeExternalLoginProviders,
|
||||
IRuntimeState runtimeState,
|
||||
IServerRoleAccessor serverRoleAccessor)
|
||||
{
|
||||
_backOfficeExternalLoginProviders = backOfficeExternalLoginProviders;
|
||||
_runtimeState = runtimeState;
|
||||
_serverRoleAccessor = serverRoleAccessor;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Handle(UmbracoApplicationStartingNotification notification)
|
||||
{
|
||||
if (_runtimeState.Level != RuntimeLevel.Run ||
|
||||
_serverRoleAccessor.CurrentServerRole == ServerRole.Subscriber)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_backOfficeExternalLoginProviders.InvalidateSessionsIfExternalLoginProvidersChanged();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"openapi": "3.0.4",
|
||||
"openapi": "3.0.1",
|
||||
"info": {
|
||||
"title": "Umbraco Management API",
|
||||
"description": "This shows all APIs available in this version of Umbraco - including all the legacy apis that are available for backward compatibility",
|
||||
@@ -3485,66 +3485,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/document-blueprint/{id}/scaffold": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Document Blueprint"
|
||||
],
|
||||
"operationId": "GetDocumentBlueprintByIdScaffold",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentBlueprintResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
},
|
||||
"403": {
|
||||
"description": "The authenticated user does not have access to this resource"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/document-blueprint/folder": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -10159,13 +10099,6 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "culture",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
@@ -15925,13 +15858,6 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "culture",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
@@ -37627,8 +37553,7 @@
|
||||
"$ref": "#/components/schemas/DocumentUrlInfoModel"
|
||||
}
|
||||
]
|
||||
},
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"oneOf": [
|
||||
@@ -39801,8 +39726,7 @@
|
||||
"$ref": "#/components/schemas/MediaUrlInfoModel"
|
||||
}
|
||||
]
|
||||
},
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
"isTrashed": {
|
||||
"type": "boolean"
|
||||
@@ -43505,8 +43429,7 @@
|
||||
"$ref": "#/components/schemas/DocumentUrlInfoModel"
|
||||
}
|
||||
]
|
||||
},
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"oneOf": [
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Security;
|
||||
|
||||
@@ -12,37 +8,13 @@ public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProvider
|
||||
{
|
||||
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
|
||||
private readonly Dictionary<string, BackOfficeExternalLoginProvider> _externalLogins;
|
||||
private readonly IKeyValueService _keyValueService;
|
||||
private readonly IExternalLoginWithKeyService _externalLoginWithKeyService;
|
||||
private readonly ILogger<BackOfficeExternalLoginProviders> _logger;
|
||||
|
||||
private const string ExternalLoginProvidersKey = "Umbraco.Cms.Web.BackOffice.Security.BackOfficeExternalLoginProviders";
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public BackOfficeExternalLoginProviders(
|
||||
IEnumerable<BackOfficeExternalLoginProvider> externalLogins,
|
||||
IAuthenticationSchemeProvider authenticationSchemeProvider)
|
||||
: this(
|
||||
externalLogins,
|
||||
authenticationSchemeProvider,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IKeyValueService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IExternalLoginWithKeyService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<BackOfficeExternalLoginProviders>>())
|
||||
{
|
||||
}
|
||||
|
||||
public BackOfficeExternalLoginProviders(
|
||||
IEnumerable<BackOfficeExternalLoginProvider> externalLogins,
|
||||
IAuthenticationSchemeProvider authenticationSchemeProvider,
|
||||
IKeyValueService keyValueService,
|
||||
IExternalLoginWithKeyService externalLoginWithKeyService,
|
||||
ILogger<BackOfficeExternalLoginProviders> logger)
|
||||
{
|
||||
_externalLogins = externalLogins.ToDictionary(x => x.AuthenticationType);
|
||||
_authenticationSchemeProvider = authenticationSchemeProvider;
|
||||
_keyValueService = keyValueService;
|
||||
_externalLoginWithKeyService = externalLoginWithKeyService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -88,25 +60,4 @@ public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProvider
|
||||
var found = _externalLogins.Values.Where(x => x.Options.DenyLocalLogin).ToList();
|
||||
return found.Count > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void InvalidateSessionsIfExternalLoginProvidersChanged()
|
||||
{
|
||||
var previousExternalLoginProvidersValue = _keyValueService.GetValue(ExternalLoginProvidersKey);
|
||||
var currentExternalLoginProvidersValue = string.Join("|", _externalLogins.Keys.OrderBy(key => key));
|
||||
|
||||
if ((previousExternalLoginProvidersValue ?? string.Empty) != currentExternalLoginProvidersValue)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"The configured external login providers have changed. Existing backoffice sessions using the removed providers will be invalidated and external login data removed.");
|
||||
|
||||
_externalLoginWithKeyService.PurgeLoginsForRemovedProviders(_externalLogins.Keys);
|
||||
|
||||
_keyValueService.SetValue(ExternalLoginProvidersKey, currentExternalLoginProvidersValue);
|
||||
}
|
||||
else if (previousExternalLoginProvidersValue is null)
|
||||
{
|
||||
_keyValueService.SetValue(ExternalLoginProvidersKey, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,4 @@ public interface IBackOfficeExternalLoginProviders
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool HasDenyLocalLogin();
|
||||
|
||||
/// <summary>
|
||||
/// Used during startup to see if the configured external login providers is different from the persisted information.
|
||||
/// If they are different, this will invalidate backoffice sessions and clear external logins for removed providers
|
||||
/// if the external login provider setup has changed.
|
||||
/// </summary>
|
||||
void InvalidateSessionsIfExternalLoginProvidersChanged() { }
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ public class ConfigureUmbracoBackofficeJsonOptions : IConfigureNamedOptions<Json
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonUdiConverter());
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonUdiRangeConverter());
|
||||
options.JsonSerializerOptions.Converters.Add(new ValidationProblemDetailsConverter());
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonObjectConverter());
|
||||
|
||||
options.JsonSerializerOptions.TypeInfoResolver = _umbracoJsonTypeInfoResolver;
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Custom JSON converter registered via <see cref="ConfigureUmbracoBackofficeJsonOptions"/> to serialize <see cref="ValidationProblemDetails" /> to JSON.
|
||||
/// </summary>
|
||||
public sealed class ValidationProblemDetailsConverter : JsonConverter<ValidationProblemDetails>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override ValidationProblemDetails? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
=> null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, ValidationProblemDetails value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("type", value.Type);
|
||||
writer.WriteString("title", value.Title);
|
||||
|
||||
if (value.Status.HasValue)
|
||||
{
|
||||
writer.WriteNumber("status", value.Status.Value);
|
||||
}
|
||||
|
||||
if (value.Detail is not null)
|
||||
{
|
||||
writer.WriteString("detail", value.Detail);
|
||||
}
|
||||
|
||||
if (value.Instance is not null)
|
||||
{
|
||||
writer.WriteString("instance", value.Instance);
|
||||
}
|
||||
|
||||
if (value.Errors.Any())
|
||||
{
|
||||
writer.WritePropertyName("errors");
|
||||
writer.WriteStartArray();
|
||||
|
||||
foreach (KeyValuePair<string, string[]> error in value.Errors)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName($"$.{string.Join(".", error.Key.Split(Constants.CharArrays.Period).Select(s => s.ToFirstLowerInvariant()))}");
|
||||
writer.WriteStartArray();
|
||||
|
||||
foreach (var errorDetails in error.Value)
|
||||
{
|
||||
writer.WriteStringValue(errorDetails);
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (value.Extensions.TryGetValue("traceId", out var traceId) && traceId is string traceIdValue)
|
||||
{
|
||||
writer.WriteString("traceId", traceIdValue);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
public class DocumentResponseModel : DocumentResponseModelBase<DocumentValueResponseModel, DocumentVariantResponseModel>
|
||||
{
|
||||
[Obsolete("This property is no longer populated. Please use /document/{id}/urls instead to retrieve the URLs for a document. Scheduled for removal in Umbraco 17.")]
|
||||
public IEnumerable<DocumentUrlInfo> Urls { get; set; } = Enumerable.Empty<DocumentUrlInfo>();
|
||||
|
||||
public ReferenceByIdModel? Template { get; set; }
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Media;
|
||||
|
||||
public class MediaResponseModel : ContentResponseModelBase<MediaValueResponseModel, MediaVariantResponseModel>
|
||||
{
|
||||
[Obsolete("This property is no longer populated. Please use /media/{id}/urls instead to retrieve the URLs for a document. Scheduled for removal in Umbraco 17.")]
|
||||
public IEnumerable<MediaUrlInfo> Urls { get; set; } = Enumerable.Empty<MediaUrlInfo>();
|
||||
|
||||
public bool IsTrashed { get; set; }
|
||||
|
||||
@@ -26,36 +26,23 @@
|
||||
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- General ignored files -->
|
||||
<ItemGroup>
|
||||
<Content Remove="wwwroot\umbraco\assets\README.md" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
<!-- BEGIN: Restore and build backoffice project -->
|
||||
<!-- Restore and build backoffice project -->
|
||||
<PropertyGroup>
|
||||
<BackofficeProjectDirectory Condition="'$(BackofficeProjectDirectory)' == ''">..\Umbraco.Web.UI.Client\</BackofficeProjectDirectory>
|
||||
<BackofficeAssetsPath>$(ProjectDir)wwwroot\umbraco\backoffice</BackofficeAssetsPath>
|
||||
<BackofficeAssetsPath>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="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="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)" />
|
||||
<Exec Command="npm ci --no-fund --no-audit --prefer-offline" WorkingDirectory="$(BackofficeProjectDirectory)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildBackoffice" DependsOnTargets="RestoreBackoffice">
|
||||
<Target Name="BuildBackoffice" DependsOnTargets="RestoreBackoffice" BeforeTargets="AssignTargetPaths" Inputs="@(BackofficeAssetsInputs)" Outputs="$(IntermediateOutputPath)backoffice.complete.txt">
|
||||
<Message Importance="high" Text="Executing Backoffice NPM build script..." />
|
||||
<Exec Command="npm run build:for:cms" WorkingDirectory="$(BackofficeProjectDirectory)" />
|
||||
<ItemGroup>
|
||||
@@ -74,46 +61,27 @@
|
||||
</DefineStaticWebAssets>
|
||||
</Target>
|
||||
|
||||
<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 -->
|
||||
<!-- Restore and build login project -->
|
||||
<PropertyGroup>
|
||||
<LoginProjectDirectory Condition="'$(LoginProjectDirectory)' == ''">..\Umbraco.Web.UI.Login\</LoginProjectDirectory>
|
||||
<LoginAssetsPath>$(ProjectDir)wwwroot\umbraco\login</LoginAssetsPath>
|
||||
<LoginAssetsPath>wwwroot\umbraco\login</LoginAssetsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<LoginAssetsInputs Include="$(LoginProjectDirectory)**" Exclude="$(DefaultItemExcludes)" />
|
||||
<Content Remove="$(LoginAssetsPath)\**" />
|
||||
</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>
|
||||
<ItemGroup>
|
||||
<Content Remove="wwwroot\umbraco\assets\README.md" />
|
||||
</ItemGroup>
|
||||
|
||||
<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 i --no-fund --no-audit" WorkingDirectory="$(LoginProjectDirectory)" />
|
||||
<Exec Command="npm ci --no-fund --no-audit --prefer-offline" WorkingDirectory="$(LoginProjectDirectory)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildLogin" DependsOnTargets="RestoreLogin">
|
||||
<Target Name="BuildLogin" DependsOnTargets="RestoreLogin" BeforeTargets="AssignTargetPaths" Inputs="@(LoginAssetsInputs)" Outputs="$(IntermediateOutputPath)login.complete.txt">
|
||||
<Message Importance="high" Text="Executing Login NPM build script..." />
|
||||
<Exec Command="npm run build" WorkingDirectory="$(LoginProjectDirectory)" />
|
||||
<ItemGroup>
|
||||
@@ -131,19 +99,4 @@
|
||||
<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>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 286 KiB |
@@ -22,6 +22,4 @@ 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,5 +1,3 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
@@ -23,11 +21,9 @@ 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,
|
||||
@@ -42,39 +38,6 @@ 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;
|
||||
@@ -86,11 +49,6 @@ 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
|
||||
@@ -125,13 +83,6 @@ 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,5 +1,3 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
@@ -20,9 +18,7 @@ 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,
|
||||
@@ -33,31 +29,6 @@ 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;
|
||||
@@ -65,9 +36,6 @@ 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
|
||||
@@ -119,13 +87,6 @@ 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,22 +71,11 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase<MemberCache
|
||||
foreach (JsonPayload p in payloads)
|
||||
{
|
||||
_idKeyMap.ClearCache(p.Id);
|
||||
if (memberCache.Success is false)
|
||||
if (memberCache.Success)
|
||||
{
|
||||
continue;
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ public static class ContentSettingsExtensions
|
||||
/// <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);
|
||||
=> contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) ||
|
||||
(contentSettings.AllowedUploadedFileExtensions.Any() == false && contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) == false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the auto-fill configuration for a specified property alias.
|
||||
|
||||
@@ -144,7 +144,7 @@ public class DeliveryApiSettings
|
||||
/// <value>
|
||||
/// The URLs allowed as redirect targets.
|
||||
/// </value>
|
||||
public IEnumerable<Uri> LoginRedirectUrls { get; set; } = [];
|
||||
public ISet<Uri> LoginRedirectUrls { get; set; } = new HashSet<Uri>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URLs allowed to use as redirect targets after a successful logout (session termination).
|
||||
@@ -155,7 +155,7 @@ public class DeliveryApiSettings
|
||||
/// <remarks>
|
||||
/// These are only required if logout is to be used.
|
||||
/// </remarks>
|
||||
public IEnumerable<Uri> LogoutRedirectUrls { get; set; } = [];
|
||||
public ISet<Uri> LogoutRedirectUrls { get; set; } = new HashSet<Uri>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models;
|
||||
|
||||
@@ -14,8 +13,6 @@ public class LoggingSettings
|
||||
{
|
||||
internal const string StaticMaxLogAge = "1.00:00:00"; // TimeSpan.FromHours(24);
|
||||
internal const string StaticDirectory = Constants.SystemDirectories.LogFiles;
|
||||
internal const string StaticFileNameFormat = LoggingConfiguration.DefaultLogFileNameFormat;
|
||||
internal const string StaticFileNameFormatArguments = "MachineName";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the maximum age of a log file.
|
||||
@@ -34,25 +31,4 @@ public class LoggingSettings
|
||||
/// </value>
|
||||
[DefaultValue(StaticDirectory)]
|
||||
public string Directory { get; set; } = StaticDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the file name format to use for log files.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The file name format.
|
||||
/// </value>
|
||||
[DefaultValue(StaticFileNameFormat)]
|
||||
public string FileNameFormat { get; set; } = StaticFileNameFormat;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the file name format arguments to use for log files.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The file name format arguments as a comma delimited string of accepted values.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Accepted values for format arguments are: MachineName, EnvironmentName.
|
||||
/// </remarks>
|
||||
[DefaultValue(StaticFileNameFormatArguments)]
|
||||
public string FileNameFormatArguments { get; set; } = StaticFileNameFormatArguments;
|
||||
}
|
||||
|
||||
@@ -98,5 +98,5 @@ public class RequestHandlerSettings
|
||||
/// <summary>
|
||||
/// Add additional character replacements, or override defaults
|
||||
/// </summary>
|
||||
public IEnumerable<CharItem> UserDefinedCharCollection { get; set; } = [];
|
||||
public ISet<CharItem> UserDefinedCharCollection { get; set; } = new HashSet<CharItem>();
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ public class RuntimeSettings
|
||||
public RuntimeMode Mode { get; set; } = RuntimeMode.BackofficeDevelopment;
|
||||
|
||||
/// <summary>
|
||||
[Obsolete("No longer used and will be removed in Umbraco 16.")]
|
||||
/// Gets or sets a value for the maximum request length in kb.
|
||||
/// </summary>
|
||||
public long? MaxRequestLength { get; set; }
|
||||
|
||||
@@ -28,8 +28,8 @@ public class SecuritySettings
|
||||
|
||||
internal const int StaticMemberDefaultLockoutTimeInMinutes = 30 * 24 * 60;
|
||||
internal const int StaticUserDefaultLockoutTimeInMinutes = 30 * 24 * 60;
|
||||
internal const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000;
|
||||
internal const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250;
|
||||
private const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000;
|
||||
private const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250;
|
||||
internal const string StaticAuthorizeCallbackPathName = "/umbraco/oauth_complete";
|
||||
internal const string StaticAuthorizeCallbackLogoutPathName = "/umbraco/logout";
|
||||
internal const string StaticAuthorizeCallbackErrorPathName = "/umbraco/error";
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Validator for configuration representated as <see cref="LoggingSettings" />.
|
||||
/// </summary>
|
||||
public class LoggingSettingsValidator : ConfigurationValidatorBase, IValidateOptions<LoggingSettings>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ValidateOptionsResult Validate(string? name, LoggingSettings options)
|
||||
{
|
||||
if (!ValidateFileNameFormatArgument(options.FileNameFormat, options.FileNameFormatArguments, out var message))
|
||||
{
|
||||
return ValidateOptionsResult.Fail(message);
|
||||
}
|
||||
|
||||
|
||||
return ValidateOptionsResult.Success;
|
||||
}
|
||||
|
||||
private bool ValidateFileNameFormatArgument(string fileNameFormat, string fileNameFormatArguments, out string message)
|
||||
{
|
||||
var fileNameFormatArgumentsAsArray = fileNameFormatArguments
|
||||
.Split([','], StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.ToArray();
|
||||
if (fileNameFormatArgumentsAsArray.Any(x => LoggingConfiguration.SupportedFileNameFormatArguments.Contains(x) is false))
|
||||
{
|
||||
message = $"The file name arguments '{string.Join(",", fileNameFormatArgumentsAsArray)}' contain one or more values that aren't in the supported list of values '{string.Join(",", LoggingConfiguration.SupportedFileNameFormatArguments)}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_ = string.Format(fileNameFormat, fileNameFormatArgumentsAsArray);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
message = $"The provided file name format '{fileNameFormat}' could not be used with the provided arguments '{string.Join(",", fileNameFormatArgumentsAsArray)}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
message = string.Empty;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.ComponentModel;
|
||||
using Umbraco.Cms.Core.Webhooks;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models;
|
||||
|
||||
@@ -11,7 +10,6 @@ public class WebhookSettings
|
||||
internal const string StaticPeriod = "00:00:10";
|
||||
private const bool StaticEnableLoggingCleanup = true;
|
||||
private const int StaticKeepLogsForDays = 30;
|
||||
private const WebhookPayloadType StaticPayloadType = Constants.Webhooks.DefaultPayloadType;
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -65,16 +63,4 @@ public class WebhookSettings
|
||||
/// </remarks>
|
||||
[DefaultValue(StaticKeepLogsForDays)]
|
||||
public int KeepLogsForDays { get; set; } = StaticKeepLogsForDays;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the type of payload used for sending webhooks
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// By default, Legacy payloads are used see <see cref="WebhookPayloadType"/> for more info.
|
||||
/// This default will change to minimal starting from v17.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[DefaultValue(StaticPayloadType)]
|
||||
public WebhookPayloadType PayloadType { get; set; } = StaticPayloadType;
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Umbraco.Cms.Core;
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class Cache
|
||||
{
|
||||
public static class Tags
|
||||
{
|
||||
public const string Content = "content";
|
||||
|
||||
public const string Media = "media";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,6 @@ public static partial class Constants
|
||||
public const string ConfigDataTypes = ConfigPrefix + "DataTypes";
|
||||
public const string ConfigPackageManifests = ConfigPrefix + "PackageManifests";
|
||||
public const string ConfigWebhook = ConfigPrefix + "Webhook";
|
||||
public const string ConfigWebhookPayloadType = ConfigWebhook + ":PayloadType";
|
||||
public const string ConfigCache = ConfigPrefix + "Cache";
|
||||
|
||||
public static class NamedOptions
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using Umbraco.Cms.Core.Webhooks;
|
||||
|
||||
namespace Umbraco.Cms.Core;
|
||||
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class Webhooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default webhook payload type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently, the default payload type is <see cref="WebhookPayloadType.Legacy"/> for backward compatibility until Umbraco 17.
|
||||
/// From Umbraco 17 this will be changed to <see cref="WebhookPayloadType.Minimal"/>.
|
||||
/// </remarks>
|
||||
public const WebhookPayloadType DefaultPayloadType = WebhookPayloadType.Legacy;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,15 @@
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public sealed class ApiContentBuilder : ApiContentBuilderBase<IApiContent>, IApiContentBuilder
|
||||
{
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
[Obsolete("Please use the constructor that takes an IVariationContextAccessor instead. Scheduled for removal in V17.")]
|
||||
public ApiContentBuilder(
|
||||
IApiContentNameProvider apiContentNameProvider,
|
||||
IApiContentRouteBuilder apiContentRouteBuilder,
|
||||
IOutputExpansionStrategyAccessor outputExpansionStrategyAccessor)
|
||||
: this(
|
||||
apiContentNameProvider,
|
||||
apiContentRouteBuilder,
|
||||
outputExpansionStrategyAccessor,
|
||||
StaticServiceProvider.Instance.CreateInstance<IVariationContextAccessor>())
|
||||
public ApiContentBuilder(IApiContentNameProvider apiContentNameProvider, IApiContentRouteBuilder apiContentRouteBuilder, IOutputExpansionStrategyAccessor outputExpansionStrategyAccessor)
|
||||
: base(apiContentNameProvider, apiContentRouteBuilder, outputExpansionStrategyAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
public ApiContentBuilder(
|
||||
IApiContentNameProvider apiContentNameProvider,
|
||||
IApiContentRouteBuilder apiContentRouteBuilder,
|
||||
IOutputExpansionStrategyAccessor outputExpansionStrategyAccessor,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
: base(apiContentNameProvider, apiContentRouteBuilder, outputExpansionStrategyAccessor)
|
||||
=> _variationContextAccessor = variationContextAccessor;
|
||||
|
||||
protected override IApiContent Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary<string, object?> properties)
|
||||
=> new ApiContent(content.Key, name, content.ContentType.Alias, content.CreateDate, content.CultureDate(_variationContextAccessor), route, properties);
|
||||
=> new ApiContent(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -8,36 +7,15 @@ namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
public class ApiContentResponseBuilder : ApiContentBuilderBase<IApiContentResponse>, IApiContentResponseBuilder
|
||||
{
|
||||
private readonly IApiContentRouteBuilder _apiContentRouteBuilder;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
[Obsolete("Please use the constructor that takes an IVariationContextAccessor instead. Scheduled for removal in V17.")]
|
||||
public ApiContentResponseBuilder(
|
||||
IApiContentNameProvider apiContentNameProvider,
|
||||
IApiContentRouteBuilder apiContentRouteBuilder,
|
||||
IOutputExpansionStrategyAccessor outputExpansionStrategyAccessor)
|
||||
: this(
|
||||
apiContentNameProvider,
|
||||
apiContentRouteBuilder,
|
||||
outputExpansionStrategyAccessor,
|
||||
StaticServiceProvider.Instance.CreateInstance<IVariationContextAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
public ApiContentResponseBuilder(
|
||||
IApiContentNameProvider apiContentNameProvider,
|
||||
IApiContentRouteBuilder apiContentRouteBuilder,
|
||||
IOutputExpansionStrategyAccessor outputExpansionStrategyAccessor,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
public ApiContentResponseBuilder(IApiContentNameProvider apiContentNameProvider, IApiContentRouteBuilder apiContentRouteBuilder, IOutputExpansionStrategyAccessor outputExpansionStrategyAccessor)
|
||||
: base(apiContentNameProvider, apiContentRouteBuilder, outputExpansionStrategyAccessor)
|
||||
{
|
||||
_apiContentRouteBuilder = apiContentRouteBuilder;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
}
|
||||
=> _apiContentRouteBuilder = apiContentRouteBuilder;
|
||||
|
||||
protected override IApiContentResponse Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary<string, object?> properties)
|
||||
{
|
||||
IDictionary<string, IApiContentRoute> cultures = GetCultures(content);
|
||||
return new ApiContentResponse(content.Key, name, content.ContentType.Alias, content.CreateDate, content.CultureDate(_variationContextAccessor), route, properties, cultures);
|
||||
return new ApiContentResponse(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties, cultures);
|
||||
}
|
||||
|
||||
protected virtual IDictionary<string, IApiContentRoute> GetCultures(IPublishedContent content)
|
||||
|
||||
@@ -7,6 +7,9 @@ public interface IRequestCultureService
|
||||
/// </summary>
|
||||
string? GetRequestedCulture();
|
||||
|
||||
[Obsolete("Use IVariationContextAccessor to manipulate the variation context. Scheduled for removal in V17.")]
|
||||
/// <summary>
|
||||
/// Updates the current request culture if applicable.
|
||||
/// </summary>
|
||||
/// <param name="culture">The culture to use for the current request.</param>
|
||||
void SetRequestCulture(string culture);
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public interface IRequestSegmmentService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the requested segment from the "Accept-Segment" header, if present.
|
||||
/// </summary>
|
||||
string? GetRequestedSegment();
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.DynamicRoot.Origin;
|
||||
using Umbraco.Cms.Core.DynamicRoot.QuerySteps;
|
||||
@@ -93,20 +91,7 @@ public static partial class UmbracoBuilderExtensions
|
||||
builder.FilterHandlers().Add(() => builder.TypeLoader.GetTypes<IFilterHandler>());
|
||||
builder.SortHandlers().Add(() => builder.TypeLoader.GetTypes<ISortHandler>());
|
||||
builder.ContentIndexHandlers().Add(() => builder.TypeLoader.GetTypes<IContentIndexHandler>());
|
||||
|
||||
WebhookPayloadType webhookPayloadType = Constants.Webhooks.DefaultPayloadType;
|
||||
|
||||
// IntelliSense indicates that GetSection cannot return null. However, in certain unit test setups,
|
||||
// the configuration may not be fully initialized, leading to GetSection returning null. This null
|
||||
// check ensures that the code behaves correctly in such scenarios and prevents potential null
|
||||
// reference exceptions during testing.
|
||||
if (builder.Config.GetSection(Constants.Configuration.ConfigWebhookPayloadType)?.Value is not null)
|
||||
{
|
||||
webhookPayloadType = builder.Config.GetValue<WebhookPayloadType>(Constants.Configuration.ConfigWebhookPayloadType);
|
||||
}
|
||||
|
||||
builder.WebhookEvents().AddCms(true, webhookPayloadType);
|
||||
|
||||
builder.WebhookEvents().AddCms(true);
|
||||
builder.ContentTypeFilters();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ public static partial class UmbracoBuilderExtensions
|
||||
builder.Services.AddSingleton<IValidateOptions<ContentSettings>, ContentSettingsValidator>();
|
||||
builder.Services.AddSingleton<IValidateOptions<GlobalSettings>, GlobalSettingsValidator>();
|
||||
builder.Services.AddSingleton<IValidateOptions<HealthChecksSettings>, HealthChecksSettingsValidator>();
|
||||
builder.Services.AddSingleton<IValidateOptions<LoggingSettings>, LoggingSettingsValidator>();
|
||||
builder.Services.AddSingleton<IValidateOptions<RequestHandlerSettings>, RequestHandlerSettingsValidator>();
|
||||
builder.Services.AddSingleton<IValidateOptions<UnattendedSettings>, UnattendedSettingsValidator>();
|
||||
builder.Services.AddSingleton<IValidateOptions<SecuritySettings>, SecuritySettingsValidator>();
|
||||
|
||||
@@ -11,18 +11,17 @@ public interface IFileType
|
||||
/// <value>
|
||||
/// <c>true</c> if the file can be set using a physical path; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
[Obsolete("An interface should not expose implementation details. Scheduled for removal in Umbraco 18.")]
|
||||
bool CanSetPhysical { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream in an asynchronous operation.
|
||||
/// Gets the stream as an asynchronous operation.
|
||||
/// </summary>
|
||||
/// <param name="udi">The UDI.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// The task object representing the asynchronous operation. The task result contains the stream.
|
||||
/// The task object representing the asynchronous operation.
|
||||
/// </returns>
|
||||
Task<Stream> GetStreamAsync(StringUdi udi, CancellationToken token); // TODO: Rename token to cancellationToken and add default value
|
||||
Task<Stream> GetStreamAsync(StringUdi udi, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the checksum stream.
|
||||
@@ -31,22 +30,8 @@ public interface IFileType
|
||||
/// <returns>
|
||||
/// The checksum stream.
|
||||
/// </returns>
|
||||
[Obsolete("Use GetChecksumStreamAsync() instead. Scheduled for removal in Umbraco 18.")]
|
||||
Stream GetChecksumStream(StringUdi udi);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the checksum stream in an asynchronous operation.
|
||||
/// </summary>
|
||||
/// <param name="udi">The UDI.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// The task object representing the asynchronous operation. The task result contains the checksum stream.
|
||||
/// </returns>
|
||||
Task<Stream> GetChecksumStreamAsync(StringUdi udi, CancellationToken cancellationToken = default)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> Task.FromResult(GetChecksumStream(udi));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file length in bytes or <c>-1</c> if not found.
|
||||
/// </summary>
|
||||
@@ -54,22 +39,8 @@ public interface IFileType
|
||||
/// <returns>
|
||||
/// The file length in bytes or <c>-1</c> if not found.
|
||||
/// </returns>
|
||||
[Obsolete("Use GetLengthAsync() instead. Scheduled for removal in Umbraco 18.")]
|
||||
long GetLength(StringUdi udi);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file length in bytes or <c>-1</c> if not found in an asynchronous operation.
|
||||
/// </summary>
|
||||
/// <param name="udi">The UDI.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// The task object representing the asynchronous operation. The task result contains the file length in bytes or <c>-1</c> if not found.
|
||||
/// </returns>
|
||||
Task<long> GetLengthAsync(StringUdi udi, CancellationToken cancellationToken = default)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> Task.FromResult(GetLength(udi));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
/// <summary>
|
||||
/// Sets the stream as an asynchronous operation.
|
||||
/// </summary>
|
||||
@@ -79,7 +50,7 @@ public interface IFileType
|
||||
/// <returns>
|
||||
/// The task object representing the asynchronous operation.
|
||||
/// </returns>
|
||||
Task SetStreamAsync(StringUdi udi, Stream stream, CancellationToken token); // TODO: Rename token to cancellationToken and add default value
|
||||
Task SetStreamAsync(StringUdi udi, Stream stream, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the physical path of the file.
|
||||
@@ -87,16 +58,20 @@ public interface IFileType
|
||||
/// <param name="udi">The UDI.</param>
|
||||
/// <param name="physicalPath">The physical path.</param>
|
||||
/// <param name="copy">If set to <c>true</c> copies the file instead of moving.</param>
|
||||
[Obsolete("Use SetStreamAsync() instead to not rely on physical file paths. Scheduled for removal in Umbraco 18.")]
|
||||
void Set(StringUdi udi, string physicalPath, bool copy = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the file, including the file name. Returns <see cref="string.Empty" /> if the file is not directly accessible.
|
||||
/// Gets the physical path or <see cref="string.Empty"/> if not found.
|
||||
/// </summary>
|
||||
/// <param name="udi">The UDI.</param>
|
||||
/// <returns>
|
||||
/// The path to the file, including the file name or <see cref="string.Empty" /> if the file is not directly accessible.
|
||||
/// The physical path or <see cref="string.Empty"/> if not found.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This is not pretty as *everywhere* in Deploy we take care of ignoring
|
||||
/// the physical path and always rely on the virtual IFileSystem,
|
||||
/// but Cloud wants to add some of these files to Git and needs the path...
|
||||
/// </remarks>
|
||||
string GetPhysicalPath(StringUdi udi);
|
||||
|
||||
/// <summary>
|
||||
@@ -106,6 +81,5 @@ public interface IFileType
|
||||
/// <returns>
|
||||
/// The virtual path or <see cref="string.Empty"/> if not found.
|
||||
/// </returns>
|
||||
[Obsolete("This is not used anymore. Scheduled for removal in Umbraco 18.")]
|
||||
string GetVirtualPath(StringUdi udi);
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
<key alias="invalidEmpty">Value cannot be empty</key>
|
||||
<key alias="invalidPattern">Value is invalid, it does not match the correct pattern</key>
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, you have entered <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
|
||||
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
|
||||
<key alias="invalidMemberGroupName">Invalid member group name</key>
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
<key alias="unexpectedRange">The value %0% is not expected to contain a range</key>
|
||||
<key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key>
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, you have entered <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
|
||||
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
|
||||
<key alias="invalidMediaType">The chosen media type is invalid.</key>
|
||||
|
||||
@@ -245,13 +245,13 @@ public static class ContentExtensions
|
||||
}
|
||||
|
||||
IEnumerable<ContentSchedule> expires = contentSchedule.GetSchedule(culture, ContentScheduleAction.Expire);
|
||||
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.UtcNow > x.Date))
|
||||
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.Now > x.Date))
|
||||
{
|
||||
return ContentStatus.Expired;
|
||||
}
|
||||
|
||||
IEnumerable<ContentSchedule> release = contentSchedule.GetSchedule(culture, ContentScheduleAction.Release);
|
||||
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.UtcNow))
|
||||
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.Now))
|
||||
{
|
||||
return ContentStatus.AwaitingRelease;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
public static class IntExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Does something 'x' amount of times.
|
||||
/// Does something 'x' amount of times
|
||||
/// </summary>
|
||||
/// <param name="n">Number of times to execute the action.</param>
|
||||
/// <param name="action">The action to execute.</param>
|
||||
/// <param name="n"></param>
|
||||
/// <param name="action"></param>
|
||||
public static void Times(this int n, Action<int> action)
|
||||
{
|
||||
for (var i = 0; i < n; i++)
|
||||
@@ -21,11 +19,11 @@ public static class IntExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Guid based on an integer value.
|
||||
/// Creates a Guid based on an integer value
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="int" /> value to convert.</param>
|
||||
/// <param name="value"><see cref="int" /> value to convert</param>
|
||||
/// <returns>
|
||||
/// The converted <see cref="Guid" />.
|
||||
/// <see cref="Guid" />
|
||||
/// </returns>
|
||||
public static Guid ToGuid(this int value)
|
||||
{
|
||||
@@ -33,25 +31,4 @@ public static class IntExtensions
|
||||
BitConverter.GetBytes(value).CopyTo(bytes);
|
||||
return new Guid(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores a GUID previously created from an integer value using <see cref="ToGuid" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="Guid" /> value to convert.</param>
|
||||
/// <param name="result">The converted <see cref="int" />.</param>
|
||||
/// <returns>
|
||||
/// True if the <see cref="int" /> value could be created, otherwise false.
|
||||
/// </returns>
|
||||
public static bool TryParseFromGuid(Guid value, [NotNullWhen(true)] out int? result)
|
||||
{
|
||||
if (value.ToString().EndsWith("-0000-0000-0000-000000000000") is false)
|
||||
{
|
||||
// We have a proper GUID, not one converted from an integer.
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
result = BitConverter.ToInt32(value.ToByteArray());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1575,22 +1575,4 @@ public static class StringExtensions
|
||||
// this is by far the fastest way to find string needles in a string haystack
|
||||
public static int CountOccurrences(this string haystack, string needle)
|
||||
=> haystack.Length - haystack.Replace(needle, string.Empty).Length;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the provided string is a valid culture code and returns it in a consistent casing.
|
||||
/// </summary>
|
||||
/// <param name="culture">Culture code.</param>
|
||||
/// <returns>Culture code in standard casing.</returns>
|
||||
public static string? EnsureCultureCode(this string? culture)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture) || culture == "*")
|
||||
{
|
||||
return culture;
|
||||
}
|
||||
|
||||
// Create as CultureInfo instance from provided name so we can ensure consistent casing of culture code when persisting.
|
||||
// This will accept mixed case but once created have a `Name` property that is consistently and correctly cased.
|
||||
// Will throw in an invalid culture code is provided.
|
||||
return new CultureInfo(culture).Name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.HostedServices;
|
||||
public interface IBackgroundTaskQueue
|
||||
{
|
||||
/// <summary>
|
||||
/// Enqueue a work item to be executed in the background.
|
||||
/// Enqueue a work item to be executed on in the background.
|
||||
/// </summary>
|
||||
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ namespace Umbraco.Cms.Core.IO
|
||||
|
||||
// nothing prevents us to reach the file, security-wise, yet it is outside
|
||||
// this filesystem's root - throw
|
||||
throw new UnauthorizedAccessException($"Requested path {originalPath} is outside this filesystem's root.");
|
||||
throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,17 +3,7 @@ namespace Umbraco.Cms.Core.Logging;
|
||||
public interface ILoggingConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the physical path where logs are stored.
|
||||
/// Gets the physical path where logs are stored
|
||||
/// </summary>
|
||||
string LogDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file name format for the log files.
|
||||
/// </summary>
|
||||
string LogFileNameFormat { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file name format arguments for the log files.
|
||||
/// </summary>
|
||||
string[] GetLogFileNameFormatArguments();
|
||||
}
|
||||
|
||||
@@ -1,73 +1,9 @@
|
||||
namespace Umbraco.Cms.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="ILoggingConfiguration"/> to provide configuration for logging to files.
|
||||
/// </summary>
|
||||
public class LoggingConfiguration : ILoggingConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// The default log file name format.
|
||||
/// </summary>
|
||||
public const string DefaultLogFileNameFormat = "UmbracoTraceLog.{0}..json";
|
||||
public LoggingConfiguration(string logDirectory) =>
|
||||
LogDirectory = logDirectory ?? throw new ArgumentNullException(nameof(logDirectory));
|
||||
|
||||
/// <summary>
|
||||
/// The default log file name format arguments.
|
||||
/// </summary>
|
||||
public const string DefaultLogFileNameFormatArguments = MachineNameFileFormatArgument;
|
||||
|
||||
/// <summary>
|
||||
/// The collection of supported file name format arguments.
|
||||
/// </summary>
|
||||
public static readonly string[] SupportedFileNameFormatArguments =
|
||||
{
|
||||
MachineNameFileFormatArgument,
|
||||
EnvironmentNameFileFormatArgument,
|
||||
};
|
||||
|
||||
private readonly string _logFileNameFormatArguments;
|
||||
|
||||
private const string MachineNameFileFormatArgument = "MachineName";
|
||||
private const string EnvironmentNameFileFormatArgument = "EnvironmentName";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoggingConfiguration"/> class with the default log file name format and arguments.
|
||||
/// </summary>
|
||||
/// <param name="logDirectory">The log file directory.</param>
|
||||
public LoggingConfiguration(string logDirectory)
|
||||
: this(logDirectory, DefaultLogFileNameFormat, DefaultLogFileNameFormatArguments)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoggingConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logDirectory">The log file directory.</param>
|
||||
/// <param name="logFileNameFormat">The log file name format.</param>
|
||||
/// <param name="logFileNameFormatArguments">The log file name format arguments as a comma delimited string.</param>
|
||||
public LoggingConfiguration(string logDirectory, string logFileNameFormat, string logFileNameFormatArguments)
|
||||
{
|
||||
LogDirectory = logDirectory;
|
||||
LogFileNameFormat = logFileNameFormat;
|
||||
_logFileNameFormatArguments = logFileNameFormatArguments;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string LogDirectory { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string LogFileNameFormat { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string[] GetLogFileNameFormatArguments() => _logFileNameFormatArguments.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.Select(GetValue)
|
||||
.ToArray();
|
||||
|
||||
private static string GetValue(string arg) =>
|
||||
arg switch
|
||||
{
|
||||
MachineNameFileFormatArgument => Environment.MachineName,
|
||||
EnvironmentNameFileFormatArgument => Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Extensions;
|
||||
@@ -290,8 +288,6 @@ public abstract class ContentBase : TreeEntityBase, IContentBase
|
||||
// set on variant content type
|
||||
if (ContentType.VariesByCulture())
|
||||
{
|
||||
culture = culture.EnsureCultureCode();
|
||||
|
||||
// invariant is ok
|
||||
if (culture.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -301,11 +297,11 @@ public abstract class ContentBase : TreeEntityBase, IContentBase
|
||||
// clear
|
||||
else if (name.IsNullOrWhiteSpace())
|
||||
{
|
||||
ClearCultureInfo(culture);
|
||||
ClearCultureInfo(culture!);
|
||||
}
|
||||
|
||||
// set
|
||||
else if (GetCultureName(culture) != name)
|
||||
else
|
||||
{
|
||||
this.SetCultureInfo(culture!, name, DateTime.Now);
|
||||
}
|
||||
@@ -326,6 +322,11 @@ public abstract class ContentBase : TreeEntityBase, IContentBase
|
||||
|
||||
private void ClearCultureInfo(string culture)
|
||||
{
|
||||
if (culture == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(culture));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(culture))
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture));
|
||||
@@ -454,13 +455,10 @@ public abstract class ContentBase : TreeEntityBase, IContentBase
|
||||
$"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\".");
|
||||
}
|
||||
|
||||
culture = culture.EnsureCultureCode();
|
||||
var updated = property.SetValue(value, culture, segment);
|
||||
if (updated)
|
||||
{
|
||||
// bump the culture to be flagged for updating
|
||||
this.TouchCulture(culture);
|
||||
}
|
||||
property?.SetValue(value, culture, segment);
|
||||
|
||||
// bump the culture to be flagged for updating
|
||||
this.TouchCulture(culture);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
public abstract class ContentEditingModelBase
|
||||
{
|
||||
public IEnumerable<PropertyValueModel> Properties { get; set; } = Array.Empty<PropertyValueModel>();
|
||||
public string? InvariantName { get; set; }
|
||||
|
||||
public IEnumerable<PropertyValueModel> InvariantProperties { get; set; } = Array.Empty<PropertyValueModel>();
|
||||
|
||||
public IEnumerable<VariantModel> Variants { get; set; } = Array.Empty<VariantModel>();
|
||||
}
|
||||
|
||||
@@ -5,8 +5,4 @@ public class PropertyValueModel
|
||||
public required string Alias { get; set; }
|
||||
|
||||
public required object? Value { get; set; }
|
||||
|
||||
public string? Culture { get; set; }
|
||||
|
||||
public string? Segment { get; set; }
|
||||
}
|
||||
|
||||
@@ -7,4 +7,6 @@ public class VariantModel
|
||||
public string? Segment { get; set; }
|
||||
|
||||
public required string Name { get; set; }
|
||||
|
||||
public required IEnumerable<PropertyValueModel> Properties { get; set; }
|
||||
}
|
||||
|
||||
@@ -162,8 +162,7 @@ public abstract class BeingDirtyBase : IRememberBeingDirty
|
||||
/// <param name="propertyName">The property name.</param>
|
||||
/// <param name="comparer">A comparer to compare property values.</param>
|
||||
/// <param name="changed">A value indicating whether we know values have changed and no comparison is required.</param>
|
||||
/// <returns>True if a change was detected, false otherwise.</returns>
|
||||
protected bool DetectChanges<T>(T value, T orig, string propertyName, IEqualityComparer<T> comparer, bool changed)
|
||||
protected void DetectChanges<T>(T value, T orig, string propertyName, IEqualityComparer<T> comparer, bool changed)
|
||||
{
|
||||
// compare values
|
||||
changed = _withChanges && (changed || !comparer.Equals(orig, value));
|
||||
@@ -173,8 +172,6 @@ public abstract class BeingDirtyBase : IRememberBeingDirty
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -32,12 +32,7 @@ public interface IProperty : IEntity, IRememberBeingDirty
|
||||
/// <summary>
|
||||
/// Sets a value.
|
||||
/// </summary>
|
||||
/// <returns>true if the value was set (updated), false otherwise.</returns>
|
||||
/// <remarks>
|
||||
/// A false return value does not indicate failure, but rather that the property value was not changed
|
||||
/// (i.e. the value passed in was equal to the current property value).
|
||||
/// </remarks>
|
||||
bool SetValue(object? value, string? culture = null, string? segment = null);
|
||||
void SetValue(object? value, string? culture = null, string? segment = null);
|
||||
|
||||
void PublishValues(string? culture = "*", string segment = "*");
|
||||
|
||||
|
||||
@@ -257,8 +257,10 @@ public class Property : EntityBase, IProperty
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SetValue(object? value, string? culture = null, string? segment = null)
|
||||
/// <summary>
|
||||
/// Sets a value.
|
||||
/// </summary>
|
||||
public void SetValue(object? value, string? culture = null, string? segment = null)
|
||||
{
|
||||
culture = culture?.NullOrWhiteSpaceAsNull();
|
||||
segment = segment?.NullOrWhiteSpaceAsNull();
|
||||
@@ -271,7 +273,6 @@ public class Property : EntityBase, IProperty
|
||||
|
||||
(IPropertyValue? pvalue, var change) = GetPValue(culture, segment, true);
|
||||
|
||||
var changed = false;
|
||||
if (pvalue is not null)
|
||||
{
|
||||
var origValue = pvalue.EditedValue;
|
||||
@@ -279,10 +280,8 @@ public class Property : EntityBase, IProperty
|
||||
|
||||
pvalue.EditedValue = setValue;
|
||||
|
||||
changed = DetectChanges(setValue, origValue, nameof(Values), PropertyValueComparer, change);
|
||||
DetectChanges(setValue, origValue, nameof(Values), PropertyValueComparer, change);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
public object? ConvertAssignedValue(object? value) =>
|
||||
|
||||
@@ -8,7 +8,7 @@ public sealed class PropertyValidationContext
|
||||
|
||||
public required IEnumerable<string> CulturesBeingValidated { get; init; }
|
||||
|
||||
public required IEnumerable<string?> SegmentsBeingValidated { get; init; }
|
||||
public required IEnumerable<string> SegmentsBeingValidated { get; init; }
|
||||
|
||||
public static PropertyValidationContext Empty() => new()
|
||||
{
|
||||
|
||||
@@ -3,29 +3,23 @@ using Umbraco.Cms.Core.Security;
|
||||
namespace Umbraco.Cms.Core.Persistence.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for external logins with Guid as key, so it can be shared for members and users.
|
||||
/// Repository for external logins with Guid as key, so it can be shared for members and users
|
||||
/// </summary>
|
||||
public interface IExternalLoginWithKeyRepository : IReadWriteQueryRepository<int, IIdentityUserLogin>,
|
||||
IQueryRepository<IIdentityUserToken>
|
||||
{
|
||||
/// <summary>
|
||||
/// Replaces all external login providers for the user/member key.
|
||||
/// Replaces all external login providers for the user/member key
|
||||
/// </summary>
|
||||
void Save(Guid userOrMemberKey, IEnumerable<IExternalLogin> logins);
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all external login provider tokens for the providers specified for the user/member key.
|
||||
/// Replaces all external login provider tokens for the providers specified for the user/member key
|
||||
/// </summary>
|
||||
void Save(Guid userOrMemberKey, IEnumerable<IExternalLoginToken> tokens);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all external logins for the specified the user/member key.
|
||||
/// Deletes all external logins for the specified the user/member key
|
||||
/// </summary>
|
||||
void DeleteUserLogins(Guid userOrMemberKey);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes external logins that aren't associated with the current collection of providers.
|
||||
/// </summary>
|
||||
/// <param name="currentLoginProviders">The names of the currently configured providers.</param>
|
||||
void DeleteUserLoginsForRemovedProviders(IEnumerable<string> currentLoginProviders) { }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Linq.Expressions;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
|
||||
@@ -160,10 +160,4 @@ public interface IUserRepository : IReadWriteQueryRepository<Guid, IUser>
|
||||
bool RemoveClientId(int id, string clientId);
|
||||
|
||||
IUser? GetByClientId(string clientId);
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates sessions for users that aren't associated with the current collection of providers.
|
||||
/// </summary>
|
||||
/// <param name="currentLoginProviders">The names of the currently configured providers.</param>
|
||||
void InvalidateSessionsForRemovedProviders(IEnumerable<string> currentLoginProviders) { }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
|
||||
@@ -15,27 +12,14 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
// TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented
|
||||
// by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection
|
||||
|
||||
private readonly ILogger<DataValueReferenceFactoryCollection> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items)
|
||||
: this(
|
||||
items,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<DataValueReferenceFactoryCollection>>())
|
||||
: base(items)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items, ILogger<DataValueReferenceFactoryCollection> logger)
|
||||
: base(items) => _logger = logger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique references from the specified properties.
|
||||
/// </summary>
|
||||
@@ -49,7 +33,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
var references = new HashSet<UmbracoEntityReference>();
|
||||
|
||||
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
|
||||
foreach (IGrouping<string, IReadOnlyCollection<IPropertyValue>> propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
|
||||
foreach (var propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
|
||||
{
|
||||
if (!propertyEditors.TryGet(propertyValuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
|
||||
{
|
||||
@@ -64,7 +48,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
values.Add(propertyValue.PublishedValue);
|
||||
}
|
||||
|
||||
references.UnionWith(GetReferences(dataEditor, values, propertyValuesByPropertyEditorAlias.Key));
|
||||
references.UnionWith(GetReferences(dataEditor, values));
|
||||
}
|
||||
|
||||
return references;
|
||||
@@ -90,18 +74,14 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
/// The references.
|
||||
/// </returns>
|
||||
public ISet<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values) =>
|
||||
GetReferencesEnumerable(dataEditor, values, null).ToHashSet();
|
||||
|
||||
private ISet<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values, string propertyEditorAlias) =>
|
||||
GetReferencesEnumerable(dataEditor, values, propertyEditorAlias).ToHashSet();
|
||||
|
||||
private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable<object?> values, string? propertyEditorAlias)
|
||||
GetReferencesEnumerable(dataEditor, values).ToHashSet();
|
||||
private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable<object?> values)
|
||||
{
|
||||
// TODO: We will need to change this once we support tracking via variants/segments
|
||||
// for now, we are tracking values from ALL variants
|
||||
if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
|
||||
{
|
||||
foreach (UmbracoEntityReference reference in GetReferencesFromPropertyValues(values, dataValueReference, propertyEditorAlias))
|
||||
foreach (UmbracoEntityReference reference in values.SelectMany(dataValueReference.GetReferences))
|
||||
{
|
||||
yield return reference;
|
||||
}
|
||||
@@ -127,38 +107,6 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<UmbracoEntityReference> GetReferencesFromPropertyValues(IEnumerable<object?> values, IDataValueReference dataValueReference, string? propertyEditorAlias)
|
||||
{
|
||||
var result = new List<UmbracoEntityReference>();
|
||||
foreach (var value in values)
|
||||
{
|
||||
// When property editors on data types are changed, we could have values that are incompatible with the new editor.
|
||||
// Leading to issues such as:
|
||||
// - https://github.com/umbraco/Umbraco-CMS/issues/17628
|
||||
// - https://github.com/umbraco/Umbraco-CMS/issues/17725
|
||||
// Although some changes like this are not intended to be compatible, we should handle them gracefully and not
|
||||
// error in retrieving references, which would prevent manipulating or deleting the content that uses the data type.
|
||||
try
|
||||
{
|
||||
IEnumerable<UmbracoEntityReference> references = dataValueReference.GetReferences(value);
|
||||
result.AddRange(references);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the exception but don't throw, continue with the next value.
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error getting references from value {Value} with data editor {DataEditor} and property editor alias {PropertyEditorAlias}.",
|
||||
value,
|
||||
dataValueReference.GetType().FullName,
|
||||
propertyEditorAlias ?? "n/a");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all relation type aliases that are automatically tracked.
|
||||
/// </summary>
|
||||
@@ -166,10 +114,6 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
/// <returns>
|
||||
/// All relation type aliases that are automatically tracked.
|
||||
/// </returns>
|
||||
[Obsolete("Use GetAllAutomaticRelationTypesAliases. This will be removed in Umbraco 15.")]
|
||||
public ISet<string> GetAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors) =>
|
||||
GetAllAutomaticRelationTypesAliases(propertyEditors);
|
||||
|
||||
public ISet<string> GetAllAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
// Always add default automatic relation types
|
||||
|
||||
@@ -15,69 +15,24 @@ using Umbraco.Extensions;
|
||||
namespace Umbraco.Cms.Core.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// Provides URLs.
|
||||
/// Provides urls.
|
||||
/// </summary>
|
||||
public class NewDefaultUrlProvider : IUrlProvider
|
||||
{
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IPublishedContentCache _publishedContentCache;
|
||||
private readonly IDomainCache _domainCache;
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
private readonly IDocumentUrlService _documentUrlService;
|
||||
private readonly IDocumentNavigationQueryService _navigationQueryService;
|
||||
private readonly IPublishedContentStatusFilteringService _publishedContentStatusFilteringService;
|
||||
private readonly ILocalizedTextService? _localizedTextService;
|
||||
private readonly ILogger<DefaultUrlProvider> _logger;
|
||||
private readonly ISiteDomainMapper _siteDomainMapper;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly UriUtility _uriUtility;
|
||||
private RequestHandlerSettings _requestSettings;
|
||||
private readonly ILanguageService _languageService;
|
||||
|
||||
// TODO (V17): When removing the obsolete constructors, remove the unused localizationService parameter from the constructor (we can't do it now
|
||||
// because it is used in the obsolete constructors and leads to an ambigious constructor error).
|
||||
// See also if we can make GetUrlFromRoute asynchronous and avoid the GetAwaiter().GetResult() in when using ILanguageService.
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewDefaultUrlProvider"/> class.
|
||||
/// </summary>
|
||||
public NewDefaultUrlProvider(
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings,
|
||||
ILogger<DefaultUrlProvider> logger,
|
||||
ISiteDomainMapper siteDomainMapper,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
UriUtility uriUtility,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
#pragma warning disable IDE0060 // Remove unused parameter
|
||||
ILocalizationService localizationService,
|
||||
#pragma warning restore IDE0060 // Remove unused parameter
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
IPublishedContentCache publishedContentCache,
|
||||
IDomainCache domainCache,
|
||||
IIdKeyMap idKeyMap,
|
||||
IDocumentUrlService documentUrlService,
|
||||
IDocumentNavigationQueryService navigationQueryService,
|
||||
IPublishedContentStatusFilteringService publishedContentStatusFilteringService,
|
||||
ILanguageService languageService)
|
||||
{
|
||||
_requestSettings = requestSettings.CurrentValue;
|
||||
_logger = logger;
|
||||
_siteDomainMapper = siteDomainMapper;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_uriUtility = uriUtility;
|
||||
_publishedContentCache = publishedContentCache;
|
||||
_domainCache = domainCache;
|
||||
_idKeyMap = idKeyMap;
|
||||
_documentUrlService = documentUrlService;
|
||||
_navigationQueryService = navigationQueryService;
|
||||
_publishedContentStatusFilteringService = publishedContentStatusFilteringService;
|
||||
_languageService = languageService;
|
||||
|
||||
requestSettings.OnChange(x => _requestSettings = x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewDefaultUrlProvider"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Use the non-obsolete constructor. Scheduled for removal in V17.")]
|
||||
public NewDefaultUrlProvider(
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings,
|
||||
ILogger<DefaultUrlProvider> logger,
|
||||
@@ -91,26 +46,23 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
IDocumentUrlService documentUrlService,
|
||||
IDocumentNavigationQueryService navigationQueryService,
|
||||
IPublishedContentStatusFilteringService publishedContentStatusFilteringService)
|
||||
: this(
|
||||
requestSettings,
|
||||
logger,
|
||||
siteDomainMapper,
|
||||
umbracoContextAccessor,
|
||||
uriUtility,
|
||||
localizationService,
|
||||
publishedContentCache,
|
||||
domainCache,
|
||||
idKeyMap,
|
||||
documentUrlService,
|
||||
navigationQueryService,
|
||||
publishedContentStatusFilteringService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILanguageService>())
|
||||
{
|
||||
_requestSettings = requestSettings.CurrentValue;
|
||||
_logger = logger;
|
||||
_siteDomainMapper = siteDomainMapper;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_uriUtility = uriUtility;
|
||||
_localizationService = localizationService;
|
||||
_publishedContentCache = publishedContentCache;
|
||||
_domainCache = domainCache;
|
||||
_idKeyMap = idKeyMap;
|
||||
_documentUrlService = documentUrlService;
|
||||
_navigationQueryService = navigationQueryService;
|
||||
_publishedContentStatusFilteringService = publishedContentStatusFilteringService;
|
||||
|
||||
requestSettings.OnChange(x => _requestSettings = x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewDefaultUrlProvider"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Use the non-obsolete constructor. Scheduled for removal in V17.")]
|
||||
public NewDefaultUrlProvider(
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings,
|
||||
@@ -140,6 +92,8 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
{
|
||||
}
|
||||
|
||||
#region GetOtherUrls
|
||||
|
||||
/// <summary>
|
||||
/// Gets the other URLs of a published content.
|
||||
/// </summary>
|
||||
@@ -154,14 +108,14 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
/// </remarks>
|
||||
public virtual IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
|
||||
{
|
||||
Attempt<Guid> keyAttempt = _idKeyMap.GetKeyForId(id, UmbracoObjectTypes.Document);
|
||||
var keyAttempt = _idKeyMap.GetKeyForId(id, UmbracoObjectTypes.Document);
|
||||
|
||||
if (keyAttempt.Success is false)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
Guid key = keyAttempt.Result;
|
||||
var key = keyAttempt.Result;
|
||||
|
||||
IPublishedContent? node = _publishedContentCache.GetById(key);
|
||||
if (node == null)
|
||||
@@ -202,7 +156,7 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
|
||||
// need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned)
|
||||
var pos = route.IndexOf('/', StringComparison.Ordinal);
|
||||
var path = pos == 0 ? route : route[pos..];
|
||||
var path = pos == 0 ? route : route.Substring(pos);
|
||||
|
||||
var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path));
|
||||
uri = _uriUtility.UriFromUmbraco(uri, _requestSettings);
|
||||
@@ -224,9 +178,17 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
private string GetLegacyRouteFormatById(Guid key, string? culture)
|
||||
{
|
||||
var isDraft = _umbracoContextAccessor.GetRequiredUmbracoContext().InPreviewMode;
|
||||
|
||||
|
||||
return _documentUrlService.GetLegacyRouteFormat(key, culture, isDraft);
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetUrl
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
|
||||
{
|
||||
@@ -255,9 +217,6 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
return GetUrlFromRoute(route, content.Id, current, mode, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL from the provided route.
|
||||
/// </summary>
|
||||
internal UrlInfo? GetUrlFromRoute(
|
||||
string? route,
|
||||
int id,
|
||||
@@ -267,7 +226,7 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(route) || route.Equals("#"))
|
||||
{
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.",
|
||||
@@ -289,7 +248,7 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
current,
|
||||
culture);
|
||||
|
||||
var defaultCulture = _languageService.GetDefaultIsoCodeAsync().GetAwaiter().GetResult();
|
||||
var defaultCulture = _localizationService.GetDefaultLanguageIsoCode();
|
||||
if (domainUri is not null ||
|
||||
string.IsNullOrEmpty(culture) ||
|
||||
culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase))
|
||||
@@ -301,6 +260,10 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utilities
|
||||
|
||||
private Uri AssembleUrl(DomainAndUri? domainUri, string path, Uri current, UrlMode mode)
|
||||
{
|
||||
Uri uri;
|
||||
@@ -368,4 +331,6 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
var path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2;
|
||||
return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -264,16 +264,7 @@ public sealed class AuditService : RepositoryService, IAuditService
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Will be moved to a new service in V17. Scheduled for removal in V18.")]
|
||||
public IAuditEntry Write(
|
||||
int performingUserId,
|
||||
string perfomingDetails,
|
||||
string performingIp,
|
||||
DateTime eventDateUtc,
|
||||
int affectedUserId,
|
||||
string? affectedDetails,
|
||||
string eventType,
|
||||
string eventDetails)
|
||||
public IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, int affectedUserId, string? affectedDetails, string eventType, string eventDetails)
|
||||
{
|
||||
if (performingUserId < 0 && performingUserId != Constants.Security.SuperUserId)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
@@ -37,21 +35,6 @@ internal sealed class ContentBlueprintEditingService
|
||||
return Task.FromResult(blueprint);
|
||||
}
|
||||
|
||||
public Task<IContent?> GetScaffoldedAsync(Guid key)
|
||||
{
|
||||
IContent? blueprint = ContentService.GetBlueprintById(key);
|
||||
if (blueprint is null)
|
||||
{
|
||||
return Task.FromResult<IContent?>(null);
|
||||
}
|
||||
|
||||
using ICoreScope scope = CoreScopeProvider.CreateCoreScope();
|
||||
scope.Notifications.Publish(new ContentScaffoldedNotification(blueprint, blueprint, Constants.System.Root, new EventMessages()));
|
||||
scope.Complete();
|
||||
|
||||
return Task.FromResult<IContent?>(blueprint);
|
||||
}
|
||||
|
||||
public async Task<Attempt<PagedModel<IContent>?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync(Guid contentTypeKey, int skip, int take)
|
||||
{
|
||||
IContentType? contentType = await ContentTypeService.GetAsync(contentTypeKey);
|
||||
@@ -86,7 +69,7 @@ internal sealed class ContentBlueprintEditingService
|
||||
|
||||
IContent blueprint = result.Result.Content!;
|
||||
|
||||
if (ValidateUniqueNames(createModel.Variants, blueprint) is false)
|
||||
if (ValidateUniqueName(createModel.InvariantName ?? string.Empty, blueprint) is false)
|
||||
{
|
||||
return Attempt.FailWithStatus(ContentEditingOperationStatus.DuplicateName, new ContentCreateResult());
|
||||
}
|
||||
@@ -133,7 +116,7 @@ internal sealed class ContentBlueprintEditingService
|
||||
return Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentUpdateResult());
|
||||
}
|
||||
|
||||
if (ValidateUniqueNames(updateModel.Variants, blueprint) is false)
|
||||
if (ValidateUniqueName(updateModel.InvariantName ?? string.Empty, blueprint) is false)
|
||||
{
|
||||
return Attempt.FailWithStatus(ContentEditingOperationStatus.DuplicateName, new ContentUpdateResult());
|
||||
}
|
||||
@@ -249,18 +232,4 @@ internal sealed class ContentBlueprintEditingService
|
||||
IEnumerable<IContent> existing = ContentService.GetBlueprintsForContentTypes(content.ContentTypeId);
|
||||
return existing.Any(c => c.Name == name && c.Id != content.Id) is false;
|
||||
}
|
||||
|
||||
private bool ValidateUniqueNames(IEnumerable<VariantModel> variants, IContent content)
|
||||
{
|
||||
IContent[] existing = ContentService.GetBlueprintsForContentTypes(content.ContentTypeId).ToArray();
|
||||
foreach (VariantModel variant in variants)
|
||||
{
|
||||
if (existing.Any(c => c.GetCultureName(variant.Culture) == variant.Name && c.Id != content.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,36 +318,34 @@ internal abstract class ContentEditingServiceBase<TContent, TContentType, TConte
|
||||
return null;
|
||||
}
|
||||
|
||||
if (contentType.VariesByNothing() && contentEditingModelBase.Variants.Any(v => v.Culture is null && v.Segment is null) is false)
|
||||
if (contentType.VariesByCulture() == false)
|
||||
{
|
||||
// does not vary by anything and is missing the invariant name = invalid
|
||||
operationStatus = ContentEditingOperationStatus.ContentTypeCultureVarianceMismatch;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (contentType.VariesByCulture() && contentEditingModelBase.Variants.Any(v => v.Culture is null))
|
||||
{
|
||||
// varies by culture with one or more variants not bound to a culture = invalid
|
||||
operationStatus = ContentEditingOperationStatus.ContentTypeCultureVarianceMismatch;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (contentType.VariesBySegment() && contentEditingModelBase.Variants.Any(v => v.Segment is null) is false)
|
||||
{
|
||||
// varies by segment with no default segment variants = invalid
|
||||
operationStatus = ContentEditingOperationStatus.ContentTypeSegmentVarianceMismatch;
|
||||
return null;
|
||||
if (contentEditingModelBase.InvariantName.IsNullOrWhiteSpace() || contentEditingModelBase.Variants.Any())
|
||||
{
|
||||
operationStatus = ContentEditingOperationStatus.ContentTypeCultureVarianceMismatch;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var propertyTypesByAlias = contentType.CompositionPropertyTypes.ToDictionary(pt => pt.Alias);
|
||||
var propertyValuesAndVariance = contentEditingModelBase
|
||||
.Properties
|
||||
.InvariantProperties
|
||||
.Select(pv => new
|
||||
{
|
||||
VariesByCulture = pv.Culture is not null,
|
||||
VariesBySegment = pv.Segment is not null,
|
||||
VariesByCulture = false,
|
||||
VariesBySegment = false,
|
||||
PropertyValue = pv
|
||||
})
|
||||
.Union(contentEditingModelBase
|
||||
.Variants
|
||||
.SelectMany(v => v
|
||||
.Properties
|
||||
.Select(vpv => new
|
||||
{
|
||||
VariesByCulture = true,
|
||||
VariesBySegment = v.Segment.IsNullOrWhiteSpace() == false,
|
||||
PropertyValue = vpv
|
||||
})))
|
||||
.ToArray();
|
||||
|
||||
// verify that all property values are defined as property types
|
||||
@@ -361,8 +359,7 @@ internal abstract class ContentEditingServiceBase<TContent, TContentType, TConte
|
||||
if (propertyValuesAndVariance.Any(pv =>
|
||||
{
|
||||
IPropertyType propertyType = propertyTypesByAlias[pv.PropertyValue.Alias];
|
||||
return (propertyType.VariesByCulture() != pv.VariesByCulture)
|
||||
|| (propertyType.VariesBySegment() is false && pv.VariesBySegment);
|
||||
return propertyType.VariesByCulture() != pv.VariesByCulture || propertyType.VariesBySegment() != pv.VariesBySegment;
|
||||
}))
|
||||
{
|
||||
operationStatus = ContentEditingOperationStatus.PropertyTypeNotFound;
|
||||
@@ -427,16 +424,10 @@ internal abstract class ContentEditingServiceBase<TContent, TContentType, TConte
|
||||
content.SetCultureName(name, culture);
|
||||
}
|
||||
}
|
||||
else if (contentType.VariesBySegment())
|
||||
{
|
||||
// this should be validated already so it's OK to throw an exception here
|
||||
content.Name = contentEditingModelBase.Variants.FirstOrDefault(v => v.Segment is null)?.Name
|
||||
?? throw new ArgumentException("Could not find the default segment variant", nameof(contentEditingModelBase));
|
||||
}
|
||||
else
|
||||
{
|
||||
// this should be validated already so it's OK to throw an exception here
|
||||
content.Name = contentEditingModelBase.Variants.FirstOrDefault(v => v.Culture is null && v.Segment is null)?.Name
|
||||
content.Name = contentEditingModelBase.InvariantName
|
||||
?? throw new ArgumentException("Could not find a culture invariant variant", nameof(contentEditingModelBase));
|
||||
}
|
||||
}
|
||||
@@ -446,19 +437,26 @@ internal abstract class ContentEditingServiceBase<TContent, TContentType, TConte
|
||||
// create a mapping dictionary for all content type property types by their property aliases
|
||||
Dictionary<string, IPropertyType> propertyTypesByAlias = GetPropertyTypesByAlias(contentType);
|
||||
|
||||
// remove any properties that do not exist on the content type
|
||||
PropertyValueModel[] propertyValues = contentEditingModelBase
|
||||
.Properties
|
||||
// flatten the invariant and variant property values from the model into one array, and remove any properties
|
||||
// that do not exist on the content type
|
||||
var propertyValues = contentEditingModelBase
|
||||
.InvariantProperties
|
||||
.Select(pv => new { Culture = (string?)null, Segment = (string?)null, Alias = pv.Alias, Value = pv.Value })
|
||||
.Union(contentEditingModelBase
|
||||
.Variants
|
||||
.SelectMany(v => v
|
||||
.Properties
|
||||
.Select(vpv => new { Culture = v.Culture, Segment = v.Segment, Alias = vpv.Alias, Value = vpv.Value })))
|
||||
.Where(propertyValue => propertyTypesByAlias.ContainsKey(propertyValue.Alias))
|
||||
.ToArray();
|
||||
|
||||
// update all properties on the content item
|
||||
foreach (PropertyValueModel propertyValue in propertyValues)
|
||||
foreach (var propertyValue in propertyValues)
|
||||
{
|
||||
// the following checks should already have been validated by now, so it's OK to throw exceptions here
|
||||
if(propertyTypesByAlias.TryGetValue(propertyValue.Alias, out IPropertyType? propertyType) == false
|
||||
|| (propertyType.VariesByCulture() && propertyValue.Culture.IsNullOrWhiteSpace())
|
||||
|| (propertyType.VariesBySegment() is false && propertyValue.Segment.IsNullOrWhiteSpace() is false))
|
||||
|| (propertyType.VariesBySegment() && propertyValue.Segment.IsNullOrWhiteSpace()))
|
||||
{
|
||||
throw new ArgumentException($"Culture or segment variance mismatch for property: {propertyValue.Alias}", nameof(contentEditingModelBase));
|
||||
}
|
||||
@@ -474,8 +472,8 @@ internal abstract class ContentEditingServiceBase<TContent, TContentType, TConte
|
||||
// create a mapping dictionary for all content type property types by their property aliases
|
||||
Dictionary<string, IPropertyType> propertyTypesByAlias = GetPropertyTypesByAlias(contentType);
|
||||
var knownPropertyAliases = contentEditingModelBase
|
||||
.Properties
|
||||
.Select(pv => pv.Alias)
|
||||
.InvariantProperties.Select(pv => pv.Alias)
|
||||
.Union(contentEditingModelBase.Variants.SelectMany(v => v.Properties.Select(vpv => vpv.Alias)))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
|
||||
@@ -238,33 +238,28 @@ internal sealed class ContentPublishingService : IContentPublishingService
|
||||
|
||||
private async Task<ContentValidationResult> ValidateCurrentContentAsync(IContent content, string[] cultures)
|
||||
{
|
||||
IEnumerable<string?> effectiveCultures = content.ContentType.VariesByCulture()
|
||||
? cultures.Union([null])
|
||||
: [null];
|
||||
|
||||
// Would be better to be able to use a mapper/factory, but currently all that functionality is very much presentation logic.
|
||||
var model = new ContentUpdateModel()
|
||||
{
|
||||
InvariantName = content.Name,
|
||||
// NOTE KJA: this needs redoing; we need to make an informed decision whether to include invariant properties, depending on if editing invariant properties is allowed on all variants, or if the default language is included in cultures
|
||||
Properties = effectiveCultures.SelectMany(culture =>
|
||||
content.Properties.Select(property => property.PropertyType.VariesByCulture() == (culture is not null)
|
||||
? new PropertyValueModel
|
||||
{
|
||||
Alias = property.Alias,
|
||||
Value = property.GetValue(culture: culture, segment: null, published: false),
|
||||
Culture = culture
|
||||
}
|
||||
: null)
|
||||
.WhereNotNull())
|
||||
.ToArray(),
|
||||
InvariantProperties = content.Properties.Where(x => x.PropertyType.VariesByCulture() is false).Select(x => new PropertyValueModel()
|
||||
{
|
||||
Alias = x.Alias,
|
||||
Value = x.GetValue()
|
||||
}),
|
||||
Variants = cultures.Select(culture => new VariantModel()
|
||||
{
|
||||
Name = content.GetPublishName(culture) ?? string.Empty,
|
||||
Culture = culture,
|
||||
Segment = null
|
||||
}).ToArray()
|
||||
Segment = null,
|
||||
Properties = content.Properties.Where(prop => prop.PropertyType.VariesByCulture()).Select(prop => new PropertyValueModel()
|
||||
{
|
||||
Alias = prop.Alias,
|
||||
Value = prop.GetValue(culture: culture, segment: null, published: false)
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
IContentType? contentType = _contentTypeService.Get(content.ContentType.Key)!;
|
||||
ContentValidationResult validationResult = await _contentValidationService.ValidatePropertiesAsync(model, contentType, cultures);
|
||||
return validationResult;
|
||||
|
||||
@@ -1108,8 +1108,14 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
if (culturesChanging != null)
|
||||
{
|
||||
var langs = GetLanguageDetailsForAuditEntry(culturesChanging);
|
||||
Audit(AuditType.SaveVariant, userId.Value, content.Id, $"Saved languages: {langs}", langs);
|
||||
IEnumerable<string>? languages = _languageRepository.GetMany()?
|
||||
.Where(x => culturesChanging.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName);
|
||||
if (languages is not null)
|
||||
{
|
||||
var langs = string.Join(", ", languages);
|
||||
Audit(AuditType.SaveVariant, userId.Value, content.Id, $"Saved languages: {langs}", langs);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1184,8 +1190,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
throw new ArgumentException("Cultures cannot be null or whitespace", nameof(cultures));
|
||||
}
|
||||
|
||||
cultures = cultures.Select(x => x.EnsureCultureCode()!).ToArray();
|
||||
|
||||
EventMessages evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
// we need to guard against unsaved changes before proceeding; the content will be saved, but we're not firing any saved notifications
|
||||
@@ -1265,7 +1269,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
EventMessages evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
culture = culture?.NullOrWhiteSpaceAsNull().EnsureCultureCode();
|
||||
culture = culture?.NullOrWhiteSpaceAsNull();
|
||||
|
||||
PublishedState publishedState = content.PublishedState;
|
||||
if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
|
||||
@@ -1621,7 +1625,9 @@ public class ContentService : RepositoryService, IContentService
|
||||
if (culturesUnpublishing != null)
|
||||
{
|
||||
// This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
|
||||
var langs = GetLanguageDetailsForAuditEntry(allLangs, culturesUnpublishing);
|
||||
var langs = string.Join(", ", allLangs
|
||||
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
|
||||
|
||||
if (publishResult == null)
|
||||
@@ -1701,7 +1707,9 @@ public class ContentService : RepositoryService, IContentService
|
||||
case PublishResultType.SuccessPublishCulture:
|
||||
if (culturesPublishing != null)
|
||||
{
|
||||
var langs = GetLanguageDetailsForAuditEntry(allLangs, culturesPublishing);
|
||||
var langs = string.Join(", ", allLangs
|
||||
.Where(x => culturesPublishing.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs);
|
||||
}
|
||||
|
||||
@@ -1709,7 +1717,9 @@ public class ContentService : RepositoryService, IContentService
|
||||
case PublishResultType.SuccessUnpublishCulture:
|
||||
if (culturesUnpublishing != null)
|
||||
{
|
||||
var langs = GetLanguageDetailsForAuditEntry(allLangs, culturesUnpublishing);
|
||||
var langs = string.Join(", ", allLangs
|
||||
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
|
||||
}
|
||||
|
||||
@@ -1731,7 +1741,9 @@ public class ContentService : RepositoryService, IContentService
|
||||
{
|
||||
if (culturesChanging != null)
|
||||
{
|
||||
var langs = GetLanguageDetailsForAuditEntry(allLangs, culturesChanging);
|
||||
var langs = string.Join(", ", allLangs
|
||||
.Where(x => culturesChanging.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs);
|
||||
}
|
||||
else
|
||||
@@ -2065,7 +2077,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
cultures = ["*"];
|
||||
}
|
||||
|
||||
return cultures.Select(x => x.EnsureCultureCode()!).ToArray();
|
||||
return cultures;
|
||||
}
|
||||
|
||||
private static bool ProvidedCulturesIndicatePublishAll(string[] cultures) => cultures.Length == 0 || (cultures.Length == 1 && cultures[0] == "invariant");
|
||||
@@ -3086,17 +3098,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
private void Audit(AuditType type, int userId, int objectId, string? message = null, string? parameters = null) =>
|
||||
_auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Document.GetName(), message, parameters));
|
||||
|
||||
private string GetLanguageDetailsForAuditEntry(IEnumerable<string> affectedCultures)
|
||||
=> GetLanguageDetailsForAuditEntry(_languageRepository.GetMany(), affectedCultures);
|
||||
|
||||
private static string GetLanguageDetailsForAuditEntry(IEnumerable<ILanguage> languages, IEnumerable<string> affectedCultures)
|
||||
{
|
||||
IEnumerable<string> languageIsoCodes = languages
|
||||
.Where(x => affectedCultures.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.IsoCode);
|
||||
return string.Join(", ", languageIsoCodes);
|
||||
}
|
||||
|
||||
private static bool IsDefaultCulture(IReadOnlyCollection<ILanguage>? langs, string culture) =>
|
||||
langs?.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture)) ?? false;
|
||||
|
||||
|
||||
@@ -325,8 +325,8 @@ internal abstract class ContentTypeEditingServiceBase<TContentType, TContentType
|
||||
// if the content type keys are already set as compositions, don't perform any additional validation
|
||||
// - this covers an edge case where compositions are configured for a content type before child content types are created
|
||||
if (contentType is not null && contentType.ContentTypeComposition
|
||||
.Select(c => c.Key)
|
||||
.ContainsAll(compositionKeys))
|
||||
.Select(c => c.Key)
|
||||
.ContainsAll(compositionKeys))
|
||||
{
|
||||
return ContentTypeOperationStatus.Success;
|
||||
}
|
||||
|
||||
@@ -31,17 +31,9 @@ internal abstract class ContentValidationServiceBase<TContentType>
|
||||
|
||||
IPropertyType[] contentTypePropertyTypes = contentType.CompositionPropertyTypes.ToArray();
|
||||
IPropertyType[] invariantPropertyTypes = contentTypePropertyTypes
|
||||
.Where(propertyType => propertyType.Variations == ContentVariation.Nothing)
|
||||
.ToArray();
|
||||
IPropertyType[] cultureVariantPropertyTypes = contentTypePropertyTypes
|
||||
.Where(propertyType => propertyType.Variations == ContentVariation.Culture)
|
||||
.ToArray();
|
||||
IPropertyType[] segmentVariantPropertyTypes = contentTypePropertyTypes
|
||||
.Where(propertyType => propertyType.Variations == ContentVariation.Segment)
|
||||
.ToArray();
|
||||
IPropertyType[] cultureAndSegmentVariantPropertyTypes = contentTypePropertyTypes
|
||||
.Where(propertyType => propertyType.Variations == ContentVariation.CultureAndSegment)
|
||||
.Where(propertyType => propertyType.VariesByNothing())
|
||||
.ToArray();
|
||||
IPropertyType[] variantPropertyTypes = contentTypePropertyTypes.Except(invariantPropertyTypes).ToArray();
|
||||
|
||||
var cultures = culturesToValidate?.WhereNotNull().Except(["*"]).ToArray();
|
||||
if (cultures?.Any() is not true)
|
||||
@@ -50,14 +42,11 @@ internal abstract class ContentValidationServiceBase<TContentType>
|
||||
}
|
||||
|
||||
// we don't have any managed segments, so we have to make do with the ones passed in the model
|
||||
var segments =
|
||||
new string?[] { null }
|
||||
.Union(contentEditingModelBase.Variants
|
||||
.Where(variant => variant.Culture is null || cultures.Contains(variant.Culture))
|
||||
.DistinctBy(variant => variant.Segment).Select(variant => variant.Segment)
|
||||
.WhereNotNull()
|
||||
)
|
||||
.ToArray();
|
||||
var segments = contentEditingModelBase.Variants
|
||||
.Where(variant => variant.Culture is null || cultures.Contains(variant.Culture))
|
||||
.DistinctBy(variant => variant.Segment).Select(variant => variant.Segment)
|
||||
.WhereNotNull()
|
||||
.ToArray();
|
||||
|
||||
foreach (IPropertyType propertyType in invariantPropertyTypes)
|
||||
{
|
||||
@@ -66,45 +55,16 @@ internal abstract class ContentValidationServiceBase<TContentType>
|
||||
Culture = null, Segment = null, CulturesBeingValidated = cultures, SegmentsBeingValidated = segments
|
||||
};
|
||||
|
||||
PropertyValueModel? propertyValueModel = contentEditingModelBase
|
||||
.Properties
|
||||
.FirstOrDefault(propertyValue => propertyValue.Alias == propertyType.Alias && propertyValue.Culture is null && propertyValue.Segment is null);
|
||||
PropertyValueModel? propertyValueModel = contentEditingModelBase.InvariantProperties.FirstOrDefault(propertyValue => propertyValue.Alias == propertyType.Alias);
|
||||
validationErrors.AddRange(ValidateProperty(propertyType, propertyValueModel, validationContext));
|
||||
}
|
||||
|
||||
foreach (IPropertyType propertyType in cultureVariantPropertyTypes)
|
||||
if (variantPropertyTypes.Any() is false)
|
||||
{
|
||||
foreach (var culture in cultures)
|
||||
{
|
||||
var validationContext = new PropertyValidationContext
|
||||
{
|
||||
Culture = culture, Segment = null, CulturesBeingValidated = cultures, SegmentsBeingValidated = segments
|
||||
};
|
||||
|
||||
PropertyValueModel? propertyValueModel = contentEditingModelBase
|
||||
.Properties
|
||||
.FirstOrDefault(propertyValue => propertyValue.Alias == propertyType.Alias && propertyValue.Culture.InvariantEquals(culture) && propertyValue.Segment is null);
|
||||
validationErrors.AddRange(ValidateProperty(propertyType, propertyValueModel, validationContext));
|
||||
}
|
||||
return new ContentValidationResult { ValidationErrors = validationErrors };
|
||||
}
|
||||
|
||||
foreach (IPropertyType propertyType in segmentVariantPropertyTypes)
|
||||
{
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
var validationContext = new PropertyValidationContext
|
||||
{
|
||||
Culture = null, Segment = segment, CulturesBeingValidated = cultures, SegmentsBeingValidated = segments
|
||||
};
|
||||
|
||||
PropertyValueModel? propertyValueModel = contentEditingModelBase
|
||||
.Properties
|
||||
.FirstOrDefault(propertyValue => propertyValue.Alias == propertyType.Alias && propertyValue.Culture is null && propertyValue.Segment.InvariantEquals(segment));
|
||||
validationErrors.AddRange(ValidateProperty(propertyType, propertyValueModel, validationContext));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (IPropertyType propertyType in cultureAndSegmentVariantPropertyTypes)
|
||||
foreach (IPropertyType propertyType in variantPropertyTypes)
|
||||
{
|
||||
foreach (var culture in cultures)
|
||||
{
|
||||
@@ -116,8 +76,10 @@ internal abstract class ContentValidationServiceBase<TContentType>
|
||||
};
|
||||
|
||||
PropertyValueModel? propertyValueModel = contentEditingModelBase
|
||||
.Variants
|
||||
.FirstOrDefault(variant => string.Equals(variant.Culture, culture, StringComparison.InvariantCultureIgnoreCase) && string.Equals(segment, variant.Segment, StringComparison.InvariantCultureIgnoreCase))?
|
||||
.Properties
|
||||
.FirstOrDefault(propertyValue => propertyValue.Alias == propertyType.Alias && propertyValue.Culture.InvariantEquals(culture) && propertyValue.Segment.InvariantEquals(segment));
|
||||
.FirstOrDefault(propertyValue => propertyValue.Alias == propertyType.Alias);
|
||||
validationErrors.AddRange(ValidateProperty(propertyType, propertyValueModel, validationContext));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,51 +44,26 @@ public class DocumentUrlService : IDocumentUrlService
|
||||
/// <summary>
|
||||
/// Model used to cache a single published document along with all it's URL segments.
|
||||
/// </summary>
|
||||
/// <remarks>Internal for the purpose of unit and benchmark testing.</remarks>
|
||||
internal class PublishedDocumentUrlSegments
|
||||
private class PublishedDocumentUrlSegments
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the document key.
|
||||
/// </summary>
|
||||
public required Guid DocumentKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the language Id.
|
||||
/// </summary>
|
||||
public required int LanguageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of <see cref="UrlSegment"/> for the document, language and state.
|
||||
/// </summary>
|
||||
public required IList<UrlSegment> UrlSegments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the document is a draft version or not.
|
||||
/// </summary>
|
||||
public required bool IsDraft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Model used to represent a URL segment for a document in the cache.
|
||||
/// </summary>
|
||||
public class UrlSegment
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UrlSegment"/> class.
|
||||
/// </summary>
|
||||
public UrlSegment(string segment, bool isPrimary)
|
||||
{
|
||||
Segment = segment;
|
||||
IsPrimary = isPrimary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL segment string.
|
||||
/// </summary>
|
||||
public string Segment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this URL segment is the primary one for the document, language and state.
|
||||
/// </summary>
|
||||
public bool IsPrimary { get; }
|
||||
}
|
||||
}
|
||||
@@ -193,40 +168,45 @@ public class DocumentUrlService : IDocumentUrlService
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a collection of <see cref="PublishedDocumentUrlSegment"/> to a collection of <see cref="PublishedDocumentUrlSegments"/> for caching purposes.
|
||||
/// </summary>
|
||||
/// <param name="publishedDocumentUrlSegments">The collection of <see cref="PublishedDocumentUrlSegment"/> retrieved from the database on startup.</param>
|
||||
/// <returns>The collection of cache models.</returns>
|
||||
/// <remarks>Internal for the purpose of unit and benchmark testing.</remarks>
|
||||
internal static IEnumerable<PublishedDocumentUrlSegments> ConvertToCacheModel(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSegments)
|
||||
private static IEnumerable<PublishedDocumentUrlSegments> ConvertToCacheModel(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSegments)
|
||||
{
|
||||
var cacheModels = new Dictionary<(Guid DocumentKey, int LanguageId, bool IsDraft), PublishedDocumentUrlSegments>();
|
||||
|
||||
var cacheModels = new List<PublishedDocumentUrlSegments>();
|
||||
foreach (PublishedDocumentUrlSegment model in publishedDocumentUrlSegments)
|
||||
{
|
||||
(Guid DocumentKey, int LanguageId, bool IsDraft) key = (model.DocumentKey, model.LanguageId, model.IsDraft);
|
||||
|
||||
if (!cacheModels.TryGetValue(key, out PublishedDocumentUrlSegments? existingCacheModel))
|
||||
PublishedDocumentUrlSegments? existingCacheModel = GetModelFromCache(cacheModels, model);
|
||||
if (existingCacheModel is null)
|
||||
{
|
||||
cacheModels[key] = new PublishedDocumentUrlSegments
|
||||
cacheModels.Add(new PublishedDocumentUrlSegments
|
||||
{
|
||||
DocumentKey = model.DocumentKey,
|
||||
LanguageId = model.LanguageId,
|
||||
UrlSegments = [new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary)],
|
||||
IsDraft = model.IsDraft,
|
||||
};
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (existingCacheModel.UrlSegments.Any(x => x.Segment == model.UrlSegment) is false)
|
||||
{
|
||||
existingCacheModel.UrlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary));
|
||||
}
|
||||
existingCacheModel.UrlSegments = GetUpdatedUrlSegments(existingCacheModel.UrlSegments, model.UrlSegment, model.IsPrimary);
|
||||
}
|
||||
}
|
||||
|
||||
return cacheModels.Values;
|
||||
return cacheModels;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static PublishedDocumentUrlSegments? GetModelFromCache(List<PublishedDocumentUrlSegments> cacheModels, PublishedDocumentUrlSegment model)
|
||||
=> cacheModels
|
||||
.SingleOrDefault(x => x.DocumentKey == model.DocumentKey && x.LanguageId == model.LanguageId && x.IsDraft == model.IsDraft);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static IList<PublishedDocumentUrlSegments.UrlSegment> GetUpdatedUrlSegments(IList<PublishedDocumentUrlSegments.UrlSegment> urlSegments, string segment, bool isPrimary)
|
||||
{
|
||||
if (urlSegments.FirstOrDefault(x => x.Segment == segment) is null)
|
||||
{
|
||||
urlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(segment, isPrimary));
|
||||
}
|
||||
|
||||
return urlSegments;
|
||||
}
|
||||
|
||||
private void RemoveFromCache(IScopeContext scopeContext, Guid documentKey, string isoCode, bool isDraft)
|
||||
|
||||
@@ -5,40 +5,21 @@ using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
public class ExternalLoginService : RepositoryService, IExternalLoginWithKeyService
|
||||
{
|
||||
private readonly IExternalLoginWithKeyRepository _externalLoginRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public ExternalLoginService(
|
||||
ICoreScopeProvider provider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IEventMessagesFactory eventMessagesFactory,
|
||||
IExternalLoginWithKeyRepository externalLoginRepository)
|
||||
: this(
|
||||
provider,
|
||||
loggerFactory,
|
||||
eventMessagesFactory,
|
||||
externalLoginRepository,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IUserRepository>())
|
||||
{
|
||||
}
|
||||
|
||||
public ExternalLoginService(
|
||||
ICoreScopeProvider provider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IEventMessagesFactory eventMessagesFactory,
|
||||
IExternalLoginWithKeyRepository externalLoginRepository,
|
||||
IUserRepository userRepository)
|
||||
: base(provider, loggerFactory, eventMessagesFactory)
|
||||
{
|
||||
: base(provider, loggerFactory, eventMessagesFactory) =>
|
||||
_externalLoginRepository = externalLoginRepository;
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public IEnumerable<IIdentityUserLogin> Find(string loginProvider, string providerKey)
|
||||
{
|
||||
@@ -99,15 +80,4 @@ public class ExternalLoginService : RepositoryService, IExternalLoginWithKeyServ
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PurgeLoginsForRemovedProviders(IEnumerable<string> currentLoginProviders)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
_userRepository.InvalidateSessionsForRemovedProviders(currentLoginProviders);
|
||||
_externalLoginRepository.DeleteUserLoginsForRemovedProviders(currentLoginProviders);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,6 @@ public interface IAuditService : IService
|
||||
/// </example>
|
||||
/// </param>
|
||||
/// <param name="eventDetails">Free-form details about the audited event.</param>
|
||||
[Obsolete("Will be moved to a new service in V17. Scheduled for removal in V18.")]
|
||||
IAuditEntry Write(
|
||||
int performingUserId,
|
||||
string perfomingDetails,
|
||||
|
||||
@@ -8,8 +8,6 @@ public interface IContentBlueprintEditingService
|
||||
{
|
||||
Task<IContent?> GetAsync(Guid key);
|
||||
|
||||
Task<IContent?> GetScaffoldedAsync(Guid key) => Task.FromResult<IContent?>(null);
|
||||
|
||||
Task<Attempt<PagedModel<IContent>?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync(
|
||||
Guid contentTypeKey,
|
||||
int skip,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
public interface IContentSearchService : IContentSearchService<IContent>
|
||||
{
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
public interface IContentSearchService<TContent>
|
||||
where TContent : class, IContentBase
|
||||
{
|
||||
Task<PagedModel<TContent>> SearchChildrenAsync(
|
||||
string? query,
|
||||
Guid? parentId,
|
||||
Ordering? ordering,
|
||||
int skip = 0,
|
||||
int take = 100);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user