From 3eb4c642c39709840ce117a00a64c63f2526c7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Mon, 9 Mar 2020 17:07:56 +0100 Subject: [PATCH 01/21] Added GetSegment() method to VariationContext and uses it when contentId is available --- .../PublishedContent/VariationContext.cs | 7 +++++++ .../VariationContextAccessorExtensions.cs | 21 ++++++++++++++++++- .../PublishedValueFallback.cs | 4 ++-- .../PublishedCache/NuCache/Property.cs | 8 +++---- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index b83002fdce..424fc7c4a8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -23,5 +23,12 @@ /// Gets the segment. /// public string Segment { get; } + + /// + /// Gets the segment for the content item + /// + /// + /// + public virtual string GetSegment(int contentId) => Segment; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index 6710d79cc6..a387ea87b8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -3,13 +3,32 @@ public static class VariationContextAccessorExtensions { public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment) + => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); + + public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string culture, ref string segment) + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); + + private static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, ref string culture, ref string segment) { if (culture != null && segment != null) return; // use context values var publishedVariationContext = variationContextAccessor?.VariationContext; if (culture == null) culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; - if (segment == null) segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : ""; + + if (segment == null) + { + if (variations.VariesBySegment()) + { + segment = contentId == null + ? publishedVariationContext?.Segment + : publishedVariationContext?.GetSegment(contentId.Value); + } + else + { + segment = ""; + } + } } } } diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index 7b467b6d15..57c3094eaf 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -111,7 +111,7 @@ namespace Umbraco.Web.Models.PublishedContent var propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) { - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); noValueProperty = content.GetProperty(alias); } @@ -168,7 +168,7 @@ namespace Umbraco.Web.Models.PublishedContent { culture = null; segment = null; - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); } property = content?.GetProperty(alias); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs index 0254b815c1..86023bb302 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs @@ -90,7 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // determines whether a property has value public override bool HasValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); var value = GetSourceValue(culture, segment); var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source); @@ -194,7 +194,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetSourceValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); if (culture == "" && segment == "") return _sourceValue; @@ -208,7 +208,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object value; lock (_locko) @@ -229,7 +229,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetXPathValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); lock (_locko) { From 8752ce871aecab986aeb1aa54b992620786d94ad Mon Sep 17 00:00:00 2001 From: SimonCropp Date: Fri, 20 Mar 2020 23:25:32 +1100 Subject: [PATCH 02/21] fix assert order of expted V actual --- src/Umbraco.Tests/Cache/AppCacheTests.cs | 2 +- .../UmbracoSettings/ContentElementTests.cs | 44 +++++++++---------- .../CoreThings/XmlExtensionsTests.cs | 6 +-- .../Persistence/BulkDataReaderTests.cs | 4 +- .../AngularAntiForgeryTests.cs | 4 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Tests/Cache/AppCacheTests.cs b/src/Umbraco.Tests/Cache/AppCacheTests.cs index 3a86feb90a..f0ac9a2b0a 100644 --- a/src/Umbraco.Tests/Cache/AppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/AppCacheTests.cs @@ -96,7 +96,7 @@ namespace Umbraco.Tests.Cache return ""; }); - Assert.AreEqual(counter, 1); + Assert.AreEqual(1, counter); } diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs index 0245159c6e..33df3caaad 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public void EmailAddress() { - Assert.AreEqual(SettingsSection.Content.NotificationEmailAddress, "robot@umbraco.dk"); + Assert.AreEqual("robot@umbraco.dk", SettingsSection.Content.NotificationEmailAddress); } [Test] public virtual void DisableHtmlEmail() @@ -27,17 +27,17 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public virtual void Can_Set_Multiple() { - Assert.AreEqual(SettingsSection.Content.Error404Collection.Count(), 3); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).Culture, "default"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).ContentId, 1047); + Assert.AreEqual(3, SettingsSection.Content.Error404Collection.Count()); + Assert.AreEqual("default", SettingsSection.Content.Error404Collection.ElementAt(0).Culture); + Assert.AreEqual(1047, SettingsSection.Content.Error404Collection.ElementAt(0).ContentId); Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentId); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentKey); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).Culture, "en-US"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath, "$site/error [@name = 'error']"); + Assert.AreEqual("en-US", SettingsSection.Content.Error404Collection.ElementAt(1).Culture); + Assert.AreEqual("$site/error [@name = 'error']", SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentId); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentKey); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).Culture, "en-UK"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey, new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC")); + Assert.AreEqual("en-UK", SettingsSection.Content.Error404Collection.ElementAt(2).Culture); + Assert.AreEqual(new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC"), SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey); Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentKey); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentId); } @@ -47,23 +47,23 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { Assert.IsTrue(SettingsSection.Content.ImageFileTypes.All(x => "jpeg,jpg,gif,bmp,png,tiff,tif".Split(',').Contains(x))); } - + [Test] public virtual void ImageAutoFillProperties() { - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.Count(), 2); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias, "umbracoFile"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias, "umbracoWidth"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias, "umbracoHeight"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias, "umbracoBytes"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias, "umbracoExtension"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias, "umbracoFile2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias, "umbracoWidth2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias, "umbracoHeight2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias, "umbracoBytes2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias, "umbracoExtension2"); + Assert.AreEqual(2, SettingsSection.Content.ImageAutoFillProperties.Count()); + Assert.AreEqual("umbracoFile", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias); + Assert.AreEqual("umbracoWidth", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias); + Assert.AreEqual("umbracoHeight", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias); + Assert.AreEqual("umbracoBytes", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias); + Assert.AreEqual("umbracoExtension", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias); + Assert.AreEqual("umbracoFile2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias); + Assert.AreEqual("umbracoWidth2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias); + Assert.AreEqual("umbracoHeight2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias); + Assert.AreEqual("umbracoBytes2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias); + Assert.AreEqual("umbracoExtension2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias); } - + [Test] public void PreviewBadge() { @@ -116,7 +116,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings Debug.WriteLine("AllowedContainsExtension: {0}", allowedContainsExtension); Debug.WriteLine("DisallowedContainsExtension: {0}", disallowedContainsExtension); - Assert.AreEqual(SettingsSection.Content.IsFileAllowedForUpload(extension), expected); + Assert.AreEqual(expected, SettingsSection.Content.IsFileAllowedForUpload(extension)); } } } diff --git a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs index 42d8f92a5d..62e385beff 100644 --- a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); } [Test] @@ -27,7 +27,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); } [Test] @@ -41,7 +41,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); Assert.AreEqual(xml, xdoc.OuterXml); } } diff --git a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs index 3ada9fcceb..c157d66b2c 100644 --- a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs +++ b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs @@ -1693,7 +1693,7 @@ namespace Umbraco.Tests.Persistence { Assert.IsTrue(testReader.Read()); - Assert.AreEqual(testReader.Depth, 0); + Assert.AreEqual(0, testReader.Depth); } } @@ -2067,7 +2067,7 @@ namespace Umbraco.Tests.Persistence { Assert.IsTrue(testReader.Read()); - Assert.AreEqual(testReader.RecordsAffected, -1); + Assert.AreEqual(-1, testReader.RecordsAffected); } } diff --git a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs index 780a3e90fd..fd30f1b5ec 100644 --- a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs +++ b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Web.AngularIntegration string cookieToken, headerToken; AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); + Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); } } @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Web.AngularIntegration string cookieToken, headerToken; AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); + Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); } } From ff2027df0731ad3a3d1b56e0c0b897bf1b31bf01 Mon Sep 17 00:00:00 2001 From: Olivier Bossaer Date: Sat, 21 Mar 2020 16:49:14 +0100 Subject: [PATCH 03/21] Fixes #7798 : Invalid implementation of IEnumerable in Content Cache --- .../PublishedContent/NuCacheChildrenTests.cs | 15 ++++++++++++--- .../PublishedCache/NuCache/ContentCache.cs | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index cc9ffdba3c..4221968429 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Linq; -using System.Reflection; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -27,7 +25,6 @@ using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.PublishedCache.NuCache.DataSource; -using Umbraco.Web.PublishedCache.NuCache.Snap; namespace Umbraco.Tests.PublishedContent { @@ -1312,6 +1309,18 @@ namespace Umbraco.Tests.PublishedContent AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1); } + [Test] + public void MultipleCacheIteration() + { + //see https://github.com/umbraco/Umbraco-CMS/issues/7798 + this.Init(this.GetInvariantKits()); + var snapshot = this._snapshotService.CreatePublishedSnapshot(previewToken: null); + this._snapshotAccessor.PublishedSnapshot = snapshot; + + var items = snapshot.Content.GetByXPath("/root/itype"); + Assert.AreEqual(items.Count(), items.Count()); + } + private void AssertLinkedNode(ContentNode node, int parent, int prevSibling, int nextSibling, int firstChild, int lastChild) { Assert.AreEqual(parent, node.ParentContentId); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 84edb9113c..24c6a7018b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -355,6 +355,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private static IEnumerable GetByXPath(XPathNodeIterator iterator) { + iterator = iterator.Clone(); while (iterator.MoveNext()) { var xnav = iterator.Current as NavigableNavigator; From 3edfc42623cc283f75c3b46bad35f0cdfb54734d Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Mar 2020 17:42:10 +0000 Subject: [PATCH 04/21] Collapse navigation items based on container size - Previously a maximum of 8 items were shown. - This change calculates the width of each item and displays as many as possible in the container. - Any overflowing items will be moved to the ... menu --- .../application/umbsections.directive.js | 37 +++++++------------ .../components/application/umb-sections.html | 8 ++-- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index a33796ab6d..190da7da0d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -12,26 +12,25 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionItemsWidth = []; var evts = []; - var maxSections = 8; //setup scope vars - scope.maxSections = maxSections; - scope.overflowingSections = 0; scope.sections = []; + scope.visibleSections = 0; scope.currentSection = appState.getSectionState("currentSection"); - scope.showTray = false; //appState.getGlobalState("showTray"); + scope.showTray = false; scope.stickyNavigation = appState.getGlobalState("stickyNavigation"); - scope.needTray = false; function loadSections() { sectionService.getSectionsForUser() .then(function (result) { scope.sections = result; + scope.visibleSections = scope.sections.length; + // store the width of each section so we can hide/show them based on browser width // we store them because the sections get removed from the dom and then we // can't tell when to show them gain $timeout(function () { - $("#applications .sections li").each(function (index) { + $("#applications .sections li:not(:last)").each(function (index) { sectionItemsWidth.push($(this).outerWidth()); }); }); @@ -42,25 +41,22 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se function calculateWidth() { $timeout(function () { //total width minus room for avatar, search, and help icon - var windowWidth = $(window).width() - 150; + var containerWidth = $(".umb-app-header").outerWidth() - $(".umb-app-header__actions").outerWidth(); + var trayToggleWidth = $("#applications .sections li.expand").outerWidth(); var sectionsWidth = 0; - scope.totalSections = scope.sections.length; - scope.maxSections = maxSections; - scope.overflowingSections = scope.maxSections - scope.totalSections; - scope.needTray = scope.sections.length > scope.maxSections; - + // detect how many sections we can show on the screen for (var i = 0; i < sectionItemsWidth.length; i++) { var sectionItemWidth = sectionItemsWidth[i]; sectionsWidth += sectionItemWidth; - if (sectionsWidth > windowWidth) { - scope.needTray = true; - scope.maxSections = i - 1; - scope.overflowingSections = scope.maxSections - scope.totalSections; - break; + if (sectionsWidth + trayToggleWidth > containerWidth) { + scope.visibleSections = i; + return; } } + + scope.visibleSections = scope.sections.length; }); } @@ -134,14 +130,9 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se }; scope.currentSectionInOverflow = function () { - if (scope.overflowingSections === 0) { - return false; - } - var currentSection = scope.sections.filter(s => s.alias === scope.currentSection); - return (scope.sections.indexOf(currentSection[0]) >= scope.maxSections); - + return currentSection.length > 0 && scope.sections.indexOf(currentSection[0]) > scope.visibleSections - 1; }; loadSections(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html index 0dac7176ee..d435c6ead7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html @@ -2,7 +2,7 @@
- - - From b082d40921d3ccc939c28f1adda7973729b63b16 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Wed, 25 Mar 2020 02:48:14 +1000 Subject: [PATCH 10/21] =?UTF-8?q?adds=20utility.js=20as=20facade=20to=20ge?= =?UTF-8?q?neric=20javascript=20utility=20functio=E2=80=A6=20(#7738)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Umbraco.Web.UI.Client/.eslintrc | 4 +- src/Umbraco.Web.UI.Client/gulp/config.js | 4 +- .../common/services/angularhelper.service.js | 10 +-- .../src/main.controller.js | 11 ++- src/Umbraco.Web.UI.Client/src/utilities.js | 86 +++++++++++++++++++ .../views/documenttypes/edit.controller.js | 2 +- .../packages/views/created.controller.js | 2 +- .../partialviewmacros/edit.controller.js | 2 +- .../src/views/users/user.controller.js | 2 +- .../users/views/groups/groups.controller.js | 2 +- .../users/views/users/users.controller.js | 2 +- 11 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/utilities.js diff --git a/src/Umbraco.Web.UI.Client/.eslintrc b/src/Umbraco.Web.UI.Client/.eslintrc index 1bfa081b9a..a4010917fb 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc +++ b/src/Umbraco.Web.UI.Client/.eslintrc @@ -19,10 +19,10 @@ "tinyMCE": false, "FileReader": false, "Umbraco": false, + "Utilities": false, "window": false, "LazyLoad": false, "ActiveXObject": false, "Bloodhound": false } - -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 59e8bf6c05..39dc9bb2a4 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -28,9 +28,9 @@ module.exports = { installer: { files: "./src/installer/**/*.js", out: "umbraco.installer.js" }, filters: { files: "./src/common/filters/**/*.js", out: "umbraco.filters.js" }, resources: { files: "./src/common/resources/**/*.js", out: "umbraco.resources.js" }, - services: { files: "./src/common/services/**/*.js", out: "umbraco.services.js" }, + services: { files: ["./src/common/services/**/*.js", "./src/utilities.js"], out: "umbraco.services.js" }, security: { files: "./src/common/interceptors/**/*.js", out: "umbraco.interceptors.js" }, - + //the controllers for views controllers: { files: [ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js index 455857c1e1..8cba83328f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js @@ -178,11 +178,11 @@ function angularHelper($q) { $valid: true, $submitted: false, $pending: undefined, - $addControl: angular.noop, - $removeControl: angular.noop, - $setValidity: angular.noop, - $setDirty: angular.noop, - $setPristine: angular.noop, + $addControl: Utilities.noop, + $removeControl: Utilities.noop, + $setValidity: Utilities.noop, + $setDirty: Utilities.noop, + $setPristine: Utilities.noop, $name: formName }; } diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 883907d1dc..198ac132c1 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -1,19 +1,18 @@ - -/** +/** * @ngdoc controller - * @name Umbraco.MainController + * @name Umbraco.MainController * @function * - * @description + * @description * The main application controller * */ function MainController($scope, $location, appState, treeService, notificationsService, userService, historyService, updateChecker, navigationService, eventsService, tmhDynamicLocale, localStorageService, editorService, overlayService, assetsService, tinyMceAssets) { - + //the null is important because we do an explicit bool check on this in the view - $scope.authenticated = null; + $scope.authenticated = null; $scope.touchDevice = appState.getGlobalState("touchDevice"); $scope.infiniteMode = false; $scope.overlay = {}; diff --git a/src/Umbraco.Web.UI.Client/src/utilities.js b/src/Umbraco.Web.UI.Client/src/utilities.js new file mode 100644 index 0000000000..bd12506358 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/utilities.js @@ -0,0 +1,86 @@ +/** + * A friendly utility collection to replace AngularJs' ng-functions + * If it doesn't exist here, it's probably available as vanilla JS + * + * Still carries a dependency on underscore, but if usages of underscore from + * elsewhere in the codebase can instead use these methods, the underscore + * dependency will be nicely abstracted and can be removed/swapped later + * + * This collection is open to extension... + */ +(function (window) { + + /** + * Equivalent to angular.noop + */ + const noop = () => { }; + + /** + * Facade to angular.copy + */ + const copy = val => angular.copy(val); + + /** + * Equivalent to angular.isArray + */ + const isArray = val => Array.isArray(val) || val instanceof Array; + + /** + * Facade to angular.equals + */ + const equals = (a, b) => angular.equals(a, b); + + /** + * Facade to angular.extend + * Use this with Angular objects, for vanilla JS objects, use Object.assign() + */ + const extend = (dst, src) => angular.extend(dst, src); + + /** + * Equivalent to angular.isFunction + */ + const isFunction = val => typeof val === 'function'; + + /** + * Equivalent to angular.isUndefined + */ + const isUndefined = val => typeof val === 'undefined'; + + /** + * Equivalent to angular.isDefined. Inverts result of const isUndefined + */ + const isDefined = val => !isUndefined(val); + + /** + * Equivalent to angular.isString + */ + const isString = val => typeof val === 'string'; + + /** + * Equivalent to angular.isNumber + */ + const isNumber = val => typeof val === 'number'; + + /** + * Equivalent to angular.isObject + */ + const isObject = val => val !== null && typeof val === 'object'; + + let _utilities = { + noop: noop, + copy: copy, + isArray: isArray, + equals: equals, + extend: extend, + isFunction: isFunction, + isUndefined: isUndefined, + isDefined: isDefined, + isString: isString, + isNumber: isNumber, + isObject: isObject + }; + + if (typeof (window.Utilities) === 'undefined') { + window.Utilities = _utilities; + } +})(window); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index b04cccbc0d..21eb77b1e9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -325,7 +325,7 @@ /* ---------- SAVE ---------- */ function save() { - saveInternal().then(angular.noop, angular.noop); + saveInternal().then(Utilities.noop, Utilities.noop); } /** This internal save method performs the actual saving and returns a promise, not to be bound to any buttons but used by other bound methods */ diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js b/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js index 295057e609..503d85fe8d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js @@ -15,7 +15,7 @@ packageResource.getAllCreated().then(createdPackages => { vm.createdPackages = createdPackages; - }, angular.noop); + }, Utilities.noop); } diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js index aa37a91919..2f7d879607 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js @@ -80,7 +80,7 @@ activate: false }); completeSave(saved); - }, angular.noop); + }, Utilities.noop); } else { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 9534eea9fd..56d01a8604 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -168,7 +168,7 @@ extendedSave(saved).then(function(result) { //if all is good, then reset the form formHelper.resetForm({ scope: $scope }); - }, angular.noop); + }, Utilities.noop); vm.user = _.omit(saved, "navigation"); //restore diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js index 984866dca1..31c52a344b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js @@ -112,7 +112,7 @@ userGroupsResource.deleteUserGroups(_.pluck(vm.selection, "id")).then(function (data) { clearSelection(); onInit(); - }, angular.noop); + }, Utilities.noop); overlayService.close(); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index f3c28fdb9d..4ac385921e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -386,7 +386,7 @@ vm.selectedBulkUserGroups = []; editorService.close(); clearSelection(); - }, angular.noop); + }, Utilities.noop); }, close: function () { vm.selectedBulkUserGroups = []; From 994c379a946fa7e36eed669e429ab26fa977b00c Mon Sep 17 00:00:00 2001 From: Matthew Wise <6782865+Matthew-Wise@users.noreply.github.com> Date: Wed, 4 Mar 2020 10:36:49 +0000 Subject: [PATCH 11/21] Removed duplications of tree routing --- .../src/common/services/navigation.service.js | 49 +++++++++++-------- src/Umbraco.Web.UI.Client/src/routes.js | 44 ++--------------- 2 files changed, 33 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 8d1caab850..ab9cfb63d2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -578,30 +578,12 @@ function navigationService($routeParams, $location, $q, $injector, eventsService if (args.action.metaData["actionView"]) { templateUrl = args.action.metaData["actionView"]; } - else { - - //by convention we will look into the /views/{treetype}/{action}.html - // for example: /views/content/create.html - - //we will also check for a 'packageName' for the current tree, if it exists then the convention will be: - // for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html - + else { var treeAlias = treeService.getTreeAlias(args.node); - var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); - if (!treeAlias) { throw "Could not get tree alias for node " + args.node.id; - } - - if (packageTreeFolder) { - templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + treeAlias + "/" + args.action.alias + ".html"; - } - else { - templateUrl = "views/" + treeAlias + "/" + args.action.alias + ".html"; - } - + } + templateUrl = this.getTreeTemplateUrl(treeAlias, args.action.alias); } setMode("dialog"); @@ -611,6 +593,31 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } }, + /** + * @ngdoc method + * @name umbraco.services.navigationService#getTreeTemplateUrl + * @methodOf umbraco.services.navigationService + * + * @param {string} treeAlias the alias of the tree to look up + * @param {string} action the view file name + * @description + * creates the templateUrl based on treeAlias and action + * by convention we will look into the /views/{treetype}/{action}.html + * for example: /views/content/create.html + * we will also check for a 'packageName' for the current tree, if it exists then the convention will be: + * for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html + */ + getTreeTemplateUrl: function(treeAlias, action) { + var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); + if (packageTreeFolder) { + return Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + + "/" + packageTreeFolder + + "/backoffice/" + treeAlias + "/" + action + ".html"; + } + else { + return "views/" + treeAlias + "/" + action + ".html"; + } + }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 556a4d6aef..93122792d3 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -154,7 +154,7 @@ app.config(function ($routeProvider) { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
", //This controller will execute for this route, then we replace the template dynamically based on the current tree. - controller: function ($scope, $routeParams, treeService) { + controller: function ($scope, $routeParams, navigationService) { if (!$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; @@ -176,24 +176,7 @@ app.config(function ($routeProvider) { $scope.templateUrl = "views/users/overview.html"; return; } - - // Here we need to figure out if this route is for a user's package tree and if so then we need - // to change it's convention view path to: - // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html - - // otherwise if it is a core tree we use the core paths: - // views/{treetype}/{method}.html - - var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); - - if (packageTreeFolder) { - $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); - } - else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); - } + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, resolve: canRoute(true) @@ -202,30 +185,13 @@ app.config(function ($routeProvider) { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
", //This controller will execute for this route, then we replace the template dynamically based on the current tree. - controller: function ($scope, $route, $routeParams, treeService) { + controller: function ($scope, $routeParams, navigationService) { if (!$routeParams.tree || !$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; + return; } - - // Here we need to figure out if this route is for a package tree and if so then we need - // to change it's convention view path to: - // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html - - // otherwise if it is a core tree we use the core paths: - // views/{treetype}/{method}.html - - var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); - - if (packageTreeFolder) { - $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); - } - else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); - } - + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, reloadOnUrl: false, From 7c841eb6ac627ca4c632ce365b76af36dce98eab Mon Sep 17 00:00:00 2001 From: Matt Barlow Date: Thu, 27 Feb 2020 12:59:34 +0100 Subject: [PATCH 12/21] 7728 - show elipsis only when > length Fixed the elipsis issue, only adds an elipisis if the text length exceeds the length that is being truncated too. --- src/Umbraco.Web/HtmlStringUtilities.cs | 28 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs index 4606a58a3a..a8cbb70019 100644 --- a/src/Umbraco.Web/HtmlStringUtilities.cs +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -243,21 +243,27 @@ namespace Umbraco.Web } } - if (!lengthReached && currentTextLength >= length) + if (!lengthReached) { - // if the last character added was the first of a two character unicode pair, add the second character - if (Char.IsHighSurrogate((char)ic)) + if (currentTextLength == length) { - var lowSurrogate = tr.Read(); - outputtw.Write((char)lowSurrogate); - } + // if the last character added was the first of a two character unicode pair, add the second character + if (char.IsHighSurrogate((char)ic)) + { + var lowSurrogate = tr.Read(); + outputtw.Write((char)lowSurrogate); + } - // Reached truncate limit. - if (addElipsis) - { - outputtw.Write(hellip); } - lengthReached = true; + // only add elipsis if current length greater than original length + if (currentTextLength > length) + { + if (addElipsis) + { + outputtw.Write(hellip); + } + lengthReached = true; + } } } From ffef6ed2a1e9dbad6f8237668ea7fd8e4c373672 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Wed, 25 Mar 2020 03:04:38 +1000 Subject: [PATCH 13/21] 7713 - changing CSS directory breaks RTE (#7726) --- .../Implement/StylesheetRepository.cs | 11 ++++++++++- .../Repositories/StylesheetRepositoryTest.cs | 16 +++++++--------- .../rte/rte.prevalues.controller.js | 10 ++++++++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs index 4c02a8f4b5..698a9f4364 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs @@ -25,8 +25,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement path = path.EnsureEndsWith(".css"); - if (FileSystem.FileExists(path) == false) + // if the css directory is changed, references to the old path can still exist (ie in RTE config) + // these old references will throw an error, which breaks the RTE + // try-catch here makes the request fail silently, and allows RTE to load correctly + try + { + if (FileSystem.FileExists(path) == false) + return null; + } catch + { return null; + } // content will be lazy-loaded when required var created = FileSystem.GetCreated(path).UtcDateTime; diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index f427f22796..304422d1e2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -294,15 +294,13 @@ namespace Umbraco.Tests.Persistence.Repositories stylesheet = repository.Get("missing.css"); Assert.IsNull(stylesheet); - // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => - { - stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist - }); - Assert.Throws(() => - { - stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists - }); + // #7713 changes behaviour to return null when outside the filesystem + // to accomodate changing the CSS path and not flooding the backoffice with errors + stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist + Assert.IsNull(stylesheet); + + stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists + Assert.IsNull(stylesheet); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js index 41097f9e9a..47b0215dac 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js @@ -39,8 +39,14 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", stylesheetResource.getAll().then(function(stylesheets){ $scope.stylesheets = stylesheets; - - _.each($scope.stylesheets, function (stylesheet) { + + // if the CSS directory changes, previously assigned stylesheets are retained, but will not be visible + // and will throw a 404 when loading the RTE. Remove them here. Still needs to be saved... + let cssPath = Umbraco.Sys.ServerVariables.umbracoSettings.cssPath; + $scope.model.value.stylesheets = $scope.model.value.stylesheets + .filter(sheet => sheet.startsWith(cssPath)); + + $scope.stylesheets.forEach(stylesheet => { // support both current format (full stylesheet path) and legacy format (stylesheet name only) stylesheet.selected = $scope.model.value.stylesheets.indexOf(stylesheet.path) >= 0 ||$scope.model.value.stylesheets.indexOf(stylesheet.name) >= 0; }); From 2ec08610518ea2cdc9a03d8c84d95530eba0365c Mon Sep 17 00:00:00 2001 From: Joe Delly Date: Tue, 24 Mar 2020 13:35:11 -0400 Subject: [PATCH 14/21] Fix for exception throw when validating URLs (#7701) --- src/Umbraco.Core/StringExtensions.cs | 15 +++++++++++++++ .../Strings/StringExtensionsTests.cs | 18 ++++++++++++++++++ .../PropertyEditors/UploadFileTypeValidator.cs | 2 +- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 4451fdbba7..c37f8bdf35 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -79,6 +79,21 @@ namespace Umbraco.Core } + /// + /// Determines the extension of the path or URL + /// + /// + /// Extension of the file + public static string GetFileExtension(this string file) + { + //Find any characters between the last . and the start of a query string or the end of the string + const string pattern = @"(?\.[^\.\?]+)(\?.*|$)"; + var match = Regex.Match(file, pattern); + return match.Success + ? match.Groups["extension"].Value + : string.Empty; + } + ///
/// Based on the input string, this will detect if the string is a JS path or a JS snippet. /// If a path cannot be determined, then it is assumed to be a snippet the original text is returned diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 4c365d733f..39ffeed205 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -70,6 +70,24 @@ namespace Umbraco.Tests.Strings Assert.AreEqual(stripped, result); } + [TestCase("../wtf.js?x=wtf", ".js")] + [TestCase(".htaccess", ".htaccess")] + [TestCase("path/to/file/image.png", ".png")] + [TestCase("c:\\abc\\def\\ghi.jkl", ".jkl")] + [TestCase("/root/folder.name/file.ext", ".ext")] + [TestCase("http://www.domain.com/folder/name/file.txt", ".txt")] + [TestCase("i/don't\\have\\an/extension", "")] + [TestCase("https://some.where/path/to/file.ext?query=this&more=that", ".ext")] + [TestCase("double_query.string/file.ext?query=abc?something.else", ".ext")] + [TestCase("test.tar.gz", ".gz")] + [TestCase("wierd.file,but._legal", "._legal")] + [TestCase("one_char.x", ".x")] + public void Get_File_Extension(string input, string result) + { + var extension = input.GetFileExtension(); + Assert.AreEqual(result, extension); + } + [TestCase("'+alert(1234)+'", "+alert1234+")] [TestCase("'+alert(56+78)+'", "+alert56+78+")] [TestCase("{{file}}", "file")] diff --git a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs index 6855ab3bb8..8d7bd29889 100644 --- a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.PropertyEditors internal static bool IsValidFileExtension(string fileName) { if (fileName.IndexOf('.') <= 0) return false; - var extension = new FileInfo(fileName).Extension.TrimStart("."); + var extension = fileName.GetFileExtension().TrimStart("."); return Current.Configs.Settings().Content.IsFileAllowedForUpload(extension); } } From 17d91da8d1444c8f2ce4c48380ead0c89bf9e65a Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Wed, 25 Mar 2020 16:29:42 +0000 Subject: [PATCH 15/21] Remove button border radius when used in a button group (#7834) --- .../src/less/components/buttons/umb-button-group.less | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less index 0465881387..02b67460f6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less @@ -17,15 +17,17 @@ .umb-button-group { .umb-button__button { - border-radius: @baseBorderRadius; + border-radius: @baseBorderRadius 0 0 @baseBorderRadius; + + &:hover { + z-index: 2; + } } .umb-button-group__toggle { border-radius: 0 @baseBorderRadius @baseBorderRadius 0; - border-left: 1px solid rgba(0,0,0,0.09); - margin-left: -2px; + margin-left: -1px; padding-left: 10px; padding-right: 10px; } - } From ec64da080460d85176960289dd7c57d75511a570 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Tue, 11 Feb 2020 16:39:18 +0000 Subject: [PATCH 16/21] Grant access to Packages section for any user groups with access to the Developer section --- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../V_8_0_0/AddPackagesSectionAccess.cs | 25 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 3 files changed, 27 insertions(+) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 7143be2b90..f0cfc08281 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -166,6 +166,7 @@ namespace Umbraco.Core.Migrations.Upgrade .As("{0576E786-5C30-4000-B969-302B61E90CA3}"); To("{48AD6CCD-C7A4-4305-A8AB-38728AD23FC5}"); + To("{DF470D86-E5CA-42AC-9780-9D28070E25F9}"); // finish migrating from v7 - recreate all keys and indexes To("{3F9764F5-73D0-4D45-8804-1240A66E43A2}"); diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs new file mode 100644 index 0000000000..d44e637a2c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class AddPackagesSectionAccess : MigrationBase + { + public AddPackagesSectionAccess(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // Any user group which had access to the Developer section should have access to Packages + Database.Execute($@" + insert into {Constants.DatabaseSchema.Tables.UserGroup2App} + select userGroupId, '{Constants.Applications.Packages}' + from {Constants.DatabaseSchema.Tables.UserGroup2App} + where app='developer'"); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 30247bed8a..24954e316c 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -256,6 +256,7 @@ + From 935690b727c395d7a451d6f23890c45d14c6ecbd Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 25 Mar 2020 18:09:03 +0100 Subject: [PATCH 17/21] Bump `caniuse-lite` dependency --- src/Umbraco.Web.UI.Client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index c150af79de..438052367e 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -50,7 +50,7 @@ "@babel/core": "7.6.4", "@babel/preset-env": "7.6.3", "autoprefixer": "9.6.5", - "caniuse-lite": "^1.0.30001002", + "caniuse-lite": "^1.0.30001037", "cssnano": "4.1.10", "fs": "0.0.2", "gulp": "4.0.2", From 972acc6718ce418e648ed4ac9ef2527f47c1aa33 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 26 Mar 2020 08:39:38 +0100 Subject: [PATCH 18/21] Installer adjustments (#7592) --- .../src/installer/installer.service.js | 25 +++++++++---------- .../installer/steps/database.controller.js | 17 +++++++------ .../installer/steps/machinekey.controller.js | 3 +-- .../src/installer/steps/starterkit.html | 7 +++--- .../src/installer/steps/user.controller.js | 12 ++++----- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index f6f162f04f..05c391c3e7 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -2,17 +2,16 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco var _status = { index: 0, - current: undefined, - steps: undefined, + current: null, + steps: null, loading: true, progress: "100%" }; - var factTimer = undefined; + var factTimer; var _installerModel = { - installId: undefined, - instructions: { - } + installId: null, + instructions: {} }; //add to umbraco installer facts here @@ -304,7 +303,7 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco }, switchToFeedback : function(){ - service.status.current = undefined; + service.status.current = null; service.status.loading = true; service.status.configuring = false; @@ -320,11 +319,11 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco switchToConfiguration : function(){ service.status.loading = false; service.status.configuring = true; - service.status.feedback = undefined; - service.status.fact = undefined; + service.status.feedback = null; + service.status.fact = null; - if(factTimer){ - clearInterval(factTimer); + if (factTimer) { + clearInterval(factTimer); } }, @@ -335,8 +334,8 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco service.status.feedback = "Redirecting you to Umbraco, please wait"; service.status.loading = false; - if(factTimer){ - clearInterval(factTimer); + if (factTimer) { + clearInterval(factTimer); } $timeout(function(){ diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js index e83c5114fc..687fce95ae 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -4,18 +4,19 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $scope.invalidDbDns = false; $scope.dbs = [ - { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0}, - { name: 'Microsoft SQL Server', id: 1}, + { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0 }, + { name: 'Microsoft SQL Server', id: 1 }, { name: 'Microsoft SQL Azure', id: 3 }, - { name: 'Custom connection string', id: -1} + { name: 'Custom connection string', id: -1 } ]; - if ( installerService.status.current.model.dbType === undefined ) { + if (angular.isUndefined(installerService.status.current.model.dbType) || installerService.status.current.model.dbType === null) { installerService.status.current.model.dbType = 0; } - $scope.validateAndForward = function(){ - if ( !$scope.checking && this.myForm.$valid ) { + $scope.validateAndForward = function() { + if (!$scope.checking && this.myForm.$valid) + { $scope.checking = true; $scope.invalidDbDns = false; @@ -23,9 +24,9 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $http.post( Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection", - model ).then( function( response ) { + model).then(function(response) { - if ( response.data === true ) { + if (response.data === true) { installerService.forward(); } else { diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js index bdcef63d95..fdd2c65c1c 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js @@ -1,5 +1,4 @@ angular.module("umbraco.install").controller("Umbraco.Installer.MachineKeyController", function ($scope, installerService) { - $scope.continue = function () { installerService.status.current.model = true; @@ -11,4 +10,4 @@ installerService.forward(); }; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html index cfb08744b2..92f5cc8d9d 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html @@ -20,8 +20,9 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js index 10c0d596eb..9f0108853f 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js @@ -13,14 +13,14 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f $scope.passwordPattern = new RegExp(exp); } - $scope.validateAndInstall = function(){ - installerService.install(); + $scope.validateAndInstall = function() { + installerService.install(); }; $scope.validateAndForward = function(){ - if(this.myForm.$valid){ - installerService.forward(); - } + if (this.myForm.$valid) { + installerService.forward(); + } }; -}); \ No newline at end of file +}); From a0e5b839141dd3d63a063656ac5f3ebc0f4aca92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gregersen?= Date: Wed, 25 Mar 2020 19:23:29 +0100 Subject: [PATCH 19/21] Add document to downloadFile-method Simple copy/paste to document a useful method --- .../common/services/umbrequesthelper.service.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 6c765ee135..0d766dc7d8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -365,12 +365,23 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe }, /** + * @ngdoc method + * @name umbraco.resources.contentResource#downloadFile + * @methodOf umbraco.resources.contentResource + * + * @description * Downloads a file to the client using AJAX/XHR - * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html - * See https://stackoverflow.com/a/24129082/694494 + * + * @param {string} httpPath the path (url) to the resource being downloaded + * @returns {Promise} http promise object. */ downloadFile : function (httpPath) { + /** + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + // Use an arraybuffer return $http.get(httpPath, { responseType: 'arraybuffer' }) .then(function (response) { From 42ddd90fd71883a3ae6c01bf2c6bbda17b610f25 Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Thu, 26 Mar 2020 15:57:15 +0000 Subject: [PATCH 20/21] Allow Macro's to be cached differently across variants (#7555) --- src/Umbraco.Web/Macros/MacroRenderer.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 8d7d3bc013..b4fd8c0d86 100755 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -40,12 +40,18 @@ namespace Umbraco.Web.Macros #region MacroContent cache // gets this macro content cache identifier - private string GetContentCacheIdentifier(MacroModel model, int pageId) + private string GetContentCacheIdentifier(MacroModel model, int pageId, string cultureName) { var id = new StringBuilder(); var alias = model.Alias; id.AppendFormat("{0}-", alias); + //always add current culture to the key to allow variants to have different cache results + if (!string.IsNullOrEmpty(cultureName)) + { + // are there any unusual culture formats we'd need to handle? + id.AppendFormat("{0}-", cultureName); + } if (model.CacheByPage) id.AppendFormat("{0}-", pageId); @@ -190,7 +196,7 @@ namespace Umbraco.Web.Macros ? macroParams[key]?.ToString() ?? string.Empty : string.Empty; } - } + } #endregion #region Render/Execute @@ -221,7 +227,8 @@ namespace Umbraco.Web.Macros foreach (var prop in macro.Properties) prop.Value = ParseAttribute(pageElements, prop.Value); - macro.CacheIdentifier = GetContentCacheIdentifier(macro, content.Id); + var cultureName = System.Threading.Thread.CurrentThread.CurrentUICulture.Name; + macro.CacheIdentifier = GetContentCacheIdentifier(macro, content.Id, cultureName); // get the macro from cache if it is there var macroContent = GetMacroContentFromCache(macro); @@ -317,7 +324,7 @@ namespace Umbraco.Web.Macros private Attempt ExecuteMacroOfType(MacroModel model, IPublishedContent content) { if (model == null) throw new ArgumentNullException(nameof(model)); - + // ensure that we are running against a published node (ie available in XML) // that may not be the case if the macro is embedded in a RTE of an unpublished document @@ -453,9 +460,9 @@ namespace Umbraco.Web.Macros return value; } - + #endregion - + } } From 5fad35d0eedbfae2a09fc29045f88ea321dc787a Mon Sep 17 00:00:00 2001 From: Olivier Bossaer Date: Sat, 28 Mar 2020 14:37:31 +0100 Subject: [PATCH 21/21] Fix #7521 - Runtime error if removing email notificationMethod --- .../HealthCheck/NotificationMethods/EmailNotificationMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs index 87c0e4f46d..873b356214 100644 --- a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods public EmailNotificationMethod(ILocalizedTextService textService, IRuntimeState runtimeState, ILogger logger) { - var recipientEmail = Settings["recipientEmail"]?.Value; + var recipientEmail = Settings?["recipientEmail"]?.Value; if (string.IsNullOrWhiteSpace(recipientEmail)) { Enabled = false;