From 5148f34b3296b694b84e666ac778e655b5d69d67 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Dec 2018 15:42:32 +1100 Subject: [PATCH 01/44] Fixes ISearchableTree lifetime registration, fixes routing that clears required query strings, fixes lucene search string, gets URls in search results working with default lang/variants --- src/Umbraco.Core/Constants-Examine.cs | 24 ---------- src/Umbraco.Core/Umbraco.Core.csproj | 1 - .../PublishedMediaCacheTests.cs | 2 - src/Umbraco.Web.UI.Client/src/init.js | 46 +++++++++++++------ .../DictionaryPublishedContent.cs | 2 +- .../XmlPublishedCache/PublishedMediaCache.cs | 4 +- src/Umbraco.Web/PublishedContentExtensions.cs | 8 ++-- src/Umbraco.Web/PublishedContentQuery.cs | 6 +-- .../Search/SearchableTreeCollectionBuilder.cs | 3 ++ src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 42 +++++++++++------ .../Trees/ContentTreeController.cs | 9 ++-- 11 files changed, 78 insertions(+), 69 deletions(-) delete mode 100644 src/Umbraco.Core/Constants-Examine.cs diff --git a/src/Umbraco.Core/Constants-Examine.cs b/src/Umbraco.Core/Constants-Examine.cs deleted file mode 100644 index ddc3500066..0000000000 --- a/src/Umbraco.Core/Constants-Examine.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Umbraco.Core -{ - public static partial class Constants - { - public static class Examine - { - /// - /// The alias of the internal member indexer - /// - public const string InternalMemberIndexer = "InternalMemberIndexer"; - - /// - /// The alias of the internal content indexer - /// - public const string InternalIndexer = "InternalIndexer"; - - /// - /// The alias of the external content indexer - /// - public const string ExternalIndexer = "ExternalIndexer"; - - } - } -} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index a8361a237a..a015d8d4f8 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -304,7 +304,6 @@ - diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index 64194ebb47..aeda2eaca2 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -111,7 +111,6 @@ namespace Umbraco.Tests.Cache.PublishedCache } [TestCase("id")] - [TestCase("nodeId")] [TestCase("__NodeId")] public void DictionaryDocument_Id_Keys(string key) { @@ -128,7 +127,6 @@ namespace Umbraco.Tests.Cache.PublishedCache } [TestCase("nodeName")] - [TestCase("__nodeName")] public void DictionaryDocument_NodeName_Keys(string key) { var dicDoc = GetDictionaryDocument(nodeNameKey: key); diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 3f44638096..eaa2fe7b31 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -47,7 +47,17 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH /** execute code on each successful route */ $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { - currentRouteParams = angular.copy(current.params); //store this so we can reference it in $routeUpdate + var toRetain = currentRouteParams ? navigationService.retainQueryStrings(currentRouteParams, current.params) : null; + + //if toRetain is not null it means that there are missing query strings and we need to update the current params + if (toRetain) { + $route.updateParams(toRetain); + currentRouteParams = toRetain; + } + else { + currentRouteParams = angular.copy(current.params); + } + var deployConfig = Umbraco.Sys.ServerVariables.deploy; var deployEnv, deployEnvTitle; @@ -122,25 +132,33 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH $route.reload(); } else { - + + var toRetain = navigationService.retainQueryStrings(currentRouteParams, next.params); + + //if toRetain is not null it means that there are missing query strings and we need to update the current params + if (toRetain) { + $route.updateParams(toRetain); + } + //check if the location being changed is only due to global/state query strings which means the location change //isn't actually going to cause a route change. - if (navigationService.isRouteChangingNavigation(currentRouteParams, next.params)) { - //The location change will cause a route change. We need to ensure that the global/state - //query strings have not been stripped out. If they have, we'll re-add them and re-route. + if (!toRetain && navigationService.isRouteChangingNavigation(currentRouteParams, next.params)) { + + //The location change will cause a route change, continue the route if the query strings haven't been updated. + $route.reload(); - var toRetain = navigationService.retainQueryStrings(currentRouteParams, next.params); - if (toRetain) { - $route.updateParams(toRetain); - } - else { - //continue the route - $route.reload(); - } } else { + //navigation is not changing but we should update the currentRouteParams to include all current parameters - currentRouteParams = angular.copy(next.params); + + if (toRetain) { + currentRouteParams = toRetain; + } + else { + currentRouteParams = angular.copy(next.params); + } + } } }); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs index 7a8ce65ae3..c9d3c79ff5 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs @@ -55,7 +55,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key"); //ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); ValidateAndSetProperty(valueDictionary, val => _sortOrder = Int32.Parse(val), "sortOrder"); - ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName", "__nodeName"); + ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName"); ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName"); ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", LuceneIndex.ItemTypeFieldName); ValidateAndSetProperty(valueDictionary, val => _documentTypeId = Int32.Parse(val), "nodeType"); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index ac6b425e27..f203d5d2c9 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -240,9 +240,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache try { - if (eMgr.TryGetIndex(Constants.Examine.InternalIndexer, out var index)) + if (eMgr.TryGetIndex(Constants.UmbracoIndexes.InternalIndexName, out var index)) return index.GetSearcher(); - throw new InvalidOperationException($"No index found by name {Constants.Examine.InternalIndexer}"); + throw new InvalidOperationException($"No index found by name {Constants.UmbracoIndexes.InternalIndexName}"); } catch (FileNotFoundException) { diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 2bc0d7be3f..e3291f9ad5 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -286,7 +286,7 @@ namespace Umbraco.Web { //fixme: pass in the IExamineManager - indexName = string.IsNullOrEmpty(indexName) ? Constants.Examine.ExternalIndexer : indexName; + indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; if (!ExamineManager.Instance.TryGetIndex(indexName, out var index)) throw new InvalidOperationException("No index found with name " + indexName); @@ -306,7 +306,7 @@ namespace Umbraco.Web { //fixme: pass in the IExamineManager - indexName = string.IsNullOrEmpty(indexName) ? Constants.Examine.ExternalIndexer : indexName; + indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; if (!ExamineManager.Instance.TryGetIndex(indexName, out var index)) throw new InvalidOperationException("No index found with name " + indexName); @@ -328,8 +328,8 @@ namespace Umbraco.Web if (searchProvider == null) { - if (!ExamineManager.Instance.TryGetIndex(Constants.Examine.ExternalIndexer, out var index)) - throw new InvalidOperationException("No index found with name " + Constants.Examine.ExternalIndexer); + if (!ExamineManager.Instance.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index)) + throw new InvalidOperationException("No index found with name " + Constants.UmbracoIndexes.ExternalIndexName); searchProvider = index.GetSearcher(); } var results = searchProvider.Search(criteria); diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index c2fb84a3da..67b54330c5 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -234,7 +234,7 @@ namespace Umbraco.Web if (_query != null) return _query.Search(skip, take, out totalRecords, term, useWildCards, indexName); indexName = string.IsNullOrEmpty(indexName) - ? Constants.Examine.ExternalIndexer + ? Constants.UmbracoIndexes.ExternalIndexName : indexName; if (!ExamineManager.Instance.TryGetIndex(indexName, out var index)) @@ -264,8 +264,8 @@ namespace Umbraco.Web //fixme: inject IExamineManager if (searcher == null) { - if (!ExamineManager.Instance.TryGetIndex(Constants.Examine.ExternalIndexer, out var index)) - throw new InvalidOperationException($"No index found by name {Constants.Examine.ExternalIndexer}"); + if (!ExamineManager.Instance.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index)) + throw new InvalidOperationException($"No index found by name {Constants.UmbracoIndexes.ExternalIndexName}"); searcher = index.GetSearcher(); } diff --git a/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs b/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs index ae83cc5eab..22db27b1fb 100644 --- a/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs +++ b/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs @@ -21,5 +21,8 @@ namespace Umbraco.Web.Search { return new SearchableTreeCollection(CreateItems(), _treeService); } + + //per request because generally an instance of ISearchableTree is a controller + protected override ILifetime CollectionLifetime => new PerRequestLifeTime(); } } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 9aab30edae..af2db4f7ac 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -10,6 +10,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Examine; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; using SearchResult = Examine.SearchResult; @@ -23,11 +24,13 @@ namespace Umbraco.Web.Search { private readonly IExamineManager _examineManager; private readonly UmbracoHelper _umbracoHelper; + private readonly ILocalizationService _languageService; - public UmbracoTreeSearcher(IExamineManager examineManager, UmbracoHelper umbracoHelper) + public UmbracoTreeSearcher(IExamineManager examineManager, UmbracoHelper umbracoHelper, ILocalizationService languageService) { _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); _umbracoHelper = umbracoHelper ?? throw new ArgumentNullException(nameof(umbracoHelper)); + _languageService = languageService; } /// @@ -51,7 +54,7 @@ namespace Umbraco.Web.Search var sb = new StringBuilder(); string type; - var indexName = Constants.Examine.InternalIndexer; + var indexName = Constants.UmbracoIndexes.InternalIndexName; var fields = new[] { "id", "__NodeId" }; var umbracoContext = _umbracoHelper.UmbracoContext; @@ -60,7 +63,7 @@ namespace Umbraco.Web.Search switch (entityType) { case UmbracoEntityTypes.Member: - indexName = Constants.Examine.InternalMemberIndexer; + indexName = Constants.UmbracoIndexes.MembersIndexName; type = "member"; fields = new[] { "id", "__NodeId", "email", "loginName" }; if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1") @@ -90,8 +93,8 @@ namespace Umbraco.Web.Search var internalSearcher = index.GetSearcher(); //build a lucene query: - // the __nodeName will be boosted 10x without wildcards - // then __nodeName will be matched normally with wildcards + // the nodeName will be boosted 10x without wildcards + // then nodeName will be matched normally with wildcards // the rest will be normal without wildcards @@ -120,7 +123,7 @@ namespace Umbraco.Web.Search query = string.Format("{0}{1}{0}", "\"", query); //node name exactly boost x 10 - sb.Append("+(__nodeName: ("); + sb.Append("+(nodeName: ("); sb.Append(query.ToLower()); sb.Append(")^10.0 "); @@ -155,14 +158,14 @@ namespace Umbraco.Web.Search var querywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); //node name exactly boost x 10 - sb.Append("+(__nodeName:"); + sb.Append("+(nodeName:"); sb.Append("\""); sb.Append(query.ToLower()); sb.Append("\""); sb.Append("^10.0 "); //node name normally with wildcards - sb.Append(" __nodeName:"); + sb.Append(" nodeName:"); sb.Append("("); foreach (var w in querywords) { @@ -333,17 +336,28 @@ namespace Umbraco.Web.Search /// private IEnumerable ContentFromSearchResults(IEnumerable results) { - var mapped = Mapper.Map>(results).ToArray(); - //add additional data - foreach (var m in mapped) + var defaultLang = _languageService.GetDefaultLanguageIsoCode(); + + foreach (var result in results) { - var intId = m.Id.TryConvertTo(); + var entity = Mapper.Map(result); + + var intId = entity.Id.TryConvertTo(); if (intId.Success) { - m.AdditionalData["Url"] = _umbracoHelper.Url(intId.Result); + //TODO: Here we need to figure out how to get the URL based on variant, etc... + if (result.Values.TryGetValue(UmbracoContentIndex.VariesByCultureFieldName, out var varies) && varies == "1") + { + entity.AdditionalData["Url"] = _umbracoHelper.Url(intId.Result, defaultLang); + } + else + { + entity.AdditionalData["Url"] = _umbracoHelper.Url(intId.Result); + } } + + yield return entity; } - return mapped; } } diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index d2b94c815b..bb38b8c578 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -36,10 +36,12 @@ namespace Umbraco.Web.Trees public class ContentTreeController : ContentTreeControllerBase, ISearchableTree { private readonly UmbracoTreeSearcher _treeSearcher; + private readonly ActionCollection _actions; - public ContentTreeController(UmbracoTreeSearcher treeSearcher) + public ContentTreeController(UmbracoTreeSearcher treeSearcher, ActionCollection actions) { _treeSearcher = treeSearcher; + _actions = actions; } protected override int RecycleBinId => Constants.System.RecycleBinContent; @@ -127,7 +129,7 @@ namespace Umbraco.Web.Trees // we need to get the default permissions as you can't set permissions on the very root node var permission = Services.UserService.GetPermissions(Security.CurrentUser, Constants.System.Root).First(); - var nodeActions = Current.Actions.FromEntityPermission(permission) + var nodeActions = _actions.FromEntityPermission(permission) .Select(x => new MenuItem(x)); //these two are the standard items @@ -313,8 +315,7 @@ namespace Umbraco.Web.Trees private void AddActionNode(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool convert = false, bool opensDialog = false) where TAction : IAction { - //fixme: Inject - var menuItem = menu.Items.Add(Services.TextService.Localize("actions", Current.Actions.GetAction().Alias), hasSeparator); + var menuItem = menu.Items.Add(Services.TextService.Localize("actions", _actions.GetAction().Alias), hasSeparator); if (convert) menuItem.ConvertLegacyMenuItem(item, "content", "content"); menuItem.OpensDialog = opensDialog; } From eb3841fc0a5332f0bd73935cae4fffa5286a803c Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Dec 2018 17:51:47 +1100 Subject: [PATCH 02/44] Searches on variant nodeName, orders ISearchableTree results by the corresponding tree SortOrder, fixes assembly scanning ISearchableTree --- src/Umbraco.Examine/ExamineExtensions.cs | 26 +++ .../Properties/AssemblyInfo.cs | 1 + .../Editors/ExamineManagementController.cs | 25 +-- .../Runtime/WebRuntimeComponent.cs | 6 +- src/Umbraco.Web/Search/ExamineComponent.cs | 3 + .../Search/SearchableTreeCollection.cs | 10 +- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 196 ++++++++++++------ 7 files changed, 177 insertions(+), 90 deletions(-) diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 3681979267..9fdf5e9123 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -1,8 +1,11 @@ using System; using Examine.LuceneEngine.Providers; +using Lucene.Net.Analysis; using Lucene.Net.Index; +using Lucene.Net.QueryParsers; using Lucene.Net.Search; using Lucene.Net.Store; +using Version = Lucene.Net.Util.Version; namespace Umbraco.Examine { @@ -11,6 +14,29 @@ namespace Umbraco.Examine /// internal static class ExamineExtensions { + public static bool TryParseLuceneQuery(string query) + { + //TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll + // also do this rudimentary check + if (!query.Contains(":")) + return false; + + try + { + //This will pass with a plain old string without any fields, need to figure out a way to have it properly parse + var parsed = new QueryParser(Version.LUCENE_30, "nodeName", new KeywordAnalyzer()).Parse(query); + return true; + } + catch (ParseException) + { + return false; + } + catch (Exception) + { + return false; + } + } + /// /// Checks if the index can be read/opened /// diff --git a/src/Umbraco.Examine/Properties/AssemblyInfo.cs b/src/Umbraco.Examine/Properties/AssemblyInfo.cs index 6713111968..5c42a236f4 100644 --- a/src/Umbraco.Examine/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Examine/Properties/AssemblyInfo.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; // Umbraco Cms [assembly: InternalsVisibleTo("Umbraco.Tests")] +[assembly: InternalsVisibleTo("Umbraco.Web")] // code analysis // IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it diff --git a/src/Umbraco.Web/Editors/ExamineManagementController.cs b/src/Umbraco.Web/Editors/ExamineManagementController.cs index 67209c91bd..2f96ee3d45 100644 --- a/src/Umbraco.Web/Editors/ExamineManagementController.cs +++ b/src/Umbraco.Web/Editors/ExamineManagementController.cs @@ -73,7 +73,7 @@ namespace Umbraco.Web.Editors if (!msg.IsSuccessStatusCode) throw new HttpResponseException(msg); - var results = TryParseLuceneQuery(query) + var results = Examine.ExamineExtensions.TryParseLuceneQuery(query) ? searcher.Search(searcher.CreateCriteria().RawQuery(query), maxResults: pageSize * (pageIndex + 1)) : searcher.Search(query, true, maxResults: pageSize * (pageIndex + 1)); @@ -92,28 +92,7 @@ namespace Umbraco.Web.Editors }; } - private bool TryParseLuceneQuery(string query) - { - //TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll - // also do this rudimentary check - if (!query.Contains(":")) - return false; - - try - { - //This will pass with a plain old string without any fields, need to figure out a way to have it properly parse - var parsed = new QueryParser(Version.LUCENE_30, "nodeName", new KeywordAnalyzer()).Parse(query); - return true; - } - catch (ParseException) - { - return false; - } - catch (Exception) - { - return false; - } - } + /// /// Check if the index has been rebuilt diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs index 2e1c934bc0..ccd9a7ef7d 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs @@ -127,8 +127,12 @@ namespace Umbraco.Web.Runtime composition.Container.RegisterUmbracoControllers(typeLoader, GetType().Assembly); composition.Container.EnableWebApi(GlobalConfiguration.Configuration); + //we aren't scanning for ISearchableTree since that is not IDiscoverable, instead we'll just filter what we've + //already scanned since all of our ISearchableTree is of type UmbracoApiController and in most cases any developers' + //own trees they want searched will also be of type UmbracoApiController. If a developer wants to replace one of ours + //then they will have to manually register/replace. composition.Container.RegisterCollectionBuilder() - .Add(() => typeLoader.GetTypes()); // fixme which searchable trees?! + .Add(() => typeLoader.GetTypes().Where(x => x.Implements())); composition.Container.Register(new PerRequestLifeTime()); diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index d8c1016c3e..65264d0308 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -26,6 +26,8 @@ using Examine.LuceneEngine.Directories; using LightInject; using Umbraco.Core.Composing; using Umbraco.Core.Strings; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; namespace Umbraco.Web.Search { @@ -53,6 +55,7 @@ namespace Umbraco.Web.Search // but greater that SafeXmlReaderWriter priority which is 60 private const int EnlistPriority = 80; + public override void Compose(Composition composition) { base.Compose(composition); diff --git a/src/Umbraco.Web/Search/SearchableTreeCollection.cs b/src/Umbraco.Web/Search/SearchableTreeCollection.cs index 86f4494353..81ee0a2898 100644 --- a/src/Umbraco.Web/Search/SearchableTreeCollection.cs +++ b/src/Umbraco.Web/Search/SearchableTreeCollection.cs @@ -19,15 +19,17 @@ namespace Umbraco.Web.Search private Dictionary CreateDictionary(IApplicationTreeService treeService) { - var appTrees = treeService.GetAll().ToArray(); + var appTrees = treeService.GetAll() + .OrderBy(x => x.SortOrder) + .ToArray(); var dictionary = new Dictionary(); var searchableTrees = this.ToArray(); - foreach (var searchableTree in searchableTrees) + foreach (var appTree in appTrees) { - var found = appTrees.FirstOrDefault(x => x.Alias == searchableTree.TreeAlias); + var found = searchableTrees.FirstOrDefault(x => x.TreeAlias == appTree.Alias); if (found != null) { - dictionary[searchableTree.TreeAlias] = new SearchableApplicationTree(found.ApplicationAlias, found.Alias, searchableTree); + dictionary[found.TreeAlias] = new SearchableApplicationTree(appTree.ApplicationAlias, appTree.Alias, found); } } return dictionary; diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index af2db4f7ac..8f977aab77 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -25,12 +25,17 @@ namespace Umbraco.Web.Search private readonly IExamineManager _examineManager; private readonly UmbracoHelper _umbracoHelper; private readonly ILocalizationService _languageService; + private readonly IEntityService _entityService; - public UmbracoTreeSearcher(IExamineManager examineManager, UmbracoHelper umbracoHelper, ILocalizationService languageService) + public UmbracoTreeSearcher(IExamineManager examineManager, + UmbracoHelper umbracoHelper, + ILocalizationService languageService, + IEntityService entityService) { _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); _umbracoHelper = umbracoHelper ?? throw new ArgumentNullException(nameof(umbracoHelper)); _languageService = languageService; + _entityService = entityService; } /// @@ -59,7 +64,13 @@ namespace Umbraco.Web.Search var umbracoContext = _umbracoHelper.UmbracoContext; - //TODO: WE should really just allow passing in a lucene raw query + //TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string + // manipulation for things like start paths, member types, etc... + //if (Examine.ExamineExtensions.TryParseLuceneQuery(query)) + //{ + + //} + switch (entityType) { case UmbracoEntityTypes.Member: @@ -75,13 +86,13 @@ namespace Umbraco.Web.Search break; case UmbracoEntityTypes.Media: type = "media"; - var allMediaStartNodes = umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(Current.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, Current.Services.EntityService); + var allMediaStartNodes = umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService); + AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, _entityService); break; case UmbracoEntityTypes.Document: type = "content"; - var allContentStartNodes = umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(Current.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, Current.Services.EntityService); + var allContentStartNodes = umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService); + AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, _entityService); break; default: throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); @@ -92,11 +103,43 @@ namespace Umbraco.Web.Search var internalSearcher = index.GetSearcher(); + if (!BuildQuery(sb, query, searchFrom, fields, type)) + { + totalFound = 0; + return Enumerable.Empty(); + } + + var raw = internalSearcher.CreateCriteria().RawQuery(sb.ToString()); + + var result = internalSearcher + //only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested + .Search(raw, Convert.ToInt32(pageSize * (pageIndex + 1))); + + totalFound = result.TotalItemCount; + + var pagedResult = result.Skip(Convert.ToInt32(pageIndex)); + + switch (entityType) + { + case UmbracoEntityTypes.Member: + return MemberFromSearchResults(pagedResult.ToArray()); + case UmbracoEntityTypes.Media: + return MediaFromSearchResults(pagedResult); + case UmbracoEntityTypes.Document: + return ContentFromSearchResults(pagedResult); + default: + throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); + } + } + + private bool BuildQuery(StringBuilder sb, string query, string searchFrom, string[] fields, string type) + { //build a lucene query: // the nodeName will be boosted 10x without wildcards // then nodeName will be matched normally with wildcards // the rest will be normal without wildcards + var allLangs = _languageService.GetAllLanguages().Select(x => x.IsoCode).ToList(); //check if text is surrounded by single or double quotes, if so, then exact match var surroundedByQuotes = Regex.IsMatch(query, "^\".*?\"$") @@ -105,15 +148,14 @@ namespace Umbraco.Web.Search if (surroundedByQuotes) { //strip quotes, escape string, the replace again - query = query.Trim(new[] { '\"', '\'' }); + query = query.Trim('\"', '\''); query = Lucene.Net.QueryParsers.QueryParser.Escape(query); //nothing to search if (searchFrom.IsNullOrWhiteSpace() && query.IsNullOrWhiteSpace()) { - totalFound = 0; - return new List(); + return false; } //update the query with the query term @@ -122,10 +164,9 @@ namespace Umbraco.Web.Search //add back the surrounding quotes query = string.Format("{0}{1}{0}", "\"", query); - //node name exactly boost x 10 - sb.Append("+(nodeName: ("); - sb.Append(query.ToLower()); - sb.Append(")^10.0 "); + sb.Append("+("); + + AppendNodeNamePhraseWithBoost(sb, query, allLangs); foreach (var f in fields) { @@ -146,8 +187,7 @@ namespace Umbraco.Web.Search //nothing to search if (searchFrom.IsNullOrWhiteSpace() && trimmed.IsNullOrWhiteSpace()) { - totalFound = 0; - return new List(); + return false; } //update the query with the query term @@ -157,24 +197,12 @@ namespace Umbraco.Web.Search var querywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - //node name exactly boost x 10 - sb.Append("+(nodeName:"); - sb.Append("\""); - sb.Append(query.ToLower()); - sb.Append("\""); - sb.Append("^10.0 "); - - //node name normally with wildcards - sb.Append(" nodeName:"); - sb.Append("("); - foreach (var w in querywords) - { - sb.Append(w.ToLower()); - sb.Append("* "); - } - sb.Append(") "); + sb.Append("+("); + AppendNodeNameExactWithBoost(sb, query, allLangs); + AppendNodeNameWithWildcards(sb, querywords, allLangs); + foreach (var f in fields) { //additional fields normally @@ -198,26 +226,69 @@ namespace Umbraco.Web.Search sb.Append("+__IndexType:"); sb.Append(type); - var raw = internalSearcher.CreateCriteria().RawQuery(sb.ToString()); + return true; + } - var result = internalSearcher - //only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested - .Search(raw, Convert.ToInt32(pageSize * (pageIndex + 1))); + private void AppendNodeNamePhraseWithBoost(StringBuilder sb, string query, IEnumerable allLangs) + { + //node name exactly boost x 10 + sb.Append("nodeName: ("); + sb.Append(query.ToLower()); + sb.Append(")^10.0 "); - totalFound = result.TotalItemCount; - - var pagedResult = result.Skip(Convert.ToInt32(pageIndex)); - - switch (entityType) + //also search on all variant node names + foreach (var lang in allLangs) { - case UmbracoEntityTypes.Member: - return MemberFromSearchResults(pagedResult.ToArray()); - case UmbracoEntityTypes.Media: - return MediaFromSearchResults(pagedResult); - case UmbracoEntityTypes.Document: - return ContentFromSearchResults(pagedResult); - default: - throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); + //node name exactly boost x 10 + sb.Append($"nodeName_{lang}: ("); + sb.Append(query.ToLower()); + sb.Append(")^10.0 "); + } + } + + private void AppendNodeNameExactWithBoost(StringBuilder sb, string query, IEnumerable allLangs) + { + //node name exactly boost x 10 + sb.Append("nodeName:"); + sb.Append("\""); + sb.Append(query.ToLower()); + sb.Append("\""); + sb.Append("^10.0 "); + //also search on all variant node names + foreach (var lang in allLangs) + { + //node name exactly boost x 10 + sb.Append($"nodeName_{lang}:"); + sb.Append("\""); + sb.Append(query.ToLower()); + sb.Append("\""); + sb.Append("^10.0 "); + } + } + + private void AppendNodeNameWithWildcards(StringBuilder sb, string[] querywords, IEnumerable allLangs) + { + //node name normally with wildcards + sb.Append("nodeName:"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + sb.Append(") "); + //also search on all variant node names + foreach (var lang in allLangs) + { + //node name normally with wildcards + sb.Append($"nodeName_{lang}:"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + sb.Append(") "); } } @@ -281,32 +352,33 @@ namespace Umbraco.Web.Search /// /// /// - private IEnumerable MemberFromSearchResults(ISearchResult[] results) + private IEnumerable MemberFromSearchResults(IEnumerable results) { - var mapped = Mapper.Map>(results).ToArray(); //add additional data - foreach (var m in mapped) + foreach (var result in results) { + var m = Mapper.Map(result); + //if no icon could be mapped, it will be set to document, so change it to picture if (m.Icon == "icon-document") { m.Icon = "icon-user"; } - - var searchResult = results.First(x => x.Id == m.Id.ToString()); - if (searchResult.Values.ContainsKey("email") && searchResult.Values["email"] != null) + + if (result.Values.ContainsKey("email") && result.Values["email"] != null) { - m.AdditionalData["Email"] = results.First(x => x.Id == m.Id.ToString()).Values["email"]; + m.AdditionalData["Email"] = result.Values["email"]; } - if (searchResult.Values.ContainsKey("__key") && searchResult.Values["__key"] != null) + if (result.Values.ContainsKey("__key") && result.Values["__key"] != null) { - if (Guid.TryParse(searchResult.Values["__key"], out var key)) + if (Guid.TryParse(result.Values["__key"], out var key)) { m.Key = key; } } + + yield return m; } - return mapped; } /// @@ -316,17 +388,17 @@ namespace Umbraco.Web.Search /// private IEnumerable MediaFromSearchResults(IEnumerable results) { - var mapped = Mapper.Map>(results).ToArray(); //add additional data - foreach (var m in mapped) + foreach (var result in results) { + var m = Mapper.Map(result); //if no icon could be mapped, it will be set to document, so change it to picture if (m.Icon == "icon-document") { m.Icon = "icon-picture"; } + yield return m; } - return mapped; } /// @@ -345,7 +417,7 @@ namespace Umbraco.Web.Search var intId = entity.Id.TryConvertTo(); if (intId.Success) { - //TODO: Here we need to figure out how to get the URL based on variant, etc... + //if it varies by culture, return the default language URL if (result.Values.TryGetValue(UmbracoContentIndex.VariesByCultureFieldName, out var varies) && varies == "1") { entity.AdditionalData["Url"] = _umbracoHelper.Url(intId.Result, defaultLang); From 1fe3045b4c912a1ed01ef301f51fd687ee9cc474 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Dec 2018 17:54:10 +1100 Subject: [PATCH 03/44] Adds TODO notes --- src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs index ea76293df5..178027857c 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs @@ -122,6 +122,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) .AfterMap((src, dest) => { + //TODO: Properly map this (not aftermap) + //get the icon if there is one dest.Icon = src.Values.ContainsKey(UmbracoExamineIndex.IconFieldName) ? src.Values[UmbracoExamineIndex.IconFieldName] From 8048d57ffe062da230151f841e771d7cddc8e93a Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Dec 2018 17:57:40 +1100 Subject: [PATCH 04/44] ensures query fields contained the lowercased iso code --- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 8f977aab77..c3ab7318a0 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -139,7 +139,7 @@ namespace Umbraco.Web.Search // then nodeName will be matched normally with wildcards // the rest will be normal without wildcards - var allLangs = _languageService.GetAllLanguages().Select(x => x.IsoCode).ToList(); + var allLangs = _languageService.GetAllLanguages().Select(x => x.IsoCode.ToLowerInvariant()).ToList(); //check if text is surrounded by single or double quotes, if so, then exact match var surroundedByQuotes = Regex.IsMatch(query, "^\".*?\"$") From c8593c23e6a60dfac11114cd4db44bea0154d65c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 12 Dec 2018 11:59:08 +0100 Subject: [PATCH 05/44] show correct createDate for variants, only show contextual variant status, only show context variant urls, add variant log colors --- .../content/umbcontentnodeinfo.directive.js | 89 ++++++++++--------- .../content/umb-content-node-info.html | 23 ++--- 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 31e847f0f6..232e31daee 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -10,6 +10,8 @@ var auditTrailLoaded = false; var labels = {}; scope.publishStatus = []; + scope.currentVariant = null; + scope.currentUrls = []; scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; scope.allowChangeDocumentType = false; @@ -17,6 +19,18 @@ function onInit() { + // set currentVariant + scope.currentVariant = _.find(scope.node.variants, (v) => v.active); + + // find the urls for the currently selected language + if(scope.node.variants.length > 1) { + // nodes with variants + scope.currentUrls = _.filter(scope.node.urls, (url) => scope.currentVariant.language.culture === url.culture); + } else { + // invariant nodes + scope.currentUrls = scope.node.urls; + } + // if there are any infinite editors open we are in infinite editing scope.isInfiniteMode = editorService.getNumberOfEditors() > 0 ? true : false; @@ -50,7 +64,7 @@ labels.unsavedChanges = data[5]; labels.doctypeChangeWarning = data[6]; - setNodePublishStatus(scope.node); + setNodePublishStatus(); }); @@ -58,6 +72,9 @@ "id": scope.node.id }; + // make sure dates are formatted to the user's locale + formatDatesToLocal(); + // get available templates scope.availableTemplates = scope.node.allowedTemplates; @@ -221,12 +238,13 @@ function setAuditTrailLogTypeColor(auditTrail) { angular.forEach(auditTrail, function (item) { - switch (item.logType) { case "Publish": + case "PublishVariant": item.logTypeColor = "success"; break; case "Unpublish": + case "UnpublishVariant": case "Delete": item.logTypeColor = "danger"; break; @@ -236,51 +254,40 @@ }); } - function setNodePublishStatus(node) { + function setNodePublishStatus() { - scope.publishStatus = []; + scope.status = {}; // deleted node - if (node.trashed === true) { - scope.publishStatus.push({ - label: labels.deleted, - color: "danger" - }); + if (scope.node.trashed === true) { + scope.status.color = "danger"; return; } - if (node.variants) { - for (var i = 0; i < node.variants.length; i++) { - - var variant = node.variants[i]; - - var status = { - culture: variant.language ? variant.language.culture : null - }; - - if (variant.state === "NotCreated") { - status.label = labels.notCreated; - status.color = "gray"; - } - else if (variant.state === "Draft") { - // draft node - status.label = labels.unpublished; - status.color = "gray"; - } - else if (variant.state === "Published") { - // published node - status.label = labels.published; - status.color = "success"; - } - else if (variant.state === "PublishedPendingChanges") { - // published node with pending changes - status.label = labels.publishedPendingChanges; - status.color = "success"; - } - - scope.publishStatus.push(status); - } + // variant status + if (scope.currentVariant.state === "NotCreated") { + // not created + scope.status.color = "gray"; } + else if (scope.currentVariant.state === "Draft") { + // draft node + scope.status.color = "gray"; + } + else if (scope.currentVariant.state === "Published") { + // published node + scope.status.color = "success"; + } + else if (scope.currentVariant.state === "PublishedPendingChanges") { + // published node with pending changes + scope.status.color = "success"; + } + } + + function formatDatesToLocal() { + // get current backoffice user and format dates + userService.getCurrentUser().then(function (currentUser) { + scope.currentVariant.createDateFormatted = dateHelper.getLocalDate(scope.currentVariant.createDate, currentUser.locale, 'LLL'); + }); } // load audit trail and redirects when on the info tab @@ -306,7 +313,7 @@ auditTrailLoaded = false; loadAuditTrail(); loadRedirectUrls(); - setNodePublishStatus(scope.node); + setNodePublishStatus(); } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 429cceb4d9..e880fc0f4e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -2,21 +2,19 @@
- + @@ -62,8 +60,9 @@
- +
@@ -96,7 +95,6 @@ class="history-item__badge" size="xs" color="{{item.logTypeColor}}"> - {{ item.logType }} @@ -130,16 +128,13 @@ -
- {{status.culture}}: - - {{status.label}} - -
+ + +
- {{node.createDateFormatted}} by {{ node.owner.name }} + {{currentVariant.createDateFormatted}} From 4f6f959d7fd9ba96bb2ea926ac02cb0fec62a401 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 12 Dec 2018 13:12:13 +0100 Subject: [PATCH 06/44] Add missing translations --- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 775b40fd0c..498744f5cf 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -147,8 +147,9 @@ Viewing for Content deleted Content unpublished - Content saved and Published - Content saved and published for languages: %0% + Content unpublished for languages: %0% + Content published + Content published for languages: %0% Content saved Content saved for languages: %0% Content moved @@ -165,6 +166,7 @@ Save Delete Unpublish + Unpublish Rollback Send To Publish Send To Publish From defa231c4bcdc54689386f98dd0270793476eee4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 12 Dec 2018 14:29:20 +0100 Subject: [PATCH 07/44] add label for history --- .../components/content/umbcontentnodeinfo.directive.js | 8 ++++++-- .../views/components/content/umb-content-node-info.html | 3 ++- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 232e31daee..7b56bb2bd0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -51,7 +51,9 @@ "content_publishedPendingChanges", "content_notCreated", "prompt_unsavedChanges", - "prompt_doctypeChangeWarning" + "prompt_doctypeChangeWarning", + "general_history", + "auditTrails_historyIncludingVariants" ]; localizationService.localizeMany(keys) @@ -63,7 +65,9 @@ labels.notCreated = data[4]; labels.unsavedChanges = data[5]; labels.doctypeChangeWarning = data[6]; - + + scope.historyLabel = scope.node.variants && scope.node.variants.length === 1 ? data[7] : data[8]; + setNodePublishStatus(); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index e880fc0f4e..b37e3c7ba4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -41,7 +41,8 @@ + title="{{historyLabel}}"> + Send To Publish Send To Publish Sort + History (all variants) To change the document type for the selected content, first select from the list of valid types for this location. From 4068dca93874eaacbef7d249284fc84fee0752d4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 17 Dec 2018 13:55:08 +0100 Subject: [PATCH 08/44] reenable infinite editing animations --- .../components/editor/umbeditors.directive.js | 67 ++++++------------- .../less/components/editor/umb-editor.less | 2 +- 2 files changed, 20 insertions(+), 49 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js index e68c2bbc9a..58afc72f6c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js @@ -25,25 +25,6 @@ // shown so we don't animate a lot of editors which aren't necessary var moveEditors = editorsElement.querySelectorAll('.umb-editor:nth-last-child(-n+'+ allowedNumberOfVisibleEditors +')'); - // this is a temporary fix because the animations doesn't perform well - // TODO: fix animation and remove this - moveEditors.forEach(function(editor, index){ - - // resize the small editors to 100% so we can easily slide them - if(editor.classList.contains("umb-editor--small")) { - editor.style.width = "100%"; - } - - // set left position to indent the editors - if(scope.editors.length >= allowedNumberOfVisibleEditors) { - $(editor).css({"left": index * editorIndent}); - } else { - $(editor).css({"left": (index + 1) * editorIndent}); - } - - }); - - /* // collapse open editors before opening the new one var collapseEditorAnimation = anime({ targets: moveEditors, @@ -61,9 +42,8 @@ return (index + 1) * editorIndent; }, easing: 'easeInOutQuint', - duration: 500 + duration: 300 }); - */ // push the new editor to the dom scope.editors.push(editor); @@ -95,7 +75,7 @@ translateX: [100 + '%', 0], opacity: [0, 1], easing: 'easeInOutQuint', - duration: 400, + duration: 300, complete: function() { $timeout(function(){ editor.animating = false; @@ -126,11 +106,12 @@ $timeout(function(){ scope.editors.splice(-1,1); removeOverlayFromPrevEditor(); - expandEditors(); }); } }); + expandEditors(); + }); } @@ -142,42 +123,32 @@ var editorsElement = el[0]; // only select the editors which are allowed to be // shown so we don't animate a lot of editors which aren't necessary - var moveEditors = editorsElement.querySelectorAll('.umb-editor:nth-last-child(-n+'+ 4 +')'); + // as the last element hasn't been removed from the dom yet we have to select the last four and then skip the last child (as it is the one closing). + var moveEditors = editorsElement.querySelectorAll('.umb-editor:nth-last-child(-n+'+ allowedNumberOfVisibleEditors + 1 +'):not(:last-child)'); + var editorWidth = editorsElement.offsetWidth; - // this is a temporary fix because the animations doesn't perform well - // TODO: fix animation and remove this - moveEditors.forEach(function(editor, index){ - // set left position - $(editor).css({"left": (index + 1) * editorIndent}); - - // if the new top editor is a small editor we will have to resize it back to the right size on - // move it all the way to the right side - if(editor.classList.contains("umb-editor--small") && index + 1 === moveEditors.length) { - editor.style.width = "500px"; - $(editor).css({"left": ""}); - } - }); - - // We need to figure out how to performance optimize this - // TODO: optimize animation - /* var expandEditorAnimation = anime({ targets: moveEditors, left: function(el, index, length){ - return (index + 1) * editorIndent; + // move the editor all the way to the right if the top one is a small + if(el.classList.contains("umb-editor--small")) { + // only change the size if it is the editor on top + if(index + 1 === length) { + return editorWidth - 500; + } + } else { + return (index + 1) * editorIndent; + } }, width: function(el, index, length) { - if(el.classList.contains("umb-editor--small")) { + // set the correct size if the top editor is of type "small" + if(el.classList.contains("umb-editor--small") && index + 1 === length) { return "500px"; } }, easing: 'easeInOutQuint', - duration: 500, - completed: function() { - - } + duration: 300 }); - */ }); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less index 640a276443..321fabadad 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less @@ -42,6 +42,6 @@ bottom: 0; right: 0; left: 0; - background: rgba(0,0,0,0.2); + background: rgba(0,0,0,0.4); z-index: @zIndexEditor; } \ No newline at end of file From a19a58796fb04f6aa62b308689f8726ebd6b232f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 18 Dec 2018 22:02:39 +1100 Subject: [PATCH 09/44] Changes IUrlProvider to return UrlInfo --- .../Routing/UrlsProviderWithDomainsTests.cs | 4 +- .../TestControllerActivatorBase.cs | 2 +- .../Testing/TestingTests/MockTests.cs | 2 +- .../Web/TemplateUtilitiesTests.cs | 2 +- src/Umbraco.Web/Routing/AliasUrlProvider.cs | 29 ++++----- .../Routing/CustomRouteUrlProvider.cs | 16 +++-- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 27 ++++---- src/Umbraco.Web/Routing/IUrlProvider.cs | 4 +- src/Umbraco.Web/Routing/UrlInfo.cs | 65 ++++++++++++++++--- src/Umbraco.Web/Routing/UrlProvider.cs | 10 +-- .../Routing/UrlProviderExtensions.cs | 46 ++++--------- 11 files changed, 118 insertions(+), 89 deletions(-) diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index c6bd0fdb88..2f1bef3b9a 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -418,8 +418,8 @@ namespace Umbraco.Tests.Routing foreach (var x in result) Console.WriteLine(x); Assert.AreEqual(2, result.Length); - Assert.IsTrue(result.Contains("http://domain1a.com/en/1001-1-1/")); - Assert.IsTrue(result.Contains("http://domain1b.com/en/1001-1-1/")); + Assert.AreEqual(result[0].Text, "http://domain1a.com/en/1001-1-1/"); + Assert.AreEqual(result[1].Text, "http://domain1b.com/en/1001-1-1/"); } } } diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 2cf64f04d1..7152622b07 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -159,7 +159,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var urlHelper = new Mock(); urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns("/hello/world/1234"); + .Returns(UrlInfo.Url("/hello/world/1234")); var membershipHelper = new MembershipHelper(umbCtx, Mock.Of(), Mock.Of()); diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 51855f7e19..5b93de0c09 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Testing.TestingTests var urlProviderMock = new Mock(); urlProviderMock.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns("/hello/world/1234"); + .Returns(UrlInfo.Url("/hello/world/1234")); var urlProvider = urlProviderMock.Object; var theUrlProvider = new UrlProvider(umbracoContext, new [] { urlProvider }, umbracoContext.VariationContextAccessor); diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 1e6229fa4c..d98afa6bcc 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -79,7 +79,7 @@ namespace Umbraco.Tests.Web var testUrlProvider = new Mock(); testUrlProvider .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlProviderMode mode, string culture, Uri url) => "/my-test-url"); + .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlProviderMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url")); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index 8c851d139f..4c879e931f 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Routing #region GetUrl /// - public string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) { return null; // we have nothing to say } @@ -51,13 +51,14 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) + public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { var node = umbracoContext.ContentCache.GetById(id); - if (node == null) return Enumerable.Empty(); + if (node == null) + yield break; if (!node.HasProperty(Constants.Conventions.Content.UrlAlias)) - return Enumerable.Empty(); + yield break; var domainHelper = umbracoContext.GetDomainHelper(_siteDomainHelper); @@ -82,20 +83,19 @@ namespace Umbraco.Web.Routing // the content finder may work, depending on the 'current' culture, // but there's no way we can return something meaningful here if (varies) - return Enumerable.Empty(); + yield break; var umbracoUrlName = node.Value(Constants.Conventions.Content.UrlAlias); if (string.IsNullOrWhiteSpace(umbracoUrlName)) - return Enumerable.Empty(); + yield break; var path = "/" + umbracoUrlName; var uri = new Uri(path, UriKind.Relative); - return new[] { UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString() }; + yield return UrlInfo.Url(UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString()); } else { // some domains: one url per domain, which is "/" - var result = new List(); foreach(var domainUri in domainUris) { // if the property is invariant, get the invariant value, url is "/" @@ -108,14 +108,13 @@ namespace Umbraco.Web.Routing ? node.Value(Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture.Name) : node.Value(Constants.Conventions.Content.UrlAlias); - if (!string.IsNullOrWhiteSpace(umbracoUrlName)) - { - var path = "/" + umbracoUrlName; - var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); - result.Add(UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString()); - } + if (string.IsNullOrWhiteSpace(umbracoUrlName)) + continue; + + var path = "/" + umbracoUrlName; + var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); + yield return UrlInfo.Url(UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString(), domainUri.Culture.Name); } - return result; } } diff --git a/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs b/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs index ce1fc1ffab..ae17ff6258 100644 --- a/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs +++ b/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.Routing /// /// This will return the URL that is returned by the assigned custom if this is a custom route /// - public string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) { if (umbracoContext?.PublishedRequest?.PublishedContent == null) return null; if (umbracoContext.HttpContext?.Request?.RequestContext?.RouteData?.DataTokens == null) return null; @@ -26,13 +26,15 @@ namespace Umbraco.Web.Routing //NOTE: This looks like it might cause an infinite loop because PublishedContentBase.Url calls into UmbracoContext.Current.UrlProvider.GetUrl which calls back into the IUrlProvider pipeline // but the specific purpose of this is that a developer is using their own IPublishedContent that returns a specific Url and doesn't go back into the UrlProvider pipeline. //TODO: We could put a try/catch here just in case, else we could do some reflection checking to see if the implementation is PublishedContentBase and the Url property is not overridden. - return content.Id == umbracoContext.PublishedRequest.PublishedContent.Id - ? umbracoContext.PublishedRequest.PublishedContent.GetUrl(culture) - : null; + return UrlInfo.Url( + content.Id == umbracoContext.PublishedRequest.PublishedContent.Id + ? umbracoContext.PublishedRequest.PublishedContent.GetUrl(culture) + : null, + culture); } /// - /// This always returns null because this url provider is used purely to deal with Umbraco custom routes with + /// This always returns an empty result because this url provider is used purely to deal with Umbraco custom routes with /// UmbracoVirtualNodeRouteHandler, we really only care about the normal URL so that RedirectToCurrentUmbracoPage() works /// with SurfaceControllers /// @@ -40,9 +42,9 @@ namespace Umbraco.Web.Routing /// /// /// - public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) + public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { - return null; + yield break; } } } diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 61437b6640..5045b1af96 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Routing #region GetUrl /// - public virtual string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public virtual UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) { if (!current.IsAbsoluteUri) throw new ArgumentException("Current url must be absolute.", nameof(current)); @@ -39,7 +39,7 @@ namespace Umbraco.Web.Routing return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture); } - internal string GetUrlFromRoute(string route, UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode, string culture) + internal UrlInfo GetUrlFromRoute(string route, UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode, string culture) { if (string.IsNullOrWhiteSpace(route)) { @@ -58,7 +58,9 @@ namespace Umbraco.Web.Routing : domainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current, culture); // assemble the url from domainUri (maybe null) and path - return AssembleUrl(domainUri, path, current, mode).ToString(); + var url = AssembleUrl(domainUri, path, current, mode).ToString(); + + return UrlInfo.Url(url, culture); } #endregion @@ -76,10 +78,11 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - public virtual IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) + public virtual IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { var node = umbracoContext.ContentCache.GetById(id); - if (node == null) return Enumerable.Empty(); + if (node == null) + yield break; var domainHelper = umbracoContext.GetDomainHelper(_siteDomainHelper); @@ -94,13 +97,14 @@ namespace Umbraco.Web.Routing // no domains = exit if (domainUris ==null) - return Enumerable.Empty(); + yield break; - var result = new List(); foreach (var d in domainUris) { + var culture = d?.Culture?.Name; + //although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok - var route = umbracoContext.ContentCache.GetRouteById(id, d?.Culture?.Name); + var route = umbracoContext.ContentCache.GetRouteById(id, culture); if (route == null) continue; //need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) @@ -109,9 +113,8 @@ namespace Umbraco.Web.Routing var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path)); uri = UriUtility.UriFromUmbraco(uri, _globalSettings, _requestSettings); - result.Add(uri.ToString()); + yield return UrlInfo.Url(uri.ToString(), culture); } - return result; } #endregion @@ -146,7 +149,7 @@ namespace Umbraco.Web.Routing uri = new Uri(path, UriKind.Relative); break; default: - throw new ArgumentOutOfRangeException("mode"); + throw new ArgumentOutOfRangeException(nameof(mode)); } } else // a domain was found @@ -169,7 +172,7 @@ namespace Umbraco.Web.Routing uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); break; default: - throw new ArgumentOutOfRangeException("mode"); + throw new ArgumentOutOfRangeException(nameof(mode)); } } diff --git a/src/Umbraco.Web/Routing/IUrlProvider.cs b/src/Umbraco.Web/Routing/IUrlProvider.cs index 0f102dceb8..55d39880d6 100644 --- a/src/Umbraco.Web/Routing/IUrlProvider.cs +++ b/src/Umbraco.Web/Routing/IUrlProvider.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Routing /// when no culture is specified, the current culture. /// If the provider is unable to provide a url, it should return null. /// - string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current); + UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current); /// /// Gets the other urls of a published content. @@ -37,6 +37,6 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current); + IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current); } } diff --git a/src/Umbraco.Web/Routing/UrlInfo.cs b/src/Umbraco.Web/Routing/UrlInfo.cs index 01dbe4a0e1..a795f1577b 100644 --- a/src/Umbraco.Web/Routing/UrlInfo.cs +++ b/src/Umbraco.Web/Routing/UrlInfo.cs @@ -1,4 +1,5 @@ -using System.Runtime.Serialization; +using System; +using System.Runtime.Serialization; namespace Umbraco.Web.Routing { @@ -6,13 +7,25 @@ namespace Umbraco.Web.Routing /// Represents infos for a url. /// [DataContract(Name = "urlInfo", Namespace = "")] - public class UrlInfo + public class UrlInfo : IEquatable { + + /// + /// Creates a instance representing a true url. + /// + public static UrlInfo Url(string text, string culture = null) => new UrlInfo(text, true, culture); + + /// + /// Creates a instance representing a message. + /// + public static UrlInfo Message(string text, string culture = null) => new UrlInfo(text, false, culture); + /// /// Initializes a new instance of the class. /// - public UrlInfo(string text, bool isUrl, string culture) + private UrlInfo(string text, bool isUrl, string culture) { + if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(text)); IsUrl = isUrl; Text = text; Culture = culture; @@ -38,13 +51,47 @@ namespace Umbraco.Web.Routing public string Text { get; } /// - /// Creates a instance representing a true url. + /// Checks equality /// - public static UrlInfo Url(string text, string culture = null) => new UrlInfo(text, true, culture); + /// + /// + /// + /// Compare both culture and Text as invariant strings since URLs are not case sensitive, nor are culture names within Umbraco + /// + public bool Equals(UrlInfo other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Culture, other.Culture, StringComparison.InvariantCultureIgnoreCase) && IsUrl == other.IsUrl && string.Equals(Text, other.Text, StringComparison.InvariantCultureIgnoreCase); + } - /// - /// Creates a instance representing a message. - /// - public static UrlInfo Message(string text, string culture = null) => new UrlInfo(text, false, culture); + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((UrlInfo) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Culture) : 0); + hashCode = (hashCode * 397) ^ IsUrl.GetHashCode(); + hashCode = (hashCode * 397) ^ (Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Text) : 0); + return hashCode; + } + } + + public static bool operator ==(UrlInfo left, UrlInfo right) + { + return Equals(left, right); + } + + public static bool operator !=(UrlInfo left, UrlInfo right) + { + return !Equals(left, right); + } } } diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index b265d48923..491deee9e3 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -202,7 +202,7 @@ namespace Umbraco.Web.Routing var url = _urlProviders.Select(provider => provider.GetUrl(_umbracoContext, content, mode, culture, current)) .FirstOrDefault(u => u != null); - return url ?? "#"; // legacy wants this + return url?.Text ?? "#"; // legacy wants this } internal string GetUrlFromRoute(int id, string route, string culture) @@ -210,7 +210,7 @@ namespace Umbraco.Web.Routing var provider = _urlProviders.OfType().FirstOrDefault(); var url = provider == null ? route // what else? - : provider.GetUrlFromRoute(route, UmbracoContext.Current, id, _umbracoContext.CleanedUmbracoUrl, Mode, culture); + : provider.GetUrlFromRoute(route, UmbracoContext.Current, id, _umbracoContext.CleanedUmbracoUrl, Mode, culture)?.Text; return url ?? "#"; } @@ -228,7 +228,7 @@ namespace Umbraco.Web.Routing /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// The results depend on the current url. /// - public IEnumerable GetOtherUrls(int id) + public IEnumerable GetOtherUrls(int id) { return GetOtherUrls(id, _umbracoContext.CleanedUmbracoUrl); } @@ -243,10 +243,10 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - public IEnumerable GetOtherUrls(int id, Uri current) + public IEnumerable GetOtherUrls(int id, Uri current) { // providers can return null or an empty list or a non-empty list, be prepared - var urls = _urlProviders.SelectMany(provider => provider.GetOtherUrls(_umbracoContext, id, current) ?? Enumerable.Empty()); + var urls = _urlProviders.SelectMany(provider => provider.GetOtherUrls(_umbracoContext, id, current)); return urls; } diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 14b9c0a465..3a0caeec78 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -34,14 +34,11 @@ namespace Umbraco.Web.Routing if (contentService == null) throw new ArgumentNullException(nameof(contentService)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - var urls = new List(); - if (content.Published == false) - { - urls.Add(UrlInfo.Message(textService.Localize("content/itemNotPublished"))); - return urls; - } - + yield return UrlInfo.Message(textService.Localize("content/itemNotPublished")); + + var urls = new HashSet(); + // build a list of urls, for the back-office // which will contain // - the 'main' urls, which is what .Url would return, for each culture @@ -92,37 +89,18 @@ namespace Umbraco.Web.Routing } } - // prepare for de-duplication - var durl = new Dictionary>(); - var dmsg = new Dictionary>(); - foreach (var url in urls) - { - var d = url.IsUrl ? durl : dmsg; - if (!d.TryGetValue(url.Text, out var l)) - d[url.Text] = l = new List(); - l.Add(url); - } - - // deduplicate, order urls first then messages, concatenate cultures (hide if 'all') - var ret = new List(); - foreach (var (text, infos) in durl) - ret.Add(UrlInfo.Url(text, infos.Count == cultures.Count ? null : string.Join(", ", infos.Select(x => x.Culture)))); - foreach (var (text, infos) in dmsg) - ret.Add(UrlInfo.Message(text, infos.Count == cultures.Count ? null : string.Join(", ", infos.Select(x => x.Culture)))); + //return the real urls first, then the messages + foreach (var urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) + foreach (var url in urlGroup) + yield return url; // get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless. // for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them. - // also, we are not dealing with cultures at all - that will have to wait - foreach(var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) - { - if (urls.Any(x => x.IsUrl && x.Text == otherUrl)) continue; - ret.Add(UrlInfo.Url(otherUrl)); - } - - return ret; + foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) + yield return otherUrl; } - private static void HandleCouldNotGetUrl(IContent content, string culture, List urls, IContentService contentService, ILocalizedTextService textService) + private static void HandleCouldNotGetUrl(IContent content, string culture, ICollection urls, IContentService contentService, ILocalizedTextService textService) { // document has a published version yet its url is "#" => a parent must be // unpublished, walk up the tree until we find it, and report. @@ -143,7 +121,7 @@ namespace Umbraco.Web.Routing urls.Add(UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] { parent.Name }), culture)); } - private static bool DetectCollision(IContent content, string url, List urls, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService) + private static bool DetectCollision(IContent content, string url, ICollection urls, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService) { // test for collisions on the 'main' url var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); From 9eecf9898e7e3b9b7f1081f00bdaec8fd680334c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 18 Dec 2018 13:24:52 +0100 Subject: [PATCH 10/44] rebind keyboard shortcuts when infinite editors closes --- .../src/common/services/editor.service.js | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 0dbd27b7a5..5985cc99b1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -78,8 +78,9 @@ (function () { "use strict"; - function editorService(eventsService) { + function editorService(eventsService, keyboardService) { + let editorsKeyboardShorcuts = []; var editors = []; /** @@ -120,6 +121,12 @@ */ function open(editor) { + /* keyboard shortcuts will be overwritten by the new infinite editor + so we need to store the shortcuts for the current editor so they can be rebound + when the infinite editor closes + */ + storeKeyboardShortcuts(); + // set flag so we know when the editor is open in "infinie mode" editor.infiniteMode = true; @@ -142,9 +149,9 @@ * Method to close the latest opened editor */ function close() { - var length = editors.length; - var closedEditor = editors[length - 1]; + // close last opened editor + const closedEditor = editors[editors.length - 1]; editors.splice(-1, 1); var args = { @@ -152,7 +159,12 @@ editor: closedEditor }; + // emit event to let components know an editor has been removed eventsService.emit("appState.editors.close", args); + + // rebind keyboard shortcuts for the new editor in focus + rebindKeyboardShortcuts(); + } /** @@ -652,6 +664,46 @@ open(editor); } + /////////////////////// + + /** + * @ngdoc method + * @name umbraco.services.editorService#storeKeyboardShortcuts + * @methodOf umbraco.services.editorService + * + * @description + * Internal method to keep track of keyboard shortcuts registered + * to each editor so they can be rebound when an editor closes + * + */ + function storeKeyboardShortcuts() { + const shortcuts = angular.copy(keyboardService.keyboardEvent); + editorsKeyboardShorcuts.push(shortcuts); + } + + /** + * @ngdoc method + * @name umbraco.services.editorService#rebindKeyboardShortcuts + * @methodOf umbraco.services.editorService + * + * @description + * Internal method to rebind keyboard shortcuts for the editor in focus + * + */ + function rebindKeyboardShortcuts() { + // find the shortcuts from the previous editor + const lastSetOfShortcutsIndex = editorsKeyboardShorcuts.length - 1; + var lastSetOfShortcuts = editorsKeyboardShorcuts[lastSetOfShortcutsIndex]; + + // rebind shortcuts + for (let [key, value] of Object.entries(lastSetOfShortcuts)) { + keyboardService.bind(key, value.callback, value.opt); + } + + // remove the shortcuts from the collection + editorsKeyboardShorcuts.splice(lastSetOfShortcutsIndex, 1); + } + var service = { getEditors: getEditors, getNumberOfEditors: getNumberOfEditors, From 88d328a4e962cf0d9a77298a35d6e2e981d63109 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 18 Dec 2018 13:40:37 +0100 Subject: [PATCH 11/44] unbind all keyboard shortcuts from the underlaying editor so only keyboard shortcuts from the open editor works --- .../src/common/services/editor.service.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 5985cc99b1..541cc9aba3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -125,7 +125,7 @@ so we need to store the shortcuts for the current editor so they can be rebound when the infinite editor closes */ - storeKeyboardShortcuts(); + unbindKeyboardShortcuts(); // set flag so we know when the editor is open in "infinie mode" editor.infiniteMode = true; @@ -676,9 +676,15 @@ * to each editor so they can be rebound when an editor closes * */ - function storeKeyboardShortcuts() { + function unbindKeyboardShortcuts() { const shortcuts = angular.copy(keyboardService.keyboardEvent); editorsKeyboardShorcuts.push(shortcuts); + + // unbind the current shortcuts because we only want to + // shortcuts from the newly opened editor working + for (let [key, value] of Object.entries(shortcuts)) { + keyboardService.unbind(key); + } } /** From 7047a07660546559088f207d2ff2f5b94b385433 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 00:03:49 +1100 Subject: [PATCH 12/44] Shows the culture for URLs if the node is invariant but it has cultural URLs (it exists underneath a variant) --- .../Routing/UrlsProviderWithDomainsTests.cs | 4 +- src/Umbraco.Web/Routing/DomainHelper.cs | 8 +- src/Umbraco.Web/Routing/UrlInfo.cs | 2 +- src/Umbraco.Web/Routing/UrlInfoComparer.cs | 66 ++++++++++++++ .../Routing/UrlProviderExtensions.cs | 89 +++++++++++++------ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 6 files changed, 141 insertions(+), 29 deletions(-) create mode 100644 src/Umbraco.Web/Routing/UrlInfoComparer.cs diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index 2f1bef3b9a..c7ce42e4bc 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -418,8 +418,8 @@ namespace Umbraco.Tests.Routing foreach (var x in result) Console.WriteLine(x); Assert.AreEqual(2, result.Length); - Assert.AreEqual(result[0].Text, "http://domain1a.com/en/1001-1-1/"); - Assert.AreEqual(result[1].Text, "http://domain1b.com/en/1001-1-1/"); + Assert.AreEqual(result[0].Text, "http://domain1b.com/en/1001-1-1/"); + Assert.AreEqual(result[1].Text, "http://domain1a.com/en/1001-1-1/"); } } } diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index b6d79e788a..9b300009d0 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -207,8 +207,14 @@ namespace Umbraco.Web.Routing var cultureDomains = domainsAndUris.Where(x => x.Culture.Name.InvariantEquals(culture)).ToList(); if (cultureDomains.Count > 0) return cultureDomains; + // if a culture is supplied, we *want* a url for that culture, else fail - throw new InvalidOperationException($"Could not find a domain for culture \"{culture}\"."); + //throw new InvalidOperationException($"Could not find a domain for culture \"{culture}\"."); + //fixme: Review - throwing here causes a problem because the UrlProviderExtensions.GetContentUrls iterates through + // ALL cultures even if those cultures are not assigned for use within a branch. Returning null + // here fixes that problem and the URLs resolve correctly, however i don't know if this is causing other + // residual problems. It would also suggest that below in GetByCulture we don't throw either but instead return null?? + return null; } if (defaultCulture != null) // try the defaultCulture culture diff --git a/src/Umbraco.Web/Routing/UrlInfo.cs b/src/Umbraco.Web/Routing/UrlInfo.cs index a795f1577b..ae5c4b412c 100644 --- a/src/Umbraco.Web/Routing/UrlInfo.cs +++ b/src/Umbraco.Web/Routing/UrlInfo.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - private UrlInfo(string text, bool isUrl, string culture) + public UrlInfo(string text, bool isUrl, string culture) { if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(text)); IsUrl = isUrl; diff --git a/src/Umbraco.Web/Routing/UrlInfoComparer.cs b/src/Umbraco.Web/Routing/UrlInfoComparer.cs new file mode 100644 index 0000000000..ff41dc6bc9 --- /dev/null +++ b/src/Umbraco.Web/Routing/UrlInfoComparer.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Web.Routing +{ + /// + /// Compares + /// + public class UrlInfoComparer : IEqualityComparer + { + private readonly bool _variesByCulture; + + public UrlInfoComparer(bool variesByCulture) + { + _variesByCulture = variesByCulture; + } + + /// + /// Determines equality between + /// + /// + /// + /// + /// + /// If variesByCulture is true, then culture is compared, otherwise culture is not compared. + /// Both culture and url are compared without case sensitivity. + /// + public bool Equals(UrlInfo x, UrlInfo y) + { + if (ReferenceEquals(null, y)) return false; + if (ReferenceEquals(null, x)) return false; + if (ReferenceEquals(x, y)) return true; + + if (_variesByCulture) + { + return string.Equals(x.Culture, y.Culture, StringComparison.InvariantCultureIgnoreCase) + && x.IsUrl == y.IsUrl + && string.Equals(x.Text, y.Text, StringComparison.InvariantCultureIgnoreCase); + } + + return x.IsUrl == y.IsUrl + && string.Equals(x.Text, y.Text, StringComparison.InvariantCultureIgnoreCase); + } + + /// + /// Calculates a hash code + /// + /// + /// + /// + /// If variesByCulture is true then culture is used in the calculation, otherwise it's not + /// + public int GetHashCode(UrlInfo obj) + { + unchecked + { + var hashCode = (obj.Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.Text) : 0); + hashCode = (hashCode * 397) ^ obj.IsUrl.GetHashCode(); + if (_variesByCulture) + hashCode = (hashCode * 397) ^ (obj.Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.Culture) : 0); + return hashCode; + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 3a0caeec78..71a0c294ab 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.Routing /// Use when displaying Urls. If errors occur when generating the Urls, they will show in the list. /// Contains all the Urls that we can figure out (based upon domains, etc). /// - public static IEnumerable GetContentUrls(this IContent content, + public static IEnumerable GetContentUrls(this IContent content, PublishedRouter publishedRouter, UmbracoContext umbracoContext, ILocalizationService localizationService, @@ -44,12 +44,57 @@ namespace Umbraco.Web.Routing // - the 'main' urls, which is what .Url would return, for each culture // - the 'other' urls we know (based upon domains, etc) // - // need to work on each culture. - // on invariant trees, each culture return the same thing - // but, we don't know if the tree to this content is invariant + // need to work through each installed culture. + // fixme: Why not just work with each culture assigned to domains in the branch? + // on invariant nodes, each culture returns the same url segment + // but, we don't know if the branch to this content is invariant so we need to ask + // for URLs for all cultures. var cultures = localizationService.GetAllLanguages().Select(x => x.IsoCode).ToList(); + //get all URLs for all cultures + foreach (var cultureUrl in GetContentUrlsByCulture(content, cultures, publishedRouter, umbracoContext, contentService, textService, logger)) + { + urls.Add(cultureUrl); + } + + //return the real urls first, then the messages + foreach (var urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) + { + //in some cases there will be the same URL for multiple cultures: + // * The entire branch is invariant + // * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed + + foreach (var dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant())) + yield return dUrl; + } + + // get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless. + // for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them. + foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) + if (urls.Add(otherUrl)) //avoid duplicates + yield return otherUrl; + } + + /// + /// Tries to return a for each culture for the content while detecting collisions/errors + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static IEnumerable GetContentUrlsByCulture(IContent content, + IEnumerable cultures, + PublishedRouter publishedRouter, + UmbracoContext umbracoContext, + IContentService contentService, + ILocalizedTextService textService, + ILogger logger) + { foreach (var culture in cultures) { // if content is variant, and culture is not published, skip @@ -73,34 +118,26 @@ namespace Umbraco.Web.Routing { // deal with 'could not get the url' case "#": - HandleCouldNotGetUrl(content, culture, urls, contentService, textService); + yield return HandleCouldNotGetUrl(content, culture, contentService, textService); break; // deal with exceptions case "#ex": - urls.Add(UrlInfo.Message(textService.Localize("content/getUrlException"), culture)); + yield return UrlInfo.Message(textService.Localize("content/getUrlException"), culture); break; // got a url, deal with collisions, add url default: - if (!DetectCollision(content, url, urls, culture, umbracoContext, publishedRouter, textService)) // detect collisions, etc - urls.Add(UrlInfo.Url(url, culture)); + if (DetectCollision(content, url, culture, umbracoContext, publishedRouter, textService, out var urlInfo)) // detect collisions, etc + yield return urlInfo; + else + yield return UrlInfo.Url(url, culture); break; } } - - //return the real urls first, then the messages - foreach (var urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) - foreach (var url in urlGroup) - yield return url; - - // get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless. - // for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them. - foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) - yield return otherUrl; } - private static void HandleCouldNotGetUrl(IContent content, string culture, ICollection urls, IContentService contentService, ILocalizedTextService textService) + private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService) { // document has a published version yet its url is "#" => a parent must be // unpublished, walk up the tree until we find it, and report. @@ -112,16 +149,16 @@ namespace Umbraco.Web.Routing while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); if (parent == null) // oops, internal error - urls.Add(UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture)); + return UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture); else if (!parent.Published) // totally not published - urls.Add(UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] { parent.Name }), culture)); + return UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] {parent.Name}), culture); else // culture not published - urls.Add(UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] { parent.Name }), culture)); + return UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] {parent.Name}), culture); } - private static bool DetectCollision(IContent content, string url, ICollection urls, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService) + private static bool DetectCollision(IContent content, string url, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService, out UrlInfo urlInfo) { // test for collisions on the 'main' url var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); @@ -130,9 +167,11 @@ namespace Umbraco.Web.Routing var pcr = publishedRouter.CreateRequest(umbracoContext, uri); publishedRouter.TryRouteRequest(pcr); + urlInfo = null; + if (pcr.HasPublishedContent == false) { - urls.Add(UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture)); + urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture); return true; } @@ -151,7 +190,7 @@ namespace Umbraco.Web.Routing l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; - urls.Add(UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture)); + urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture); return true; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 699c985af3..6700c0b182 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -167,6 +167,7 @@ + From 36fd32ee9a8a9613c173b5658be67687140e9f56 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 00:04:35 +1100 Subject: [PATCH 13/44] remove unused comparer --- src/Umbraco.Web/Routing/UrlInfoComparer.cs | 66 ---------------------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 2 files changed, 67 deletions(-) delete mode 100644 src/Umbraco.Web/Routing/UrlInfoComparer.cs diff --git a/src/Umbraco.Web/Routing/UrlInfoComparer.cs b/src/Umbraco.Web/Routing/UrlInfoComparer.cs deleted file mode 100644 index ff41dc6bc9..0000000000 --- a/src/Umbraco.Web/Routing/UrlInfoComparer.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Web.Routing -{ - /// - /// Compares - /// - public class UrlInfoComparer : IEqualityComparer - { - private readonly bool _variesByCulture; - - public UrlInfoComparer(bool variesByCulture) - { - _variesByCulture = variesByCulture; - } - - /// - /// Determines equality between - /// - /// - /// - /// - /// - /// If variesByCulture is true, then culture is compared, otherwise culture is not compared. - /// Both culture and url are compared without case sensitivity. - /// - public bool Equals(UrlInfo x, UrlInfo y) - { - if (ReferenceEquals(null, y)) return false; - if (ReferenceEquals(null, x)) return false; - if (ReferenceEquals(x, y)) return true; - - if (_variesByCulture) - { - return string.Equals(x.Culture, y.Culture, StringComparison.InvariantCultureIgnoreCase) - && x.IsUrl == y.IsUrl - && string.Equals(x.Text, y.Text, StringComparison.InvariantCultureIgnoreCase); - } - - return x.IsUrl == y.IsUrl - && string.Equals(x.Text, y.Text, StringComparison.InvariantCultureIgnoreCase); - } - - /// - /// Calculates a hash code - /// - /// - /// - /// - /// If variesByCulture is true then culture is used in the calculation, otherwise it's not - /// - public int GetHashCode(UrlInfo obj) - { - unchecked - { - var hashCode = (obj.Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.Text) : 0); - hashCode = (hashCode * 397) ^ obj.IsUrl.GetHashCode(); - if (_variesByCulture) - hashCode = (hashCode * 397) ^ (obj.Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.Culture) : 0); - return hashCode; - } - - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6700c0b182..699c985af3 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -167,7 +167,6 @@ - From abb4ab6b0aba36d1ab2cacf5215afab610c86586 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 18 Dec 2018 21:56:06 +0100 Subject: [PATCH 14/44] fix open template/open doctype buttons --- .../content/umbcontentnodeinfo.directive.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 31e847f0f6..5da37ff64e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -21,14 +21,11 @@ scope.isInfiniteMode = editorService.getNumberOfEditors() > 0 ? true : false; userService.getCurrentUser().then(function(user){ - // only allow change of media type if user has access to the settings sections - angular.forEach(user.sections, function(section){ - if(section.alias === "settings" && !scope.isInfiniteMode) { - scope.allowChangeDocumentType = true; - scope.allowChangeTemplate = true; - } - }); - }); + // only allow change of media type if user has access to the settings sections + const hasAccessToSettings = user.allowedSections.indexOf("settings") !== -1 ? true : false; + scope.allowChangeDocumentType = hasAccessToSettings; + scope.allowChangeTemplate = hasAccessToSettings; + }); var keys = [ "general_deleted", From 72d42e42281a76bbdedb5f8901c7abf88045883a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 15:58:18 +1100 Subject: [PATCH 15/44] updates the UI to show culture when the item is not variant and shows the unpublished status if the culture has no variants --- .../content/umbcontentnodeinfo.directive.js | 15 ++++++++++++++- .../components/content/umb-content-node-info.html | 8 +++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 7b56bb2bd0..4feea82d47 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -53,7 +53,8 @@ "prompt_unsavedChanges", "prompt_doctypeChangeWarning", "general_history", - "auditTrails_historyIncludingVariants" + "auditTrails_historyIncludingVariants", + "content_itemNotPublished" ]; localizationService.localizeMany(keys) @@ -65,11 +66,23 @@ labels.notCreated = data[4]; labels.unsavedChanges = data[5]; labels.doctypeChangeWarning = data[6]; + labels.notPublished = data[9]; scope.historyLabel = scope.node.variants && scope.node.variants.length === 1 ? data[7] : data[8]; setNodePublishStatus(); + if (scope.currentUrls.length === 0) { + if (scope.node.id > 0) { + //it's created but not published + scope.currentUrls.push({ text: labels.notPublished, isUrl: false }); + } + else { + //it's new + scope.currentUrls.push({ text: labels.notCreated, isUrl: false }) + } + } + }); scope.auditTrailOptions = { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index b37e3c7ba4..b1757d927f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -8,13 +8,19 @@ From 6a8c7d1a3bf1323323bcae8e0caefbe2bd56cd82 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 17:13:46 +1100 Subject: [PATCH 16/44] Writes some tests, fixes a bug, orders results, writes notes --- .../Routing/GetContentUrlsTests.cs | 136 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Routing/UrlProviderExtensions.cs | 7 +- 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Tests/Routing/GetContentUrlsTests.cs diff --git a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs new file mode 100644 index 0000000000..a2a627227e --- /dev/null +++ b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs @@ -0,0 +1,136 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Web.Routing; + +namespace Umbraco.Tests.Routing +{ + [TestFixture] + public class GetContentUrlsTests : UrlRoutingTestBase + { + private IUmbracoSettingsSection _umbracoSettings; + + public override void SetUp() + { + base.SetUp(); + + //generate new mock settings and assign so we can configure in individual tests + _umbracoSettings = SettingsForTests.GenerateMockUmbracoSettings(); + SettingsForTests.ConfigureSettings(_umbracoSettings); + } + + private ILocalizedTextService GetTextService() + { + var textService = Mock.Of( + x => x.Localize("content/itemNotPublished", + It.IsAny(), + It.IsAny>()) == "content/itemNotPublished"); + return textService; + } + + private ILocalizationService GetLangService(params string[] isoCodes) + { + var allLangs = isoCodes + .Select(CultureInfo.GetCultureInfo) + .Select(culture => new Language(culture.Name) + { + CultureName = culture.DisplayName, + IsDefault = true, + IsMandatory = true + }).ToArray(); + + var langService = Mock.Of(x => x.GetAllLanguages() == allLangs); + return langService; + } + + [Test] + public void Content_Not_Published() + { + var contentType = MockedContentTypes.CreateBasicContentType(); + var content = MockedContent.CreateBasicContent(contentType); + content.Id = 1046; //fixme: we are using this ID only because it's built into the test XML published cache + content.Path = "-1,1046"; + + var umbContext = GetUmbracoContext("http://localhost:8000"); + var publishedRouter = CreatePublishedRouter(Container, + contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(Logger) })); + var urls = content.GetContentUrls(publishedRouter, + umbContext, + GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, + Logger).ToList(); + + Assert.AreEqual(1, urls.Count); + Assert.AreEqual("content/itemNotPublished", urls[0].Text); + Assert.IsFalse(urls[0].IsUrl); + } + + [Test] + public void Invariant_Root_Content_Published_No_Domains() + { + var contentType = MockedContentTypes.CreateBasicContentType(); + var content = MockedContent.CreateBasicContent(contentType); + content.Id = 1046; //fixme: we are using this ID only because it's built into the test XML published cache + content.Path = "-1,1046"; + content.Published = true; + + var umbContext = GetUmbracoContext("http://localhost:8000", + urlProviders: new []{ new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper()) }); + var publishedRouter = CreatePublishedRouter(Container, + contentFinders:new ContentFinderCollection(new[]{new ContentFinderByUrl(Logger) })); + var urls = content.GetContentUrls(publishedRouter, + umbContext, + GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, + Logger).ToList(); + + Assert.AreEqual(1, urls.Count); + Assert.AreEqual("/home/", urls[0].Text); + Assert.AreEqual("en-US", urls[0].Culture); + Assert.IsTrue(urls[0].IsUrl); + } + + [Test] + public void Invariant_Child_Content_Published_No_Domains() + { + var contentType = MockedContentTypes.CreateBasicContentType(); + var parent = MockedContent.CreateBasicContent(contentType); + parent.Id = 1046; //fixme: we are using this ID only because it's built into the test XML published cache + parent.Name = "home"; + parent.Path = "-1,1046"; + parent.Published = true; + var child = MockedContent.CreateBasicContent(contentType); + child.Name = "sub1"; + child.Id = 1173; //fixme: we are using this ID only because it's built into the test XML published cache + child.Path = "-1,1046,1173"; + child.Published = true; + + var umbContext = GetUmbracoContext("http://localhost:8000", + urlProviders: new[] { new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper()) }); + var publishedRouter = CreatePublishedRouter(Container, + contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(Logger) })); + var urls = child.GetContentUrls(publishedRouter, + umbContext, + GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, + Logger).ToList(); + + Assert.AreEqual(1, urls.Count); + Assert.AreEqual("/home/sub1/", urls[0].Text); + Assert.AreEqual("en-US", urls[0].Culture); + Assert.IsTrue(urls[0].IsUrl); + } + + //TODO: We need a lot of tests here, the above was just to get started with being able to unit test this method + // * variant URLs without domains assigned, what happens? + // * variant URLs with domains assigned, but also having more languages installed than there are domains/cultures assigned + // * variant URLs with an ancestor culture unpublished + // * invariant URLs with ancestors as variants + // * ... probably a lot more + + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index baeb667fcc..ed2324e02c 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -132,6 +132,7 @@ + diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 71a0c294ab..de9314704b 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -35,7 +35,10 @@ namespace Umbraco.Web.Routing if (logger == null) throw new ArgumentNullException(nameof(logger)); if (content.Published == false) + { yield return UrlInfo.Message(textService.Localize("content/itemNotPublished")); + yield break; + } var urls = new HashSet(); @@ -65,13 +68,13 @@ namespace Umbraco.Web.Routing // * The entire branch is invariant // * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed - foreach (var dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant())) + foreach (var dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture)) yield return dUrl; } // get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless. // for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them. - foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) + foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture)) if (urls.Add(otherUrl)) //avoid duplicates yield return otherUrl; } From 0ad66c488bede675355bf7d5984cecb3b1d36728 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 22:06:04 +1100 Subject: [PATCH 17/44] Long live Operathor --- .../Editors/TemplateQueryController.cs | 32 +++++++++---------- .../{Operathor.cs => Operator.cs} | 2 +- .../{OperathorTerm.cs => OperatorTerm.cs} | 12 +++---- .../Models/TemplateQuery/QueryCondition.cs | 20 ++++++------ src/Umbraco.Web/Umbraco.Web.csproj | 4 +-- 5 files changed, 35 insertions(+), 35 deletions(-) rename src/Umbraco.Web/Models/TemplateQuery/{Operathor.cs => Operator.cs} (90%) rename src/Umbraco.Web/Models/TemplateQuery/{OperathorTerm.cs => OperatorTerm.cs} (56%) diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 0d0438138a..3122390047 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -18,26 +18,26 @@ namespace Umbraco.Web.Editors [JsonCamelCaseFormatter] public class TemplateQueryController : UmbracoAuthorizedJsonController { - private IEnumerable Terms + private IEnumerable Terms { get { - return new List() + return new List() { - new OperathorTerm(Services.TextService.Localize("template/is"), Operathor.Equals, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/isNot"), Operathor.NotEquals, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/before"), Operathor.LessThan, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/beforeIncDate"), Operathor.LessThanEqualTo, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/after"), Operathor.GreaterThan, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/afterIncDate"), Operathor.GreaterThanEqualTo, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/equals"), Operathor.Equals, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/doesNotEqual"), Operathor.NotEquals, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/contains"), Operathor.Contains, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/doesNotContain"), Operathor.NotContains, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/greaterThan"), Operathor.GreaterThan, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/greaterThanEqual"), Operathor.GreaterThanEqualTo, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/lessThan"), Operathor.LessThan, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/lessThanEqual"), Operathor.LessThanEqualTo, new [] {"int"}) + new OperatorTerm(Services.TextService.Localize("template/is"), Operator.Equals, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/isNot"), Operator.NotEquals, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/before"), Operator.LessThan, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/beforeIncDate"), Operator.LessThanEqualTo, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/after"), Operator.GreaterThan, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/afterIncDate"), Operator.GreaterThanEqualTo, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/equals"), Operator.Equals, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/doesNotEqual"), Operator.NotEquals, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/contains"), Operator.Contains, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/doesNotContain"), Operator.NotContains, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/greaterThan"), Operator.GreaterThan, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/greaterThanEqual"), Operator.GreaterThanEqualTo, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/lessThan"), Operator.LessThan, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/lessThanEqual"), Operator.LessThanEqualTo, new [] {"int"}) }; } } diff --git a/src/Umbraco.Web/Models/TemplateQuery/Operathor.cs b/src/Umbraco.Web/Models/TemplateQuery/Operator.cs similarity index 90% rename from src/Umbraco.Web/Models/TemplateQuery/Operathor.cs rename to src/Umbraco.Web/Models/TemplateQuery/Operator.cs index 561caec362..135c43507e 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/Operathor.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/Operator.cs @@ -1,6 +1,6 @@ namespace Umbraco.Web.Models.TemplateQuery { - public enum Operathor + public enum Operator { Equals = 1, NotEquals = 2, diff --git a/src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs b/src/Umbraco.Web/Models/TemplateQuery/OperatorTerm.cs similarity index 56% rename from src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs rename to src/Umbraco.Web/Models/TemplateQuery/OperatorTerm.cs index c14e1854aa..086f0ff818 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/OperatorTerm.cs @@ -2,24 +2,24 @@ namespace Umbraco.Web.Models.TemplateQuery { - public class OperathorTerm + public class OperatorTerm { - public OperathorTerm() + public OperatorTerm() { Name = "is"; - Operathor = Operathor.Equals; + Operator = Operator.Equals; AppliesTo = new [] { "string" }; } - public OperathorTerm(string name, Operathor operathor, IEnumerable appliesTo) + public OperatorTerm(string name, Operator @operator, IEnumerable appliesTo) { Name = name; - Operathor = operathor; + Operator = @operator; AppliesTo = appliesTo; } public string Name { get; set; } - public Operathor Operathor { get; set; } + public Operator Operator { get; set; } public IEnumerable AppliesTo { get; set; } } } diff --git a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs index 8ba943756f..9c5f2c80c0 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs @@ -4,7 +4,7 @@ { public PropertyModel Property { get; set; } - public OperathorTerm Term { get; set; } + public OperatorTerm Term { get; set; } public string ConstraintValue { get; set; } } @@ -53,30 +53,30 @@ } - switch (condition.Term.Operathor) + switch (condition.Term.Operator) { - case Operathor.Equals: + case Operator.Equals: operand = " == "; break; - case Operathor.NotEquals: + case Operator.NotEquals: operand = " != "; break; - case Operathor.GreaterThan: + case Operator.GreaterThan: operand = " > "; break; - case Operathor.GreaterThanEqualTo: + case Operator.GreaterThanEqualTo: operand = " >= "; break; - case Operathor.LessThan: + case Operator.LessThan: operand = " < "; break; - case Operathor.LessThanEqualTo: + case Operator.LessThanEqualTo: operand = " <= "; break; - case Operathor.Contains: + case Operator.Contains: value = string.Format("{0}{1}.Contains({2})", prefix, condition.Property.Alias, constraintValue); break; - case Operathor.NotContains: + case Operator.NotContains: value = string.Format("!{0}{1}.Contains({2})", prefix, condition.Property.Alias, constraintValue); break; default : diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 699c985af3..c7e8b0d109 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -685,8 +685,8 @@ - - + + From f72474822574554786dbd56994bb648c4d5dd538 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 23:27:47 +1100 Subject: [PATCH 18/44] No more IUserService.SavePassword - passwords are managed with Identity. --- src/Umbraco.Core/Services/IUserService.cs | 10 -------- .../Services/Implement/UserService.cs | 24 ------------------- .../Editors/TemplateQueryController.cs | 1 + 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index cb2d90aa63..a926ce32aa 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -90,16 +90,6 @@ namespace Umbraco.Core.Services string[] userGroups = null, string filter = null); - /// - /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method - /// - /// - /// This method exists so that Umbraco developers can use one entry point to create/update users if they choose to. - /// - /// The user to save the password for - /// The password to save - void SavePassword(IUser user, string password); - /// /// Deletes or disables a User /// diff --git a/src/Umbraco.Core/Services/Implement/UserService.cs b/src/Umbraco.Core/Services/Implement/UserService.cs index 44358caa84..188c6feb04 100644 --- a/src/Umbraco.Core/Services/Implement/UserService.cs +++ b/src/Umbraco.Core/Services/Implement/UserService.cs @@ -227,30 +227,6 @@ namespace Umbraco.Core.Services.Implement Save(membershipUser); } - [Obsolete("ASP.NET Identity APIs like the BackOfficeUserManager should be used to manage passwords, this will not work with correct security practices because you would need the existing password")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void SavePassword(IUser user, string password) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - var provider = MembershipProviderExtensions.GetUsersMembershipProvider(); - - if (provider.IsUmbracoMembershipProvider() == false) - throw new NotSupportedException("When using a non-Umbraco membership provider you must change the user password by using the MembershipProvider.ChangePassword method"); - - provider.ChangePassword(user.Username, "", password); - - //go re-fetch the member and update the properties that may have changed - var result = GetByUsername(user.Username); - if (result != null) - { - //should never be null but it could have been deleted by another thread. - user.RawPasswordValue = result.RawPasswordValue; - user.LastPasswordChangeDate = result.LastPasswordChangeDate; - user.UpdateDate = result.UpdateDate; - } - } - /// /// Deletes or disables a User /// diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 3122390047..00e124cb29 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -67,6 +67,7 @@ namespace Umbraco.Web.Editors sb.Append("Model.Root()"); + //fixme: This timer thing is not correct, it's definitely not timing the resulting query, the timer really isn't important and might as well be removed var timer = new Stopwatch(); timer.Start(); From cf56dbd34e43628c9b1bbc15489e048e54c96800 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 19 Dec 2018 13:43:58 +0100 Subject: [PATCH 19/44] add more docs for infinite editing - how to create a custom infinite editor --- .../src/common/services/editor.service.js | 114 +++++++++++++++--- 1 file changed, 99 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 541cc9aba3..0d6432b01f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -5,29 +5,29 @@ * @description * Added in Umbraco 8.0. Application-wide service for handling infinite editing. * + * * + * +

Open a build-in infinite editor (media picker)

Markup example

-    
- - - +
+

Controller example

     (function () {
-
         "use strict";
 
-        function Controller() {
+        function MediaPickerController(editorService) {
 
             var vm = this;
 
-            vm.open = open;
+            vm.openMediaPicker = openMediaPicker;
 
-            function open() {
+            function openMediaPicker() {
                 var mediaPickerOptions = {
                     multiPicker: true,
                     submit: function(model) {
@@ -36,22 +36,29 @@
                     close: function() {
                         editorService.close();
                     }
-                }
+                };
                 editorService.mediaPicker(mediaPickerOptions);
             };
         }
 
-        angular.module("umbraco").controller("My.Controller", Controller);
+        angular.module("umbraco").controller("My.MediaPickerController", MediaPickerController);
     })();
 
-

Custom view example

+

Building a custom infinite editor

+

Open the custom infinite editor (Markup)

+
+    
+ +
+
+ +

Open the custom infinite editor (Controller)

     (function () {
-
         "use strict";
 
-        function Controller() {
+        function Controller(editorService) {
 
             var vm = this;
 
@@ -59,14 +66,15 @@
 
             function open() {
                 var options = {
-                    view: "path/to/view.html"
+                    title: "My custom infinite editor",
+                    view: "path/to/view.html",
                     submit: function(model) {
                         editorService.close();
                     },
                     close: function() {
                         editorService.close();
                     }
-                }
+                };
                 editorService.open(options);
             };
         }
@@ -74,7 +82,83 @@
         angular.module("umbraco").controller("My.Controller", Controller);
     })();
 
+ +

The custom infinite editor view

+When building a custom infinite editor view you can use the same components as a normal editor ({@link umbraco.directives.directive:umbEditorView umbEditorView}). +
+    
+ + + + + + + + + + {{model | json}} + + + + + + + + + + + + + + + +
+
+ +

The custom infinite editor controller

+
+    (function () {
+        "use strict";
+
+        function InfiniteEditorController($scope) {
+
+            var vm = this;
+
+            vm.submit = submit;
+            vm.close = close;
+
+            function submit() {
+                if($scope.model.submit) {
+                    $scope.model.submit($scope.model);
+                }
+            }
+
+            function close() {
+                if($scope.model.close) {
+                    $scope.model.close();
+                }
+            }
+
+        }
+
+        angular.module("umbraco").controller("My.InfiniteEditorController", InfiniteEditorController);
+    })();
+
*/ + (function () { "use strict"; From b81f2512cb8a5c91210b4098848364549b2a142e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 19 Dec 2018 15:01:18 +0100 Subject: [PATCH 20/44] Fix the disappearing preview/save/publish buttons in listviews (#3906) --- .../components/content/edit.controller.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index f1e2150579..8564cf2c43 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -24,16 +24,19 @@ $scope.page.hideActionsMenu = infiniteMode ? true : false; $scope.page.hideChangeVariant = infiniteMode ? true : false; $scope.allowOpen = true; + $scope.app = null; function init(content) { - - // set first app to active - content.apps[0].active = true; + if (!$scope.app) { + // set first app to active + content.apps[0].active = true; + $scope.app = content.apps[0]; + } if (infiniteMode) { createInfiniteModeButtons(content); } else { - createButtons(content, content.apps[0]); + createButtons(content); } editorState.set($scope.content); @@ -146,11 +149,11 @@ * @param {any} content the content node * @param {any} app the active content app */ - function createButtons(content, app) { + function createButtons(content) { // only create the save/publish/preview buttons if the // content app is "Conent" - if (app && app.alias !== "umbContent" && app.alias !== "umbInfo") { + if ($scope.app && $scope.app.alias !== "umbContent" && $scope.app.alias !== "umbInfo") { $scope.defaultButton = null; $scope.subButtons = null; $scope.page.showSaveButton = false; @@ -899,7 +902,8 @@ * @param {any} app */ $scope.appChanged = function (app) { - createButtons($scope.content, app); + $scope.app = app; + createButtons($scope.content); }; // methods for infinite editing From 7b804e27ad2a6c7d90f6e78a2c7d7fdfba0972d7 Mon Sep 17 00:00:00 2001 From: Thomas Morris Date: Fri, 14 Dec 2018 12:20:04 +0000 Subject: [PATCH 21/44] Removes umbraco.aspx --- .../Configurations/GlobalSettingsTests.cs | 3 +- .../CoreThings/UriExtensionsTests.cs | 6 +-- .../Routing/UmbracoModuleTests.cs | 3 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 8 ---- src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 4 +- src/Umbraco.Web.UI/Umbraco/umbraco.aspx | 2 - src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs | 19 --------- .../Umbraco/umbraco.aspx.designer.cs | 15 ------- src/Umbraco.Web.UI/default.aspx | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 3 -- .../umbraco/Default.aspx.cs | 39 ------------------- 11 files changed, 7 insertions(+), 97 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/umbraco.aspx delete mode 100644 src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs diff --git a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs index ebba8bc1cc..34fb2add8b 100644 --- a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs +++ b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs @@ -52,8 +52,7 @@ namespace Umbraco.Tests.Configurations SystemDirectories.Root = rootPath; Assert.AreEqual(outcome, UmbracoConfig.For.GlobalSettings().GetUmbracoMvcArea()); } - - [TestCase("/umbraco/umbraco.aspx")] + [TestCase("/umbraco/editContent.aspx")] [TestCase("/install/default.aspx")] [TestCase("/install/")] diff --git a/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs index 7f00bed123..f253d44973 100644 --- a/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs @@ -32,8 +32,7 @@ namespace Umbraco.Tests.CoreThings [TestCase("http://www.domain.com/umbraco/test/test.js", "", true)] [TestCase("http://www.domain.com/umbrac", "", false)] [TestCase("http://www.domain.com/test", "", false)] - [TestCase("http://www.domain.com/test/umbraco", "", false)] - [TestCase("http://www.domain.com/test/umbraco.aspx", "", false)] + [TestCase("http://www.domain.com/test/umbraco", "", false)] [TestCase("http://www.domain.com/Umbraco/restServices/blah", "", true)] [TestCase("http://www.domain.com/Umbraco/Backoffice/blah", "", true)] [TestCase("http://www.domain.com/Umbraco/anything", "", true)] @@ -62,8 +61,7 @@ namespace Umbraco.Tests.CoreThings [TestCase("http://www.domain.com/install/test/test.js", true)] [TestCase("http://www.domain.com/instal", false)] [TestCase("http://www.domain.com/umbraco", false)] - [TestCase("http://www.domain.com/umbraco/umbraco", false)] - [TestCase("http://www.domain.com/test/umbraco.aspx", false)] + [TestCase("http://www.domain.com/umbraco/umbraco", false)] public void Is_Installer_Request(string input, bool expected) { var source = new Uri(input); diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index 5f42c8d3ae..69533c3c77 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -62,8 +62,7 @@ namespace Umbraco.Tests.Routing // do not test for /base here as it's handled before EnsureUmbracoRoutablePage is called [TestCase("/umbraco_client/Tree/treeIcons.css", false)] - [TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", false)] - [TestCase("/umbraco/umbraco.aspx", false)] + [TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", false)] [TestCase("/umbraco/editContent.aspx", false)] [TestCase("/install/default.aspx", false)] [TestCase("/install/?installStep=license", false)] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 5751e9155c..b4a020deea 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -218,14 +218,6 @@ - - - umbraco.aspx - ASPXCodeBehind - - - umbraco.aspx - diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index b2296b1ccf..0edabebb76 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -881,7 +881,7 @@ das Dokument '%1%' wurde von '%2%' zur Übersetzung in '%5%' freigegeben. Zum Bearbeiten verwenden Sie bitte diesen Link: http://%3%/translation/details.aspx?id=%4%. -Sie können sich auch alle anstehenden Übersetzungen gesammelt im Umbraco-Verwaltungsbereich anzeigen lassen: http://%3%/Umbraco.aspx +Sie können sich auch alle anstehenden Übersetzungen gesammelt im Umbraco-Verwaltungsbereich anzeigen lassen: http://%3%/umbraco Einen schönen Tag wünscht Ihr freundlicher Umbraco-Robot @@ -977,4 +977,4 @@ Ihr freundlicher Umbraco-Robot Ihr Verlauf Sitzung läuft ab in - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx b/src/Umbraco.Web.UI/Umbraco/umbraco.aspx deleted file mode 100644 index 6e70513afd..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx +++ /dev/null @@ -1,2 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="umbraco.aspx.cs" Inherits="Umbraco.Web.UI.Umbraco.umbraco" %> - diff --git a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs b/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs deleted file mode 100644 index b70378bb01..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.UI; -using System.Web.UI.WebControls; -using Umbraco.Core.Configuration; - -namespace Umbraco.Web.UI.Umbraco -{ - public partial class umbraco : System.Web.UI.Page - { - protected void Page_Load(object sender, EventArgs e) - { - Response.Status = "301 Moved Permanently"; - Response.AddHeader("Location", UmbracoConfig.For.GlobalSettings().Path); - } - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs b/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs deleted file mode 100644 index c9a577fb34..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs +++ /dev/null @@ -1,15 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco { - - - public partial class umbraco { - } -} diff --git a/src/Umbraco.Web.UI/default.aspx b/src/Umbraco.Web.UI/default.aspx index 75b0e5d28c..a41d1eccf7 100644 --- a/src/Umbraco.Web.UI/default.aspx +++ b/src/Umbraco.Web.UI/default.aspx @@ -1,2 +1,2 @@ -<%@ Page language="c#" Codebehind="default.aspx.cs" AutoEventWireup="True" Inherits="umbraco.UmbracoDefault" trace="true" validateRequest="false" %> +<%@ Page language="c#" AutoEventWireup="True" Inherits="umbraco.UmbracoDefault" trace="true" validateRequest="false" %> diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c7e8b0d109..ea3ff32220 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1166,9 +1166,6 @@ ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs deleted file mode 100644 index f1a05c1185..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.Mvc; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; - -namespace umbraco -{ - /// - /// Summary description for _Default. - /// - public partial class _Default : System.Web.UI.Page - { - protected void Page_Load(object sender, System.EventArgs e) - { - //var mvcHandler = new MvcHandler() - //Server.TransferRequest(); - //Server.Transfer("~/Umbraco/Default"); - //Server.Transfer("umbraco.aspx"); - // Put user code to initialize the page here - } - - /// - /// Form1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlForm Form1; - - } -} From b087b862e8ed82cb03c9a3d05f420f23cb23c663 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 26 Nov 2018 08:56:13 +0100 Subject: [PATCH 22/44] Make Nested Content support variant doctypes --- .../PropertyEditors/NestedContentPropertyEditor.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 07c5aac5bb..74c8744ead 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -175,13 +175,17 @@ namespace Umbraco.Web.PropertyEditors { // create a temp property with the value var tempProp = new Property(propType); - tempProp.SetValue(propValues[propAlias] == null ? null : propValues[propAlias].ToString()); + // if the property varies by culture, make sure we save using the current culture + var propCulture = propType.VariesByCulture() || propType.VariesByCultureAndSegment() + ? culture + : null; + tempProp.SetValue(propValues[propAlias] == null ? null : propValues[propAlias].ToString(), propCulture); // convert that temp property, and store the converted value var propEditor = _propertyEditors[propType.PropertyEditorAlias]; var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; var valEditor = propEditor.GetValueEditor(tempConfig); - var convValue = valEditor.ToEditor(tempProp, dataTypeService); + var convValue = valEditor.ToEditor(tempProp, dataTypeService, propCulture); propValues[propAlias] = convValue == null ? null : JToken.FromObject(convValue); } catch (InvalidOperationException) From c79f557e946598cb48c54bcbea61afc0c8d8d2d1 Mon Sep 17 00:00:00 2001 From: Thomas Morris Date: Fri, 14 Dec 2018 15:57:34 +0000 Subject: [PATCH 23/44] Remove ClientRedirect.aspx --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 - .../Umbraco/ClientRedirect.aspx | 64 ------------------- 2 files changed, 65 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/ClientRedirect.aspx diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index b4a020deea..8e76894e7a 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -213,7 +213,6 @@ - diff --git a/src/Umbraco.Web.UI/Umbraco/ClientRedirect.aspx b/src/Umbraco.Web.UI/Umbraco/ClientRedirect.aspx deleted file mode 100644 index 2a10d4d344..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/ClientRedirect.aspx +++ /dev/null @@ -1,64 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" Inherits="Umbraco.Web.UI.Pages.UmbracoEnsuredPage" %> -<%-- - This page is required because we cannot reload the angular app with a changed Hash since it just detects the hash and doesn't reload. - So this is used purely for a full reload of an angular app with a changed hash. ---%> - - - - Redirecting... - - - - Redirecting... - - From 65f7e44338118520193feb881ca1f535f32ee7de Mon Sep 17 00:00:00 2001 From: Tristan Thompson Date: Fri, 14 Dec 2018 13:11:36 +0000 Subject: [PATCH 24/44] Remove/Replace endPreview.aspx with MVC endpoint --- .../UmbracoSettings/ContentElement.cs | 2 +- .../UmbracoSettings/ContentElementTests.cs | 2 +- .../UmbracoSettings/umbracoSettings.config | 2 +- .../src/preview/preview.controller.js | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 - src/Umbraco.Web.UI/Umbraco/endPreview.aspx | 3 -- .../config/umbracoSettings.Release.config | 2 +- .../config/umbracoSettings.config | 2 +- src/Umbraco.Web/Editors/PreviewController.cs | 18 ++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 3 -- .../umbraco/endPreview.aspx.cs | 36 ------------------- 11 files changed, 24 insertions(+), 49 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/endPreview.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/endPreview.aspx.cs diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index d2236bab70..91627edb8b 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { internal class ContentElement : UmbracoConfigurationElement, IContentSection { - private const string DefaultPreviewBadge = @"In Preview Mode - click to end"; + private const string DefaultPreviewBadge = @"In Preview Mode - click to end"; [ConfigurationProperty("imaging")] internal ContentImagingElement Imaging => (ContentImagingElement) this["imaging"]; diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs index f1ac463305..962d6d13a9 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs @@ -143,7 +143,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public void PreviewBadge() { - Assert.AreEqual(SettingsSection.Content.PreviewBadge, @"In Preview Mode - click to end"); + Assert.AreEqual(SettingsSection.Content.PreviewBadge, @"In Preview Mode - click to end"); } [Test] public void ResolveUrlsFromTextString() diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config index a436dad9f5..4c64485503 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config @@ -77,7 +77,7 @@ In Preview Mode - click to end + In Preview Mode - click to end ]]> diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 42c8b01e24..df6fe953fe 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -41,7 +41,7 @@ In Preview Mode - click to end + In Preview Mode - click to end ]]> In Preview Mode - click to end + In Preview Mode - click to end ]]> diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index 6a91d20ae0..6e119d68d9 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -88,5 +88,23 @@ namespace Umbraco.Web.Editors // if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException(nameof(editor)); // return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + editor.Replace(".html", string.Empty) + ".cshtml"); //} + + public ActionResult End(string redir = null) + { + var previewToken = Request.GetPreviewCookieValue(); + var service = Current.PublishedSnapshotService; + service.ExitPreview(previewToken); + + System.Web.HttpContext.Current.ExpireCookie(Constants.Web.PreviewCookieName); + + if (Uri.IsWellFormedUriString(redir, UriKind.Relative) + && redir.StartsWith("//") == false + && Uri.TryCreate(redir, UriKind.Relative, out Uri url)) + { + return Redirect(url.ToString()); + } + + return Redirect("/"); + } } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ea3ff32220..d64e3dcd81 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -525,9 +525,6 @@ - - ASPXCodeBehind - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/endPreview.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/endPreview.aspx.cs deleted file mode 100644 index 822f346705..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/endPreview.aspx.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Web; -using System.Web.UI; -using Umbraco.Core; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web.PublishedCache; - -namespace umbraco.presentation -{ - public class endPreview : Page - { - protected void Page_Load(object sender, EventArgs e) - { - var request = (new HttpRequestWrapper(Request)); - - var previewToken = request.GetPreviewCookieValue(); - var service = Current.PublishedSnapshotService; - service.ExitPreview(previewToken); - - HttpContext.Current.ExpireCookie(Constants.Web.PreviewCookieName); - - var redir = Request.QueryString["redir"]; - Uri url = null; - - if (Uri.IsWellFormedUriString(redir, UriKind.Relative) == false - || redir.StartsWith("//") - || Uri.TryCreate(redir, UriKind.Relative, out url) == false) - { - Response.Redirect("/", true); - } - - Response.Redirect(url.ToString(), true); - } - } -} From 6365f910bc577aa971313d33e1f6750e67761906 Mon Sep 17 00:00:00 2001 From: Gregory Dove <35264602+g-dove@users.noreply.github.com> Date: Wed, 19 Dec 2018 14:58:06 +0000 Subject: [PATCH 25/44] Better Validation Options (#3882) --- .../propertysettings/propertysettings.controller.js | 4 ++-- .../infiniteeditors/propertysettings/propertysettings.html | 2 +- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js index 7239fd22e7..89987a3f27 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js @@ -17,7 +17,7 @@ vm.showValidationPattern = false; vm.focusOnPatternField = false; vm.focusOnMandatoryField = false; - vm.selectedValidationType = {}; + vm.selectedValidationType = null; vm.validationTypes = []; vm.labels = {}; @@ -238,4 +238,4 @@ angular.module("umbraco").controller("Umbraco.Editors.PropertySettingsController", PropertySettingsEditor); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index 610af28cb8..df5bbe8ca5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -90,7 +90,7 @@