{"results":{"result":{"added-files":{"code-health":8.514949716797133,"old-code-health":0.0,"files":[{"file":"src/Umbraco.Infrastructure/PropertyEditors/FileUploadValueParser.cs","loc":26,"code-health":10.0},{"file":"src/Umbraco.Infrastructure/Extensions/RichTextEditorValueExtensions.cs","loc":26,"code-health":10.0},{"file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs","loc":227,"code-health":7.321402295524544},{"file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs","loc":19,"code-health":10.0},{"file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs","loc":159,"code-health":8.049014201035103},{"file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs","loc":19,"code-health":10.0},{"file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaSavingNotificationHandler.cs","loc":63,"code-health":9.842730062691357},{"file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs","loc":19,"code-health":10.0},{"file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadNotificationHandlerBase.cs","loc":67,"code-health":10.0}]},"external-review-url":"https://github.com/umbraco/Umbraco-CMS/pull/18976","old-code-health":8.604705109411308,"modified-files":{"code-health":8.636515872460263,"old-code-health":8.604705109411308,"files":[{"file":"src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs","loc":95,"old-loc":91,"code-health":9.6882083290695,"old-code-health":9.6882083290695},{"file":"src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs","loc":500,"old-loc":445,"code-health":6.947345647493966,"old-code-health":6.542409529804078},{"file":"src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs","loc":134,"old-loc":149,"code-health":9.310683610933202,"old-code-health":9.022772285419025},{"file":"src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs","loc":313,"old-loc":302,"code-health":9.387218218812514,"old-code-health":9.387218218812514},{"file":"src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs","loc":130,"old-loc":125,"code-health":8.071417696154342,"old-code-health":8.502934016329126},{"file":"tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs","loc":312,"old-loc":159,"code-health":8.700724603707975,"old-code-health":9.387218218812514},{"file":"src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs","loc":70,"old-loc":146,"code-health":9.387218218812514,"old-code-health":8.413528317237752},{"file":"tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs","loc":246,"old-loc":244,"code-health":10.0,"old-code-health":10.0},{"file":"src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs","loc":368,"old-loc":367,"code-health":8.867030537929534,"old-code-health":8.867030537929534}]},"removed-files":{"code-health":0.0,"old-code-health":0.0,"files":[]},"external-review-id":"18976","analysis-time":"2025-06-30T09:55:47Z","negative-impact-count":17,"suppressions":{"number-of-types":0,"number-of-files-touched":0,"findings":[]},"affected-hotspots":0,"commits":["5841e38576601a7e9037529300accbc95d16d834","7ea92705e064d70bf2adbff4adbdc022e9ea4c81","c6fc1e5b2e7832ddea6fbde5f02f654c7916e800","0ed2358d4d3e17d0de06907bbf7e3d3a29bf295f","dd7eb0a099172d53095f5d429cdef2f43b9913c2","7292d9d3d818a36721327ca1b27b94d6532f00c0","f01efa2ec2b719a210d42da2a65793e7ac8f903f","10bde34cf147e7668e4fe83c3b8ad4852abe63b9","5414937bd595c9903d52711485ec59e44ba33f25","bba1f21b013a252a902c39f534c24ad686cf5e74","d164bcc03094db160b7a66244343cb9f4c5b3dad","bbd2387cb3d6138c4f84983489d440efe391afea","5586531d059af95417018e1005f840ee2bdfee7c","e6e007e448108ff4c74f0255fb40d18bb9347671","ea06fb2c0730d2852ab4aa77d677b10ca6871287","e0f1c1bbfb6044932146f92e4fe7d579bc2e4e95","468beb1f46aee12154fdd71b0702a1a19fa5aada","fd5c8bc73a59bf9f8470e18d06b08b2c22d0fd8b","a9f5e816b3d9e6d8998926f219f694054a6a0b53","f3e928d8697e05e3fe00322e301764c6dad8e597","a53ddec9220553bd56ba03754f868a059db400c2","54d1b7e67a639e145726ad8cf380f440e2da2c7c","8ecd27951fad75b62843f708e8d9411e47d14179","d2f45e887ba9bf1db91627fc51f58dd898aafe8b","430b90ff38769f6e49603f935714864b941d8948","270cd88da1c6e09a24c586dc5339a1373e1dc191","b2fa3d92edaeda034f0c7363c6842ac85ea7ad5b","653a3c28595b990ffab6f23d870ac51076af4d41","d40a7cd5c89cb6dec17d6f3d9b1998c76f08dbf2","f016933d0da8226847c32d711e42d4b84066ca94","7cd6f81be51e33da5000dc9de9c347b1b9278c4e","fc5a2575a115bf920d37df8431cab24401fa3d53","579a81243625d588f9d049cca0fdf1922ac32383","0d77fc642b3e414577c71bc8b27db845f097440b","0070a3f91e341f849baba5b19b1d48a98e52e1e4","2211eb69eaf09998bd5d6897cf0a68239cb6c635","0af6e987f9bfa2484ef266b9130e60eeead68322","0b77239ab2fc50a05fa5891b83fa6afa83a73b0e","4c8c9c9fcf743ea8298021c04167aa08b89e7d20","71357ede9118860fbf6ec868db86dd76c829d4b4","1808b0ef0c495b0dfa5b47b5b7a9b3f5cfb0094e","9492892b2ebd4934837e540be4d2d7952a3101eb","831b34d9a2e01d6fcc5a0b9803e4a91bac39f256","4cf6b584d2ca565c6404865bd85410d03d45eee8","41ee355a2d68d12caedf02a310db51225bfe8fd0"],"is-negative-review":true,"negative-findings":{"number-of-types":8,"number-of-files-touched":5,"findings":[{"method":"GetBlockEditorDataValidation","why-it-occurs":"A Complex Method has a high cyclomatic complexity. The recommended threshold for the C# language is a cyclomatic complexity lower than 9.","name":"Complex Method","file":"src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs","refactoring-examples":[{"architectural-component-id":null,"author-name":"Bjarke Berg","training-data":{"loc-added":"24","loc-deleted":"26","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mail@bergmania.dk","commit-full-message":"* AB40660 - untangle the preview cookie from the auth cookie\r\n\r\n* Clean up\r\n\r\n* Allow anonymous to end preview sessions\r\n\r\n* Some refinements\r\n\r\n* update OpenApi.json\r\n\r\n* Fix enter preview test\r\n\r\n* correct tests to match new expectations of the preview cookie\r\n\r\n* sync preview tests with correct expectations of access level\r\n\r\n---------\r\n\r\nCo-authored-by: Sven Geusens <sge@umbraco.dk>\r\nCo-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>","commit-date":"2024-05-17T14:06:26Z","current-rev":"11e5257b56","filename":"Umbraco-CMS/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs","previous-rev":"80794f3efd","commit-title":"V14: Untangle the preview functionality from the auth cookie (#16308)","language":"C#","id":"7ecfb0a869f8c3df64c3c2a4602ef34642b49720","model-score":0.52,"author-id":null,"project-id":33308,"delta-file-score":1.044829,"diff":"diff --git a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\nindex 5bd16867ca..e61b8682af 100644\n--- a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n+++ b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n@@ -7,2 +7,8 @@\n using Microsoft.Extensions.Options;\n+using Umbraco.Cms.Core;\n+using Umbraco.Cms.Core.Preview;\n+using Umbraco.Cms.Core.Security;\n+using Umbraco.Cms.Core.Services;\n+using Umbraco.Cms.Web.Common.AspNetCore;\n+using Umbraco.Cms.Web.Common.Security;\n using Umbraco.Extensions;\n@@ -18,4 +24,15 @@ public class PreviewAuthenticationMiddleware : IMiddleware\n     private readonly ILogger<PreviewAuthenticationMiddleware> _logger;\n+    private readonly IPreviewTokenGenerator _previewTokenGenerator;\n+    private readonly IPreviewService _previewService;\n \n-    public PreviewAuthenticationMiddleware(ILogger<PreviewAuthenticationMiddleware> logger) => _logger = logger;\n+\n+    public PreviewAuthenticationMiddleware(\n+        ILogger<PreviewAuthenticationMiddleware> logger,\n+        IPreviewTokenGenerator previewTokenGenerator,\n+        IPreviewService previewService)\n+    {\n+        _logger = logger;\n+        _previewTokenGenerator = previewTokenGenerator;\n+        _previewService = previewService;\n+    }\n \n@@ -40,33 +57,14 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)\n             {\n-                CookieAuthenticationOptions? cookieOptions = context.RequestServices\n-                    .GetRequiredService<IOptionsSnapshot<CookieAuthenticationOptions>>()\n-                    .Get(Core.Constants.Security.BackOfficeAuthenticationType);\n+                Attempt<ClaimsIdentity> backOfficeIdentityAttempt = await _previewService.TryGetPreviewClaimsIdentityAsync();\n \n-                if (cookieOptions == null)\n+                if (backOfficeIdentityAttempt.Success)\n                 {\n-                    throw new InvalidOperationException(\"No cookie options found with name \" +\n-                                                        Core.Constants.Security.BackOfficeAuthenticationType);\n+                    // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n+                    // Principal, this means we'll have 2 identities assigned to the principal which we can\n+                    // use to authorize the preview and allow for a back office User.\n+                    context.User.AddIdentity(backOfficeIdentityAttempt.Result!);\n                 }\n-\n-                // If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing.\n-                // In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication\n-                // for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered.\n-                if (cookieOptions.Cookie.Name != null)\n+                else\n                 {\n-                    var chunkingCookieManager = new ChunkingCookieManager();\n-                    var cookie = chunkingCookieManager.GetRequestCookie(context, cookieOptions.Cookie.Name);\n-\n-                    if (!string.IsNullOrEmpty(cookie))\n-                    {\n-                        AuthenticationTicket? unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie);\n-                        ClaimsIdentity? backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity();\n-\n-                        if (backOfficeIdentity != null)\n-                        {\n-                            // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n-                            // Principal, this means we'll have 2 identities assigned to the principal which we can\n-                            // use to authorize the preview and allow for a back office User.\n-                            context.User.AddIdentity(backOfficeIdentity);\n-                        }\n-                    }\n+                    _logger.LogDebug(\"Could not transform previewCookie value into a claimsIdentity\");\n                 }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Sven Geusens","training-data":{"loc-added":"74","loc-deleted":"9","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"sge@umbraco.dk","commit-full-message":"* Support new localLink format in core link parsing\r\n\r\n* Updated devliery api to work with the new locallinks format\r\n\r\nAdded tests for old and new format handling.\r\n\r\n* Fix error regarding type attribute not always being present (for example old format or non local links)","commit-date":"2024-07-02T12:22:19Z","current-rev":"46acd51759","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs","previous-rev":"13b77d35df","commit-title":"[V14] Make the backend work with the new localLinks format (#16661)","language":"C#","id":"7d7b589a86f716e705df415598cc4c8f2a02af1b","model-score":0.42,"author-id":null,"project-id":33308,"delta-file-score":0.3009901,"diff":"diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\nindex 7723fc835c..407bc1a022 100644\n--- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\n+++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\n@@ -6,2 +6,3 @@\n using Umbraco.Cms.Core.PublishedCache;\n+using Umbraco.Cms.Core.Templates;\n \n@@ -20,3 +21,17 @@ protected ApiRichTextParserBase(IApiContentRouteBuilder apiContentRouteBuilder,\n \n-    protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl, Action handleInvalidLink)\n+    protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, string type, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl, Action handleInvalidLink)\n+    {\n+        ReplaceStatus replaceAttempt = ReplaceLocalLink(publishedSnapshot, href, type, handleContentRoute, handleMediaUrl);\n+        if (replaceAttempt == ReplaceStatus.Success)\n+        {\n+            return;\n+        }\n+\n+        if (replaceAttempt == ReplaceStatus.InvalidEntityType || ReplaceLegacyLocalLink(publishedSnapshot, href, handleContentRoute, handleMediaUrl) == ReplaceStatus.InvalidEntityType)\n+        {\n+            handleInvalidLink();\n+        }\n+    }\n+\n+    private ReplaceStatus ReplaceLocalLink(IPublishedSnapshot publishedSnapshot, string href, string type, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl)\n     {\n@@ -25,11 +40,12 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n         {\n-            return;\n+            return ReplaceStatus.NoMatch;\n         }\n \n-        if (UdiParser.TryParse(match.Groups[\"udi\"].Value, out Udi? udi) is false)\n+        if (Guid.TryParse(match.Groups[\"guid\"].Value, out Guid guid) is false)\n         {\n-            return;\n+            return ReplaceStatus.NoMatch;\n         }\n \n-        bool handled = false;\n+        var udi = new GuidUdi(type, guid);\n+\n         switch (udi.EntityType)\n@@ -43,4 +59,4 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n                 {\n-                    handled = true;\n                     handleContentRoute(route);\n+                    return ReplaceStatus.Success;\n                 }\n@@ -52,4 +68,4 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n                 {\n-                    handled = true;\n                     handleMediaUrl(_apiMediaUrlProvider.GetUrl(media));\n+                    return ReplaceStatus.Success;\n                 }\n@@ -59,6 +75,45 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n \n-        if(handled is false)\n+        return ReplaceStatus.InvalidEntityType;\n+    }\n+\n+    private ReplaceStatus ReplaceLegacyLocalLink(IPublishedSnapshot publishedSnapshot, string href, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl)\n+    {\n+        Match match = LegacyLocalLinkRegex().Match(href);\n+        if (match.Success is false)\n         {\n-            handleInvalidLink();\n+            return ReplaceStatus.NoMatch;\n+        }\n+\n+        if (UdiParser.TryParse(match.Groups[\"udi\"].Value, out Udi? udi) is false)\n+        {\n+            return ReplaceStatus.NoMatch;\n+        }\n+\n+\n+        switch (udi.EntityType)\n+        {\n+            case Constants.UdiEntityType.Document:\n+                IPublishedContent? content = publishedSnapshot.Content?.GetById(udi);\n+                IApiContentRoute? route = content != null\n+                    ? _apiContentRouteBuilder.Build(content)\n+                    : null;\n+                if (route != null)\n+                {\n+                    handleContentRoute(route);\n+                    return ReplaceStatus.Success;\n+                }\n+\n+                break;\n+            case Constants.UdiEntityType.Media:\n+                IPublishedContent? media = publishedSnapshot.Media?.GetById(udi);\n+                if (media != null)\n+                {\n+                    handleMediaUrl(_apiMediaUrlProvider.GetUrl(media));\n+                    return ReplaceStatus.Success;\n+                }\n+\n+                break;\n         }\n+\n+        return ReplaceStatus.InvalidEntityType;\n     }\n@@ -82,3 +137,13 @@ protected void ReplaceLocalImages(IPublishedSnapshot publishedSnapshot, string u\n     [GeneratedRegex(\"{localLink:(?<udi>umb:.+)}\")]\n+    private static partial Regex LegacyLocalLinkRegex();\n+\n+    [GeneratedRegex(\"{localLink:(?<guid>.+)}\")]\n     private static partial Regex LocalLinkRegex();\n+\n+    private enum ReplaceStatus\n+    {\n+        NoMatch,\n+        Success,\n+        InvalidEntityType\n+    }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vitor Rodrigues","training-data":{"loc-added":"16","loc-deleted":"14","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"8.545379580978913"},"author-email":"vsilvar@users.noreply.github.com","commit-full-message":"* Fixes #15136: Search includes fields from other cultures\r\n\r\nRegex was updated to support block list fields\r\nUnpublished nodes on the supplied culture are not filtered out\r\n\r\n* Making the code non-breaking\r\n\r\n* Fixed failing publish content query integration tests\r\n\r\nThe tests were not setting the content as publish in the specifed culture\r\ncausing the content items to be ignored\r\n\r\n---------\r\n\r\nCo-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>","commit-date":"2024-02-07T12:43:37Z","current-rev":"839b2ff6a2","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/PublishedContentQuery.cs","previous-rev":"2221c4f1c7","commit-title":"Fixes #15136: Search includes fields from other cultures (#15148)","language":"C#","id":"ac0709776b22e60e777651712009344d5d3c3529","model-score":0.3,"author-id":null,"project-id":33308,"delta-file-score":0.2613985,"diff":"diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs\nindex d075e8b9d2..758115b9ca 100644\n--- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs\n+++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs\n@@ -295,17 +295,13 @@ public IEnumerable<PublishedSearchResult> Search(string term, int skip, int take\n                     .ToArray(); // Get all index fields suffixed with the culture name supplied\n-            ordering = query.ManagedQuery(term, fields);\n-        }\n-\n-            // Filter selected fields because results are loaded from the published snapshot based on these\n-            IOrdering? queryExecutor = ordering.SelectFields(_returnedQueryFields);\n-\n-\n-        ISearchResults? results = skip == 0 && take == 0\n-            ? queryExecutor.Execute()\n-            : queryExecutor.Execute(QueryOptions.SkipTake(skip, take));\n \n-        totalRecords = results.TotalItemCount;\n+            // Filter out unpublished content for the specified culture if the content varies by culture\n+            // The published__{culture} field is not populated when the content is not published in that culture\n+            ordering = query\n+                .ManagedQuery(term, fields)\n+                .Not().Group(q => q\n+                    .Field(UmbracoExamineFieldNames.VariesByCultureFieldName, \"y\")\n+                    .Not().Field($\"{UmbracoExamineFieldNames.PublishedFieldName}_{culture.ToLowerInvariant()}\", \"y\"));\n+        }\n \n-        return new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content),\n-            _variationContextAccessor, culture);\n+        return Search(ordering, skip, take, out totalRecords, culture);\n     }\n@@ -318,2 +314,6 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query)\n     public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords)\n+        => Search(query, skip, take, out totalRecords, null);\n+\n+    /// <inheritdoc />\n+    public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords, string? culture)\n     {\n@@ -333,4 +333,4 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip,\n         {\n-                // Filter selected fields because results are loaded from the published snapshot based on these\n-                query = ordering.SelectFields(_returnedQueryFields);\n+            // Filter selected fields because results are loaded from the published snapshot based on these\n+            query = ordering.SelectFields(_returnedQueryFields);\n         }\n@@ -343,3 +343,5 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip,\n \n-        return results.ToPublishedSearchResults(_publishedSnapshot);\n+        return culture.IsNullOrWhiteSpace()\n+            ? results.ToPublishedSearchResults(_publishedSnapshot)\n+            : new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture);\n     }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nikolaj E. Lauridsen","training-data":{"loc-added":"6","loc-deleted":"36","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"nikolajlauridsen@protonmail.ch","commit-full-message":"* Remove nucache reference from Web.Common\r\n\r\n* Get tests building-ish\r\n\r\n* Move ReservedFieldNamesService to the right project\r\n\r\n* Remove IPublishedSnapshotStatus\r\n\r\n* Added functionality to the INavigationQueryService to get root keys\r\n\r\n* Fixed issue with navigation\r\n\r\n* Remove IPublishedSnapshot from UmbracoContext\r\n\r\n* Begin removing usage of IPublishedSnapshot from PublishedContentExtensions\r\n\r\n* Fix PublishedContentExtensions.cs\r\n\r\n* Don't use snapshots in delivery media api\r\n\r\n* Use IPublishedMediaCache in QueryMediaApiController\r\n\r\n* Remove more usages of IPublishedSnapshotAccessor\r\n\r\n* Comment out tests\r\n\r\n* Remove more usages of PublishedSnapshotAccessor\r\n\r\n* Remove PublishedSnapshot from property\r\n\r\n* Fixed test build\r\n\r\n* Fix errors\r\n\r\n* Fix some tests\r\n\r\n* Delete NuCache 🎉\r\n\r\n* Implement DatabaseCacheRebuilder\r\n\r\n* Remove usage of IPublishedSnapshotService\r\n\r\n* Remove IPublishedSnapshotService\r\n\r\n* Remove TestPublishedSnapshotAccessor and make tests build\r\n\r\n* Don't test Snapshot cachelevel\r\n\r\nIt's no longer supported\r\n\r\n* Fix BlockEditorConverter\r\n\r\nElement != Element document type\r\n\r\n* Remember to set cachemanager\r\n\r\n* Fix RichTextParserTests\r\n\r\n* Implement TryGetLevel on INavigationQueryService\r\n\r\n* Fake level and obsolete it in PublishedContent\r\n\r\n* Remove ChildrenForAllCultures\r\n\r\n* Hack Path property on PublishedContent\r\n\r\n* Remove usages of IPublishedSnapshot in tests\r\n\r\n* More ConvertersTests\r\n\r\n* Add hybrid cache to integration tests\r\n\r\nWe can actually do this now because we no longer save files on disk\r\n\r\n* Rename IPublishedSnapshotRebuilder to ICacheRebuilder\r\n\r\n* Comment out tests\r\n\r\n* V15: Replacing the usages of Parent (navigation data) from IPublishedContent (#17125)\r\n\r\n* Fix .Parent references in PublishedContentExtensions\r\n\r\n* Add missing methods to FriendlyPublishedContentExtensions (ones that you were able to call on the content directly as they now require extra params)\r\n\r\n* Fix references from the extension methods\r\n\r\n* Fix dependencies in tests\r\n\r\n* Replace IPublishedSnapshotAccessor with the content cache in tests\r\n\r\n* Resolving more .Parent references\r\n\r\n* Fix unit tests\r\n\r\n* Obsolete and use extension methods\r\n\r\n* Remove private method and use extension instead\r\n\r\n* Moving code around\r\n\r\n* Fix tests\r\n\r\n* Fix more references\r\n\r\n* Cleanup\r\n\r\n* Fix more usages\r\n\r\n* Resolve merge conflict\r\n\r\n* Fix tests\r\n\r\n* Cleanup\r\n\r\n* Fix more tests\r\n\r\n* Fixed unit tests\r\n\r\n* Cleanup\r\n\r\n* Replace last usages\r\n\r\n---------\r\n\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\n\r\n* Remove usage of IPublishedSnapshotAccessor from IRequestItemProvider\r\n\r\n* Post merge fixup\r\n\r\n* Remo IPublishedSnapshot\r\n\r\n* Add HasAny to IDocumentUrlService\r\n\r\n* Fix TextBuilder\r\n\r\n* Fix modelsbuilder tests\r\n\r\n* Use explicit types\r\n\r\n* Implement GetByContentType\r\n\r\n* Support element types in PublishedContentTypeCache\r\n\r\n* Run enlistments before publishing notifications\r\n\r\n* Fix elements cache refreshing\r\n\r\n* Implement GetByUdi\r\n\r\n* Implement GetAtRoot\r\n\r\n* Implement GetByRoute\r\n\r\n* Reimplement GetRouteById\r\n\r\n* Fix blocks unit tests\r\n\r\n* Initialize domain cache on boot\r\n\r\n* Only return routes with domains on non default lanauges\r\n\r\n* V15: Replacing the usages of `Children` (navigation data) from `IPublishedContent` (#17159)\r\n\r\n* Update params in PublishedContentExtensions to the general interfaces for the published cache and navigation service, so that we can use the extension methods on both documents and media\r\n\r\n* Introduce GetParent() which uses the right services\r\n\r\n* Fix obsolete message on .Parent\r\n\r\n* Obsolete .Children\r\n\r\n* Fix usages of Children for ApiMediaQueryService\r\n\r\n* Fix usage in internal\r\n\r\n* Fix usages in views\r\n\r\n* Fix indentation\r\n\r\n* Fix issue with delete language\r\n\r\n* Update nuget pacakges\r\n\r\n* Clear elements cache when content is deleted\r\n\r\ninstead of trying to update it\r\n\r\n* Reset publishedModelFactory\r\n\r\n* Fixed publishing\r\n\r\n---------\r\n\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\nCo-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>\r\nCo-authored-by: kjac <kja@umbraco.dk>","commit-date":"2024-10-01T13:03:02Z","current-rev":"1258962429","filename":"Umbraco-CMS/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs","previous-rev":"7ca96423f8","commit-title":"V15: Remove Nucache (#17166)","language":"C#","id":"c5107d73a299c92b12ef250416ebfdc0fe984c06","model-score":0.2,"author-id":null,"project-id":33308,"delta-file-score":0.29056275,"diff":"diff --git a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\nindex 53e8156538..0452cf0b03 100644\n--- a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\n+++ b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\n@@ -17,3 +17,2 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase\n     private readonly object _locko = new();\n-    private readonly IPublishedSnapshotAccessor? _publishedSnapshotAccessor;\n     private readonly object? _sourceValue;\n@@ -21,2 +20,3 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase\n     protected readonly bool IsPreviewing;\n+    private readonly ICacheManager? _cacheManager;\n     private CacheValues? _cacheValues;\n@@ -32,4 +32,4 @@ public PublishedElementPropertyBase(\n         PropertyCacheLevel referenceCacheLevel,\n-        object? sourceValue = null,\n-        IPublishedSnapshotAccessor? publishedSnapshotAccessor = null)\n+        ICacheManager? cacheManager,\n+        object? sourceValue = null)\n         : base(propertyType, referenceCacheLevel)\n@@ -37,5 +37,5 @@ public PublishedElementPropertyBase(\n         _sourceValue = sourceValue;\n-        _publishedSnapshotAccessor = publishedSnapshotAccessor;\n         Element = element;\n         IsPreviewing = previewing;\n+        _cacheManager = cacheManager;\n         IsMember = propertyType.ContentType?.ItemType == PublishedItemType.Member;\n@@ -120,23 +120,2 @@ private void GetCacheLevels(PropertyCacheLevel propertyTypeCacheLevel, out Prope\n \n-    private IAppCache? GetSnapshotCache()\n-    {\n-        // cache within the snapshot cache, unless previewing, then use the snapshot or\n-        // elements cache (if we don't want to pollute the elements cache with short-lived\n-        // data) depending on settings\n-        // for members, always cache in the snapshot cache - never pollute elements cache\n-        if (_publishedSnapshotAccessor is null)\n-        {\n-            return null;\n-        }\n-\n-        if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot))\n-        {\n-            return null;\n-        }\n-\n-        return (IsPreviewing == false || FullCacheWhenPreviewing) && IsMember == false\n-            ? publishedSnapshot!.ElementsCache\n-            : publishedSnapshot!.SnapshotCache;\n-    }\n-\n     private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n@@ -147,2 +126,3 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n             case PropertyCacheLevel.None:\n+            case PropertyCacheLevel.Snapshot:\n                 // never cache anything\n@@ -155,13 +135,3 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n             case PropertyCacheLevel.Elements:\n-                // cache within the elements  cache, depending...\n-                IAppCache? snapshotCache = GetSnapshotCache();\n-                cacheValues = (CacheValues?)snapshotCache?.Get(ValuesCacheKey, () => new CacheValues()) ??\n-                              new CacheValues();\n-                break;\n-            case PropertyCacheLevel.Snapshot:\n-                IPublishedSnapshot? publishedSnapshot = _publishedSnapshotAccessor?.GetRequiredPublishedSnapshot();\n-\n-                // cache within the snapshot cache\n-                IAppCache? facadeCache = publishedSnapshot?.SnapshotCache;\n-                cacheValues = (CacheValues?)facadeCache?.Get(ValuesCacheKey, () => new CacheValues()) ??\n+                cacheValues = (CacheValues?)_cacheManager?.ElementsCache.Get(ValuesCacheKey, () => new CacheValues()) ??\n                               new CacheValues();\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nikolaj E. Lauridsen","training-data":{"loc-added":"11","loc-deleted":"48","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"nikolajlauridsen@protonmail.ch","commit-full-message":"","commit-date":"2024-12-03T12:40:05Z","current-rev":"380f3f7e87","filename":"Umbraco-CMS/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs","previous-rev":"437fcba4e5","commit-title":"Clear elements cache instead of refreshing it (#17708)","language":"C#","id":"520cf3bc55fbfa6fb3c07f3560d781b1e2034c12","model-score":0.19,"author-id":null,"project-id":33308,"delta-file-score":0.7076424,"diff":"diff --git a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\nindex f61968365c..6ff5a5fe1e 100644\n--- a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\n+++ b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\n@@ -47,3 +47,3 @@ public async Task HandleAsync(ContentRefreshNotification notification, Cancellat\n     {\n-        await RefreshElementsCacheAsync(notification.Entity);\n+        ClearElementsCache();\n \n@@ -56,3 +56,3 @@ public async Task HandleAsync(ContentDeletedNotification notification, Cancellat\n         {\n-            RemoveFromElementsCache(deletedEntity);\n+            ClearElementsCache();\n             await _documentCacheService.DeleteItemAsync(deletedEntity);\n@@ -63,3 +63,3 @@ public async Task HandleAsync(MediaRefreshNotification notification, Cancellatio\n     {\n-        await RefreshElementsCacheAsync(notification.Entity);\n+        ClearElementsCache();\n         await _mediaCacheService.RefreshMediaAsync(notification.Entity);\n@@ -71,3 +71,3 @@ public async Task HandleAsync(MediaDeletedNotification notification, Cancellatio\n         {\n-            RemoveFromElementsCache(deletedEntity);\n+            ClearElementsCache();\n             await _mediaCacheService.DeleteItemAsync(deletedEntity);\n@@ -76,49 +76,12 @@ public async Task HandleAsync(MediaDeletedNotification notification, Cancellatio\n \n-    private async Task RefreshElementsCacheAsync(IUmbracoEntity content)\n+    private void ClearElementsCache()\n     {\n-        IEnumerable<IRelation> parentRelations = _relationService.GetByParent(content)!;\n-        IEnumerable<IRelation> childRelations = _relationService.GetByChild(content);\n-\n-        var ids = parentRelations.Select(x => x.ChildId).Concat(childRelations.Select(x => x.ParentId)).ToHashSet();\n-        // We need to add ourselves to the list of ids to clear\n-        ids.Add(content.Id);\n-        foreach (var id in ids)\n-        {\n-            if (await _documentCacheService.HasContentByIdAsync(id) is false)\n-            {\n-                continue;\n-            }\n-\n-            IPublishedContent? publishedContent = await _documentCacheService.GetByIdAsync(id);\n-            if (publishedContent is null)\n-            {\n-                continue;\n-            }\n-\n-            foreach (IPublishedProperty publishedProperty in publishedContent.Properties)\n-            {\n-                var property = (PublishedProperty) publishedProperty;\n-                if (property.ReferenceCacheLevel is PropertyCacheLevel.Elements\n-                    || property.PropertyType.DeliveryApiCacheLevel is PropertyCacheLevel.Elements\n-                    || property.PropertyType.DeliveryApiCacheLevelForExpansion is PropertyCacheLevel.Elements)\n-                {\n-                    _elementsCache.ClearByKey(property.ValuesCacheKey);\n-                }\n-            }\n-        }\n+        // Ideally we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.\n+        // The reason for this is that we have no way to know which elements are affected by the changes. or what their keys are.\n+        // This is because currently published elements lives exclusively in a JSON blob in the umbracoPropertyData table.\n+        // This means that the only way to resolve these keys are to actually parse this data with a specific value converter, and for all cultures, which is not feasible.\n+        // If published elements become their own entities with relations, instead of just property data, we can revisit this,\n+        _elementsCache.Clear();\n     }\n \n-    private void RemoveFromElementsCache(IUmbracoEntity content)\n-    {\n-        // ClearByKey clears by \"startsWith\" so we'll clear by the cachekey prefix + contentKey\n-        // This will clear any and all properties for this content item, this is important because\n-        // we cannot resolve the PublishedContent for this entity since it and its content type is deleted.\n-        _elementsCache.ClearByKey(GetContentWideCacheKey(content.Key, true));\n-        _elementsCache.ClearByKey(GetContentWideCacheKey(content.Key, false));\n-    }\n-\n-    private string GetContentWideCacheKey(Guid contentKey, bool isPreviewing) => isPreviewing\n-        ? CacheKeys.PreviewPropertyCacheKeyPrefix + contentKey\n-        : CacheKeys.PropertyCacheKeyPrefix + contentKey;\n-\n     public Task HandleAsync(ContentTypeRefreshedNotification notification, CancellationToken cancellationToken)\n","improvement-type":"Complex Method"}],"change-level":"warning","is-hotspot?":false,"line":60,"what-changed":"GetBlockEditorDataValidation increases in cyclomatic complexity from 24 to 26, threshold = 9","how-to-fix":"There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html). Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring.","change-type":"degraded"},{"method":"GetBlockEditorDataValidation","why-it-occurs":"A Bumpy Road is a function that contains multiple chunks of nested conditional logic inside the same function. The deeper the nesting and the more bumps, the lower the code health.\n\nA bumpy code road represents a lack of encapsulation which becomes an obstacle to comprehension. In imperative languages there’s also an increased risk for feature entanglement, which leads to complex state management. CodeScene considers the following rules for the code health impact: 1) The deeper the nested conditional logic of each bump, the higher the tax on our working memory. 2) The more bumps inside a function, the more expensive it is to refactor as each bump represents a missing abstraction. 3) The larger each bump – that is, the more lines of code it spans – the harder it is to build up a mental model of the function. The nesting depth for what is considered a bump is  levels of conditionals.","name":"Bumpy Road Ahead","file":"src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs","refactoring-examples":null,"change-level":"warning","is-hotspot?":false,"line":60,"what-changed":"GetBlockEditorDataValidation increases from 4 to 5 logical blocks with deeply nested code, threshold is one single block per function","how-to-fix":"Bumpy Road implementations indicate a lack of encapsulation. Check out the detailed description of the [Bumpy Road code health issue](https://codescene.com/blog/bumpy-road-code-complexity-in-context/).\n\nA Bumpy Road often suggests that the function/method does too many things. The first refactoring step is to identify the different possible responsibilities of the function. Consider extracting those responsibilities into smaller, cohesive, and well-named functions. The [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html) refactoring is the primary response.","change-type":"degraded"},{"method":"CreateValueEditor","why-it-occurs":"Overly long functions make the code harder to read. The recommended maximum function length for the C# language is 70 lines of code. Severity: Brain Method - Complex Method - Long Method.","name":"Large Method","file":"tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs","refactoring-examples":null,"change-level":"warning","is-hotspot?":false,"line":227,"what-changed":"CreateValueEditor has 79 lines, threshold = 70","how-to-fix":"We recommend to be careful here -- just splitting long functions don't necessarily make the code easier to read. Instead, look for natural chunks inside the functions that expresses a specific task or concern. Often, such concerns are indicated by a Code Comment followed by an if-statement. Use the [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html) refactoring to encapsulate that concern.","change-type":"introduced"},{"why-it-occurs":"Code that uses a high degree of built-in, primitives such as integers, strings, floats, lacks a domain language that encapsulates the validation and semantics of function arguments. Primitive Obsession has several consequences: 1) In a statically typed language, the compiler will detect less erroneous assignments. 2) Security impact since the possible value range of a variable/argument isn't retricted.\n\nIn this module, 72 % of all functions have primitive types as arguments.","name":"Primitive Obsession","file":"tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs","refactoring-examples":[{"diff":"diff --git a/primitive-obsession.ts b/primitive-obsession.ts\nindex 38bae186cc..24116eddcc 100644\n--- a/primitive-obsession.ts\n+++ b/primitive-obsession.ts\n@@ -1,8 +1,8 @@\n-// Problem: It's hard to know what this function does, and what values are valid as parameters.\n-function getPopularRepositories(String baseURL, String query, Integer pages, Integer pageSize, String sortorder): Json {\n-\tlet pages == null ? 10 : pages\n-\tlet pageSize == null ? 10 : pageSize\n-  return httpClient.get(`${baseURL}?q=${query}&pages=${pages}&pageSize=${pageSize}&sortorder=${sortorder}`)\n+// Refactoring: extract the pagination & API logic into a class, and it will\n+// attract validation and other logic related to the specific query. It's now\n+// easier to use and to maintain the getPopularRepositories function!\n+function getPopularRepositories(query: PaginatedRepoQuery): Json {\n+  return httpClient.get(query.getURL())\n     .map(json => json.repositories)\n     .filter(repository => repositry.stargazersCount > 1000)\n }\n","language":"c#","improvement-type":"Primitive Obsession"}],"change-level":"warning","is-hotspot?":false,"what-changed":"In this module, 72.2% of all function arguments are primitive types, threshold = 30.0%","how-to-fix":"Primitive Obsession indicates a missing domain language. Introduce data types that encapsulate the details and constraints of your domain. For example, instead of `int userId`, consider `User clicked`.","change-type":"introduced"},{"why-it-occurs":"Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.","name":"Code Duplication","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs","refactoring-examples":null,"change-level":"warning","is-hotspot?":false,"line":136,"what-changed":"The module contains 2 functions with similar structure: UpdateBlockEditorData,UpdateBlockEditorData","how-to-fix":"A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. [Read More](https://codescene.com/blog/software-revolution-part3/)\n\nOnce you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.","change-type":"introduced"},{"method":"UpdateBlockPropertyValues","why-it-occurs":"A Complex Method has a high cyclomatic complexity. The recommended threshold for the C# language is a cyclomatic complexity lower than 9.","name":"Complex Method","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs","refactoring-examples":[{"architectural-component-id":null,"author-name":"Bjarke Berg","training-data":{"loc-added":"24","loc-deleted":"26","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mail@bergmania.dk","commit-full-message":"* AB40660 - untangle the preview cookie from the auth cookie\r\n\r\n* Clean up\r\n\r\n* Allow anonymous to end preview sessions\r\n\r\n* Some refinements\r\n\r\n* update OpenApi.json\r\n\r\n* Fix enter preview test\r\n\r\n* correct tests to match new expectations of the preview cookie\r\n\r\n* sync preview tests with correct expectations of access level\r\n\r\n---------\r\n\r\nCo-authored-by: Sven Geusens <sge@umbraco.dk>\r\nCo-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>","commit-date":"2024-05-17T14:06:26Z","current-rev":"11e5257b56","filename":"Umbraco-CMS/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs","previous-rev":"80794f3efd","commit-title":"V14: Untangle the preview functionality from the auth cookie (#16308)","language":"C#","id":"7ecfb0a869f8c3df64c3c2a4602ef34642b49720","model-score":0.52,"author-id":null,"project-id":33308,"delta-file-score":1.044829,"diff":"diff --git a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\nindex 5bd16867ca..e61b8682af 100644\n--- a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n+++ b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n@@ -7,2 +7,8 @@\n using Microsoft.Extensions.Options;\n+using Umbraco.Cms.Core;\n+using Umbraco.Cms.Core.Preview;\n+using Umbraco.Cms.Core.Security;\n+using Umbraco.Cms.Core.Services;\n+using Umbraco.Cms.Web.Common.AspNetCore;\n+using Umbraco.Cms.Web.Common.Security;\n using Umbraco.Extensions;\n@@ -18,4 +24,15 @@ public class PreviewAuthenticationMiddleware : IMiddleware\n     private readonly ILogger<PreviewAuthenticationMiddleware> _logger;\n+    private readonly IPreviewTokenGenerator _previewTokenGenerator;\n+    private readonly IPreviewService _previewService;\n \n-    public PreviewAuthenticationMiddleware(ILogger<PreviewAuthenticationMiddleware> logger) => _logger = logger;\n+\n+    public PreviewAuthenticationMiddleware(\n+        ILogger<PreviewAuthenticationMiddleware> logger,\n+        IPreviewTokenGenerator previewTokenGenerator,\n+        IPreviewService previewService)\n+    {\n+        _logger = logger;\n+        _previewTokenGenerator = previewTokenGenerator;\n+        _previewService = previewService;\n+    }\n \n@@ -40,33 +57,14 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)\n             {\n-                CookieAuthenticationOptions? cookieOptions = context.RequestServices\n-                    .GetRequiredService<IOptionsSnapshot<CookieAuthenticationOptions>>()\n-                    .Get(Core.Constants.Security.BackOfficeAuthenticationType);\n+                Attempt<ClaimsIdentity> backOfficeIdentityAttempt = await _previewService.TryGetPreviewClaimsIdentityAsync();\n \n-                if (cookieOptions == null)\n+                if (backOfficeIdentityAttempt.Success)\n                 {\n-                    throw new InvalidOperationException(\"No cookie options found with name \" +\n-                                                        Core.Constants.Security.BackOfficeAuthenticationType);\n+                    // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n+                    // Principal, this means we'll have 2 identities assigned to the principal which we can\n+                    // use to authorize the preview and allow for a back office User.\n+                    context.User.AddIdentity(backOfficeIdentityAttempt.Result!);\n                 }\n-\n-                // If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing.\n-                // In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication\n-                // for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered.\n-                if (cookieOptions.Cookie.Name != null)\n+                else\n                 {\n-                    var chunkingCookieManager = new ChunkingCookieManager();\n-                    var cookie = chunkingCookieManager.GetRequestCookie(context, cookieOptions.Cookie.Name);\n-\n-                    if (!string.IsNullOrEmpty(cookie))\n-                    {\n-                        AuthenticationTicket? unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie);\n-                        ClaimsIdentity? backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity();\n-\n-                        if (backOfficeIdentity != null)\n-                        {\n-                            // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n-                            // Principal, this means we'll have 2 identities assigned to the principal which we can\n-                            // use to authorize the preview and allow for a back office User.\n-                            context.User.AddIdentity(backOfficeIdentity);\n-                        }\n-                    }\n+                    _logger.LogDebug(\"Could not transform previewCookie value into a claimsIdentity\");\n                 }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Sven Geusens","training-data":{"loc-added":"74","loc-deleted":"9","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"sge@umbraco.dk","commit-full-message":"* Support new localLink format in core link parsing\r\n\r\n* Updated devliery api to work with the new locallinks format\r\n\r\nAdded tests for old and new format handling.\r\n\r\n* Fix error regarding type attribute not always being present (for example old format or non local links)","commit-date":"2024-07-02T12:22:19Z","current-rev":"46acd51759","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs","previous-rev":"13b77d35df","commit-title":"[V14] Make the backend work with the new localLinks format (#16661)","language":"C#","id":"7d7b589a86f716e705df415598cc4c8f2a02af1b","model-score":0.42,"author-id":null,"project-id":33308,"delta-file-score":0.3009901,"diff":"diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\nindex 7723fc835c..407bc1a022 100644\n--- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\n+++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\n@@ -6,2 +6,3 @@\n using Umbraco.Cms.Core.PublishedCache;\n+using Umbraco.Cms.Core.Templates;\n \n@@ -20,3 +21,17 @@ protected ApiRichTextParserBase(IApiContentRouteBuilder apiContentRouteBuilder,\n \n-    protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl, Action handleInvalidLink)\n+    protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, string type, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl, Action handleInvalidLink)\n+    {\n+        ReplaceStatus replaceAttempt = ReplaceLocalLink(publishedSnapshot, href, type, handleContentRoute, handleMediaUrl);\n+        if (replaceAttempt == ReplaceStatus.Success)\n+        {\n+            return;\n+        }\n+\n+        if (replaceAttempt == ReplaceStatus.InvalidEntityType || ReplaceLegacyLocalLink(publishedSnapshot, href, handleContentRoute, handleMediaUrl) == ReplaceStatus.InvalidEntityType)\n+        {\n+            handleInvalidLink();\n+        }\n+    }\n+\n+    private ReplaceStatus ReplaceLocalLink(IPublishedSnapshot publishedSnapshot, string href, string type, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl)\n     {\n@@ -25,11 +40,12 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n         {\n-            return;\n+            return ReplaceStatus.NoMatch;\n         }\n \n-        if (UdiParser.TryParse(match.Groups[\"udi\"].Value, out Udi? udi) is false)\n+        if (Guid.TryParse(match.Groups[\"guid\"].Value, out Guid guid) is false)\n         {\n-            return;\n+            return ReplaceStatus.NoMatch;\n         }\n \n-        bool handled = false;\n+        var udi = new GuidUdi(type, guid);\n+\n         switch (udi.EntityType)\n@@ -43,4 +59,4 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n                 {\n-                    handled = true;\n                     handleContentRoute(route);\n+                    return ReplaceStatus.Success;\n                 }\n@@ -52,4 +68,4 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n                 {\n-                    handled = true;\n                     handleMediaUrl(_apiMediaUrlProvider.GetUrl(media));\n+                    return ReplaceStatus.Success;\n                 }\n@@ -59,6 +75,45 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n \n-        if(handled is false)\n+        return ReplaceStatus.InvalidEntityType;\n+    }\n+\n+    private ReplaceStatus ReplaceLegacyLocalLink(IPublishedSnapshot publishedSnapshot, string href, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl)\n+    {\n+        Match match = LegacyLocalLinkRegex().Match(href);\n+        if (match.Success is false)\n         {\n-            handleInvalidLink();\n+            return ReplaceStatus.NoMatch;\n+        }\n+\n+        if (UdiParser.TryParse(match.Groups[\"udi\"].Value, out Udi? udi) is false)\n+        {\n+            return ReplaceStatus.NoMatch;\n+        }\n+\n+\n+        switch (udi.EntityType)\n+        {\n+            case Constants.UdiEntityType.Document:\n+                IPublishedContent? content = publishedSnapshot.Content?.GetById(udi);\n+                IApiContentRoute? route = content != null\n+                    ? _apiContentRouteBuilder.Build(content)\n+                    : null;\n+                if (route != null)\n+                {\n+                    handleContentRoute(route);\n+                    return ReplaceStatus.Success;\n+                }\n+\n+                break;\n+            case Constants.UdiEntityType.Media:\n+                IPublishedContent? media = publishedSnapshot.Media?.GetById(udi);\n+                if (media != null)\n+                {\n+                    handleMediaUrl(_apiMediaUrlProvider.GetUrl(media));\n+                    return ReplaceStatus.Success;\n+                }\n+\n+                break;\n         }\n+\n+        return ReplaceStatus.InvalidEntityType;\n     }\n@@ -82,3 +137,13 @@ protected void ReplaceLocalImages(IPublishedSnapshot publishedSnapshot, string u\n     [GeneratedRegex(\"{localLink:(?<udi>umb:.+)}\")]\n+    private static partial Regex LegacyLocalLinkRegex();\n+\n+    [GeneratedRegex(\"{localLink:(?<guid>.+)}\")]\n     private static partial Regex LocalLinkRegex();\n+\n+    private enum ReplaceStatus\n+    {\n+        NoMatch,\n+        Success,\n+        InvalidEntityType\n+    }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vitor Rodrigues","training-data":{"loc-added":"16","loc-deleted":"14","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"8.545379580978913"},"author-email":"vsilvar@users.noreply.github.com","commit-full-message":"* Fixes #15136: Search includes fields from other cultures\r\n\r\nRegex was updated to support block list fields\r\nUnpublished nodes on the supplied culture are not filtered out\r\n\r\n* Making the code non-breaking\r\n\r\n* Fixed failing publish content query integration tests\r\n\r\nThe tests were not setting the content as publish in the specifed culture\r\ncausing the content items to be ignored\r\n\r\n---------\r\n\r\nCo-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>","commit-date":"2024-02-07T12:43:37Z","current-rev":"839b2ff6a2","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/PublishedContentQuery.cs","previous-rev":"2221c4f1c7","commit-title":"Fixes #15136: Search includes fields from other cultures (#15148)","language":"C#","id":"ac0709776b22e60e777651712009344d5d3c3529","model-score":0.3,"author-id":null,"project-id":33308,"delta-file-score":0.2613985,"diff":"diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs\nindex d075e8b9d2..758115b9ca 100644\n--- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs\n+++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs\n@@ -295,17 +295,13 @@ public IEnumerable<PublishedSearchResult> Search(string term, int skip, int take\n                     .ToArray(); // Get all index fields suffixed with the culture name supplied\n-            ordering = query.ManagedQuery(term, fields);\n-        }\n-\n-            // Filter selected fields because results are loaded from the published snapshot based on these\n-            IOrdering? queryExecutor = ordering.SelectFields(_returnedQueryFields);\n-\n-\n-        ISearchResults? results = skip == 0 && take == 0\n-            ? queryExecutor.Execute()\n-            : queryExecutor.Execute(QueryOptions.SkipTake(skip, take));\n \n-        totalRecords = results.TotalItemCount;\n+            // Filter out unpublished content for the specified culture if the content varies by culture\n+            // The published__{culture} field is not populated when the content is not published in that culture\n+            ordering = query\n+                .ManagedQuery(term, fields)\n+                .Not().Group(q => q\n+                    .Field(UmbracoExamineFieldNames.VariesByCultureFieldName, \"y\")\n+                    .Not().Field($\"{UmbracoExamineFieldNames.PublishedFieldName}_{culture.ToLowerInvariant()}\", \"y\"));\n+        }\n \n-        return new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content),\n-            _variationContextAccessor, culture);\n+        return Search(ordering, skip, take, out totalRecords, culture);\n     }\n@@ -318,2 +314,6 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query)\n     public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords)\n+        => Search(query, skip, take, out totalRecords, null);\n+\n+    /// <inheritdoc />\n+    public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords, string? culture)\n     {\n@@ -333,4 +333,4 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip,\n         {\n-                // Filter selected fields because results are loaded from the published snapshot based on these\n-                query = ordering.SelectFields(_returnedQueryFields);\n+            // Filter selected fields because results are loaded from the published snapshot based on these\n+            query = ordering.SelectFields(_returnedQueryFields);\n         }\n@@ -343,3 +343,5 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip,\n \n-        return results.ToPublishedSearchResults(_publishedSnapshot);\n+        return culture.IsNullOrWhiteSpace()\n+            ? results.ToPublishedSearchResults(_publishedSnapshot)\n+            : new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture);\n     }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nikolaj E. Lauridsen","training-data":{"loc-added":"6","loc-deleted":"36","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"nikolajlauridsen@protonmail.ch","commit-full-message":"* Remove nucache reference from Web.Common\r\n\r\n* Get tests building-ish\r\n\r\n* Move ReservedFieldNamesService to the right project\r\n\r\n* Remove IPublishedSnapshotStatus\r\n\r\n* Added functionality to the INavigationQueryService to get root keys\r\n\r\n* Fixed issue with navigation\r\n\r\n* Remove IPublishedSnapshot from UmbracoContext\r\n\r\n* Begin removing usage of IPublishedSnapshot from PublishedContentExtensions\r\n\r\n* Fix PublishedContentExtensions.cs\r\n\r\n* Don't use snapshots in delivery media api\r\n\r\n* Use IPublishedMediaCache in QueryMediaApiController\r\n\r\n* Remove more usages of IPublishedSnapshotAccessor\r\n\r\n* Comment out tests\r\n\r\n* Remove more usages of PublishedSnapshotAccessor\r\n\r\n* Remove PublishedSnapshot from property\r\n\r\n* Fixed test build\r\n\r\n* Fix errors\r\n\r\n* Fix some tests\r\n\r\n* Delete NuCache 🎉\r\n\r\n* Implement DatabaseCacheRebuilder\r\n\r\n* Remove usage of IPublishedSnapshotService\r\n\r\n* Remove IPublishedSnapshotService\r\n\r\n* Remove TestPublishedSnapshotAccessor and make tests build\r\n\r\n* Don't test Snapshot cachelevel\r\n\r\nIt's no longer supported\r\n\r\n* Fix BlockEditorConverter\r\n\r\nElement != Element document type\r\n\r\n* Remember to set cachemanager\r\n\r\n* Fix RichTextParserTests\r\n\r\n* Implement TryGetLevel on INavigationQueryService\r\n\r\n* Fake level and obsolete it in PublishedContent\r\n\r\n* Remove ChildrenForAllCultures\r\n\r\n* Hack Path property on PublishedContent\r\n\r\n* Remove usages of IPublishedSnapshot in tests\r\n\r\n* More ConvertersTests\r\n\r\n* Add hybrid cache to integration tests\r\n\r\nWe can actually do this now because we no longer save files on disk\r\n\r\n* Rename IPublishedSnapshotRebuilder to ICacheRebuilder\r\n\r\n* Comment out tests\r\n\r\n* V15: Replacing the usages of Parent (navigation data) from IPublishedContent (#17125)\r\n\r\n* Fix .Parent references in PublishedContentExtensions\r\n\r\n* Add missing methods to FriendlyPublishedContentExtensions (ones that you were able to call on the content directly as they now require extra params)\r\n\r\n* Fix references from the extension methods\r\n\r\n* Fix dependencies in tests\r\n\r\n* Replace IPublishedSnapshotAccessor with the content cache in tests\r\n\r\n* Resolving more .Parent references\r\n\r\n* Fix unit tests\r\n\r\n* Obsolete and use extension methods\r\n\r\n* Remove private method and use extension instead\r\n\r\n* Moving code around\r\n\r\n* Fix tests\r\n\r\n* Fix more references\r\n\r\n* Cleanup\r\n\r\n* Fix more usages\r\n\r\n* Resolve merge conflict\r\n\r\n* Fix tests\r\n\r\n* Cleanup\r\n\r\n* Fix more tests\r\n\r\n* Fixed unit tests\r\n\r\n* Cleanup\r\n\r\n* Replace last usages\r\n\r\n---------\r\n\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\n\r\n* Remove usage of IPublishedSnapshotAccessor from IRequestItemProvider\r\n\r\n* Post merge fixup\r\n\r\n* Remo IPublishedSnapshot\r\n\r\n* Add HasAny to IDocumentUrlService\r\n\r\n* Fix TextBuilder\r\n\r\n* Fix modelsbuilder tests\r\n\r\n* Use explicit types\r\n\r\n* Implement GetByContentType\r\n\r\n* Support element types in PublishedContentTypeCache\r\n\r\n* Run enlistments before publishing notifications\r\n\r\n* Fix elements cache refreshing\r\n\r\n* Implement GetByUdi\r\n\r\n* Implement GetAtRoot\r\n\r\n* Implement GetByRoute\r\n\r\n* Reimplement GetRouteById\r\n\r\n* Fix blocks unit tests\r\n\r\n* Initialize domain cache on boot\r\n\r\n* Only return routes with domains on non default lanauges\r\n\r\n* V15: Replacing the usages of `Children` (navigation data) from `IPublishedContent` (#17159)\r\n\r\n* Update params in PublishedContentExtensions to the general interfaces for the published cache and navigation service, so that we can use the extension methods on both documents and media\r\n\r\n* Introduce GetParent() which uses the right services\r\n\r\n* Fix obsolete message on .Parent\r\n\r\n* Obsolete .Children\r\n\r\n* Fix usages of Children for ApiMediaQueryService\r\n\r\n* Fix usage in internal\r\n\r\n* Fix usages in views\r\n\r\n* Fix indentation\r\n\r\n* Fix issue with delete language\r\n\r\n* Update nuget pacakges\r\n\r\n* Clear elements cache when content is deleted\r\n\r\ninstead of trying to update it\r\n\r\n* Reset publishedModelFactory\r\n\r\n* Fixed publishing\r\n\r\n---------\r\n\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\nCo-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>\r\nCo-authored-by: kjac <kja@umbraco.dk>","commit-date":"2024-10-01T13:03:02Z","current-rev":"1258962429","filename":"Umbraco-CMS/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs","previous-rev":"7ca96423f8","commit-title":"V15: Remove Nucache (#17166)","language":"C#","id":"c5107d73a299c92b12ef250416ebfdc0fe984c06","model-score":0.2,"author-id":null,"project-id":33308,"delta-file-score":0.29056275,"diff":"diff --git a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\nindex 53e8156538..0452cf0b03 100644\n--- a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\n+++ b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\n@@ -17,3 +17,2 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase\n     private readonly object _locko = new();\n-    private readonly IPublishedSnapshotAccessor? _publishedSnapshotAccessor;\n     private readonly object? _sourceValue;\n@@ -21,2 +20,3 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase\n     protected readonly bool IsPreviewing;\n+    private readonly ICacheManager? _cacheManager;\n     private CacheValues? _cacheValues;\n@@ -32,4 +32,4 @@ public PublishedElementPropertyBase(\n         PropertyCacheLevel referenceCacheLevel,\n-        object? sourceValue = null,\n-        IPublishedSnapshotAccessor? publishedSnapshotAccessor = null)\n+        ICacheManager? cacheManager,\n+        object? sourceValue = null)\n         : base(propertyType, referenceCacheLevel)\n@@ -37,5 +37,5 @@ public PublishedElementPropertyBase(\n         _sourceValue = sourceValue;\n-        _publishedSnapshotAccessor = publishedSnapshotAccessor;\n         Element = element;\n         IsPreviewing = previewing;\n+        _cacheManager = cacheManager;\n         IsMember = propertyType.ContentType?.ItemType == PublishedItemType.Member;\n@@ -120,23 +120,2 @@ private void GetCacheLevels(PropertyCacheLevel propertyTypeCacheLevel, out Prope\n \n-    private IAppCache? GetSnapshotCache()\n-    {\n-        // cache within the snapshot cache, unless previewing, then use the snapshot or\n-        // elements cache (if we don't want to pollute the elements cache with short-lived\n-        // data) depending on settings\n-        // for members, always cache in the snapshot cache - never pollute elements cache\n-        if (_publishedSnapshotAccessor is null)\n-        {\n-            return null;\n-        }\n-\n-        if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot))\n-        {\n-            return null;\n-        }\n-\n-        return (IsPreviewing == false || FullCacheWhenPreviewing) && IsMember == false\n-            ? publishedSnapshot!.ElementsCache\n-            : publishedSnapshot!.SnapshotCache;\n-    }\n-\n     private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n@@ -147,2 +126,3 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n             case PropertyCacheLevel.None:\n+            case PropertyCacheLevel.Snapshot:\n                 // never cache anything\n@@ -155,13 +135,3 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n             case PropertyCacheLevel.Elements:\n-                // cache within the elements  cache, depending...\n-                IAppCache? snapshotCache = GetSnapshotCache();\n-                cacheValues = (CacheValues?)snapshotCache?.Get(ValuesCacheKey, () => new CacheValues()) ??\n-                              new CacheValues();\n-                break;\n-            case PropertyCacheLevel.Snapshot:\n-                IPublishedSnapshot? publishedSnapshot = _publishedSnapshotAccessor?.GetRequiredPublishedSnapshot();\n-\n-                // cache within the snapshot cache\n-                IAppCache? facadeCache = publishedSnapshot?.SnapshotCache;\n-                cacheValues = (CacheValues?)facadeCache?.Get(ValuesCacheKey, () => new CacheValues()) ??\n+                cacheValues = (CacheValues?)_cacheManager?.ElementsCache.Get(ValuesCacheKey, () => new CacheValues()) ??\n                               new CacheValues();\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nikolaj E. Lauridsen","training-data":{"loc-added":"11","loc-deleted":"48","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"nikolajlauridsen@protonmail.ch","commit-full-message":"","commit-date":"2024-12-03T12:40:05Z","current-rev":"380f3f7e87","filename":"Umbraco-CMS/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs","previous-rev":"437fcba4e5","commit-title":"Clear elements cache instead of refreshing it (#17708)","language":"C#","id":"520cf3bc55fbfa6fb3c07f3560d781b1e2034c12","model-score":0.19,"author-id":null,"project-id":33308,"delta-file-score":0.7076424,"diff":"diff --git a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\nindex f61968365c..6ff5a5fe1e 100644\n--- a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\n+++ b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\n@@ -47,3 +47,3 @@ public async Task HandleAsync(ContentRefreshNotification notification, Cancellat\n     {\n-        await RefreshElementsCacheAsync(notification.Entity);\n+        ClearElementsCache();\n \n@@ -56,3 +56,3 @@ public async Task HandleAsync(ContentDeletedNotification notification, Cancellat\n         {\n-            RemoveFromElementsCache(deletedEntity);\n+            ClearElementsCache();\n             await _documentCacheService.DeleteItemAsync(deletedEntity);\n@@ -63,3 +63,3 @@ public async Task HandleAsync(MediaRefreshNotification notification, Cancellatio\n     {\n-        await RefreshElementsCacheAsync(notification.Entity);\n+        ClearElementsCache();\n         await _mediaCacheService.RefreshMediaAsync(notification.Entity);\n@@ -71,3 +71,3 @@ public async Task HandleAsync(MediaDeletedNotification notification, Cancellatio\n         {\n-            RemoveFromElementsCache(deletedEntity);\n+            ClearElementsCache();\n             await _mediaCacheService.DeleteItemAsync(deletedEntity);\n@@ -76,49 +76,12 @@ public async Task HandleAsync(MediaDeletedNotification notification, Cancellatio\n \n-    private async Task RefreshElementsCacheAsync(IUmbracoEntity content)\n+    private void ClearElementsCache()\n     {\n-        IEnumerable<IRelation> parentRelations = _relationService.GetByParent(content)!;\n-        IEnumerable<IRelation> childRelations = _relationService.GetByChild(content);\n-\n-        var ids = parentRelations.Select(x => x.ChildId).Concat(childRelations.Select(x => x.ParentId)).ToHashSet();\n-        // We need to add ourselves to the list of ids to clear\n-        ids.Add(content.Id);\n-        foreach (var id in ids)\n-        {\n-            if (await _documentCacheService.HasContentByIdAsync(id) is false)\n-            {\n-                continue;\n-            }\n-\n-            IPublishedContent? publishedContent = await _documentCacheService.GetByIdAsync(id);\n-            if (publishedContent is null)\n-            {\n-                continue;\n-            }\n-\n-            foreach (IPublishedProperty publishedProperty in publishedContent.Properties)\n-            {\n-                var property = (PublishedProperty) publishedProperty;\n-                if (property.ReferenceCacheLevel is PropertyCacheLevel.Elements\n-                    || property.PropertyType.DeliveryApiCacheLevel is PropertyCacheLevel.Elements\n-                    || property.PropertyType.DeliveryApiCacheLevelForExpansion is PropertyCacheLevel.Elements)\n-                {\n-                    _elementsCache.ClearByKey(property.ValuesCacheKey);\n-                }\n-            }\n-        }\n+        // Ideally we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.\n+        // The reason for this is that we have no way to know which elements are affected by the changes. or what their keys are.\n+        // This is because currently published elements lives exclusively in a JSON blob in the umbracoPropertyData table.\n+        // This means that the only way to resolve these keys are to actually parse this data with a specific value converter, and for all cultures, which is not feasible.\n+        // If published elements become their own entities with relations, instead of just property data, we can revisit this,\n+        _elementsCache.Clear();\n     }\n \n-    private void RemoveFromElementsCache(IUmbracoEntity content)\n-    {\n-        // ClearByKey clears by \"startsWith\" so we'll clear by the cachekey prefix + contentKey\n-        // This will clear any and all properties for this content item, this is important because\n-        // we cannot resolve the PublishedContent for this entity since it and its content type is deleted.\n-        _elementsCache.ClearByKey(GetContentWideCacheKey(content.Key, true));\n-        _elementsCache.ClearByKey(GetContentWideCacheKey(content.Key, false));\n-    }\n-\n-    private string GetContentWideCacheKey(Guid contentKey, bool isPreviewing) => isPreviewing\n-        ? CacheKeys.PreviewPropertyCacheKeyPrefix + contentKey\n-        : CacheKeys.PropertyCacheKeyPrefix + contentKey;\n-\n     public Task HandleAsync(ContentTypeRefreshedNotification notification, CancellationToken cancellationToken)\n","improvement-type":"Complex Method"}],"change-level":"warning","is-hotspot?":false,"line":206,"what-changed":"UpdateBlockPropertyValues has a cyclomatic complexity of 17, threshold = 9","how-to-fix":"There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html). Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring.","change-type":"introduced"},{"method":"Handle","why-it-occurs":"A Complex Method has a high cyclomatic complexity. The recommended threshold for the C# language is a cyclomatic complexity lower than 9.","name":"Complex Method","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs","refactoring-examples":[{"architectural-component-id":null,"author-name":"Bjarke Berg","training-data":{"loc-added":"24","loc-deleted":"26","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mail@bergmania.dk","commit-full-message":"* AB40660 - untangle the preview cookie from the auth cookie\r\n\r\n* Clean up\r\n\r\n* Allow anonymous to end preview sessions\r\n\r\n* Some refinements\r\n\r\n* update OpenApi.json\r\n\r\n* Fix enter preview test\r\n\r\n* correct tests to match new expectations of the preview cookie\r\n\r\n* sync preview tests with correct expectations of access level\r\n\r\n---------\r\n\r\nCo-authored-by: Sven Geusens <sge@umbraco.dk>\r\nCo-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>","commit-date":"2024-05-17T14:06:26Z","current-rev":"11e5257b56","filename":"Umbraco-CMS/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs","previous-rev":"80794f3efd","commit-title":"V14: Untangle the preview functionality from the auth cookie (#16308)","language":"C#","id":"7ecfb0a869f8c3df64c3c2a4602ef34642b49720","model-score":0.52,"author-id":null,"project-id":33308,"delta-file-score":1.044829,"diff":"diff --git a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\nindex 5bd16867ca..e61b8682af 100644\n--- a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n+++ b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n@@ -7,2 +7,8 @@\n using Microsoft.Extensions.Options;\n+using Umbraco.Cms.Core;\n+using Umbraco.Cms.Core.Preview;\n+using Umbraco.Cms.Core.Security;\n+using Umbraco.Cms.Core.Services;\n+using Umbraco.Cms.Web.Common.AspNetCore;\n+using Umbraco.Cms.Web.Common.Security;\n using Umbraco.Extensions;\n@@ -18,4 +24,15 @@ public class PreviewAuthenticationMiddleware : IMiddleware\n     private readonly ILogger<PreviewAuthenticationMiddleware> _logger;\n+    private readonly IPreviewTokenGenerator _previewTokenGenerator;\n+    private readonly IPreviewService _previewService;\n \n-    public PreviewAuthenticationMiddleware(ILogger<PreviewAuthenticationMiddleware> logger) => _logger = logger;\n+\n+    public PreviewAuthenticationMiddleware(\n+        ILogger<PreviewAuthenticationMiddleware> logger,\n+        IPreviewTokenGenerator previewTokenGenerator,\n+        IPreviewService previewService)\n+    {\n+        _logger = logger;\n+        _previewTokenGenerator = previewTokenGenerator;\n+        _previewService = previewService;\n+    }\n \n@@ -40,33 +57,14 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)\n             {\n-                CookieAuthenticationOptions? cookieOptions = context.RequestServices\n-                    .GetRequiredService<IOptionsSnapshot<CookieAuthenticationOptions>>()\n-                    .Get(Core.Constants.Security.BackOfficeAuthenticationType);\n+                Attempt<ClaimsIdentity> backOfficeIdentityAttempt = await _previewService.TryGetPreviewClaimsIdentityAsync();\n \n-                if (cookieOptions == null)\n+                if (backOfficeIdentityAttempt.Success)\n                 {\n-                    throw new InvalidOperationException(\"No cookie options found with name \" +\n-                                                        Core.Constants.Security.BackOfficeAuthenticationType);\n+                    // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n+                    // Principal, this means we'll have 2 identities assigned to the principal which we can\n+                    // use to authorize the preview and allow for a back office User.\n+                    context.User.AddIdentity(backOfficeIdentityAttempt.Result!);\n                 }\n-\n-                // If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing.\n-                // In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication\n-                // for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered.\n-                if (cookieOptions.Cookie.Name != null)\n+                else\n                 {\n-                    var chunkingCookieManager = new ChunkingCookieManager();\n-                    var cookie = chunkingCookieManager.GetRequestCookie(context, cookieOptions.Cookie.Name);\n-\n-                    if (!string.IsNullOrEmpty(cookie))\n-                    {\n-                        AuthenticationTicket? unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie);\n-                        ClaimsIdentity? backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity();\n-\n-                        if (backOfficeIdentity != null)\n-                        {\n-                            // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n-                            // Principal, this means we'll have 2 identities assigned to the principal which we can\n-                            // use to authorize the preview and allow for a back office User.\n-                            context.User.AddIdentity(backOfficeIdentity);\n-                        }\n-                    }\n+                    _logger.LogDebug(\"Could not transform previewCookie value into a claimsIdentity\");\n                 }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Sven Geusens","training-data":{"loc-added":"74","loc-deleted":"9","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"sge@umbraco.dk","commit-full-message":"* Support new localLink format in core link parsing\r\n\r\n* Updated devliery api to work with the new locallinks format\r\n\r\nAdded tests for old and new format handling.\r\n\r\n* Fix error regarding type attribute not always being present (for example old format or non local links)","commit-date":"2024-07-02T12:22:19Z","current-rev":"46acd51759","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs","previous-rev":"13b77d35df","commit-title":"[V14] Make the backend work with the new localLinks format (#16661)","language":"C#","id":"7d7b589a86f716e705df415598cc4c8f2a02af1b","model-score":0.42,"author-id":null,"project-id":33308,"delta-file-score":0.3009901,"diff":"diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\nindex 7723fc835c..407bc1a022 100644\n--- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\n+++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\n@@ -6,2 +6,3 @@\n using Umbraco.Cms.Core.PublishedCache;\n+using Umbraco.Cms.Core.Templates;\n \n@@ -20,3 +21,17 @@ protected ApiRichTextParserBase(IApiContentRouteBuilder apiContentRouteBuilder,\n \n-    protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl, Action handleInvalidLink)\n+    protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, string type, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl, Action handleInvalidLink)\n+    {\n+        ReplaceStatus replaceAttempt = ReplaceLocalLink(publishedSnapshot, href, type, handleContentRoute, handleMediaUrl);\n+        if (replaceAttempt == ReplaceStatus.Success)\n+        {\n+            return;\n+        }\n+\n+        if (replaceAttempt == ReplaceStatus.InvalidEntityType || ReplaceLegacyLocalLink(publishedSnapshot, href, handleContentRoute, handleMediaUrl) == ReplaceStatus.InvalidEntityType)\n+        {\n+            handleInvalidLink();\n+        }\n+    }\n+\n+    private ReplaceStatus ReplaceLocalLink(IPublishedSnapshot publishedSnapshot, string href, string type, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl)\n     {\n@@ -25,11 +40,12 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n         {\n-            return;\n+            return ReplaceStatus.NoMatch;\n         }\n \n-        if (UdiParser.TryParse(match.Groups[\"udi\"].Value, out Udi? udi) is false)\n+        if (Guid.TryParse(match.Groups[\"guid\"].Value, out Guid guid) is false)\n         {\n-            return;\n+            return ReplaceStatus.NoMatch;\n         }\n \n-        bool handled = false;\n+        var udi = new GuidUdi(type, guid);\n+\n         switch (udi.EntityType)\n@@ -43,4 +59,4 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n                 {\n-                    handled = true;\n                     handleContentRoute(route);\n+                    return ReplaceStatus.Success;\n                 }\n@@ -52,4 +68,4 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n                 {\n-                    handled = true;\n                     handleMediaUrl(_apiMediaUrlProvider.GetUrl(media));\n+                    return ReplaceStatus.Success;\n                 }\n@@ -59,6 +75,45 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n \n-        if(handled is false)\n+        return ReplaceStatus.InvalidEntityType;\n+    }\n+\n+    private ReplaceStatus ReplaceLegacyLocalLink(IPublishedSnapshot publishedSnapshot, string href, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl)\n+    {\n+        Match match = LegacyLocalLinkRegex().Match(href);\n+        if (match.Success is false)\n         {\n-            handleInvalidLink();\n+            return ReplaceStatus.NoMatch;\n+        }\n+\n+        if (UdiParser.TryParse(match.Groups[\"udi\"].Value, out Udi? udi) is false)\n+        {\n+            return ReplaceStatus.NoMatch;\n+        }\n+\n+\n+        switch (udi.EntityType)\n+        {\n+            case Constants.UdiEntityType.Document:\n+                IPublishedContent? content = publishedSnapshot.Content?.GetById(udi);\n+                IApiContentRoute? route = content != null\n+                    ? _apiContentRouteBuilder.Build(content)\n+                    : null;\n+                if (route != null)\n+                {\n+                    handleContentRoute(route);\n+                    return ReplaceStatus.Success;\n+                }\n+\n+                break;\n+            case Constants.UdiEntityType.Media:\n+                IPublishedContent? media = publishedSnapshot.Media?.GetById(udi);\n+                if (media != null)\n+                {\n+                    handleMediaUrl(_apiMediaUrlProvider.GetUrl(media));\n+                    return ReplaceStatus.Success;\n+                }\n+\n+                break;\n         }\n+\n+        return ReplaceStatus.InvalidEntityType;\n     }\n@@ -82,3 +137,13 @@ protected void ReplaceLocalImages(IPublishedSnapshot publishedSnapshot, string u\n     [GeneratedRegex(\"{localLink:(?<udi>umb:.+)}\")]\n+    private static partial Regex LegacyLocalLinkRegex();\n+\n+    [GeneratedRegex(\"{localLink:(?<guid>.+)}\")]\n     private static partial Regex LocalLinkRegex();\n+\n+    private enum ReplaceStatus\n+    {\n+        NoMatch,\n+        Success,\n+        InvalidEntityType\n+    }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vitor Rodrigues","training-data":{"loc-added":"16","loc-deleted":"14","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"8.545379580978913"},"author-email":"vsilvar@users.noreply.github.com","commit-full-message":"* Fixes #15136: Search includes fields from other cultures\r\n\r\nRegex was updated to support block list fields\r\nUnpublished nodes on the supplied culture are not filtered out\r\n\r\n* Making the code non-breaking\r\n\r\n* Fixed failing publish content query integration tests\r\n\r\nThe tests were not setting the content as publish in the specifed culture\r\ncausing the content items to be ignored\r\n\r\n---------\r\n\r\nCo-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>","commit-date":"2024-02-07T12:43:37Z","current-rev":"839b2ff6a2","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/PublishedContentQuery.cs","previous-rev":"2221c4f1c7","commit-title":"Fixes #15136: Search includes fields from other cultures (#15148)","language":"C#","id":"ac0709776b22e60e777651712009344d5d3c3529","model-score":0.3,"author-id":null,"project-id":33308,"delta-file-score":0.2613985,"diff":"diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs\nindex d075e8b9d2..758115b9ca 100644\n--- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs\n+++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs\n@@ -295,17 +295,13 @@ public IEnumerable<PublishedSearchResult> Search(string term, int skip, int take\n                     .ToArray(); // Get all index fields suffixed with the culture name supplied\n-            ordering = query.ManagedQuery(term, fields);\n-        }\n-\n-            // Filter selected fields because results are loaded from the published snapshot based on these\n-            IOrdering? queryExecutor = ordering.SelectFields(_returnedQueryFields);\n-\n-\n-        ISearchResults? results = skip == 0 && take == 0\n-            ? queryExecutor.Execute()\n-            : queryExecutor.Execute(QueryOptions.SkipTake(skip, take));\n \n-        totalRecords = results.TotalItemCount;\n+            // Filter out unpublished content for the specified culture if the content varies by culture\n+            // The published__{culture} field is not populated when the content is not published in that culture\n+            ordering = query\n+                .ManagedQuery(term, fields)\n+                .Not().Group(q => q\n+                    .Field(UmbracoExamineFieldNames.VariesByCultureFieldName, \"y\")\n+                    .Not().Field($\"{UmbracoExamineFieldNames.PublishedFieldName}_{culture.ToLowerInvariant()}\", \"y\"));\n+        }\n \n-        return new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content),\n-            _variationContextAccessor, culture);\n+        return Search(ordering, skip, take, out totalRecords, culture);\n     }\n@@ -318,2 +314,6 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query)\n     public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords)\n+        => Search(query, skip, take, out totalRecords, null);\n+\n+    /// <inheritdoc />\n+    public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords, string? culture)\n     {\n@@ -333,4 +333,4 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip,\n         {\n-                // Filter selected fields because results are loaded from the published snapshot based on these\n-                query = ordering.SelectFields(_returnedQueryFields);\n+            // Filter selected fields because results are loaded from the published snapshot based on these\n+            query = ordering.SelectFields(_returnedQueryFields);\n         }\n@@ -343,3 +343,5 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip,\n \n-        return results.ToPublishedSearchResults(_publishedSnapshot);\n+        return culture.IsNullOrWhiteSpace()\n+            ? results.ToPublishedSearchResults(_publishedSnapshot)\n+            : new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture);\n     }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nikolaj E. Lauridsen","training-data":{"loc-added":"6","loc-deleted":"36","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"nikolajlauridsen@protonmail.ch","commit-full-message":"* Remove nucache reference from Web.Common\r\n\r\n* Get tests building-ish\r\n\r\n* Move ReservedFieldNamesService to the right project\r\n\r\n* Remove IPublishedSnapshotStatus\r\n\r\n* Added functionality to the INavigationQueryService to get root keys\r\n\r\n* Fixed issue with navigation\r\n\r\n* Remove IPublishedSnapshot from UmbracoContext\r\n\r\n* Begin removing usage of IPublishedSnapshot from PublishedContentExtensions\r\n\r\n* Fix PublishedContentExtensions.cs\r\n\r\n* Don't use snapshots in delivery media api\r\n\r\n* Use IPublishedMediaCache in QueryMediaApiController\r\n\r\n* Remove more usages of IPublishedSnapshotAccessor\r\n\r\n* Comment out tests\r\n\r\n* Remove more usages of PublishedSnapshotAccessor\r\n\r\n* Remove PublishedSnapshot from property\r\n\r\n* Fixed test build\r\n\r\n* Fix errors\r\n\r\n* Fix some tests\r\n\r\n* Delete NuCache 🎉\r\n\r\n* Implement DatabaseCacheRebuilder\r\n\r\n* Remove usage of IPublishedSnapshotService\r\n\r\n* Remove IPublishedSnapshotService\r\n\r\n* Remove TestPublishedSnapshotAccessor and make tests build\r\n\r\n* Don't test Snapshot cachelevel\r\n\r\nIt's no longer supported\r\n\r\n* Fix BlockEditorConverter\r\n\r\nElement != Element document type\r\n\r\n* Remember to set cachemanager\r\n\r\n* Fix RichTextParserTests\r\n\r\n* Implement TryGetLevel on INavigationQueryService\r\n\r\n* Fake level and obsolete it in PublishedContent\r\n\r\n* Remove ChildrenForAllCultures\r\n\r\n* Hack Path property on PublishedContent\r\n\r\n* Remove usages of IPublishedSnapshot in tests\r\n\r\n* More ConvertersTests\r\n\r\n* Add hybrid cache to integration tests\r\n\r\nWe can actually do this now because we no longer save files on disk\r\n\r\n* Rename IPublishedSnapshotRebuilder to ICacheRebuilder\r\n\r\n* Comment out tests\r\n\r\n* V15: Replacing the usages of Parent (navigation data) from IPublishedContent (#17125)\r\n\r\n* Fix .Parent references in PublishedContentExtensions\r\n\r\n* Add missing methods to FriendlyPublishedContentExtensions (ones that you were able to call on the content directly as they now require extra params)\r\n\r\n* Fix references from the extension methods\r\n\r\n* Fix dependencies in tests\r\n\r\n* Replace IPublishedSnapshotAccessor with the content cache in tests\r\n\r\n* Resolving more .Parent references\r\n\r\n* Fix unit tests\r\n\r\n* Obsolete and use extension methods\r\n\r\n* Remove private method and use extension instead\r\n\r\n* Moving code around\r\n\r\n* Fix tests\r\n\r\n* Fix more references\r\n\r\n* Cleanup\r\n\r\n* Fix more usages\r\n\r\n* Resolve merge conflict\r\n\r\n* Fix tests\r\n\r\n* Cleanup\r\n\r\n* Fix more tests\r\n\r\n* Fixed unit tests\r\n\r\n* Cleanup\r\n\r\n* Replace last usages\r\n\r\n---------\r\n\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\n\r\n* Remove usage of IPublishedSnapshotAccessor from IRequestItemProvider\r\n\r\n* Post merge fixup\r\n\r\n* Remo IPublishedSnapshot\r\n\r\n* Add HasAny to IDocumentUrlService\r\n\r\n* Fix TextBuilder\r\n\r\n* Fix modelsbuilder tests\r\n\r\n* Use explicit types\r\n\r\n* Implement GetByContentType\r\n\r\n* Support element types in PublishedContentTypeCache\r\n\r\n* Run enlistments before publishing notifications\r\n\r\n* Fix elements cache refreshing\r\n\r\n* Implement GetByUdi\r\n\r\n* Implement GetAtRoot\r\n\r\n* Implement GetByRoute\r\n\r\n* Reimplement GetRouteById\r\n\r\n* Fix blocks unit tests\r\n\r\n* Initialize domain cache on boot\r\n\r\n* Only return routes with domains on non default lanauges\r\n\r\n* V15: Replacing the usages of `Children` (navigation data) from `IPublishedContent` (#17159)\r\n\r\n* Update params in PublishedContentExtensions to the general interfaces for the published cache and navigation service, so that we can use the extension methods on both documents and media\r\n\r\n* Introduce GetParent() which uses the right services\r\n\r\n* Fix obsolete message on .Parent\r\n\r\n* Obsolete .Children\r\n\r\n* Fix usages of Children for ApiMediaQueryService\r\n\r\n* Fix usage in internal\r\n\r\n* Fix usages in views\r\n\r\n* Fix indentation\r\n\r\n* Fix issue with delete language\r\n\r\n* Update nuget pacakges\r\n\r\n* Clear elements cache when content is deleted\r\n\r\ninstead of trying to update it\r\n\r\n* Reset publishedModelFactory\r\n\r\n* Fixed publishing\r\n\r\n---------\r\n\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\nCo-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>\r\nCo-authored-by: kjac <kja@umbraco.dk>","commit-date":"2024-10-01T13:03:02Z","current-rev":"1258962429","filename":"Umbraco-CMS/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs","previous-rev":"7ca96423f8","commit-title":"V15: Remove Nucache (#17166)","language":"C#","id":"c5107d73a299c92b12ef250416ebfdc0fe984c06","model-score":0.2,"author-id":null,"project-id":33308,"delta-file-score":0.29056275,"diff":"diff --git a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\nindex 53e8156538..0452cf0b03 100644\n--- a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\n+++ b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\n@@ -17,3 +17,2 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase\n     private readonly object _locko = new();\n-    private readonly IPublishedSnapshotAccessor? _publishedSnapshotAccessor;\n     private readonly object? _sourceValue;\n@@ -21,2 +20,3 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase\n     protected readonly bool IsPreviewing;\n+    private readonly ICacheManager? _cacheManager;\n     private CacheValues? _cacheValues;\n@@ -32,4 +32,4 @@ public PublishedElementPropertyBase(\n         PropertyCacheLevel referenceCacheLevel,\n-        object? sourceValue = null,\n-        IPublishedSnapshotAccessor? publishedSnapshotAccessor = null)\n+        ICacheManager? cacheManager,\n+        object? sourceValue = null)\n         : base(propertyType, referenceCacheLevel)\n@@ -37,5 +37,5 @@ public PublishedElementPropertyBase(\n         _sourceValue = sourceValue;\n-        _publishedSnapshotAccessor = publishedSnapshotAccessor;\n         Element = element;\n         IsPreviewing = previewing;\n+        _cacheManager = cacheManager;\n         IsMember = propertyType.ContentType?.ItemType == PublishedItemType.Member;\n@@ -120,23 +120,2 @@ private void GetCacheLevels(PropertyCacheLevel propertyTypeCacheLevel, out Prope\n \n-    private IAppCache? GetSnapshotCache()\n-    {\n-        // cache within the snapshot cache, unless previewing, then use the snapshot or\n-        // elements cache (if we don't want to pollute the elements cache with short-lived\n-        // data) depending on settings\n-        // for members, always cache in the snapshot cache - never pollute elements cache\n-        if (_publishedSnapshotAccessor is null)\n-        {\n-            return null;\n-        }\n-\n-        if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot))\n-        {\n-            return null;\n-        }\n-\n-        return (IsPreviewing == false || FullCacheWhenPreviewing) && IsMember == false\n-            ? publishedSnapshot!.ElementsCache\n-            : publishedSnapshot!.SnapshotCache;\n-    }\n-\n     private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n@@ -147,2 +126,3 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n             case PropertyCacheLevel.None:\n+            case PropertyCacheLevel.Snapshot:\n                 // never cache anything\n@@ -155,13 +135,3 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n             case PropertyCacheLevel.Elements:\n-                // cache within the elements  cache, depending...\n-                IAppCache? snapshotCache = GetSnapshotCache();\n-                cacheValues = (CacheValues?)snapshotCache?.Get(ValuesCacheKey, () => new CacheValues()) ??\n-                              new CacheValues();\n-                break;\n-            case PropertyCacheLevel.Snapshot:\n-                IPublishedSnapshot? publishedSnapshot = _publishedSnapshotAccessor?.GetRequiredPublishedSnapshot();\n-\n-                // cache within the snapshot cache\n-                IAppCache? facadeCache = publishedSnapshot?.SnapshotCache;\n-                cacheValues = (CacheValues?)facadeCache?.Get(ValuesCacheKey, () => new CacheValues()) ??\n+                cacheValues = (CacheValues?)_cacheManager?.ElementsCache.Get(ValuesCacheKey, () => new CacheValues()) ??\n                               new CacheValues();\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nikolaj E. Lauridsen","training-data":{"loc-added":"11","loc-deleted":"48","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"nikolajlauridsen@protonmail.ch","commit-full-message":"","commit-date":"2024-12-03T12:40:05Z","current-rev":"380f3f7e87","filename":"Umbraco-CMS/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs","previous-rev":"437fcba4e5","commit-title":"Clear elements cache instead of refreshing it (#17708)","language":"C#","id":"520cf3bc55fbfa6fb3c07f3560d781b1e2034c12","model-score":0.19,"author-id":null,"project-id":33308,"delta-file-score":0.7076424,"diff":"diff --git a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\nindex f61968365c..6ff5a5fe1e 100644\n--- a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\n+++ b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\n@@ -47,3 +47,3 @@ public async Task HandleAsync(ContentRefreshNotification notification, Cancellat\n     {\n-        await RefreshElementsCacheAsync(notification.Entity);\n+        ClearElementsCache();\n \n@@ -56,3 +56,3 @@ public async Task HandleAsync(ContentDeletedNotification notification, Cancellat\n         {\n-            RemoveFromElementsCache(deletedEntity);\n+            ClearElementsCache();\n             await _documentCacheService.DeleteItemAsync(deletedEntity);\n@@ -63,3 +63,3 @@ public async Task HandleAsync(MediaRefreshNotification notification, Cancellatio\n     {\n-        await RefreshElementsCacheAsync(notification.Entity);\n+        ClearElementsCache();\n         await _mediaCacheService.RefreshMediaAsync(notification.Entity);\n@@ -71,3 +71,3 @@ public async Task HandleAsync(MediaDeletedNotification notification, Cancellatio\n         {\n-            RemoveFromElementsCache(deletedEntity);\n+            ClearElementsCache();\n             await _mediaCacheService.DeleteItemAsync(deletedEntity);\n@@ -76,49 +76,12 @@ public async Task HandleAsync(MediaDeletedNotification notification, Cancellatio\n \n-    private async Task RefreshElementsCacheAsync(IUmbracoEntity content)\n+    private void ClearElementsCache()\n     {\n-        IEnumerable<IRelation> parentRelations = _relationService.GetByParent(content)!;\n-        IEnumerable<IRelation> childRelations = _relationService.GetByChild(content);\n-\n-        var ids = parentRelations.Select(x => x.ChildId).Concat(childRelations.Select(x => x.ParentId)).ToHashSet();\n-        // We need to add ourselves to the list of ids to clear\n-        ids.Add(content.Id);\n-        foreach (var id in ids)\n-        {\n-            if (await _documentCacheService.HasContentByIdAsync(id) is false)\n-            {\n-                continue;\n-            }\n-\n-            IPublishedContent? publishedContent = await _documentCacheService.GetByIdAsync(id);\n-            if (publishedContent is null)\n-            {\n-                continue;\n-            }\n-\n-            foreach (IPublishedProperty publishedProperty in publishedContent.Properties)\n-            {\n-                var property = (PublishedProperty) publishedProperty;\n-                if (property.ReferenceCacheLevel is PropertyCacheLevel.Elements\n-                    || property.PropertyType.DeliveryApiCacheLevel is PropertyCacheLevel.Elements\n-                    || property.PropertyType.DeliveryApiCacheLevelForExpansion is PropertyCacheLevel.Elements)\n-                {\n-                    _elementsCache.ClearByKey(property.ValuesCacheKey);\n-                }\n-            }\n-        }\n+        // Ideally we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.\n+        // The reason for this is that we have no way to know which elements are affected by the changes. or what their keys are.\n+        // This is because currently published elements lives exclusively in a JSON blob in the umbracoPropertyData table.\n+        // This means that the only way to resolve these keys are to actually parse this data with a specific value converter, and for all cultures, which is not feasible.\n+        // If published elements become their own entities with relations, instead of just property data, we can revisit this,\n+        _elementsCache.Clear();\n     }\n \n-    private void RemoveFromElementsCache(IUmbracoEntity content)\n-    {\n-        // ClearByKey clears by \"startsWith\" so we'll clear by the cachekey prefix + contentKey\n-        // This will clear any and all properties for this content item, this is important because\n-        // we cannot resolve the PublishedContent for this entity since it and its content type is deleted.\n-        _elementsCache.ClearByKey(GetContentWideCacheKey(content.Key, true));\n-        _elementsCache.ClearByKey(GetContentWideCacheKey(content.Key, false));\n-    }\n-\n-    private string GetContentWideCacheKey(Guid contentKey, bool isPreviewing) => isPreviewing\n-        ? CacheKeys.PreviewPropertyCacheKeyPrefix + contentKey\n-        : CacheKeys.PropertyCacheKeyPrefix + contentKey;\n-\n     public Task HandleAsync(ContentTypeRefreshedNotification notification, CancellationToken cancellationToken)\n","improvement-type":"Complex Method"}],"change-level":"warning","is-hotspot?":false,"line":44,"what-changed":"Handle has a cyclomatic complexity of 11, threshold = 9","how-to-fix":"There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html). Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring.","change-type":"introduced"},{"method":"UpdateUploadFieldProperty","why-it-occurs":"A complex conditional is an expression inside a branch such as an <code>if</code>-statmeent which consists of multiple, logical operations. Example: <code>if (x.started() && y.running())</code>.Complex conditionals make the code even harder to read, and contribute to the Complex Method code smell. Encapsulate them.","name":"Complex Conditional","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs","refactoring-examples":[{"diff":"diff --git a/complex_conditional.js b/complex_conditional.js\nindex c43da09584..94259ce874 100644\n--- a/complex_conditional.js\n+++ b/complex_conditional.js\n@@ -1,16 +1,34 @@\n function messageReceived(message, timeReceived) {\n-   // Ignore all messages which aren't from known customers:\n-   if (!message.sender &&\n-       customers.getId(message.name) == null) {\n+   // Refactoring #1: encapsulate the business rule in a\n+   // function. A clear name replaces the need for the comment:\n+   if (!knownCustomer(message)) {\n      log('spam received -- ignoring');\n      return;\n    }\n \n-  // Provide an auto-reply when outside business hours:\n-  if ((timeReceived.getHours() > 17) ||\n-      (timeReceived.getHours() < 8)) {\n+  // Refactoring #2: encapsulate the business rule.\n+  // Again, note how a clear function name replaces the\n+  // need for a code comment:\n+  if (outsideBusinessHours(timeReceived)) {\n     return autoReplyTo(message);\n   }\n \n   pingAgentFor(message);\n+}\n+\n+function outsideBusinessHours(timeReceived) {\n+  // Refactoring #3: replace magic numbers with\n+  // symbols that communicate with the code reader:\n+  const closingHour = 17;\n+  const openingHour = 8;\n+\n+  const hours = timeReceived.getHours();\n+\n+  // Refactoring #4: simple conditional rules can\n+  // be further clarified by introducing a variable:\n+  const afterClosing = hours > closingHour;\n+  const beforeOpening = hours < openingHour;\n+\n+  // Yeah -- look how clear the business rule is now!\n+  return afterClosing || beforeOpening;\n }\n\\ No newline at end of file\n","language":"c#","improvement-type":"Complex Conditional"}],"change-level":"warning","is-hotspot?":false,"line":96,"what-changed":"UpdateUploadFieldProperty has 1 complex conditionals with 2 branches, threshold = 2","how-to-fix":"Apply the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring so that the complex conditional is encapsulated in a separate function with a good name that captures the business rule. Optionally, for simple expressions, introduce a new variable which holds the result of the complex conditional.","change-type":"introduced"},{"method":"UpdateBlockPropertyValues","why-it-occurs":"A Bumpy Road is a function that contains multiple chunks of nested conditional logic inside the same function. The deeper the nesting and the more bumps, the lower the code health.\n\nA bumpy code road represents a lack of encapsulation which becomes an obstacle to comprehension. In imperative languages there’s also an increased risk for feature entanglement, which leads to complex state management. CodeScene considers the following rules for the code health impact: 1) The deeper the nested conditional logic of each bump, the higher the tax on our working memory. 2) The more bumps inside a function, the more expensive it is to refactor as each bump represents a missing abstraction. 3) The larger each bump – that is, the more lines of code it spans – the harder it is to build up a mental model of the function. The nesting depth for what is considered a bump is  levels of conditionals.","name":"Bumpy Road Ahead","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs","refactoring-examples":null,"change-level":"warning","is-hotspot?":false,"line":206,"what-changed":"UpdateBlockPropertyValues has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function","how-to-fix":"Bumpy Road implementations indicate a lack of encapsulation. Check out the detailed description of the [Bumpy Road code health issue](https://codescene.com/blog/bumpy-road-code-complexity-in-context/).\n\nA Bumpy Road often suggests that the function/method does too many things. The first refactoring step is to identify the different possible responsibilities of the function. Consider extracting those responsibilities into smaller, cohesive, and well-named functions. The [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html) refactoring is the primary response.","change-type":"introduced"},{"why-it-occurs":"Overall Code Complexity is measured by the mean cyclomatic complexity across all functions in the file. The lower the number, the better.\n\nCyclomatic complexity is a function level metric that measures the number of logical branches (if-else, loops, etc.). Cyclomatic complexity is a rough complexity measure, but useful as a way of estimating the minimum number of unit tests you would need. As such, prefer functions with low cyclomatic complexity (2-3 branches).","name":"Overall Code Complexity","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs","refactoring-examples":null,"change-level":"warning","is-hotspot?":false,"what-changed":"This module has a mean cyclomatic complexity of 4.50 across 12 functions. The mean complexity threshold is 4","how-to-fix":"You address the overall cyclomatic complexity by a) modularizing the code, and b) abstract away the complexity. Let's look at some examples:\n\nModularizing the Code: Do an X-Ray and inspect the local hotspots. Are there any complex conditional expressions? If yes, then do a [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring. Extract the conditional logic into a separate function and put a good name on that function. This clarifies the intent and makes the original function easier to read. Repeat until all complex conditional expressions have been simplified.\n\n","change-type":"introduced"},{"method":"UpdateBlockPropertyValues","why-it-occurs":"Deep nested logic means that you have control structures like if-statements or loops inside other control structures. Deep nested logic increases the cognitive load on the programmer reading the code. The human working memory has a maximum capacity of 3-4 items; beyond that threshold, we struggle with keeping things in our head. Consequently, deep nested logic has a strong correlation to defects and accounts for roughly 20% of all programming mistakes.\n\nCodeScene measures the maximum nesting depth inside each function. The deeper the nesting, the lower the code health. The threshold for the C# language is 4 levels of nesting.","name":"Deep, Nested Complexity","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs","refactoring-examples":[{"architectural-component-id":null,"author-name":"Marco Teodoro","training-data":{"loc-added":"12","loc-deleted":"10","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"2.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"marcoteodoro@double.pt","commit-full-message":"* fix: #12253 Anchor picker does not appear\r\n\r\n* Update ContentServiceExtensions.cs\r\n\r\nfix: #12253  #13492 ; implement PR suggestions, test the new changes to confirm that they fix the issue with the anchorlink implementation on RTE from blocklist, blockgrid and normal RTE properties\r\nCo-Authored-By: Laura Neto <12862535+lauraneto@users.noreply.github.com>\r\n\r\n* Removed unnecessary using and code\r\n\r\n* Small adjustments\r\n\r\n---------\r\n\r\nCo-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>","commit-date":"2023-06-16T12:40:19Z","current-rev":"1ba834a9ca","filename":"Umbraco-CMS/src/Umbraco.Core/Services/ContentServiceExtensions.cs","previous-rev":"542d0f7f74","commit-title":"fix: #12253 Anchor picker does not appear (#13492)","language":"C#","id":"6fca7f305e465282704fde5020db28c946b569c4","model-score":0.58,"author-id":null,"project-id":33308,"delta-file-score":0.59155285,"diff":"diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs\nindex b042612b1a..aaba622412 100644\n--- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs\n+++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs\n@@ -1,2 +1,2 @@\n-﻿// Copyright (c) Umbraco.\n+// Copyright (c) Umbraco.\n // See LICENSE for more details.\n@@ -18,3 +18,4 @@ public static class ContentServiceExtensions\n \n-    private static readonly Regex AnchorRegex = new(\"<a id=\\\"(.*?)\\\">\", RegexOptions.Compiled);\n+    private static readonly Regex AnchorRegex = new(@\"<a id=\\\\*\"\"(.*?)\\\\*\"\">\", RegexOptions.Compiled);\n+    private static readonly string[] _propertyTypesWithRte = new[] { Constants.PropertyEditors.Aliases.TinyMce, Constants.PropertyEditors.Aliases.BlockList, Constants.PropertyEditors.Aliases.BlockGrid };\n \n@@ -69,17 +70,18 @@ public static IEnumerable<string> GetAnchorValuesFromRTEs(this IContentService c\n         var result = new List<string>();\n+\n+        culture = culture is not \"*\" ? culture : null;\n+\n         IContent? content = contentService.GetById(id);\n \n-        if (content is not null)\n+        if (content is null)\n+        {\n+            return result;\n+        }\n+\n+        foreach (IProperty contentProperty in content.Properties.Where(s => _propertyTypesWithRte.Contains(s.PropertyType.PropertyEditorAlias)))\n         {\n-            foreach (IProperty contentProperty in content.Properties)\n+            var value = contentProperty.GetValue(culture)?.ToString();\n+            if (!string.IsNullOrEmpty(value))\n             {\n-                if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases\n-                        .TinyMce))\n-                {\n-                    var value = contentProperty.GetValue(culture)?.ToString();\n-                    if (!string.IsNullOrEmpty(value))\n-                    {\n-                        result.AddRange(contentService.GetAnchorValuesFromRTEContent(value));\n-                    }\n-                }\n+                result.AddRange(contentService.GetAnchorValuesFromRTEContent(value));\n             }\n@@ -98,3 +100,3 @@ public static IEnumerable<string> GetAnchorValuesFromRTEContent(\n         {\n-            result.Add(match.Value.Split(Constants.CharArrays.DoubleQuote)[1]);\n+            result.Add(match.Groups[1].Value);\n         }\n","improvement-type":"Deep, Nested Complexity"},{"architectural-component-id":null,"author-name":"Bjarke Berg","training-data":{"loc-added":"24","loc-deleted":"26","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"2.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mail@bergmania.dk","commit-full-message":"* AB40660 - untangle the preview cookie from the auth cookie\r\n\r\n* Clean up\r\n\r\n* Allow anonymous to end preview sessions\r\n\r\n* Some refinements\r\n\r\n* update OpenApi.json\r\n\r\n* Fix enter preview test\r\n\r\n* correct tests to match new expectations of the preview cookie\r\n\r\n* sync preview tests with correct expectations of access level\r\n\r\n---------\r\n\r\nCo-authored-by: Sven Geusens <sge@umbraco.dk>\r\nCo-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>","commit-date":"2024-05-17T14:06:26Z","current-rev":"11e5257b56","filename":"Umbraco-CMS/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs","previous-rev":"80794f3efd","commit-title":"V14: Untangle the preview functionality from the auth cookie (#16308)","language":"C#","id":"04d02f224e9033ddada25774318da1d2ebcef49e","model-score":0.53,"author-id":null,"project-id":33308,"delta-file-score":1.044829,"diff":"diff --git a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\nindex 5bd16867ca..e61b8682af 100644\n--- a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n+++ b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n@@ -7,2 +7,8 @@\n using Microsoft.Extensions.Options;\n+using Umbraco.Cms.Core;\n+using Umbraco.Cms.Core.Preview;\n+using Umbraco.Cms.Core.Security;\n+using Umbraco.Cms.Core.Services;\n+using Umbraco.Cms.Web.Common.AspNetCore;\n+using Umbraco.Cms.Web.Common.Security;\n using Umbraco.Extensions;\n@@ -18,4 +24,15 @@ public class PreviewAuthenticationMiddleware : IMiddleware\n     private readonly ILogger<PreviewAuthenticationMiddleware> _logger;\n+    private readonly IPreviewTokenGenerator _previewTokenGenerator;\n+    private readonly IPreviewService _previewService;\n \n-    public PreviewAuthenticationMiddleware(ILogger<PreviewAuthenticationMiddleware> logger) => _logger = logger;\n+\n+    public PreviewAuthenticationMiddleware(\n+        ILogger<PreviewAuthenticationMiddleware> logger,\n+        IPreviewTokenGenerator previewTokenGenerator,\n+        IPreviewService previewService)\n+    {\n+        _logger = logger;\n+        _previewTokenGenerator = previewTokenGenerator;\n+        _previewService = previewService;\n+    }\n \n@@ -40,33 +57,14 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)\n             {\n-                CookieAuthenticationOptions? cookieOptions = context.RequestServices\n-                    .GetRequiredService<IOptionsSnapshot<CookieAuthenticationOptions>>()\n-                    .Get(Core.Constants.Security.BackOfficeAuthenticationType);\n+                Attempt<ClaimsIdentity> backOfficeIdentityAttempt = await _previewService.TryGetPreviewClaimsIdentityAsync();\n \n-                if (cookieOptions == null)\n+                if (backOfficeIdentityAttempt.Success)\n                 {\n-                    throw new InvalidOperationException(\"No cookie options found with name \" +\n-                                                        Core.Constants.Security.BackOfficeAuthenticationType);\n+                    // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n+                    // Principal, this means we'll have 2 identities assigned to the principal which we can\n+                    // use to authorize the preview and allow for a back office User.\n+                    context.User.AddIdentity(backOfficeIdentityAttempt.Result!);\n                 }\n-\n-                // If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing.\n-                // In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication\n-                // for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered.\n-                if (cookieOptions.Cookie.Name != null)\n+                else\n                 {\n-                    var chunkingCookieManager = new ChunkingCookieManager();\n-                    var cookie = chunkingCookieManager.GetRequestCookie(context, cookieOptions.Cookie.Name);\n-\n-                    if (!string.IsNullOrEmpty(cookie))\n-                    {\n-                        AuthenticationTicket? unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie);\n-                        ClaimsIdentity? backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity();\n-\n-                        if (backOfficeIdentity != null)\n-                        {\n-                            // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n-                            // Principal, this means we'll have 2 identities assigned to the principal which we can\n-                            // use to authorize the preview and allow for a back office User.\n-                            context.User.AddIdentity(backOfficeIdentity);\n-                        }\n-                    }\n+                    _logger.LogDebug(\"Could not transform previewCookie value into a claimsIdentity\");\n                 }\n","improvement-type":"Deep, Nested Complexity"},{"architectural-component-id":null,"author-name":"Andy Butland","training-data":{"loc-added":"11","loc-deleted":"28","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"2.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"abutland73@gmail.com","commit-full-message":"* Code tidy - XML header comments, method ordering, warning resolution.\n\n* Add extension method for retrieving all URL segments for a document.\n\n* Cache and persist multiple URL segments per document.\n\n* Allowed segment providers to terminate or allow additional segments.\nSet up currently failing integration test for expected routes.\n\n* Resolved cache issue to ensure passing new integration tests.\n\n* Fixed failing integration test.\n\n* Test class naming tidy up.\n\n* Added resolution and persistance of a primary segment, to retain previous behaviour when a single segment is retrieved.\n\n* Further integration tests.\n\n* Resolved backward compatibility of interface.\n\n* Supress amends made to integration tests.\n\n* Aligned naming of integration tests.\n\n* Removed unused using, added XML header comment.\n\n* Throw on missing table in migration.\n\n* Code clean-up.\n\n* Fix multiple enumeration\n\n* Used default on migrated column.\n\n* Use 1 over true for default value.\n\n* Remove unused logger\n\n---------\n\nCo-authored-by: mole <nikolajlauridsen@protonmail.ch>","commit-date":"2025-03-13T12:47:46Z","current-rev":"e9c97f8c9b","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentUrlRepository.cs","previous-rev":"da5669d9fc","commit-title":"Allow multiple URL segments per document (#18603)","language":"C#","id":"0a85604449d96eb11237ee85dc0a439903a40727","model-score":0.46,"author-id":null,"project-id":33308,"delta-file-score":0.7593437,"diff":"diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentUrlRepository.cs\nindex ded011440d..07a36f49ee 100644\n--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentUrlRepository.cs\n+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentUrlRepository.cs\n@@ -1,12 +1,6 @@\n-using Microsoft.Extensions.Logging;\n using NPoco;\n using Umbraco.Cms.Core;\n-using Umbraco.Cms.Core.Cache;\n using Umbraco.Cms.Core.Models;\n-using Umbraco.Cms.Core.Models.Entities;\n-using Umbraco.Cms.Core.Persistence.Querying;\n using Umbraco.Cms.Core.Persistence.Repositories;\n-using Umbraco.Cms.Core.Security;\n using Umbraco.Cms.Infrastructure.Persistence.Dtos;\n-using Umbraco.Cms.Infrastructure.Persistence.Factories;\n using Umbraco.Cms.Infrastructure.Scoping;\n@@ -14,3 +8,2 @@\n \n-\n namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;\n@@ -38,10 +31,11 @@ public void Save(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSe\n     {\n-        //TODO avoid this is called as first thing on first restart after install\n+        // TODO: avoid this is called as first thing on first restart after install\n         IEnumerable<Guid> documentKeys = publishedDocumentUrlSegments.Select(x => x.DocumentKey).Distinct();\n \n-        Dictionary<(Guid UniqueId, int LanguageId, bool isDraft), DocumentUrlDto> dtoDictionary = publishedDocumentUrlSegments.Select(BuildDto).ToDictionary(x=> (x.UniqueId, x.LanguageId, x.IsDraft));\n+        Dictionary<(Guid UniqueId, int LanguageId, bool isDraft, string urlSegment), DocumentUrlDto> dtoDictionary = publishedDocumentUrlSegments\n+            .Select(BuildDto)\n+            .ToDictionary(x => (x.UniqueId, x.LanguageId, x.IsDraft, x.UrlSegment));\n \n-        var toUpdate = new List<DocumentUrlDto>();\n         var toDelete = new List<int>();\n-        var toInsert = dtoDictionary.Values.ToDictionary(x => (x.UniqueId, x.LanguageId, x.IsDraft));\n+        var toInsert = dtoDictionary.Values.ToDictionary(x => (x.UniqueId, x.LanguageId, x.IsDraft, x.UrlSegment));\n \n@@ -59,4 +53,3 @@ public void Save(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSe\n             {\n-\n-                if (dtoDictionary.TryGetValue((existing.UniqueId, existing.LanguageId, existing.IsDraft), out DocumentUrlDto? found))\n+                if (dtoDictionary.TryGetValue((existing.UniqueId, existing.LanguageId, existing.IsDraft, existing.UrlSegment), out DocumentUrlDto? found))\n                 {\n@@ -64,9 +57,4 @@ public void Save(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSe\n \n-                    // Only update if the url segment is different\n-                    if (found.UrlSegment != existing.UrlSegment)\n-                    {\n-                        toUpdate.Add(found);\n-                    }\n-                    // if we found it, we know we should not insert it as a new\n-                    toInsert.Remove((found.UniqueId, found.LanguageId, found.IsDraft));\n+                    // If we found it, we know we should not insert it as a new record.\n+                    toInsert.Remove((found.UniqueId, found.LanguageId, found.IsDraft, found.UrlSegment));\n                 }\n@@ -85,10 +73,2 @@ public void Save(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSe\n \n-        if (toUpdate.Any())\n-        {\n-            foreach (DocumentUrlDto updated in toUpdate)\n-            {\n-                Database.Update(updated);\n-            }\n-        }\n-\n         Database.InsertBulk(toInsert.Values);\n@@ -117,3 +97,4 @@ private PublishedDocumentUrlSegment BuildModel(DocumentUrlDto dto) =>\n             LanguageId = dto.LanguageId,\n-            IsDraft = dto.IsDraft\n+            IsDraft = dto.IsDraft,\n+            IsPrimary = dto.IsPrimary\n         };\n@@ -128,2 +109,3 @@ private DocumentUrlDto BuildDto(PublishedDocumentUrlSegment model)\n             IsDraft = model.IsDraft,\n+            IsPrimary = model.IsPrimary,\n         };\n","improvement-type":"Deep, Nested Complexity"},{"architectural-component-id":null,"author-name":"Elitsa Marinovska","training-data":{"loc-added":"23","loc-deleted":"36","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"2.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"21998037+elit0451@users.noreply.github.com","commit-full-message":"* Sends GUID instead of the numeric ID for SignalR Preview Hub\r\n\r\n* Add possibility to set cookies as HttpOnly\r\n\r\n* Set UMB_PREVIEW cookie as HttpOnly\r\n\r\n* fixup! Add possibility to set cookies as HttpOnly\r\n\r\n* Refactor ContentFinderByIdPath to more readable\r\n\r\n* Create ContentFinderByKeyPath reusing logic from ContentFinderByIdPath\r\n\r\n* Add a comment to DisableFindContentByIdPath setting\r\n\r\n* Append new content finder\r\n\r\n* Change ordering of content finders registrations\r\n\r\n* Refactor with a base class\r\n\r\n* Update/refactor and add tests regarding ContentFindersByIdentifier\r\n\r\n* Fix comment\r\n\r\n* Avoiding breaking change\r\n\r\n* Make usages use non-obsolete implementation\r\n\r\n* Fixed todo in config instead of use the one old legacy name even more. Also obsoleted the ContentFinderByIdPath\r\n\r\n* add `preview` as an allowed backoffice client route\r\n\r\n---------\r\n\r\nCo-authored-by: Sven Geusens <sge@umbraco.dk>\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\nCo-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>","commit-date":"2024-05-16T13:53:42Z","current-rev":"295f6f8720","filename":"Umbraco-CMS/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs","previous-rev":"93db1912a6","commit-title":"V14: Backend changes to facilitate Preview mode in Bellissimma (#16279)","language":"C#","id":"5b3117ccd8b875b09e266f2cfb46b138ee80b73c","model-score":0.22,"author-id":null,"project-id":33308,"delta-file-score":0.4795866,"diff":"diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs\nindex c7089f0824..86a559b5db 100644\n--- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs\n+++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs\n@@ -15,6 +15,6 @@ namespace Umbraco.Cms.Core.Routing;\n /// </remarks>\n-public class ContentFinderByIdPath : IContentFinder\n+[Obsolete(\"Use ContentFinderByKeyPath instead. This will be removed in Umbraco 15.\")]\n+public class ContentFinderByIdPath : ContentFinderByIdentifierPathBase, IContentFinder\n {\n     private readonly ILogger<ContentFinderByIdPath> _logger;\n-    private readonly IRequestAccessor _requestAccessor;\n     private readonly IUmbracoContextAccessor _umbracoContextAccessor;\n@@ -22,2 +22,4 @@ public class ContentFinderByIdPath : IContentFinder\n \n+    protected override string FailureLogMessageTemplate => \"Not a node id\";\n+\n     /// <summary>\n@@ -30,2 +32,3 @@ public ContentFinderByIdPath(\n         IUmbracoContextAccessor umbracoContextAccessor)\n+        : base(requestAccessor, logger)\n     {\n@@ -34,3 +37,2 @@ public ContentFinderByIdPath(\n         _logger = logger ?? throw new ArgumentNullException(nameof(logger));\n-        _requestAccessor = requestAccessor ?? throw new ArgumentNullException(nameof(requestAccessor));\n         _umbracoContextAccessor =\n@@ -53,3 +55,3 @@ public Task<bool> TryFindContent(IPublishedRequestBuilder frequest)\n \n-        if (umbracoContext.InPreviewMode == false && _webRoutingSettings.DisableFindContentByIdPath)\n+        if (umbracoContext.InPreviewMode == false && (_webRoutingSettings.DisableFindContentByIdPath || _webRoutingSettings.DisableFindContentByIdentifierPath))\n         {\n@@ -58,59 +60,44 @@ public Task<bool> TryFindContent(IPublishedRequestBuilder frequest)\n \n-        IPublishedContent? node = null;\n         var path = frequest.AbsolutePathDecoded;\n \n-        var nodeId = -1;\n-\n         // no id if \"/\"\n-        if (path != \"/\")\n+        if (path == \"/\")\n+        {\n+            return LogAndReturnFailure();\n+        }\n+\n+        var noSlashPath = path.Substring(1);\n+\n+        if (int.TryParse(noSlashPath, NumberStyles.Integer, CultureInfo.InvariantCulture, out var nodeId) == false)\n+        {\n+            return LogAndReturnFailure();\n+        }\n+\n+        // NodeId cannot be negative or 0\n+        if (nodeId < 1)\n         {\n-            var noSlashPath = path.Substring(1);\n-\n-            if (int.TryParse(noSlashPath, NumberStyles.Integer, CultureInfo.InvariantCulture, out nodeId) == false)\n-            {\n-                nodeId = -1;\n-            }\n-\n-            if (nodeId > 0)\n-            {\n-                if (_logger.IsEnabled(LogLevel.Debug))\n-                {\n-                    _logger.LogDebug(\"Id={NodeId}\", nodeId);\n-                }\n-\n-                node = umbracoContext.Content?.GetById(nodeId);\n-\n-                if (node != null)\n-                {\n-                    var cultureFromQuerystring = _requestAccessor.GetQueryStringValue(\"culture\");\n-\n-                    // if we have a node, check if we have a culture in the query string\n-                    if (!string.IsNullOrEmpty(cultureFromQuerystring))\n-                    {\n-                        // we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though\n-                        frequest.SetCulture(cultureFromQuerystring);\n-                    }\n-\n-                    frequest.SetPublishedContent(node);\n-                    if (_logger.IsEnabled(LogLevel.Debug))\n-                    {\n-                        _logger.LogDebug(\"Found node with id={PublishedContentId}\", node.Id);\n-                    }\n-                }\n-                else\n-                {\n-                    nodeId = -1; // trigger message below\n-                }\n-            }\n+            return LogAndReturnFailure();\n         }\n \n-        if (nodeId == -1)\n+        if (_logger.IsEnabled(LogLevel.Debug))\n+        {\n+            _logger.LogDebug(\"Id={NodeId}\", nodeId);\n+        }\n+\n+        IPublishedContent? node = umbracoContext.Content?.GetById(nodeId);\n+\n+        if (node is null)\n+        {\n+            return LogAndReturnFailure();\n+        }\n+\n+        ResolveAndSetCultureOnRequest(frequest);\n+\n+        frequest.SetPublishedContent(node);\n+        if (_logger.IsEnabled(LogLevel.Debug))\n         {\n-            if (_logger.IsEnabled(LogLevel.Debug))\n-            {\n-                _logger.LogDebug(\"Not a node id\");\n-            }\n+            _logger.LogDebug(\"Found node with id={PublishedContentId}\", node.Id);\n         }\n \n-        return Task.FromResult(node != null);\n+        return Task.FromResult(true);\n     }\n","improvement-type":"Deep, Nested Complexity"},{"architectural-component-id":null,"author-name":"Henrik","training-data":{"loc-added":"17","loc-deleted":"38","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"2.0","delta-n-functions":"0","current-file-score":"8.95517096544394"},"author-email":"Henrik.Gedionsen@gmail.com","commit-full-message":"* Avoid doing multiple lookups in dictionaries, avoid doing string interpolation & adding single char strings to a StringBuilder, made some private/internal classes & some private methods static when possible, use FrozenSet for InvalidFileNameChars\r\n\r\n* Avoid some array + list allocations & async methods and made some private methods static\r\n\r\n* Avoid double lookup of XML attribute (and double null check) & avoid an unneeded lookup before writing to a dictionary\r\n\r\n* Avoid some double lookups\r\n\r\n# Conflicts:\r\n#\tsrc/Umbraco.Core/Services/LocalizedTextService.cs\r\n\r\n* Avoid double lookups\r\n\r\n# Conflicts:\r\n#\tsrc/Umbraco.Core/Services/LocalizedTextService.cs\r\n\r\n* Avoid double lookups\r\n\r\n* List AsSpan, also to trigger a new build that hopefully goes through\r\n\r\n* Avoid concatting strings when using writer & more static\r\n\r\n* Updated CollectionBenchmarks to show that ToArray isn't always the fastest & Lists can be iterated nearly as fast as arrays (and that ToList is nearly as fast as ToArray on IReadOnlyLists in .NET 8)\r\n\r\n* Fix rebase\r\n\r\n* Use explicit types ❤️ (I thought it was the other way round...)\r\n\r\n# Conflicts:\r\n#\tsrc/Umbraco.Core/Services/LocalizedTextService.cs\r\n\r\n* Reduce number of lines in HtmlStringUtilities.Truncate to pass code quality analysis\r\n\r\n* Avoid double lookups & allocating empty arrays\r\n\r\n* Use native List Find instead of LINQ","commit-date":"2025-01-31T09:31:06Z","current-rev":"c64ec51305","filename":"Umbraco-CMS/src/Umbraco.Core/Services/LocalizedTextService.cs","previous-rev":"55b5d7eecd","commit-title":"Nonbreaking performance tweaks (#17106)","language":"C#","id":"b661ba4b759601c6fccd22a7671bf64bfa55055c","model-score":0.16,"author-id":null,"project-id":33308,"delta-file-score":0.69170636,"diff":"diff --git a/src/Umbraco.Core/Services/LocalizedTextService.cs b/src/Umbraco.Core/Services/LocalizedTextService.cs\nindex 51012200bd..df4f7b3940 100644\n--- a/src/Umbraco.Core/Services/LocalizedTextService.cs\n+++ b/src/Umbraco.Core/Services/LocalizedTextService.cs\n@@ -135,3 +135,3 @@ public IDictionary<string, string> GetAllStoredValues(CultureInfo culture)\n \n-        if (DictionarySource.ContainsKey(culture) == false)\n+        if (DictionarySource.TryGetValue(culture, out Lazy<IDictionary<string, IDictionary<string, string>>>? valueForCulture) == false)\n         {\n@@ -146,3 +146,3 @@ public IDictionary<string, string> GetAllStoredValues(CultureInfo culture)\n         // convert all areas + keys to a single key with a '/'\n-        foreach (KeyValuePair<string, IDictionary<string, string>> area in DictionarySource[culture].Value)\n+        foreach (KeyValuePair<string, IDictionary<string, string>> area in valueForCulture.Value)\n         {\n@@ -150,9 +150,6 @@ public IDictionary<string, string> GetAllStoredValues(CultureInfo culture)\n             {\n-                var dictionaryKey = string.Format(\"{0}/{1}\", area.Key, key.Key);\n+                var dictionaryKey = $\"{area.Key}/{key.Key}\";\n \n                 // i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case.\n-                if (result.ContainsKey(dictionaryKey) == false)\n-                {\n-                    result.Add(dictionaryKey, key.Value);\n-                }\n+                result.TryAdd(dictionaryKey, key.Value);\n             }\n@@ -221,3 +218,3 @@ public IDictionary<string, IDictionary<string, string>> GetAllStoredValuesByArea\n \n-        if (DictionarySource.ContainsKey(culture) == false)\n+        if (DictionarySource.TryGetValue(culture, out Lazy<IDictionary<string, IDictionary<string, string>>>? valueForCulture) == false)\n         {\n@@ -229,3 +226,3 @@ public IDictionary<string, IDictionary<string, string>> GetAllStoredValuesByArea\n \n-        return DictionarySource[culture].Value;\n+        return valueForCulture.Value;\n     }\n@@ -290,6 +287,3 @@ private static Dictionary<string, string> GetAliasValues(\n             {\n-                if (!aliasValue.ContainsKey(alias.Key))\n-                {\n-                    aliasValue.Add(alias.Key, alias.Value);\n-                }\n+                aliasValue.TryAdd(alias.Key, alias.Value);\n             }\n@@ -345,3 +339,3 @@ private IDictionary<CultureInfo, Lazy<IDictionary<string, IDictionary<string, st\n \n-    private IDictionary<string, IDictionary<string, string>> GetAreaStoredTranslations(\n+    private static IDictionary<string, IDictionary<string, string>> GetAreaStoredTranslations(\n         IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo cult)\n@@ -366,6 +360,3 @@ private IDictionary<string, IDictionary<string, string>> GetAreaStoredTranslatio\n                 // there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files\n-                if (result.ContainsKey(dictionaryKey) == false)\n-                {\n-                    result.Add(dictionaryKey, key.Value);\n-                }\n+                result.TryAdd(dictionaryKey, key.Value);\n             }\n@@ -399,12 +390,6 @@ private IDictionary<string, IDictionary<string, string>> GetAreaStoredTranslatio\n                     // there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files\n-                    if (result.ContainsKey(dictionaryKey) == false)\n-                    {\n-                        result.Add(dictionaryKey, key.Value);\n-                    }\n+                    result.TryAdd(dictionaryKey, key.Value);\n                 }\n \n-                if (!overallResult.ContainsKey(areaAlias))\n-                {\n-                    overallResult.Add(areaAlias, result);\n-                }\n+                overallResult.TryAdd(areaAlias, result);\n             }\n@@ -415,3 +400,3 @@ private IDictionary<string, IDictionary<string, string>> GetAreaStoredTranslatio\n \n-    private Dictionary<string, string> GetNoAreaStoredTranslations(\n+    private static Dictionary<string, string> GetNoAreaStoredTranslations(\n         IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo cult)\n@@ -427,6 +412,3 @@ private Dictionary<string, string> GetNoAreaStoredTranslations(\n             // there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files\n-            if (result.ContainsKey(dictionaryKey) == false)\n-            {\n-                result.Add(dictionaryKey, key.Value);\n-            }\n+            result.TryAdd(dictionaryKey, key.Value);\n         }\n@@ -445,6 +427,3 @@ private Dictionary<string, string> GetNoAreaStoredTranslations(\n                 // there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files\n-                if (result.ContainsKey(dictionaryKey) == false)\n-                {\n-                    result.Add(dictionaryKey, key.Value);\n-                }\n+                result.TryAdd(dictionaryKey, key.Value);\n             }\n@@ -455,3 +434,3 @@ private Dictionary<string, string> GetNoAreaStoredTranslations(\n \n-    private Dictionary<string, IDictionary<string, string>> GetAreaStoredTranslations(\n+    private static Dictionary<string, IDictionary<string, string>> GetAreaStoredTranslations(\n         IDictionary<CultureInfo, Lazy<IDictionary<string, IDictionary<string, string>>>> dictionarySource,\n@@ -483,3 +462,3 @@ private string GetFromDictionarySource(CultureInfo culture, string? area, string\n     {\n-        if (DictionarySource.ContainsKey(culture) == false)\n+        if (DictionarySource.TryGetValue(culture, out Lazy<IDictionary<string, IDictionary<string, string>>>? valueForCulture) == false)\n         {\n@@ -498,3 +477,3 @@ private string GetFromDictionarySource(CultureInfo culture, string? area, string\n         {\n-            if (DictionarySource[culture].Value.TryGetValue(area, out IDictionary<string, string>? areaDictionary))\n+            if (valueForCulture.Value.TryGetValue(area, out IDictionary<string, string>? areaDictionary))\n             {\n","improvement-type":"Deep, Nested Complexity"}],"change-level":"warning","is-hotspot?":false,"line":206,"what-changed":"UpdateBlockPropertyValues has a nested complexity depth of 4, threshold = 4","how-to-fix":"Occassionally, it's possible to get rid of the nested logic by [Replacing Conditionals with Guard Clauses](https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html).\n\nAnother viable strategy is to identify smaller building blocks inside the nested chunks of logic and extract those responsibilities into smaller, cohesive, and well-named functions. The [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html) refactoring explains the steps.","change-type":"introduced"},{"method":"GetPathsFromBlockValue","why-it-occurs":"A Complex Method has a high cyclomatic complexity. The recommended threshold for the C# language is a cyclomatic complexity lower than 9.","name":"Complex Method","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs","refactoring-examples":[{"architectural-component-id":null,"author-name":"Bjarke Berg","training-data":{"loc-added":"24","loc-deleted":"26","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mail@bergmania.dk","commit-full-message":"* AB40660 - untangle the preview cookie from the auth cookie\r\n\r\n* Clean up\r\n\r\n* Allow anonymous to end preview sessions\r\n\r\n* Some refinements\r\n\r\n* update OpenApi.json\r\n\r\n* Fix enter preview test\r\n\r\n* correct tests to match new expectations of the preview cookie\r\n\r\n* sync preview tests with correct expectations of access level\r\n\r\n---------\r\n\r\nCo-authored-by: Sven Geusens <sge@umbraco.dk>\r\nCo-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>","commit-date":"2024-05-17T14:06:26Z","current-rev":"11e5257b56","filename":"Umbraco-CMS/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs","previous-rev":"80794f3efd","commit-title":"V14: Untangle the preview functionality from the auth cookie (#16308)","language":"C#","id":"7ecfb0a869f8c3df64c3c2a4602ef34642b49720","model-score":0.52,"author-id":null,"project-id":33308,"delta-file-score":1.044829,"diff":"diff --git a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\nindex 5bd16867ca..e61b8682af 100644\n--- a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n+++ b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n@@ -7,2 +7,8 @@\n using Microsoft.Extensions.Options;\n+using Umbraco.Cms.Core;\n+using Umbraco.Cms.Core.Preview;\n+using Umbraco.Cms.Core.Security;\n+using Umbraco.Cms.Core.Services;\n+using Umbraco.Cms.Web.Common.AspNetCore;\n+using Umbraco.Cms.Web.Common.Security;\n using Umbraco.Extensions;\n@@ -18,4 +24,15 @@ public class PreviewAuthenticationMiddleware : IMiddleware\n     private readonly ILogger<PreviewAuthenticationMiddleware> _logger;\n+    private readonly IPreviewTokenGenerator _previewTokenGenerator;\n+    private readonly IPreviewService _previewService;\n \n-    public PreviewAuthenticationMiddleware(ILogger<PreviewAuthenticationMiddleware> logger) => _logger = logger;\n+\n+    public PreviewAuthenticationMiddleware(\n+        ILogger<PreviewAuthenticationMiddleware> logger,\n+        IPreviewTokenGenerator previewTokenGenerator,\n+        IPreviewService previewService)\n+    {\n+        _logger = logger;\n+        _previewTokenGenerator = previewTokenGenerator;\n+        _previewService = previewService;\n+    }\n \n@@ -40,33 +57,14 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)\n             {\n-                CookieAuthenticationOptions? cookieOptions = context.RequestServices\n-                    .GetRequiredService<IOptionsSnapshot<CookieAuthenticationOptions>>()\n-                    .Get(Core.Constants.Security.BackOfficeAuthenticationType);\n+                Attempt<ClaimsIdentity> backOfficeIdentityAttempt = await _previewService.TryGetPreviewClaimsIdentityAsync();\n \n-                if (cookieOptions == null)\n+                if (backOfficeIdentityAttempt.Success)\n                 {\n-                    throw new InvalidOperationException(\"No cookie options found with name \" +\n-                                                        Core.Constants.Security.BackOfficeAuthenticationType);\n+                    // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n+                    // Principal, this means we'll have 2 identities assigned to the principal which we can\n+                    // use to authorize the preview and allow for a back office User.\n+                    context.User.AddIdentity(backOfficeIdentityAttempt.Result!);\n                 }\n-\n-                // If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing.\n-                // In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication\n-                // for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered.\n-                if (cookieOptions.Cookie.Name != null)\n+                else\n                 {\n-                    var chunkingCookieManager = new ChunkingCookieManager();\n-                    var cookie = chunkingCookieManager.GetRequestCookie(context, cookieOptions.Cookie.Name);\n-\n-                    if (!string.IsNullOrEmpty(cookie))\n-                    {\n-                        AuthenticationTicket? unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie);\n-                        ClaimsIdentity? backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity();\n-\n-                        if (backOfficeIdentity != null)\n-                        {\n-                            // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n-                            // Principal, this means we'll have 2 identities assigned to the principal which we can\n-                            // use to authorize the preview and allow for a back office User.\n-                            context.User.AddIdentity(backOfficeIdentity);\n-                        }\n-                    }\n+                    _logger.LogDebug(\"Could not transform previewCookie value into a claimsIdentity\");\n                 }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Sven Geusens","training-data":{"loc-added":"74","loc-deleted":"9","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"sge@umbraco.dk","commit-full-message":"* Support new localLink format in core link parsing\r\n\r\n* Updated devliery api to work with the new locallinks format\r\n\r\nAdded tests for old and new format handling.\r\n\r\n* Fix error regarding type attribute not always being present (for example old format or non local links)","commit-date":"2024-07-02T12:22:19Z","current-rev":"46acd51759","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs","previous-rev":"13b77d35df","commit-title":"[V14] Make the backend work with the new localLinks format (#16661)","language":"C#","id":"7d7b589a86f716e705df415598cc4c8f2a02af1b","model-score":0.42,"author-id":null,"project-id":33308,"delta-file-score":0.3009901,"diff":"diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\nindex 7723fc835c..407bc1a022 100644\n--- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\n+++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\n@@ -6,2 +6,3 @@\n using Umbraco.Cms.Core.PublishedCache;\n+using Umbraco.Cms.Core.Templates;\n \n@@ -20,3 +21,17 @@ protected ApiRichTextParserBase(IApiContentRouteBuilder apiContentRouteBuilder,\n \n-    protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl, Action handleInvalidLink)\n+    protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, string type, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl, Action handleInvalidLink)\n+    {\n+        ReplaceStatus replaceAttempt = ReplaceLocalLink(publishedSnapshot, href, type, handleContentRoute, handleMediaUrl);\n+        if (replaceAttempt == ReplaceStatus.Success)\n+        {\n+            return;\n+        }\n+\n+        if (replaceAttempt == ReplaceStatus.InvalidEntityType || ReplaceLegacyLocalLink(publishedSnapshot, href, handleContentRoute, handleMediaUrl) == ReplaceStatus.InvalidEntityType)\n+        {\n+            handleInvalidLink();\n+        }\n+    }\n+\n+    private ReplaceStatus ReplaceLocalLink(IPublishedSnapshot publishedSnapshot, string href, string type, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl)\n     {\n@@ -25,11 +40,12 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n         {\n-            return;\n+            return ReplaceStatus.NoMatch;\n         }\n \n-        if (UdiParser.TryParse(match.Groups[\"udi\"].Value, out Udi? udi) is false)\n+        if (Guid.TryParse(match.Groups[\"guid\"].Value, out Guid guid) is false)\n         {\n-            return;\n+            return ReplaceStatus.NoMatch;\n         }\n \n-        bool handled = false;\n+        var udi = new GuidUdi(type, guid);\n+\n         switch (udi.EntityType)\n@@ -43,4 +59,4 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n                 {\n-                    handled = true;\n                     handleContentRoute(route);\n+                    return ReplaceStatus.Success;\n                 }\n@@ -52,4 +68,4 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n                 {\n-                    handled = true;\n                     handleMediaUrl(_apiMediaUrlProvider.GetUrl(media));\n+                    return ReplaceStatus.Success;\n                 }\n@@ -59,6 +75,45 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n \n-        if(handled is false)\n+        return ReplaceStatus.InvalidEntityType;\n+    }\n+\n+    private ReplaceStatus ReplaceLegacyLocalLink(IPublishedSnapshot publishedSnapshot, string href, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl)\n+    {\n+        Match match = LegacyLocalLinkRegex().Match(href);\n+        if (match.Success is false)\n         {\n-            handleInvalidLink();\n+            return ReplaceStatus.NoMatch;\n+        }\n+\n+        if (UdiParser.TryParse(match.Groups[\"udi\"].Value, out Udi? udi) is false)\n+        {\n+            return ReplaceStatus.NoMatch;\n+        }\n+\n+\n+        switch (udi.EntityType)\n+        {\n+            case Constants.UdiEntityType.Document:\n+                IPublishedContent? content = publishedSnapshot.Content?.GetById(udi);\n+                IApiContentRoute? route = content != null\n+                    ? _apiContentRouteBuilder.Build(content)\n+                    : null;\n+                if (route != null)\n+                {\n+                    handleContentRoute(route);\n+                    return ReplaceStatus.Success;\n+                }\n+\n+                break;\n+            case Constants.UdiEntityType.Media:\n+                IPublishedContent? media = publishedSnapshot.Media?.GetById(udi);\n+                if (media != null)\n+                {\n+                    handleMediaUrl(_apiMediaUrlProvider.GetUrl(media));\n+                    return ReplaceStatus.Success;\n+                }\n+\n+                break;\n         }\n+\n+        return ReplaceStatus.InvalidEntityType;\n     }\n@@ -82,3 +137,13 @@ protected void ReplaceLocalImages(IPublishedSnapshot publishedSnapshot, string u\n     [GeneratedRegex(\"{localLink:(?<udi>umb:.+)}\")]\n+    private static partial Regex LegacyLocalLinkRegex();\n+\n+    [GeneratedRegex(\"{localLink:(?<guid>.+)}\")]\n     private static partial Regex LocalLinkRegex();\n+\n+    private enum ReplaceStatus\n+    {\n+        NoMatch,\n+        Success,\n+        InvalidEntityType\n+    }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vitor Rodrigues","training-data":{"loc-added":"16","loc-deleted":"14","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"8.545379580978913"},"author-email":"vsilvar@users.noreply.github.com","commit-full-message":"* Fixes #15136: Search includes fields from other cultures\r\n\r\nRegex was updated to support block list fields\r\nUnpublished nodes on the supplied culture are not filtered out\r\n\r\n* Making the code non-breaking\r\n\r\n* Fixed failing publish content query integration tests\r\n\r\nThe tests were not setting the content as publish in the specifed culture\r\ncausing the content items to be ignored\r\n\r\n---------\r\n\r\nCo-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>","commit-date":"2024-02-07T12:43:37Z","current-rev":"839b2ff6a2","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/PublishedContentQuery.cs","previous-rev":"2221c4f1c7","commit-title":"Fixes #15136: Search includes fields from other cultures (#15148)","language":"C#","id":"ac0709776b22e60e777651712009344d5d3c3529","model-score":0.3,"author-id":null,"project-id":33308,"delta-file-score":0.2613985,"diff":"diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs\nindex d075e8b9d2..758115b9ca 100644\n--- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs\n+++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs\n@@ -295,17 +295,13 @@ public IEnumerable<PublishedSearchResult> Search(string term, int skip, int take\n                     .ToArray(); // Get all index fields suffixed with the culture name supplied\n-            ordering = query.ManagedQuery(term, fields);\n-        }\n-\n-            // Filter selected fields because results are loaded from the published snapshot based on these\n-            IOrdering? queryExecutor = ordering.SelectFields(_returnedQueryFields);\n-\n-\n-        ISearchResults? results = skip == 0 && take == 0\n-            ? queryExecutor.Execute()\n-            : queryExecutor.Execute(QueryOptions.SkipTake(skip, take));\n \n-        totalRecords = results.TotalItemCount;\n+            // Filter out unpublished content for the specified culture if the content varies by culture\n+            // The published__{culture} field is not populated when the content is not published in that culture\n+            ordering = query\n+                .ManagedQuery(term, fields)\n+                .Not().Group(q => q\n+                    .Field(UmbracoExamineFieldNames.VariesByCultureFieldName, \"y\")\n+                    .Not().Field($\"{UmbracoExamineFieldNames.PublishedFieldName}_{culture.ToLowerInvariant()}\", \"y\"));\n+        }\n \n-        return new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content),\n-            _variationContextAccessor, culture);\n+        return Search(ordering, skip, take, out totalRecords, culture);\n     }\n@@ -318,2 +314,6 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query)\n     public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords)\n+        => Search(query, skip, take, out totalRecords, null);\n+\n+    /// <inheritdoc />\n+    public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords, string? culture)\n     {\n@@ -333,4 +333,4 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip,\n         {\n-                // Filter selected fields because results are loaded from the published snapshot based on these\n-                query = ordering.SelectFields(_returnedQueryFields);\n+            // Filter selected fields because results are loaded from the published snapshot based on these\n+            query = ordering.SelectFields(_returnedQueryFields);\n         }\n@@ -343,3 +343,5 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip,\n \n-        return results.ToPublishedSearchResults(_publishedSnapshot);\n+        return culture.IsNullOrWhiteSpace()\n+            ? results.ToPublishedSearchResults(_publishedSnapshot)\n+            : new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture);\n     }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nikolaj E. Lauridsen","training-data":{"loc-added":"6","loc-deleted":"36","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"nikolajlauridsen@protonmail.ch","commit-full-message":"* Remove nucache reference from Web.Common\r\n\r\n* Get tests building-ish\r\n\r\n* Move ReservedFieldNamesService to the right project\r\n\r\n* Remove IPublishedSnapshotStatus\r\n\r\n* Added functionality to the INavigationQueryService to get root keys\r\n\r\n* Fixed issue with navigation\r\n\r\n* Remove IPublishedSnapshot from UmbracoContext\r\n\r\n* Begin removing usage of IPublishedSnapshot from PublishedContentExtensions\r\n\r\n* Fix PublishedContentExtensions.cs\r\n\r\n* Don't use snapshots in delivery media api\r\n\r\n* Use IPublishedMediaCache in QueryMediaApiController\r\n\r\n* Remove more usages of IPublishedSnapshotAccessor\r\n\r\n* Comment out tests\r\n\r\n* Remove more usages of PublishedSnapshotAccessor\r\n\r\n* Remove PublishedSnapshot from property\r\n\r\n* Fixed test build\r\n\r\n* Fix errors\r\n\r\n* Fix some tests\r\n\r\n* Delete NuCache 🎉\r\n\r\n* Implement DatabaseCacheRebuilder\r\n\r\n* Remove usage of IPublishedSnapshotService\r\n\r\n* Remove IPublishedSnapshotService\r\n\r\n* Remove TestPublishedSnapshotAccessor and make tests build\r\n\r\n* Don't test Snapshot cachelevel\r\n\r\nIt's no longer supported\r\n\r\n* Fix BlockEditorConverter\r\n\r\nElement != Element document type\r\n\r\n* Remember to set cachemanager\r\n\r\n* Fix RichTextParserTests\r\n\r\n* Implement TryGetLevel on INavigationQueryService\r\n\r\n* Fake level and obsolete it in PublishedContent\r\n\r\n* Remove ChildrenForAllCultures\r\n\r\n* Hack Path property on PublishedContent\r\n\r\n* Remove usages of IPublishedSnapshot in tests\r\n\r\n* More ConvertersTests\r\n\r\n* Add hybrid cache to integration tests\r\n\r\nWe can actually do this now because we no longer save files on disk\r\n\r\n* Rename IPublishedSnapshotRebuilder to ICacheRebuilder\r\n\r\n* Comment out tests\r\n\r\n* V15: Replacing the usages of Parent (navigation data) from IPublishedContent (#17125)\r\n\r\n* Fix .Parent references in PublishedContentExtensions\r\n\r\n* Add missing methods to FriendlyPublishedContentExtensions (ones that you were able to call on the content directly as they now require extra params)\r\n\r\n* Fix references from the extension methods\r\n\r\n* Fix dependencies in tests\r\n\r\n* Replace IPublishedSnapshotAccessor with the content cache in tests\r\n\r\n* Resolving more .Parent references\r\n\r\n* Fix unit tests\r\n\r\n* Obsolete and use extension methods\r\n\r\n* Remove private method and use extension instead\r\n\r\n* Moving code around\r\n\r\n* Fix tests\r\n\r\n* Fix more references\r\n\r\n* Cleanup\r\n\r\n* Fix more usages\r\n\r\n* Resolve merge conflict\r\n\r\n* Fix tests\r\n\r\n* Cleanup\r\n\r\n* Fix more tests\r\n\r\n* Fixed unit tests\r\n\r\n* Cleanup\r\n\r\n* Replace last usages\r\n\r\n---------\r\n\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\n\r\n* Remove usage of IPublishedSnapshotAccessor from IRequestItemProvider\r\n\r\n* Post merge fixup\r\n\r\n* Remo IPublishedSnapshot\r\n\r\n* Add HasAny to IDocumentUrlService\r\n\r\n* Fix TextBuilder\r\n\r\n* Fix modelsbuilder tests\r\n\r\n* Use explicit types\r\n\r\n* Implement GetByContentType\r\n\r\n* Support element types in PublishedContentTypeCache\r\n\r\n* Run enlistments before publishing notifications\r\n\r\n* Fix elements cache refreshing\r\n\r\n* Implement GetByUdi\r\n\r\n* Implement GetAtRoot\r\n\r\n* Implement GetByRoute\r\n\r\n* Reimplement GetRouteById\r\n\r\n* Fix blocks unit tests\r\n\r\n* Initialize domain cache on boot\r\n\r\n* Only return routes with domains on non default lanauges\r\n\r\n* V15: Replacing the usages of `Children` (navigation data) from `IPublishedContent` (#17159)\r\n\r\n* Update params in PublishedContentExtensions to the general interfaces for the published cache and navigation service, so that we can use the extension methods on both documents and media\r\n\r\n* Introduce GetParent() which uses the right services\r\n\r\n* Fix obsolete message on .Parent\r\n\r\n* Obsolete .Children\r\n\r\n* Fix usages of Children for ApiMediaQueryService\r\n\r\n* Fix usage in internal\r\n\r\n* Fix usages in views\r\n\r\n* Fix indentation\r\n\r\n* Fix issue with delete language\r\n\r\n* Update nuget pacakges\r\n\r\n* Clear elements cache when content is deleted\r\n\r\ninstead of trying to update it\r\n\r\n* Reset publishedModelFactory\r\n\r\n* Fixed publishing\r\n\r\n---------\r\n\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\nCo-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>\r\nCo-authored-by: kjac <kja@umbraco.dk>","commit-date":"2024-10-01T13:03:02Z","current-rev":"1258962429","filename":"Umbraco-CMS/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs","previous-rev":"7ca96423f8","commit-title":"V15: Remove Nucache (#17166)","language":"C#","id":"c5107d73a299c92b12ef250416ebfdc0fe984c06","model-score":0.2,"author-id":null,"project-id":33308,"delta-file-score":0.29056275,"diff":"diff --git a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\nindex 53e8156538..0452cf0b03 100644\n--- a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\n+++ b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\n@@ -17,3 +17,2 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase\n     private readonly object _locko = new();\n-    private readonly IPublishedSnapshotAccessor? _publishedSnapshotAccessor;\n     private readonly object? _sourceValue;\n@@ -21,2 +20,3 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase\n     protected readonly bool IsPreviewing;\n+    private readonly ICacheManager? _cacheManager;\n     private CacheValues? _cacheValues;\n@@ -32,4 +32,4 @@ public PublishedElementPropertyBase(\n         PropertyCacheLevel referenceCacheLevel,\n-        object? sourceValue = null,\n-        IPublishedSnapshotAccessor? publishedSnapshotAccessor = null)\n+        ICacheManager? cacheManager,\n+        object? sourceValue = null)\n         : base(propertyType, referenceCacheLevel)\n@@ -37,5 +37,5 @@ public PublishedElementPropertyBase(\n         _sourceValue = sourceValue;\n-        _publishedSnapshotAccessor = publishedSnapshotAccessor;\n         Element = element;\n         IsPreviewing = previewing;\n+        _cacheManager = cacheManager;\n         IsMember = propertyType.ContentType?.ItemType == PublishedItemType.Member;\n@@ -120,23 +120,2 @@ private void GetCacheLevels(PropertyCacheLevel propertyTypeCacheLevel, out Prope\n \n-    private IAppCache? GetSnapshotCache()\n-    {\n-        // cache within the snapshot cache, unless previewing, then use the snapshot or\n-        // elements cache (if we don't want to pollute the elements cache with short-lived\n-        // data) depending on settings\n-        // for members, always cache in the snapshot cache - never pollute elements cache\n-        if (_publishedSnapshotAccessor is null)\n-        {\n-            return null;\n-        }\n-\n-        if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot))\n-        {\n-            return null;\n-        }\n-\n-        return (IsPreviewing == false || FullCacheWhenPreviewing) && IsMember == false\n-            ? publishedSnapshot!.ElementsCache\n-            : publishedSnapshot!.SnapshotCache;\n-    }\n-\n     private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n@@ -147,2 +126,3 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n             case PropertyCacheLevel.None:\n+            case PropertyCacheLevel.Snapshot:\n                 // never cache anything\n@@ -155,13 +135,3 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n             case PropertyCacheLevel.Elements:\n-                // cache within the elements  cache, depending...\n-                IAppCache? snapshotCache = GetSnapshotCache();\n-                cacheValues = (CacheValues?)snapshotCache?.Get(ValuesCacheKey, () => new CacheValues()) ??\n-                              new CacheValues();\n-                break;\n-            case PropertyCacheLevel.Snapshot:\n-                IPublishedSnapshot? publishedSnapshot = _publishedSnapshotAccessor?.GetRequiredPublishedSnapshot();\n-\n-                // cache within the snapshot cache\n-                IAppCache? facadeCache = publishedSnapshot?.SnapshotCache;\n-                cacheValues = (CacheValues?)facadeCache?.Get(ValuesCacheKey, () => new CacheValues()) ??\n+                cacheValues = (CacheValues?)_cacheManager?.ElementsCache.Get(ValuesCacheKey, () => new CacheValues()) ??\n                               new CacheValues();\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nikolaj E. Lauridsen","training-data":{"loc-added":"11","loc-deleted":"48","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"nikolajlauridsen@protonmail.ch","commit-full-message":"","commit-date":"2024-12-03T12:40:05Z","current-rev":"380f3f7e87","filename":"Umbraco-CMS/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs","previous-rev":"437fcba4e5","commit-title":"Clear elements cache instead of refreshing it (#17708)","language":"C#","id":"520cf3bc55fbfa6fb3c07f3560d781b1e2034c12","model-score":0.19,"author-id":null,"project-id":33308,"delta-file-score":0.7076424,"diff":"diff --git a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\nindex f61968365c..6ff5a5fe1e 100644\n--- a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\n+++ b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\n@@ -47,3 +47,3 @@ public async Task HandleAsync(ContentRefreshNotification notification, Cancellat\n     {\n-        await RefreshElementsCacheAsync(notification.Entity);\n+        ClearElementsCache();\n \n@@ -56,3 +56,3 @@ public async Task HandleAsync(ContentDeletedNotification notification, Cancellat\n         {\n-            RemoveFromElementsCache(deletedEntity);\n+            ClearElementsCache();\n             await _documentCacheService.DeleteItemAsync(deletedEntity);\n@@ -63,3 +63,3 @@ public async Task HandleAsync(MediaRefreshNotification notification, Cancellatio\n     {\n-        await RefreshElementsCacheAsync(notification.Entity);\n+        ClearElementsCache();\n         await _mediaCacheService.RefreshMediaAsync(notification.Entity);\n@@ -71,3 +71,3 @@ public async Task HandleAsync(MediaDeletedNotification notification, Cancellatio\n         {\n-            RemoveFromElementsCache(deletedEntity);\n+            ClearElementsCache();\n             await _mediaCacheService.DeleteItemAsync(deletedEntity);\n@@ -76,49 +76,12 @@ public async Task HandleAsync(MediaDeletedNotification notification, Cancellatio\n \n-    private async Task RefreshElementsCacheAsync(IUmbracoEntity content)\n+    private void ClearElementsCache()\n     {\n-        IEnumerable<IRelation> parentRelations = _relationService.GetByParent(content)!;\n-        IEnumerable<IRelation> childRelations = _relationService.GetByChild(content);\n-\n-        var ids = parentRelations.Select(x => x.ChildId).Concat(childRelations.Select(x => x.ParentId)).ToHashSet();\n-        // We need to add ourselves to the list of ids to clear\n-        ids.Add(content.Id);\n-        foreach (var id in ids)\n-        {\n-            if (await _documentCacheService.HasContentByIdAsync(id) is false)\n-            {\n-                continue;\n-            }\n-\n-            IPublishedContent? publishedContent = await _documentCacheService.GetByIdAsync(id);\n-            if (publishedContent is null)\n-            {\n-                continue;\n-            }\n-\n-            foreach (IPublishedProperty publishedProperty in publishedContent.Properties)\n-            {\n-                var property = (PublishedProperty) publishedProperty;\n-                if (property.ReferenceCacheLevel is PropertyCacheLevel.Elements\n-                    || property.PropertyType.DeliveryApiCacheLevel is PropertyCacheLevel.Elements\n-                    || property.PropertyType.DeliveryApiCacheLevelForExpansion is PropertyCacheLevel.Elements)\n-                {\n-                    _elementsCache.ClearByKey(property.ValuesCacheKey);\n-                }\n-            }\n-        }\n+        // Ideally we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.\n+        // The reason for this is that we have no way to know which elements are affected by the changes. or what their keys are.\n+        // This is because currently published elements lives exclusively in a JSON blob in the umbracoPropertyData table.\n+        // This means that the only way to resolve these keys are to actually parse this data with a specific value converter, and for all cultures, which is not feasible.\n+        // If published elements become their own entities with relations, instead of just property data, we can revisit this,\n+        _elementsCache.Clear();\n     }\n \n-    private void RemoveFromElementsCache(IUmbracoEntity content)\n-    {\n-        // ClearByKey clears by \"startsWith\" so we'll clear by the cachekey prefix + contentKey\n-        // This will clear any and all properties for this content item, this is important because\n-        // we cannot resolve the PublishedContent for this entity since it and its content type is deleted.\n-        _elementsCache.ClearByKey(GetContentWideCacheKey(content.Key, true));\n-        _elementsCache.ClearByKey(GetContentWideCacheKey(content.Key, false));\n-    }\n-\n-    private string GetContentWideCacheKey(Guid contentKey, bool isPreviewing) => isPreviewing\n-        ? CacheKeys.PreviewPropertyCacheKeyPrefix + contentKey\n-        : CacheKeys.PropertyCacheKeyPrefix + contentKey;\n-\n     public Task HandleAsync(ContentTypeRefreshedNotification notification, CancellationToken cancellationToken)\n","improvement-type":"Complex Method"}],"change-level":"warning","is-hotspot?":false,"line":118,"what-changed":"GetPathsFromBlockValue has a cyclomatic complexity of 17, threshold = 9","how-to-fix":"There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html). Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring.","change-type":"introduced"},{"method":"ContainedFilePaths","why-it-occurs":"A Complex Method has a high cyclomatic complexity. The recommended threshold for the C# language is a cyclomatic complexity lower than 9.","name":"Complex Method","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs","refactoring-examples":[{"architectural-component-id":null,"author-name":"Bjarke Berg","training-data":{"loc-added":"24","loc-deleted":"26","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mail@bergmania.dk","commit-full-message":"* AB40660 - untangle the preview cookie from the auth cookie\r\n\r\n* Clean up\r\n\r\n* Allow anonymous to end preview sessions\r\n\r\n* Some refinements\r\n\r\n* update OpenApi.json\r\n\r\n* Fix enter preview test\r\n\r\n* correct tests to match new expectations of the preview cookie\r\n\r\n* sync preview tests with correct expectations of access level\r\n\r\n---------\r\n\r\nCo-authored-by: Sven Geusens <sge@umbraco.dk>\r\nCo-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>","commit-date":"2024-05-17T14:06:26Z","current-rev":"11e5257b56","filename":"Umbraco-CMS/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs","previous-rev":"80794f3efd","commit-title":"V14: Untangle the preview functionality from the auth cookie (#16308)","language":"C#","id":"7ecfb0a869f8c3df64c3c2a4602ef34642b49720","model-score":0.52,"author-id":null,"project-id":33308,"delta-file-score":1.044829,"diff":"diff --git a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\nindex 5bd16867ca..e61b8682af 100644\n--- a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n+++ b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs\n@@ -7,2 +7,8 @@\n using Microsoft.Extensions.Options;\n+using Umbraco.Cms.Core;\n+using Umbraco.Cms.Core.Preview;\n+using Umbraco.Cms.Core.Security;\n+using Umbraco.Cms.Core.Services;\n+using Umbraco.Cms.Web.Common.AspNetCore;\n+using Umbraco.Cms.Web.Common.Security;\n using Umbraco.Extensions;\n@@ -18,4 +24,15 @@ public class PreviewAuthenticationMiddleware : IMiddleware\n     private readonly ILogger<PreviewAuthenticationMiddleware> _logger;\n+    private readonly IPreviewTokenGenerator _previewTokenGenerator;\n+    private readonly IPreviewService _previewService;\n \n-    public PreviewAuthenticationMiddleware(ILogger<PreviewAuthenticationMiddleware> logger) => _logger = logger;\n+\n+    public PreviewAuthenticationMiddleware(\n+        ILogger<PreviewAuthenticationMiddleware> logger,\n+        IPreviewTokenGenerator previewTokenGenerator,\n+        IPreviewService previewService)\n+    {\n+        _logger = logger;\n+        _previewTokenGenerator = previewTokenGenerator;\n+        _previewService = previewService;\n+    }\n \n@@ -40,33 +57,14 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)\n             {\n-                CookieAuthenticationOptions? cookieOptions = context.RequestServices\n-                    .GetRequiredService<IOptionsSnapshot<CookieAuthenticationOptions>>()\n-                    .Get(Core.Constants.Security.BackOfficeAuthenticationType);\n+                Attempt<ClaimsIdentity> backOfficeIdentityAttempt = await _previewService.TryGetPreviewClaimsIdentityAsync();\n \n-                if (cookieOptions == null)\n+                if (backOfficeIdentityAttempt.Success)\n                 {\n-                    throw new InvalidOperationException(\"No cookie options found with name \" +\n-                                                        Core.Constants.Security.BackOfficeAuthenticationType);\n+                    // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n+                    // Principal, this means we'll have 2 identities assigned to the principal which we can\n+                    // use to authorize the preview and allow for a back office User.\n+                    context.User.AddIdentity(backOfficeIdentityAttempt.Result!);\n                 }\n-\n-                // If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing.\n-                // In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication\n-                // for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered.\n-                if (cookieOptions.Cookie.Name != null)\n+                else\n                 {\n-                    var chunkingCookieManager = new ChunkingCookieManager();\n-                    var cookie = chunkingCookieManager.GetRequestCookie(context, cookieOptions.Cookie.Name);\n-\n-                    if (!string.IsNullOrEmpty(cookie))\n-                    {\n-                        AuthenticationTicket? unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie);\n-                        ClaimsIdentity? backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity();\n-\n-                        if (backOfficeIdentity != null)\n-                        {\n-                            // Ok, we've got a real ticket, now we can add this ticket's identity to the current\n-                            // Principal, this means we'll have 2 identities assigned to the principal which we can\n-                            // use to authorize the preview and allow for a back office User.\n-                            context.User.AddIdentity(backOfficeIdentity);\n-                        }\n-                    }\n+                    _logger.LogDebug(\"Could not transform previewCookie value into a claimsIdentity\");\n                 }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Sven Geusens","training-data":{"loc-added":"74","loc-deleted":"9","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"sge@umbraco.dk","commit-full-message":"* Support new localLink format in core link parsing\r\n\r\n* Updated devliery api to work with the new locallinks format\r\n\r\nAdded tests for old and new format handling.\r\n\r\n* Fix error regarding type attribute not always being present (for example old format or non local links)","commit-date":"2024-07-02T12:22:19Z","current-rev":"46acd51759","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs","previous-rev":"13b77d35df","commit-title":"[V14] Make the backend work with the new localLinks format (#16661)","language":"C#","id":"7d7b589a86f716e705df415598cc4c8f2a02af1b","model-score":0.42,"author-id":null,"project-id":33308,"delta-file-score":0.3009901,"diff":"diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\nindex 7723fc835c..407bc1a022 100644\n--- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\n+++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs\n@@ -6,2 +6,3 @@\n using Umbraco.Cms.Core.PublishedCache;\n+using Umbraco.Cms.Core.Templates;\n \n@@ -20,3 +21,17 @@ protected ApiRichTextParserBase(IApiContentRouteBuilder apiContentRouteBuilder,\n \n-    protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl, Action handleInvalidLink)\n+    protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, string type, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl, Action handleInvalidLink)\n+    {\n+        ReplaceStatus replaceAttempt = ReplaceLocalLink(publishedSnapshot, href, type, handleContentRoute, handleMediaUrl);\n+        if (replaceAttempt == ReplaceStatus.Success)\n+        {\n+            return;\n+        }\n+\n+        if (replaceAttempt == ReplaceStatus.InvalidEntityType || ReplaceLegacyLocalLink(publishedSnapshot, href, handleContentRoute, handleMediaUrl) == ReplaceStatus.InvalidEntityType)\n+        {\n+            handleInvalidLink();\n+        }\n+    }\n+\n+    private ReplaceStatus ReplaceLocalLink(IPublishedSnapshot publishedSnapshot, string href, string type, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl)\n     {\n@@ -25,11 +40,12 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n         {\n-            return;\n+            return ReplaceStatus.NoMatch;\n         }\n \n-        if (UdiParser.TryParse(match.Groups[\"udi\"].Value, out Udi? udi) is false)\n+        if (Guid.TryParse(match.Groups[\"guid\"].Value, out Guid guid) is false)\n         {\n-            return;\n+            return ReplaceStatus.NoMatch;\n         }\n \n-        bool handled = false;\n+        var udi = new GuidUdi(type, guid);\n+\n         switch (udi.EntityType)\n@@ -43,4 +59,4 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n                 {\n-                    handled = true;\n                     handleContentRoute(route);\n+                    return ReplaceStatus.Success;\n                 }\n@@ -52,4 +68,4 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n                 {\n-                    handled = true;\n                     handleMediaUrl(_apiMediaUrlProvider.GetUrl(media));\n+                    return ReplaceStatus.Success;\n                 }\n@@ -59,6 +75,45 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr\n \n-        if(handled is false)\n+        return ReplaceStatus.InvalidEntityType;\n+    }\n+\n+    private ReplaceStatus ReplaceLegacyLocalLink(IPublishedSnapshot publishedSnapshot, string href, Action<IApiContentRoute> handleContentRoute, Action<string> handleMediaUrl)\n+    {\n+        Match match = LegacyLocalLinkRegex().Match(href);\n+        if (match.Success is false)\n         {\n-            handleInvalidLink();\n+            return ReplaceStatus.NoMatch;\n+        }\n+\n+        if (UdiParser.TryParse(match.Groups[\"udi\"].Value, out Udi? udi) is false)\n+        {\n+            return ReplaceStatus.NoMatch;\n+        }\n+\n+\n+        switch (udi.EntityType)\n+        {\n+            case Constants.UdiEntityType.Document:\n+                IPublishedContent? content = publishedSnapshot.Content?.GetById(udi);\n+                IApiContentRoute? route = content != null\n+                    ? _apiContentRouteBuilder.Build(content)\n+                    : null;\n+                if (route != null)\n+                {\n+                    handleContentRoute(route);\n+                    return ReplaceStatus.Success;\n+                }\n+\n+                break;\n+            case Constants.UdiEntityType.Media:\n+                IPublishedContent? media = publishedSnapshot.Media?.GetById(udi);\n+                if (media != null)\n+                {\n+                    handleMediaUrl(_apiMediaUrlProvider.GetUrl(media));\n+                    return ReplaceStatus.Success;\n+                }\n+\n+                break;\n         }\n+\n+        return ReplaceStatus.InvalidEntityType;\n     }\n@@ -82,3 +137,13 @@ protected void ReplaceLocalImages(IPublishedSnapshot publishedSnapshot, string u\n     [GeneratedRegex(\"{localLink:(?<udi>umb:.+)}\")]\n+    private static partial Regex LegacyLocalLinkRegex();\n+\n+    [GeneratedRegex(\"{localLink:(?<guid>.+)}\")]\n     private static partial Regex LocalLinkRegex();\n+\n+    private enum ReplaceStatus\n+    {\n+        NoMatch,\n+        Success,\n+        InvalidEntityType\n+    }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vitor Rodrigues","training-data":{"loc-added":"16","loc-deleted":"14","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"8.545379580978913"},"author-email":"vsilvar@users.noreply.github.com","commit-full-message":"* Fixes #15136: Search includes fields from other cultures\r\n\r\nRegex was updated to support block list fields\r\nUnpublished nodes on the supplied culture are not filtered out\r\n\r\n* Making the code non-breaking\r\n\r\n* Fixed failing publish content query integration tests\r\n\r\nThe tests were not setting the content as publish in the specifed culture\r\ncausing the content items to be ignored\r\n\r\n---------\r\n\r\nCo-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>","commit-date":"2024-02-07T12:43:37Z","current-rev":"839b2ff6a2","filename":"Umbraco-CMS/src/Umbraco.Infrastructure/PublishedContentQuery.cs","previous-rev":"2221c4f1c7","commit-title":"Fixes #15136: Search includes fields from other cultures (#15148)","language":"C#","id":"ac0709776b22e60e777651712009344d5d3c3529","model-score":0.3,"author-id":null,"project-id":33308,"delta-file-score":0.2613985,"diff":"diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs\nindex d075e8b9d2..758115b9ca 100644\n--- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs\n+++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs\n@@ -295,17 +295,13 @@ public IEnumerable<PublishedSearchResult> Search(string term, int skip, int take\n                     .ToArray(); // Get all index fields suffixed with the culture name supplied\n-            ordering = query.ManagedQuery(term, fields);\n-        }\n-\n-            // Filter selected fields because results are loaded from the published snapshot based on these\n-            IOrdering? queryExecutor = ordering.SelectFields(_returnedQueryFields);\n-\n-\n-        ISearchResults? results = skip == 0 && take == 0\n-            ? queryExecutor.Execute()\n-            : queryExecutor.Execute(QueryOptions.SkipTake(skip, take));\n \n-        totalRecords = results.TotalItemCount;\n+            // Filter out unpublished content for the specified culture if the content varies by culture\n+            // The published__{culture} field is not populated when the content is not published in that culture\n+            ordering = query\n+                .ManagedQuery(term, fields)\n+                .Not().Group(q => q\n+                    .Field(UmbracoExamineFieldNames.VariesByCultureFieldName, \"y\")\n+                    .Not().Field($\"{UmbracoExamineFieldNames.PublishedFieldName}_{culture.ToLowerInvariant()}\", \"y\"));\n+        }\n \n-        return new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content),\n-            _variationContextAccessor, culture);\n+        return Search(ordering, skip, take, out totalRecords, culture);\n     }\n@@ -318,2 +314,6 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query)\n     public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords)\n+        => Search(query, skip, take, out totalRecords, null);\n+\n+    /// <inheritdoc />\n+    public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords, string? culture)\n     {\n@@ -333,4 +333,4 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip,\n         {\n-                // Filter selected fields because results are loaded from the published snapshot based on these\n-                query = ordering.SelectFields(_returnedQueryFields);\n+            // Filter selected fields because results are loaded from the published snapshot based on these\n+            query = ordering.SelectFields(_returnedQueryFields);\n         }\n@@ -343,3 +343,5 @@ public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip,\n \n-        return results.ToPublishedSearchResults(_publishedSnapshot);\n+        return culture.IsNullOrWhiteSpace()\n+            ? results.ToPublishedSearchResults(_publishedSnapshot)\n+            : new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture);\n     }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nikolaj E. Lauridsen","training-data":{"loc-added":"6","loc-deleted":"36","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"nikolajlauridsen@protonmail.ch","commit-full-message":"* Remove nucache reference from Web.Common\r\n\r\n* Get tests building-ish\r\n\r\n* Move ReservedFieldNamesService to the right project\r\n\r\n* Remove IPublishedSnapshotStatus\r\n\r\n* Added functionality to the INavigationQueryService to get root keys\r\n\r\n* Fixed issue with navigation\r\n\r\n* Remove IPublishedSnapshot from UmbracoContext\r\n\r\n* Begin removing usage of IPublishedSnapshot from PublishedContentExtensions\r\n\r\n* Fix PublishedContentExtensions.cs\r\n\r\n* Don't use snapshots in delivery media api\r\n\r\n* Use IPublishedMediaCache in QueryMediaApiController\r\n\r\n* Remove more usages of IPublishedSnapshotAccessor\r\n\r\n* Comment out tests\r\n\r\n* Remove more usages of PublishedSnapshotAccessor\r\n\r\n* Remove PublishedSnapshot from property\r\n\r\n* Fixed test build\r\n\r\n* Fix errors\r\n\r\n* Fix some tests\r\n\r\n* Delete NuCache 🎉\r\n\r\n* Implement DatabaseCacheRebuilder\r\n\r\n* Remove usage of IPublishedSnapshotService\r\n\r\n* Remove IPublishedSnapshotService\r\n\r\n* Remove TestPublishedSnapshotAccessor and make tests build\r\n\r\n* Don't test Snapshot cachelevel\r\n\r\nIt's no longer supported\r\n\r\n* Fix BlockEditorConverter\r\n\r\nElement != Element document type\r\n\r\n* Remember to set cachemanager\r\n\r\n* Fix RichTextParserTests\r\n\r\n* Implement TryGetLevel on INavigationQueryService\r\n\r\n* Fake level and obsolete it in PublishedContent\r\n\r\n* Remove ChildrenForAllCultures\r\n\r\n* Hack Path property on PublishedContent\r\n\r\n* Remove usages of IPublishedSnapshot in tests\r\n\r\n* More ConvertersTests\r\n\r\n* Add hybrid cache to integration tests\r\n\r\nWe can actually do this now because we no longer save files on disk\r\n\r\n* Rename IPublishedSnapshotRebuilder to ICacheRebuilder\r\n\r\n* Comment out tests\r\n\r\n* V15: Replacing the usages of Parent (navigation data) from IPublishedContent (#17125)\r\n\r\n* Fix .Parent references in PublishedContentExtensions\r\n\r\n* Add missing methods to FriendlyPublishedContentExtensions (ones that you were able to call on the content directly as they now require extra params)\r\n\r\n* Fix references from the extension methods\r\n\r\n* Fix dependencies in tests\r\n\r\n* Replace IPublishedSnapshotAccessor with the content cache in tests\r\n\r\n* Resolving more .Parent references\r\n\r\n* Fix unit tests\r\n\r\n* Obsolete and use extension methods\r\n\r\n* Remove private method and use extension instead\r\n\r\n* Moving code around\r\n\r\n* Fix tests\r\n\r\n* Fix more references\r\n\r\n* Cleanup\r\n\r\n* Fix more usages\r\n\r\n* Resolve merge conflict\r\n\r\n* Fix tests\r\n\r\n* Cleanup\r\n\r\n* Fix more tests\r\n\r\n* Fixed unit tests\r\n\r\n* Cleanup\r\n\r\n* Replace last usages\r\n\r\n---------\r\n\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\n\r\n* Remove usage of IPublishedSnapshotAccessor from IRequestItemProvider\r\n\r\n* Post merge fixup\r\n\r\n* Remo IPublishedSnapshot\r\n\r\n* Add HasAny to IDocumentUrlService\r\n\r\n* Fix TextBuilder\r\n\r\n* Fix modelsbuilder tests\r\n\r\n* Use explicit types\r\n\r\n* Implement GetByContentType\r\n\r\n* Support element types in PublishedContentTypeCache\r\n\r\n* Run enlistments before publishing notifications\r\n\r\n* Fix elements cache refreshing\r\n\r\n* Implement GetByUdi\r\n\r\n* Implement GetAtRoot\r\n\r\n* Implement GetByRoute\r\n\r\n* Reimplement GetRouteById\r\n\r\n* Fix blocks unit tests\r\n\r\n* Initialize domain cache on boot\r\n\r\n* Only return routes with domains on non default lanauges\r\n\r\n* V15: Replacing the usages of `Children` (navigation data) from `IPublishedContent` (#17159)\r\n\r\n* Update params in PublishedContentExtensions to the general interfaces for the published cache and navigation service, so that we can use the extension methods on both documents and media\r\n\r\n* Introduce GetParent() which uses the right services\r\n\r\n* Fix obsolete message on .Parent\r\n\r\n* Obsolete .Children\r\n\r\n* Fix usages of Children for ApiMediaQueryService\r\n\r\n* Fix usage in internal\r\n\r\n* Fix usages in views\r\n\r\n* Fix indentation\r\n\r\n* Fix issue with delete language\r\n\r\n* Update nuget pacakges\r\n\r\n* Clear elements cache when content is deleted\r\n\r\ninstead of trying to update it\r\n\r\n* Reset publishedModelFactory\r\n\r\n* Fixed publishing\r\n\r\n---------\r\n\r\nCo-authored-by: Bjarke Berg <mail@bergmania.dk>\r\nCo-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>\r\nCo-authored-by: kjac <kja@umbraco.dk>","commit-date":"2024-10-01T13:03:02Z","current-rev":"1258962429","filename":"Umbraco-CMS/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs","previous-rev":"7ca96423f8","commit-title":"V15: Remove Nucache (#17166)","language":"C#","id":"c5107d73a299c92b12ef250416ebfdc0fe984c06","model-score":0.2,"author-id":null,"project-id":33308,"delta-file-score":0.29056275,"diff":"diff --git a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\nindex 53e8156538..0452cf0b03 100644\n--- a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\n+++ b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs\n@@ -17,3 +17,2 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase\n     private readonly object _locko = new();\n-    private readonly IPublishedSnapshotAccessor? _publishedSnapshotAccessor;\n     private readonly object? _sourceValue;\n@@ -21,2 +20,3 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase\n     protected readonly bool IsPreviewing;\n+    private readonly ICacheManager? _cacheManager;\n     private CacheValues? _cacheValues;\n@@ -32,4 +32,4 @@ public PublishedElementPropertyBase(\n         PropertyCacheLevel referenceCacheLevel,\n-        object? sourceValue = null,\n-        IPublishedSnapshotAccessor? publishedSnapshotAccessor = null)\n+        ICacheManager? cacheManager,\n+        object? sourceValue = null)\n         : base(propertyType, referenceCacheLevel)\n@@ -37,5 +37,5 @@ public PublishedElementPropertyBase(\n         _sourceValue = sourceValue;\n-        _publishedSnapshotAccessor = publishedSnapshotAccessor;\n         Element = element;\n         IsPreviewing = previewing;\n+        _cacheManager = cacheManager;\n         IsMember = propertyType.ContentType?.ItemType == PublishedItemType.Member;\n@@ -120,23 +120,2 @@ private void GetCacheLevels(PropertyCacheLevel propertyTypeCacheLevel, out Prope\n \n-    private IAppCache? GetSnapshotCache()\n-    {\n-        // cache within the snapshot cache, unless previewing, then use the snapshot or\n-        // elements cache (if we don't want to pollute the elements cache with short-lived\n-        // data) depending on settings\n-        // for members, always cache in the snapshot cache - never pollute elements cache\n-        if (_publishedSnapshotAccessor is null)\n-        {\n-            return null;\n-        }\n-\n-        if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot))\n-        {\n-            return null;\n-        }\n-\n-        return (IsPreviewing == false || FullCacheWhenPreviewing) && IsMember == false\n-            ? publishedSnapshot!.ElementsCache\n-            : publishedSnapshot!.SnapshotCache;\n-    }\n-\n     private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n@@ -147,2 +126,3 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n             case PropertyCacheLevel.None:\n+            case PropertyCacheLevel.Snapshot:\n                 // never cache anything\n@@ -155,13 +135,3 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)\n             case PropertyCacheLevel.Elements:\n-                // cache within the elements  cache, depending...\n-                IAppCache? snapshotCache = GetSnapshotCache();\n-                cacheValues = (CacheValues?)snapshotCache?.Get(ValuesCacheKey, () => new CacheValues()) ??\n-                              new CacheValues();\n-                break;\n-            case PropertyCacheLevel.Snapshot:\n-                IPublishedSnapshot? publishedSnapshot = _publishedSnapshotAccessor?.GetRequiredPublishedSnapshot();\n-\n-                // cache within the snapshot cache\n-                IAppCache? facadeCache = publishedSnapshot?.SnapshotCache;\n-                cacheValues = (CacheValues?)facadeCache?.Get(ValuesCacheKey, () => new CacheValues()) ??\n+                cacheValues = (CacheValues?)_cacheManager?.ElementsCache.Get(ValuesCacheKey, () => new CacheValues()) ??\n                               new CacheValues();\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nikolaj E. Lauridsen","training-data":{"loc-added":"11","loc-deleted":"48","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"nikolajlauridsen@protonmail.ch","commit-full-message":"","commit-date":"2024-12-03T12:40:05Z","current-rev":"380f3f7e87","filename":"Umbraco-CMS/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs","previous-rev":"437fcba4e5","commit-title":"Clear elements cache instead of refreshing it (#17708)","language":"C#","id":"520cf3bc55fbfa6fb3c07f3560d781b1e2034c12","model-score":0.19,"author-id":null,"project-id":33308,"delta-file-score":0.7076424,"diff":"diff --git a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\nindex f61968365c..6ff5a5fe1e 100644\n--- a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\n+++ b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/CacheRefreshingNotificationHandler.cs\n@@ -47,3 +47,3 @@ public async Task HandleAsync(ContentRefreshNotification notification, Cancellat\n     {\n-        await RefreshElementsCacheAsync(notification.Entity);\n+        ClearElementsCache();\n \n@@ -56,3 +56,3 @@ public async Task HandleAsync(ContentDeletedNotification notification, Cancellat\n         {\n-            RemoveFromElementsCache(deletedEntity);\n+            ClearElementsCache();\n             await _documentCacheService.DeleteItemAsync(deletedEntity);\n@@ -63,3 +63,3 @@ public async Task HandleAsync(MediaRefreshNotification notification, Cancellatio\n     {\n-        await RefreshElementsCacheAsync(notification.Entity);\n+        ClearElementsCache();\n         await _mediaCacheService.RefreshMediaAsync(notification.Entity);\n@@ -71,3 +71,3 @@ public async Task HandleAsync(MediaDeletedNotification notification, Cancellatio\n         {\n-            RemoveFromElementsCache(deletedEntity);\n+            ClearElementsCache();\n             await _mediaCacheService.DeleteItemAsync(deletedEntity);\n@@ -76,49 +76,12 @@ public async Task HandleAsync(MediaDeletedNotification notification, Cancellatio\n \n-    private async Task RefreshElementsCacheAsync(IUmbracoEntity content)\n+    private void ClearElementsCache()\n     {\n-        IEnumerable<IRelation> parentRelations = _relationService.GetByParent(content)!;\n-        IEnumerable<IRelation> childRelations = _relationService.GetByChild(content);\n-\n-        var ids = parentRelations.Select(x => x.ChildId).Concat(childRelations.Select(x => x.ParentId)).ToHashSet();\n-        // We need to add ourselves to the list of ids to clear\n-        ids.Add(content.Id);\n-        foreach (var id in ids)\n-        {\n-            if (await _documentCacheService.HasContentByIdAsync(id) is false)\n-            {\n-                continue;\n-            }\n-\n-            IPublishedContent? publishedContent = await _documentCacheService.GetByIdAsync(id);\n-            if (publishedContent is null)\n-            {\n-                continue;\n-            }\n-\n-            foreach (IPublishedProperty publishedProperty in publishedContent.Properties)\n-            {\n-                var property = (PublishedProperty) publishedProperty;\n-                if (property.ReferenceCacheLevel is PropertyCacheLevel.Elements\n-                    || property.PropertyType.DeliveryApiCacheLevel is PropertyCacheLevel.Elements\n-                    || property.PropertyType.DeliveryApiCacheLevelForExpansion is PropertyCacheLevel.Elements)\n-                {\n-                    _elementsCache.ClearByKey(property.ValuesCacheKey);\n-                }\n-            }\n-        }\n+        // Ideally we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.\n+        // The reason for this is that we have no way to know which elements are affected by the changes. or what their keys are.\n+        // This is because currently published elements lives exclusively in a JSON blob in the umbracoPropertyData table.\n+        // This means that the only way to resolve these keys are to actually parse this data with a specific value converter, and for all cultures, which is not feasible.\n+        // If published elements become their own entities with relations, instead of just property data, we can revisit this,\n+        _elementsCache.Clear();\n     }\n \n-    private void RemoveFromElementsCache(IUmbracoEntity content)\n-    {\n-        // ClearByKey clears by \"startsWith\" so we'll clear by the cachekey prefix + contentKey\n-        // This will clear any and all properties for this content item, this is important because\n-        // we cannot resolve the PublishedContent for this entity since it and its content type is deleted.\n-        _elementsCache.ClearByKey(GetContentWideCacheKey(content.Key, true));\n-        _elementsCache.ClearByKey(GetContentWideCacheKey(content.Key, false));\n-    }\n-\n-    private string GetContentWideCacheKey(Guid contentKey, bool isPreviewing) => isPreviewing\n-        ? CacheKeys.PreviewPropertyCacheKeyPrefix + contentKey\n-        : CacheKeys.PropertyCacheKeyPrefix + contentKey;\n-\n     public Task HandleAsync(ContentTypeRefreshedNotification notification, CancellationToken cancellationToken)\n","improvement-type":"Complex Method"}],"change-level":"warning","is-hotspot?":false,"line":49,"what-changed":"ContainedFilePaths has a cyclomatic complexity of 10, threshold = 9","how-to-fix":"There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html). Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring.","change-type":"introduced"},{"method":"GetPathsFromUploadFieldProperty","why-it-occurs":"A complex conditional is an expression inside a branch such as an <code>if</code>-statmeent which consists of multiple, logical operations. Example: <code>if (x.started() && y.running())</code>.Complex conditionals make the code even harder to read, and contribute to the Complex Method code smell. Encapsulate them.","name":"Complex Conditional","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs","refactoring-examples":[{"diff":"diff --git a/complex_conditional.js b/complex_conditional.js\nindex c43da09584..94259ce874 100644\n--- a/complex_conditional.js\n+++ b/complex_conditional.js\n@@ -1,16 +1,34 @@\n function messageReceived(message, timeReceived) {\n-   // Ignore all messages which aren't from known customers:\n-   if (!message.sender &&\n-       customers.getId(message.name) == null) {\n+   // Refactoring #1: encapsulate the business rule in a\n+   // function. A clear name replaces the need for the comment:\n+   if (!knownCustomer(message)) {\n      log('spam received -- ignoring');\n      return;\n    }\n \n-  // Provide an auto-reply when outside business hours:\n-  if ((timeReceived.getHours() > 17) ||\n-      (timeReceived.getHours() < 8)) {\n+  // Refactoring #2: encapsulate the business rule.\n+  // Again, note how a clear function name replaces the\n+  // need for a code comment:\n+  if (outsideBusinessHours(timeReceived)) {\n     return autoReplyTo(message);\n   }\n \n   pingAgentFor(message);\n+}\n+\n+function outsideBusinessHours(timeReceived) {\n+  // Refactoring #3: replace magic numbers with\n+  // symbols that communicate with the code reader:\n+  const closingHour = 17;\n+  const openingHour = 8;\n+\n+  const hours = timeReceived.getHours();\n+\n+  // Refactoring #4: simple conditional rules can\n+  // be further clarified by introducing a variable:\n+  const afterClosing = hours > closingHour;\n+  const beforeOpening = hours < openingHour;\n+\n+  // Yeah -- look how clear the business rule is now!\n+  return afterClosing || beforeOpening;\n }\n\\ No newline at end of file\n","language":"c#","improvement-type":"Complex Conditional"}],"change-level":"warning","is-hotspot?":false,"line":91,"what-changed":"GetPathsFromUploadFieldProperty has 2 complex conditionals with 4 branches, threshold = 2","how-to-fix":"Apply the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring so that the complex conditional is encapsulated in a separate function with a good name that captures the business rule. Optionally, for simple expressions, introduce a new variable which holds the result of the complex conditional.","change-type":"introduced"},{"method":"GetPathsFromBlockValue","why-it-occurs":"A Bumpy Road is a function that contains multiple chunks of nested conditional logic inside the same function. The deeper the nesting and the more bumps, the lower the code health.\n\nA bumpy code road represents a lack of encapsulation which becomes an obstacle to comprehension. In imperative languages there’s also an increased risk for feature entanglement, which leads to complex state management. CodeScene considers the following rules for the code health impact: 1) The deeper the nested conditional logic of each bump, the higher the tax on our working memory. 2) The more bumps inside a function, the more expensive it is to refactor as each bump represents a missing abstraction. 3) The larger each bump – that is, the more lines of code it spans – the harder it is to build up a mental model of the function. The nesting depth for what is considered a bump is  levels of conditionals.","name":"Bumpy Road Ahead","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs","refactoring-examples":null,"change-level":"warning","is-hotspot?":false,"line":118,"what-changed":"GetPathsFromBlockValue has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function","how-to-fix":"Bumpy Road implementations indicate a lack of encapsulation. Check out the detailed description of the [Bumpy Road code health issue](https://codescene.com/blog/bumpy-road-code-complexity-in-context/).\n\nA Bumpy Road often suggests that the function/method does too many things. The first refactoring step is to identify the different possible responsibilities of the function. Consider extracting those responsibilities into smaller, cohesive, and well-named functions. The [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html) refactoring is the primary response.","change-type":"introduced"},{"why-it-occurs":"Overall Code Complexity is measured by the mean cyclomatic complexity across all functions in the file. The lower the number, the better.\n\nCyclomatic complexity is a function level metric that measures the number of logical branches (if-else, loops, etc.). Cyclomatic complexity is a rough complexity measure, but useful as a way of estimating the minimum number of unit tests you would need. As such, prefer functions with low cyclomatic complexity (2-3 branches).","name":"Overall Code Complexity","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs","refactoring-examples":null,"change-level":"warning","is-hotspot?":false,"what-changed":"This module has a mean cyclomatic complexity of 4.78 across 9 functions. The mean complexity threshold is 4","how-to-fix":"You address the overall cyclomatic complexity by a) modularizing the code, and b) abstract away the complexity. Let's look at some examples:\n\nModularizing the Code: Do an X-Ray and inspect the local hotspots. Are there any complex conditional expressions? If yes, then do a [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring. Extract the conditional logic into a separate function and put a good name on that function. This clarifies the intent and makes the original function easier to read. Repeat until all complex conditional expressions have been simplified.\n\n","change-type":"introduced"},{"method":"AutoFillProperties","why-it-occurs":"A Bumpy Road is a function that contains multiple chunks of nested conditional logic inside the same function. The deeper the nesting and the more bumps, the lower the code health.\n\nA bumpy code road represents a lack of encapsulation which becomes an obstacle to comprehension. In imperative languages there’s also an increased risk for feature entanglement, which leads to complex state management. CodeScene considers the following rules for the code health impact: 1) The deeper the nested conditional logic of each bump, the higher the tax on our working memory. 2) The more bumps inside a function, the more expensive it is to refactor as each bump represents a missing abstraction. 3) The larger each bump – that is, the more lines of code it spans – the harder it is to build up a mental model of the function. The nesting depth for what is considered a bump is  levels of conditionals.","name":"Bumpy Road Ahead","file":"src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaSavingNotificationHandler.cs","refactoring-examples":null,"change-level":"warning","is-hotspot?":false,"line":46,"what-changed":"AutoFillProperties has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function","how-to-fix":"Bumpy Road implementations indicate a lack of encapsulation. Check out the detailed description of the [Bumpy Road code health issue](https://codescene.com/blog/bumpy-road-code-complexity-in-context/).\n\nA Bumpy Road often suggests that the function/method does too many things. The first refactoring step is to identify the different possible responsibilities of the function. Consider extracting those responsibilities into smaller, cohesive, and well-named functions. The [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html) refactoring is the primary response.","change-type":"introduced"}]},"positive-impact-count":6,"repo":"Umbraco-CMS","code-health":8.609312561579685,"version":"3.0","authors":["PeterKvayt","Andy Butland","Peter Kvayt"],"directives":{"added":[],"removed":[]},"positive-findings":{"number-of-types":4,"number-of-files-touched":3,"findings":[{"name":"Code Duplication","file":"src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs","change-type":"fixed","change-level":"improvement","is-hotspot?":false,"why-it-occurs":"Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.","how-to-fix":"A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. [Read More](https://codescene.com/blog/software-revolution-part3/)\n\nOnce you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.","what-changed":"The module no longer contains too many functions with similar structure"},{"name":"Primitive Obsession","file":"src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs","change-type":"improved","change-level":"improvement","is-hotspot?":false,"why-it-occurs":"Code that uses a high degree of built-in, primitives such as integers, strings, floats, lacks a domain language that encapsulates the validation and semantics of function arguments. Primitive Obsession has several consequences: 1) In a statically typed language, the compiler will detect less erroneous assignments. 2) Security impact since the possible value range of a variable/argument isn't retricted.\n\nIn this module, 32 % of all functions have primitive types as arguments.","how-to-fix":"Primitive Obsession indicates a missing domain language. Introduce data types that encapsulate the details and constraints of your domain. For example, instead of `int userId`, consider `User clicked`.","what-changed":"The ratio of primitive types in function arguments decreases from 33.33% to 32.35%, threshold = 30.0%"},{"name":"Primitive Obsession","file":"src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs","change-type":"fixed","change-level":"improvement","is-hotspot?":false,"why-it-occurs":"Code that uses a high degree of built-in, primitives such as integers, strings, floats, lacks a domain language that encapsulates the validation and semantics of function arguments. Primitive Obsession has several consequences: 1) In a statically typed language, the compiler will detect less erroneous assignments. 2) Security impact since the possible value range of a variable/argument isn't retricted.\n\nIn this module, 45 % of all functions have primitive types as arguments.","how-to-fix":"Primitive Obsession indicates a missing domain language. Introduce data types that encapsulate the details and constraints of your domain. For example, instead of `int userId`, consider `User clicked`.","what-changed":"The ratio of primivite types in function arguments is no longer above the threshold"},{"method":"Handle","why-it-occurs":"A complex conditional is an expression inside a branch such as an <code>if</code>-statmeent which consists of multiple, logical operations. Example: <code>if (x.started() && y.running())</code>.Complex conditionals make the code even harder to read, and contribute to the Complex Method code smell. Encapsulate them.","name":"Complex Conditional","file":"src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs","change-level":"improvement","is-hotspot?":false,"line":75,"what-changed":"Handle no longer has a complex conditional","how-to-fix":"Apply the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring so that the complex conditional is encapsulated in a separate function with a good name that captures the business rule. Optionally, for simple expressions, introduce a new variable which holds the result of the complex conditional.","change-type":"fixed"},{"method":"GetFilePathsFromPropertyValues","why-it-occurs":"A complex conditional is an expression inside a branch such as an <code>if</code>-statmeent which consists of multiple, logical operations. Example: <code>if (x.started() && y.running())</code>.Complex conditionals make the code even harder to read, and contribute to the Complex Method code smell. Encapsulate them.","name":"Complex Conditional","file":"src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs","change-level":"improvement","is-hotspot?":false,"line":152,"what-changed":"GetFilePathsFromPropertyValues no longer has a complex conditional","how-to-fix":"Apply the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring so that the complex conditional is encapsulated in a separate function with a good name that captures the business rule. Optionally, for simple expressions, introduce a new variable which holds the result of the complex conditional.","change-type":"fixed"},{"method":"AutoFillProperties","why-it-occurs":"A Bumpy Road is a function that contains multiple chunks of nested conditional logic inside the same function. The deeper the nesting and the more bumps, the lower the code health.\n\nA bumpy code road represents a lack of encapsulation which becomes an obstacle to comprehension. In imperative languages there’s also an increased risk for feature entanglement, which leads to complex state management. CodeScene considers the following rules for the code health impact: 1) The deeper the nested conditional logic of each bump, the higher the tax on our working memory. 2) The more bumps inside a function, the more expensive it is to refactor as each bump represents a missing abstraction. 3) The larger each bump – that is, the more lines of code it spans – the harder it is to build up a mental model of the function. The nesting depth for what is considered a bump is  levels of conditionals.","name":"Bumpy Road Ahead","file":"src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs","change-level":"improvement","is-hotspot?":false,"line":175,"what-changed":"AutoFillProperties is no longer above the threshold for logical blocks with deeply nested code","how-to-fix":"Bumpy Road implementations indicate a lack of encapsulation. Check out the detailed description of the [Bumpy Road code health issue](https://codescene.com/blog/bumpy-road-code-complexity-in-context/).\n\nA Bumpy Road often suggests that the function/method does too many things. The first refactoring step is to identify the different possible responsibilities of the function. Consider extracting those responsibilities into smaller, cohesive, and well-named functions. The [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html) refactoring is the primary response.","change-type":"fixed"}]},"notices":{"number-of-types":0,"number-of-files-touched":0,"findings":[]},"external-review-provider":"GitHub"},"analysistime":"2025-06-30T09:55:47.000Z","project-name":"Umbraco-CMS","repository":"https://github.com/umbraco/Umbraco-CMS.git"}}