From b40a1fa6132ad4dda8411036fdbf23df851696c0 Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 12 Sep 2016 12:47:12 +0200 Subject: [PATCH 01/88] U4-8963 Updates for ImageProcessor parameters to support background color --- .../PropertyEditors/ImageCropperTest.cs | 12 +++++++ src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 10 +++--- .../ImageCropperTemplateExtensions.cs | 32 +++++++++++++------ src/Umbraco.Web/UrlHelperRenderExtensions.cs | 18 +++++++---- 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 450c69abb4..d3d6bf156d 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -358,5 +358,17 @@ namespace Umbraco.Tests.PropertyEditors var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, height: 200); Assert.AreEqual(mediaPath + "?anchor=center&mode=crop&height=200", urlString); } + + /// + /// Test to check result when using a background color with padding + /// + [Test] + public void GetCropUrl_BackgroundColorParameter() + { + var cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"" + mediaPath + "\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; + + var urlString = mediaPath.GetCropUrl(400, 400, cropperJson, imageCropMode: ImageCropMode.Pad, backgroundColor: "fff"); + Assert.AreEqual(mediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 3062613b6b..2802e4fe80 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -228,12 +228,13 @@ namespace Umbraco.Web bool cacheBuster = true, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) + bool upScale = true, + string backgroundColor = null) { return new HtmlString(mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, - upScale)); + upScale, backgroundColor)); } [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] @@ -252,12 +253,13 @@ namespace Umbraco.Web string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) + bool upScale = true, + string backgroundColor = null) { return new HtmlString(imageUrl.GetCropUrl(width, height, imageCropperValue, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, - upScale)); + upScale, backgroundColor)); } #endregion diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index e518c5e246..a9b0140d20 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -91,10 +91,13 @@ namespace Umbraco.Web /// /// /// Use a dimension as a ratio - /// + /// /// /// If the image should be upscaled to requested dimensions - /// + /// + /// + /// Changes the background color of the image. Used when adding a background when resizing image formats without an alpha channel. + /// /// /// The . /// @@ -112,7 +115,8 @@ namespace Umbraco.Web bool cacheBuster = true, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) + bool upScale = true, + string backgroundColor = null) { if (mediaItem == null) throw new ArgumentNullException("mediaItem"); @@ -132,7 +136,7 @@ namespace Umbraco.Web mediaItemUrl = stronglyTyped.Src; return GetCropUrl( mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, - cacheBusterValue, furtherOptions, ratioMode, upScale); + cacheBusterValue, furtherOptions, ratioMode, upScale, backgroundColor); } //this shouldn't be the case but we'll check @@ -143,14 +147,14 @@ namespace Umbraco.Web mediaItemUrl = stronglyTyped.Src; return GetCropUrl( mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, - cacheBusterValue, furtherOptions, ratioMode, upScale); + cacheBusterValue, furtherOptions, ratioMode, upScale, backgroundColor); } //it's a single string mediaItemUrl = cropperValue.ToString(); return GetCropUrl( mediaItemUrl, width, height, mediaItemUrl, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, - cacheBusterValue, furtherOptions, ratioMode, upScale); + cacheBusterValue, furtherOptions, ratioMode, upScale, backgroundColor); } /// @@ -198,6 +202,9 @@ namespace Umbraco.Web /// /// If the image should be upscaled to requested dimensions /// + /// + /// Changes the background color of the image. Used when adding a background when resizing image formats without an alpha channel. + /// /// /// The . /// @@ -215,7 +222,8 @@ namespace Umbraco.Web string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) + bool upScale = true, + string backgroundColor = null) { if (string.IsNullOrEmpty(imageUrl)) return string.Empty; @@ -226,7 +234,7 @@ namespace Umbraco.Web } return GetCropUrl( imageUrl, cropDataSet, width, height, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale, backgroundColor); } public static string GetCropUrl( @@ -243,7 +251,8 @@ namespace Umbraco.Web string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) + bool upScale = true, + string backgroundColor = null) { if (string.IsNullOrEmpty(imageUrl) == false) { @@ -350,6 +359,11 @@ namespace Umbraco.Web imageProcessorUrl.Append("&upscale=false"); } + if (backgroundColor != null) + { + imageProcessorUrl.Append("&bgcolor=" + backgroundColor); + } + if (furtherOptions != null) { imageProcessorUrl.Append(furtherOptions); diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs index 629d69ed4a..76a000aded 100644 --- a/src/Umbraco.Web/UrlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs @@ -114,6 +114,9 @@ namespace Umbraco.Web /// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be /// set to false if using the result of this method for CSS. /// + /// + /// Changes the background color of the image. Used when adding a background when resizing image formats without an alpha channel. + /// /// /// The . /// @@ -132,11 +135,12 @@ namespace Umbraco.Web string furtherOptions = null, ImageCropRatioMode? ratioMode = null, bool upScale = true, - bool htmlEncode = true) + bool htmlEncode = true, + string backgroundColor = null) { var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, - upScale); + upScale, backgroundColor); return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url); } @@ -190,6 +194,9 @@ namespace Umbraco.Web /// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be /// set to false if using the result of this method for CSS. /// + /// + /// Changes the background color of the image. Used when adding a background when resizing image formats without an alpha channel. + /// /// /// The . /// @@ -208,11 +215,12 @@ namespace Umbraco.Web string furtherOptions = null, ImageCropRatioMode? ratioMode = null, bool upScale = true, - bool htmlEncode = true) + bool htmlEncode = true, + string backgroundColor = null) { var url = imageUrl.GetCropUrl(width, height, imageCropperValue, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, - upScale); + upScale, backgroundColor); return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url); } @@ -333,7 +341,5 @@ namespace Umbraco.Web { return url.SurfaceAction(action, typeof (T), additionalRouteVals); } - - } } \ No newline at end of file From 87814fa43c952ac16062c176aa307d0b6cd382fa Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Sat, 12 Nov 2016 12:12:41 +0700 Subject: [PATCH 02/88] Update UI lang file for ru-ru All latest 7.5.5 changes --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 255bf62d5d..c6f94949d2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -27,6 +27,9 @@ Опубликовать Обновить узлы Опубликовать весь сайт + Установить разрешения для страницы '%0%' + Выберите, куда переместить + В структуре документов ниже Восстановить Разрешения Откатить @@ -189,7 +192,7 @@ Роль участника Тип участника Дата не указана - Заголовок страницы + Заголовок ссылки Не является членом групп(ы) Свойства Этот документ опубликован, но скрыт, потому что его родительский документ '%0%' не опубликован @@ -341,9 +344,10 @@ Просмотр элемента кэша Создать папку... Связать с оригиналом + Включая все дочерние Самое дружелюбное сообщество Ссылка на страницу - Открывает документ по ссылке в новом окне или вкладке браузера + Открывать ссылку в новом окне или вкладке браузера Ссылка на медиа-файл Выбрать медиа Выбрать значок From 9de3acfae2c8182b6d813d68aee2f85775086f3f Mon Sep 17 00:00:00 2001 From: Mark320 Date: Thu, 17 Nov 2016 14:22:12 +0000 Subject: [PATCH 03/88] U4-9192 Add jQuery migrate for user controls used in tabs that will improve backwards compatibility --- .../Umbraco/dashboard/UserControlProxy.aspx | 1 + .../Umbraco/dashboard/UserControlProxy.aspx.designer.cs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx index 59a96e9a37..7728d281c3 100644 --- a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx +++ b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx @@ -13,6 +13,7 @@ + diff --git a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs index 19ac019a8f..628e82a767 100644 --- a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs +++ b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs @@ -48,6 +48,15 @@ namespace Umbraco.Web.UI.Umbraco.Dashboard { /// protected global::ClientDependency.Core.Controls.JsInclude JsInclude3; + /// + /// JsInclude4 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude4; + /// /// JsInclude6 control. /// From bc8b8a69b887f7335862f901735364ec431213e6 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Mon, 21 Nov 2016 10:26:08 +0100 Subject: [PATCH 04/88] Typo (sorta) --- src/Umbraco.Web/ImageCropperTemplateExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index e518c5e246..63157e4fee 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -81,7 +81,7 @@ namespace Umbraco.Web /// Use focal point, to generate an output image using the focal point instead of the predefined crop /// /// - /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters>. + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. /// /// /// Add a serialised date of the last edit of the item to ensure client cache refresh when updated From f62a9ed199d98e7d1d0b6b55c244e7f273bbf348 Mon Sep 17 00:00:00 2001 From: Enkel Media Date: Wed, 30 Nov 2016 11:17:21 +0100 Subject: [PATCH 05/88] Fixed simple typo for modelsbuilder --- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 173da6cc9b..f68359b0af 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1142,7 +1142,7 @@ To manage your website, simply open the Umbraco back office and start adding con Building models - this can take abit of time, don't worry + this can take a bit of time, don't worry Models generated Models could not be generated Models generation has failed, see exception in U log From 90136c524c071a3d8ac2552ac7e87796e3608566 Mon Sep 17 00:00:00 2001 From: Chris Houston Date: Tue, 6 Dec 2016 11:05:59 +0000 Subject: [PATCH 06/88] Small English grammar error. --- src/Umbraco.Core/CoreBootManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 4ef08bd02f..e78d2adc5b 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -415,7 +415,7 @@ namespace Umbraco.Core if (currentTry == 5) { - throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database."); + throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but Umbraco cannot connect to the database."); } } From 5bc66c5b7fe2bdf311370ece1b998dfff530c5ff Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 7 Dec 2016 14:18:27 +0100 Subject: [PATCH 07/88] Bumps the version --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index df03fe8381..345fce6961 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,2 +1,2 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.5.5 \ No newline at end of file +7.5.6 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index b3b5633864..6821a737af 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.5.5")] -[assembly: AssemblyInformationalVersion("7.5.5")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.5.6")] +[assembly: AssemblyInformationalVersion("7.5.6")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index ccf4aecf13..0b3a17c8d6 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.5.5"); + private static readonly Version Version = new Version("7.5.6"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index c8c90c2ed5..8424c750c7 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2415,9 +2415,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" True True - 7550 + 7560 / - http://localhost:7550 + http://localhost:7560 False False From 1a9f336a59bf8d34064d224de3d4b4c026be5bcb Mon Sep 17 00:00:00 2001 From: Niels Hartvig Date: Mon, 9 Jan 2017 14:45:40 +0100 Subject: [PATCH 08/88] Add support for GUIDs in media and content pickers --- .../mediaPicker/mediapicker.controller.js | 7 +- .../Editors/BackOfficeController.cs | 2 +- src/Umbraco.Web/Editors/MediaController.cs | 64 +++++++++++++++++-- .../Editors/MediaTypeController.cs | 19 +++++- .../Trees/ContentTreeController.cs | 6 +- .../Trees/ContentTreeControllerBase.cs | 60 ++++++++++++++--- 6 files changed, 137 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index 43c65d99ee..3b6c39e84a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Overlays.MediaPickerController", - function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, $cookies, $cookieStore, localizationService) { + function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, $cookies, localStorageService, localizationService) { if (!$scope.model.title) { $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); @@ -15,7 +15,7 @@ angular.module("umbraco") $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; $scope.cropSize = dialogOptions.cropSize; - $scope.lastOpenedNode = $cookieStore.get("umbLastOpenedMediaNodeId"); + $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); if ($scope.onlyImages) { $scope.acceptedFileTypes = mediaHelper .formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); @@ -133,8 +133,7 @@ angular.module("umbraco") }); $scope.currentFolder = folder; - // for some reason i cannot set cookies with cookieStore - document.cookie = "umbLastOpenedMediaNodeId=" + folder.id; + localStorageService.set("umbLastOpenedMediaNodeId", folder.id); }; diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 199ffd9bed..74e616e756 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -262,7 +262,7 @@ namespace Umbraco.Web.Editors }, { "mediaTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllowedChildren(0)) + controller => controller.GetAllowedChildren("0")) }, { "macroApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 7a7e349a3c..1f95034c34 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -36,6 +36,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.FaultHandling; using Umbraco.Web.UI; using Notification = Umbraco.Web.Models.ContentEditing.Notification; +using Umbraco.Core.Persistence; namespace Umbraco.Web.Editors { @@ -175,7 +176,37 @@ namespace Umbraco.Web.Editors /// Returns the child media objects /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(int id, + public PagedResult> GetChildren(string id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + int idInt; Guid idGuid; + + if (Guid.TryParse(id, out idGuid)) + { + var entity = Services.EntityService.GetByKey(idGuid); + if (entity != null) + { + return getChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + else + { + throw new EntityNotFoundException(id, "The passed id doesn't exist"); + } + } + else if (int.TryParse(id, out idInt)) + { + return getChildren(idInt, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + + throw new InvalidCastException("Id must be either an integer or a Guid"); + } + + private PagedResult> getChildren(int id, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -448,12 +479,37 @@ namespace Umbraco.Web.Editors } //get the string json from the request - int parentId; - if (int.TryParse(result.FormData["currentFolder"], out parentId) == false) + int parentId; bool entityFound; + string currentFolderId = result.FormData["currentFolder"]; + if (int.TryParse(currentFolderId, out parentId) == false) { - return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer"); + // if a guid then try to look up the entity + Guid idGuid; + if (Guid.TryParse(currentFolderId, out idGuid)) + { + var entity = Services.EntityService.GetByKey(idGuid); + if (entity != null) + { + entityFound = true; + parentId = entity.Id; + } + else + { + throw new EntityNotFoundException(currentFolderId, "The passed id doesn't exist"); + } + } + else + { + return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer or Guid"); + } + + if (entityFound == false) + { + return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer or Guid"); + } } + //ensure the user has access to this folder by parent id! if (CheckPermissions( new Dictionary(), diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 0f83ad6d03..a40b83c9ea 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -11,6 +11,8 @@ using System.Net; using System.Net.Http; using Umbraco.Web.WebApi; using Umbraco.Core.Services; +using Umbraco.Core.Models.EntityBase; +using System; namespace Umbraco.Web.Editors { @@ -174,7 +176,22 @@ namespace Umbraco.Web.Editors /// /// [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] - public IEnumerable GetAllowedChildren(int contentId) + public IEnumerable GetAllowedChildren(string contentId) + { + Guid idGuid = Guid.Empty; + int idInt; + if (Guid.TryParse(contentId, out idGuid)) { + var entity = ApplicationContext.Services.EntityService.GetByKey(idGuid); + return getAllowedChildren(entity.Id); + } else if (int.TryParse(contentId, out idInt)) + { + return getAllowedChildren(idInt); + } + + throw new InvalidCastException("Id must be either an integer or a Guid"); + } + + private IEnumerable getAllowedChildren(int contentId) { if (contentId == Constants.System.RecycleBinContent) return Enumerable.Empty(); diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 9f33a44ea9..9a5f90ff89 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -202,11 +202,13 @@ namespace Umbraco.Web.Trees /// protected override bool HasPathAccess(string id, FormDataCollection queryStrings) { - var content = Services.ContentService.GetById(int.Parse(id)); - if (content == null) + var entity = GetEntityFromId(id); + if (entity == null) { return false; } + + IContent content = Services.ContentService.GetById(entity.Id); return Security.CurrentUser.HasPathAccess(content); } diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 3708c6fd4f..99ec60be75 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -96,7 +96,7 @@ namespace Umbraco.Web.Trees LogHelper.Warn("The user " + Security.CurrentUser.Username + " does not have access to the tree node " + id); return new TreeNodeCollection(); } - + // So there's an alt id specified, it's not the root node and the user has access to it, great! But there's one thing we // need to consider: // If the tree is being rendered in a dialog view we want to render only the children of the specified id, but @@ -110,7 +110,7 @@ namespace Umbraco.Web.Trees id = Constants.System.Root.ToString(CultureInfo.InvariantCulture); } } - + var entities = GetChildEntities(id); nodes.AddRange(entities.Select(entity => GetSingleTreeNode(entity, id, queryStrings)).Where(node => node != null)); return nodes; @@ -122,11 +122,24 @@ namespace Umbraco.Web.Trees protected IEnumerable GetChildEntities(string id) { + // use helper method to ensure we support both integer and guid lookups int iid; - if (int.TryParse(id, out iid) == false) + + // if it's the root node, we won't use the look up + if (id != "-1") { - throw new InvalidCastException("The id for the media tree must be an integer"); + var idEntity = GetEntityFromId(id); + if (idEntity == null) + { + throw new EntityNotFoundException(id, "The passed id doesn't exist"); + } + iid = idEntity.Id; } + else + { + iid = int.Parse(id); + } + //if a request is made for the root node data but the user's start node is not the default, then // we need to return their start node data @@ -134,11 +147,12 @@ namespace Umbraco.Web.Trees { //just return their single start node, it will show up under the 'Content' label var startNode = Services.EntityService.Get(UserStartNode, UmbracoObjectType); - if (startNode == null) + if (startNode != null) + return new[] { startNode }; + else { throw new EntityNotFoundException(UserStartNode, "User's start content node could not be found"); } - return new[] { startNode }; } return Services.EntityService.GetChildren(iid, UmbracoObjectType).ToArray(); @@ -176,9 +190,9 @@ namespace Umbraco.Web.Trees { id = altStartId; } - + var nodes = GetTreeNodesInternal(id, queryStrings); - + //only render the recycle bin if we are not in dialog and the start id id still the root if (IsDialog(queryStrings) == false && id == Constants.System.Root.ToInvariantString()) { @@ -210,8 +224,9 @@ namespace Umbraco.Web.Trees /// private TreeNodeCollection GetTreeNodesInternal(string id, FormDataCollection queryStrings) { + IUmbracoEntity current = GetEntityFromId(id); + //before we get the children we need to see if this is a container node - var current = Services.EntityService.Get(int.Parse(id), UmbracoObjectType); //test if the parent is a listview / container if (current != null && current.IsContainer()) @@ -286,5 +301,32 @@ namespace Umbraco.Web.Trees { return allowedUserOptions.Select(x => x.Action).OfType().Any(); } + + /// + /// Get an entity via an id that can be either an integer or a Guid + /// + /// + /// + internal IUmbracoEntity GetEntityFromId(string id) + { + IUmbracoEntity entity; + Guid idGuid = Guid.Empty; + int idInt; + if (Guid.TryParse(id, out idGuid)) + { + entity = Services.EntityService.GetByKey(idGuid, UmbracoObjectType); + + } + else if (int.TryParse(id, out idInt)) + { + entity = Services.EntityService.Get(idInt, UmbracoObjectType); + } + else + { + throw new InvalidCastException("Id must be either an integer or a Guid"); + } + + return entity; + } } } \ No newline at end of file From f75e0cf4ba54fa4bafc433b2605ced275f9d046d Mon Sep 17 00:00:00 2001 From: Niels Hartvig Date: Tue, 10 Jan 2017 12:47:00 +0100 Subject: [PATCH 09/88] Updates to PR based on Review --- src/Umbraco.Web/Editors/MediaController.cs | 4 ++-- src/Umbraco.Web/Editors/MediaTypeController.cs | 8 ++++---- src/Umbraco.Web/Trees/ContentTreeControllerBase.cs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 1f95034c34..5dd0c3073f 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -195,7 +195,7 @@ namespace Umbraco.Web.Editors } else { - throw new EntityNotFoundException(id, "The passed id doesn't exist"); + throw new HttpResponseException(HttpStatusCode.NotFound); } } else if (int.TryParse(id, out idInt)) @@ -203,7 +203,7 @@ namespace Umbraco.Web.Editors return getChildren(idInt, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } - throw new InvalidCastException("Id must be either an integer or a Guid"); + throw new HttpResponseException(HttpStatusCode.NotFound); } private PagedResult> getChildren(int id, diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index a40b83c9ea..67b8ae6d1f 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -182,16 +182,16 @@ namespace Umbraco.Web.Editors int idInt; if (Guid.TryParse(contentId, out idGuid)) { var entity = ApplicationContext.Services.EntityService.GetByKey(idGuid); - return getAllowedChildren(entity.Id); + return GetAllowedChildrenInternal(entity.Id); } else if (int.TryParse(contentId, out idInt)) { - return getAllowedChildren(idInt); + return GetAllowedChildrenInternal(idInt); } - throw new InvalidCastException("Id must be either an integer or a Guid"); + throw new HttpResponseException(HttpStatusCode.NotFound); } - private IEnumerable getAllowedChildren(int contentId) + private IEnumerable GetAllowedChildrenInternal(int contentId) { if (contentId == Constants.System.RecycleBinContent) return Enumerable.Empty(); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 99ec60be75..027e4e488c 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -131,7 +131,7 @@ namespace Umbraco.Web.Trees var idEntity = GetEntityFromId(id); if (idEntity == null) { - throw new EntityNotFoundException(id, "The passed id doesn't exist"); + throw new HttpResponseException(HttpStatusCode.NotFound); } iid = idEntity.Id; } @@ -323,7 +323,7 @@ namespace Umbraco.Web.Trees } else { - throw new InvalidCastException("Id must be either an integer or a Guid"); + return null; } return entity; From ba501dbbb83b35eb77df7bd37e29a8edc743b34a Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 13 Jan 2017 10:43:34 +1100 Subject: [PATCH 10/88] U4-9371 Examine indexes inherited unpublished nodes on index rebuild part 2 --- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../UmbracoContentIndexerTests.cs | 107 ++++++++++++++++++ src/UmbracoExamine/UmbracoContentIndexer.cs | 37 +++--- 3 files changed, 125 insertions(+), 20 deletions(-) create mode 100644 src/Umbraco.Tests/UmbracoExamine/UmbracoContentIndexerTests.cs diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index d90c0e6345..dbb877ee64 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -163,6 +163,7 @@ + diff --git a/src/Umbraco.Tests/UmbracoExamine/UmbracoContentIndexerTests.cs b/src/Umbraco.Tests/UmbracoExamine/UmbracoContentIndexerTests.cs new file mode 100644 index 0000000000..3278130022 --- /dev/null +++ b/src/Umbraco.Tests/UmbracoExamine/UmbracoContentIndexerTests.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using UmbracoExamine; + +namespace Umbraco.Tests.UmbracoExamine +{ + [TestFixture] + public class UmbracoContentIndexerTests : ExamineBaseTest + { + [Test] + public void Get_Serialized_Content_No_Published_Content() + { + var contentSet = new List + { + Mock.Of(c => c.Id == 1 && c.Path == "-1,1" && c.Published && c.Level == 1), + Mock.Of(c => c.Id == 2 && c.Path == "-1,2" && c.Published && c.Level == 1), + Mock.Of(c => c.Id == 3 && c.Path == "-1,3" && c.Published == false && c.Level == 1), // no + Mock.Of(c => c.Id == 4 && c.Path == "-1,4" && c.Published == false && c.Level == 1), // no + + Mock.Of(c => c.Id == 5 && c.Path == "-1,1,5" && c.Published && c.Level == 2), + Mock.Of(c => c.Id == 6 && c.Path == "-1,2,6" && c.Published == false && c.Level == 2), // no + Mock.Of(c => c.Id == 7 && c.Path == "-1,3,7" && c.Published && c.Level == 2), // no + Mock.Of(c => c.Id == 8 && c.Path == "-1,4,8" && c.Published && c.Level == 2), // no + Mock.Of(c => c.Id == 9 && c.Path == "-1,4,9" && c.Published && c.Level == 2), // no + + Mock.Of(c => c.Id == 10 && c.Path == "-1,1,5,10" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 15 && c.Path == "-1,1,5,15" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 11 && c.Path == "-1,2,6,11" && c.Published && c.Level == 3), // no + Mock.Of(c => c.Id == 16 && c.Path == "-1,2,6,16" && c.Published && c.Level == 3), // no + Mock.Of(c => c.Id == 12 && c.Path == "-1,3,7,12" && c.Published && c.Level == 3), // no + Mock.Of(c => c.Id == 17 && c.Path == "-1,3,7,17" && c.Published && c.Level == 3), // no + Mock.Of(c => c.Id == 13 && c.Path == "-1,4,8,13" && c.Published && c.Level == 3), // no + Mock.Of(c => c.Id == 18 && c.Path == "-1,4,8,18" && c.Published && c.Level == 3), // no + Mock.Of(c => c.Id == 14 && c.Path == "-1,4,9,14" && c.Published && c.Level == 3), // no + Mock.Of(c => c.Id == 19 && c.Path == "-1,4,9,19" && c.Published && c.Level == 3), // no + }; + + //ensure the rest of the required values are populted + foreach (var content in contentSet) + { + var mock = Mock.Get(content); + mock.Setup(x => x.ContentType).Returns(Mock.Of(type => type.Icon == "hello")); + } + + contentSet.Sort((a, b) => Comparer.Default.Compare(a.Level, b.Level)); + + var published = new HashSet(); + + var result = UmbracoContentIndexer.GetSerializedContent(false, content => new XElement("test"), contentSet, published) + .WhereNotNull() + .ToArray(); + + Assert.AreEqual(5, result.Length); + } + + [Test] + public void Get_Serialized_Content_With_Published_Content() + { + var contentSet = new List + { + Mock.Of(c => c.Id == 1 && c.Path == "-1,1" && c.Published && c.Level == 1), + Mock.Of(c => c.Id == 2 && c.Path == "-1,2" && c.Published && c.Level == 1), + Mock.Of(c => c.Id == 3 && c.Path == "-1,3" && c.Published == false && c.Level == 1), + Mock.Of(c => c.Id == 4 && c.Path == "-1,4" && c.Published == false && c.Level == 1), + + Mock.Of(c => c.Id == 5 && c.Path == "-1,1,5" && c.Published && c.Level == 2), + Mock.Of(c => c.Id == 6 && c.Path == "-1,2,6" && c.Published == false && c.Level == 2), + Mock.Of(c => c.Id == 7 && c.Path == "-1,3,7" && c.Published && c.Level == 2), + Mock.Of(c => c.Id == 8 && c.Path == "-1,4,8" && c.Published && c.Level == 2), + Mock.Of(c => c.Id == 9 && c.Path == "-1,4,9" && c.Published && c.Level == 2), + + Mock.Of(c => c.Id == 10 && c.Path == "-1,1,5,10" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 15 && c.Path == "-1,1,5,15" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 11 && c.Path == "-1,2,6,11" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 16 && c.Path == "-1,2,6,16" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 12 && c.Path == "-1,3,7,12" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 17 && c.Path == "-1,3,7,17" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 13 && c.Path == "-1,4,8,13" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 18 && c.Path == "-1,4,8,18" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 14 && c.Path == "-1,4,9,14" && c.Published && c.Level == 3), + Mock.Of(c => c.Id == 19 && c.Path == "-1,4,9,19" && c.Published && c.Level == 3), + }; + + //ensure the rest of the required values are populted + foreach (var content in contentSet) + { + var mock = Mock.Get(content); + mock.Setup(x => x.ContentType).Returns(Mock.Of(type => type.Icon == "hello")); + } + + contentSet.Sort((a, b) => Comparer.Default.Compare(a.Level, b.Level)); + + var published = new HashSet(); + + var result = UmbracoContentIndexer.GetSerializedContent(true, content => new XElement("test"), contentSet, published) + .WhereNotNull() + .ToArray(); + + Assert.AreEqual(19, result.Length); + } + } +} \ No newline at end of file diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index efc3e4a214..ff3e4617e0 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -359,12 +359,7 @@ namespace UmbracoExamine } #endregion - #region Protected - - /// - /// This is a static query, it's parameters don't change so store statically - /// - private IQuery _publishedQuery; + #region Protected protected override void PerformIndexAll(string type) { @@ -395,11 +390,6 @@ namespace UmbracoExamine } else { - if (_publishedQuery == null) - { - _publishedQuery = Query.Builder.Where(x => x.Published == true); - } - //get all paged records but order by level ascending, we need to do this because we need to track which nodes are not published so that we can determine // which descendent nodes are implicitly not published descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "level", Direction.Ascending, true, (string)null); @@ -415,7 +405,12 @@ namespace UmbracoExamine { content = descendants.ToArray(); } - AddNodesToIndex(GetSerializedContent(content, notPublished).WhereNotNull(), type); + + AddNodesToIndex(GetSerializedContent( + SupportUnpublishedContent, + c => _serializer.Serialize(_contentService, _dataTypeService, _userService, c), + content, notPublished).WhereNotNull(), type); + pageIndex++; } while (content.Length == pageSize); @@ -473,17 +468,22 @@ namespace UmbracoExamine } } - private IEnumerable GetSerializedContent(IEnumerable content, ISet notPublished) + internal static IEnumerable GetSerializedContent( + bool supportUnpublishdContent, + Func serializer, + IEnumerable content, + ISet notPublished) { foreach (var c in content) { - if (SupportUnpublishedContent == false) + if (supportUnpublishdContent == false) { //if we don't support published content and this is not published then track it and return null if (c.Published == false) { notPublished.Add(c.Path); yield return null; + continue; } //if we don't support published content, check if this content item exists underneath any already tracked @@ -491,14 +491,11 @@ namespace UmbracoExamine if (notPublished.Any(path => c.Path.StartsWith(string.Format("{0},", path)))) { yield return null; + continue; } - } + } - var xml = _serializer.Serialize( - _contentService, - _dataTypeService, - _userService, - c); + var xml = serializer(c); //add a custom 'icon' attribute xml.Add(new XAttribute("icon", c.ContentType.Icon)); From bde439d102b895bfc281c933beac9e9dcf597f91 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 13 Jan 2017 12:34:58 +1100 Subject: [PATCH 11/88] fixes tests --- .../UmbracoExamine/IndexInitializer.cs | 8 +- src/Umbraco.Tests/UmbracoExamine/IndexTest.cs | 343 +++++++++--------- .../UmbracoExamine/SearchTests.cs | 27 +- src/UmbracoExamine/BaseUmbracoIndexer.cs | 2 +- 4 files changed, 187 insertions(+), 193 deletions(-) diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 00e94ced63..f57b1af213 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -41,7 +41,8 @@ namespace Umbraco.Tests.UmbracoExamine IDataTypeService dataTypeService = null, IMemberService memberService = null, IUserService userService = null, - IContentTypeService contentTypeService = null) + IContentTypeService contentTypeService = null, + bool supportUnpublishedContent = false) { if (dataService == null) { @@ -185,7 +186,10 @@ namespace Umbraco.Tests.UmbracoExamine userService, contentTypeService, analyzer, - false); + false) + { + SupportUnpublishedContent = supportUnpublishedContent + }; //i.IndexSecondsInterval = 1; diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 5d6812f94a..5f3f5525c1 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -22,211 +22,222 @@ namespace Umbraco.Tests.UmbracoExamine [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] [TestFixture, RequiresSTA] public class IndexTest : ExamineBaseTest - { + { + /// + /// Check that the node signalled as protected in the content service is not present in the index. + /// + [Test] + public void Index_Protected_Content_Not_Indexed() + { - ///// - /// - /// Check that the node signalled as protected in the content service is not present in the index. - /// - [Test] - public void Index_Protected_Content_Not_Indexed() - { + using (var luceneDir = new RAMDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir)) + { + indexer.RebuildIndex(); - var protectedQuery = new BooleanQuery(); - protectedQuery.Add( - new BooleanClause( - new TermQuery(new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content)), - BooleanClause.Occur.MUST)); + var protectedQuery = new BooleanQuery(); + protectedQuery.Add( + new BooleanClause( + new TermQuery(new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content)), + BooleanClause.Occur.MUST)); - protectedQuery.Add( - new BooleanClause( - new TermQuery(new Term(LuceneIndexer.IndexNodeIdFieldName, TestContentService.ProtectedNode.ToString())), - BooleanClause.Occur.MUST)); + protectedQuery.Add( + new BooleanClause( + new TermQuery(new Term(LuceneIndexer.IndexNodeIdFieldName, TestContentService.ProtectedNode.ToString())), + BooleanClause.Occur.MUST)); - var collector = new AllHitsCollector(false, true); - var s = _searcher.GetSearcher(); - s.Search(protectedQuery, collector); + var collector = new AllHitsCollector(false, true); + var s = searcher.GetSearcher(); + s.Search(protectedQuery, collector); - Assert.AreEqual(0, collector.Count, "Protected node should not be indexed"); + Assert.AreEqual(0, collector.Count, "Protected node should not be indexed"); + } - } + } - [Test] - public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID() - { - //change parent id to 1116 - var existingCriteria = _indexer.IndexerData; - _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, - 1116); - - //rebuild so it excludes children unless they are under 1116 - _indexer.RebuildIndex(); + [Test] + public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID() + { + using (var luceneDir = new RAMDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir)) + { + indexer.RebuildIndex(); - //ensure that node 2112 doesn't exist - var results = _searcher.Search(_searcher.CreateSearchCriteria().Id(2112).Compile()); - Assert.AreEqual(0, results.Count()); + var mediaService = new TestMediaService(); - //get a node from the data repo (this one exists underneath 2222) - var node = _mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]") - .Root - .Elements() - .Where(x => (int)x.Attribute("id") == 2112) - .First(); + //change parent id to 1116 + var existingCriteria = indexer.IndexerData; + indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, + 1116); - var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112 - Assert.AreEqual("-1,1111,2222,2112", currPath); + //rebuild so it excludes children unless they are under 1116 + indexer.RebuildIndex(); - //now mimic moving 2112 to 1116 - //node.SetAttributeValue("path", currPath.Replace("2222", "1116")); - node.SetAttributeValue("path", "-1,1116,2112"); - node.SetAttributeValue("parentID", "1116"); + //ensure that node 2112 doesn't exist + var results = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile()); + Assert.AreEqual(0, results.Count()); - //now reindex the node, this should first delete it and then WILL add it because of the parent id constraint - _indexer.ReIndexNode(node, IndexTypes.Media); + //get a node from the data repo (this one exists underneath 2222) + var node = mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]") + .Root + .Elements() + .First(x => (int)x.Attribute("id") == 2112); - //RESET the parent id - existingCriteria = ((IndexCriteria)_indexer.IndexerData); - _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, - null); + var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112 + Assert.AreEqual("-1,1111,2222,2112", currPath); - //now ensure it's deleted - var newResults = _searcher.Search(_searcher.CreateSearchCriteria().Id(2112).Compile()); - Assert.AreEqual(1, newResults.Count()); - } + //now mimic moving 2112 to 1116 + //node.SetAttributeValue("path", currPath.Replace("2222", "1116")); + node.SetAttributeValue("path", "-1,1116,2112"); + node.SetAttributeValue("parentID", "1116"); - [Test] + //now reindex the node, this should first delete it and then WILL add it because of the parent id constraint + indexer.ReIndexNode(node, IndexTypes.Media); + + //RESET the parent id + existingCriteria = ((IndexCriteria)indexer.IndexerData); + indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, + null); + + //now ensure it's deleted + var newResults = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile()); + Assert.AreEqual(1, newResults.Count()); + } + + + } + + [Test] [Ignore] - public void Index_Move_Media_To_Non_Indexable_ParentID() - { - //get a node from the data repo (this one exists underneath 2222) - var node = _mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]") - .Root - .Elements() - .Where(x => (int)x.Attribute("id") == 2112) - .First(); + public void Index_Move_Media_To_Non_Indexable_ParentID() + { + using (var luceneDir = new RAMDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir)) + { + indexer.RebuildIndex(); - var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112 - Assert.AreEqual("-1,1111,2222,2112", currPath); + var mediaService = new TestMediaService(); - //ensure it's indexed - _indexer.ReIndexNode(node, IndexTypes.Media); + //get a node from the data repo (this one exists underneath 2222) + var node = mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]") + .Root + .Elements() + .First(x => (int)x.Attribute("id") == 2112); - //change the parent node id to be the one it used to exist under - var existingCriteria = _indexer.IndexerData; - _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, - 2222); + var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112 + Assert.AreEqual("-1,1111,2222,2112", currPath); - //now mimic moving the node underneath 1116 instead of 2222 - node.SetAttributeValue("path", currPath.Replace("2222", "1116")); - node.SetAttributeValue("parentID", "1116"); + //ensure it's indexed + indexer.ReIndexNode(node, IndexTypes.Media); - //now reindex the node, this should first delete it and then NOT add it because of the parent id constraint - _indexer.ReIndexNode(node, IndexTypes.Media); + //change the parent node id to be the one it used to exist under + var existingCriteria = indexer.IndexerData; + indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, + 2222); - //RESET the parent id - existingCriteria = ((IndexCriteria)_indexer.IndexerData); - _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, - null); + //now mimic moving the node underneath 1116 instead of 2222 + node.SetAttributeValue("path", currPath.Replace("2222", "1116")); + node.SetAttributeValue("parentID", "1116"); - //now ensure it's deleted - var results = _searcher.Search(_searcher.CreateSearchCriteria().Id(2112).Compile()); - Assert.AreEqual(0, results.Count()); + //now reindex the node, this should first delete it and then NOT add it because of the parent id constraint + indexer.ReIndexNode(node, IndexTypes.Media); - } + //RESET the parent id + existingCriteria = ((IndexCriteria)indexer.IndexerData); + indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, + null); + + //now ensure it's deleted + var results = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile()); + Assert.AreEqual(0, results.Count()); + } + } - /// - /// This will ensure that all 'Content' (not media) is cleared from the index using the Lucene API directly. - /// We then call the Examine method to re-index Content and do some comparisons to ensure that it worked correctly. - /// - [Test] - public void Index_Reindex_Content() - { - var s = (IndexSearcher)_searcher.GetSearcher(); + /// + /// This will ensure that all 'Content' (not media) is cleared from the index using the Lucene API directly. + /// We then call the Examine method to re-index Content and do some comparisons to ensure that it worked correctly. + /// + [Test] + public void Index_Reindex_Content() + { + using (var luceneDir = new RAMDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir, supportUnpublishedContent:true)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir)) + { + indexer.RebuildIndex(); - //first delete all 'Content' (not media). This is done by directly manipulating the index with the Lucene API, not examine! - - var contentTerm = new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content); - var writer = _indexer.GetIndexWriter(); - writer.DeleteDocuments(contentTerm); - writer.Commit(); - + var s = (IndexSearcher)searcher.GetSearcher(); - //make sure the content is gone. This is done with lucene APIs, not examine! - var collector = new AllHitsCollector(false, true); - var query = new TermQuery(contentTerm); - s = (IndexSearcher)_searcher.GetSearcher(); //make sure the searcher is up do date. - s.Search(query, collector); - Assert.AreEqual(0, collector.Count); + //first delete all 'Content' (not media). This is done by directly manipulating the index with the Lucene API, not examine! - //call our indexing methods - _indexer.IndexAll(IndexTypes.Content); + var contentTerm = new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content); + var writer = indexer.GetIndexWriter(); + writer.DeleteDocuments(contentTerm); + writer.Commit(); + + //make sure the content is gone. This is done with lucene APIs, not examine! + var collector = new AllHitsCollector(false, true); + var query = new TermQuery(contentTerm); + s = (IndexSearcher)searcher.GetSearcher(); //make sure the searcher is up do date. + s.Search(query, collector); + Assert.AreEqual(0, collector.Count); - collector = new AllHitsCollector(false, true); - s = (IndexSearcher)_searcher.GetSearcher(); //make sure the searcher is up do date. - s.Search(query, collector); - //var ids = new List(); - //for (var i = 0; i < collector.Count;i++) - //{ - // ids.Add(s.Doc(collector.GetDocId(i)).GetValues("__NodeId")[0]); - //} - Assert.AreEqual(20, collector.Count); - } + //call our indexing methods + indexer.IndexAll(IndexTypes.Content); - /// - /// This will delete an item from the index and ensure that all children of the node are deleted too! - /// - [Test] + collector = new AllHitsCollector(false, true); + s = (IndexSearcher)searcher.GetSearcher(); //make sure the searcher is up do date. + s.Search(query, collector); + //var ids = new List(); + //for (var i = 0; i < collector.Count;i++) + //{ + // ids.Add(s.Doc(collector.GetDocId(i)).GetValues("__NodeId")[0]); + //} + Assert.AreEqual(21, collector.Count); + } + } + + /// + /// This will delete an item from the index and ensure that all children of the node are deleted too! + /// + [Test] [Ignore] - public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed() - { + public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed() + { - //now delete a node that has children + using (var luceneDir = new RAMDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir)) + { + indexer.RebuildIndex(); - _indexer.DeleteFromIndex(1140.ToString()); - //this node had children: 1141 & 1142, let's ensure they are also removed + //now delete a node that has children - var results = _searcher.Search(_searcher.CreateSearchCriteria().Id(1141).Compile()); - Assert.AreEqual(0, results.Count()); + indexer.DeleteFromIndex(1140.ToString()); + //this node had children: 1141 & 1142, let's ensure they are also removed - results = _searcher.Search(_searcher.CreateSearchCriteria().Id(1142).Compile()); - Assert.AreEqual(0, results.Count()); + var results = searcher.Search(searcher.CreateSearchCriteria().Id(1141).Compile()); + Assert.AreEqual(0, results.Count()); - } - - #region Private methods and properties - - private readonly TestContentService _contentService = new TestContentService(); - private readonly TestMediaService _mediaService = new TestMediaService(); - - private static UmbracoExamineSearcher _searcher; - private static UmbracoContentIndexer _indexer; - - #endregion - - #region Initialize and Cleanup - - private Lucene.Net.Store.Directory _luceneDir; - - public override void TearDown() - { - base.TearDown(); - _luceneDir.Dispose(); + results = searcher.Search(searcher.CreateSearchCriteria().Id(1142).Compile()); + Assert.AreEqual(0, results.Count()); + } + } + + #region Initialize and Cleanup + + public override void TearDown() + { + base.TearDown(); + UmbracoExamineSearcher.DisableInitializationCheck = null; BaseUmbracoIndexer.DisableInitializationCheck = null; } - - public override void Initialize() - { - base.Initialize(); - _luceneDir = new RAMDirectory(); - _indexer = IndexInitializer.GetUmbracoIndexer(_luceneDir); - _indexer.RebuildIndex(); - _searcher = IndexInitializer.GetUmbracoSearcher(_luceneDir); - } - - - #endregion - } + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index 9b8b3d50d7..7eb92ad49d 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -20,17 +20,14 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Test_Sort_Order_Sorting() { - //var newIndexFolder = new DirectoryInfo(Path.Combine("App_Data\\SearchTests", Guid.NewGuid().ToString())); - //System.IO.Directory.CreateDirectory(newIndexFolder.FullName); - using (var luceneDir = new RAMDirectory()) - //using (var luceneDir = new SimpleFSDirectory(newIndexFolder)) { var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir, null, new TestDataService() { ContentService = new TestContentService(TestFiles.umbraco_sort) - }); + }, + supportUnpublishedContent:true); indexer.RebuildIndex(); var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); @@ -69,25 +66,7 @@ namespace Umbraco.Tests.UmbracoExamine currentSort = sort; } return true; - } - - //[Test] - //public void Test_Index_Type_With_German_Analyzer() - //{ - // using (var luceneDir = new RAMDirectory()) - // { - // var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir, - // new GermanAnalyzer()); - // indexer.RebuildIndex(); - // var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - // } - //} - - //private readonly TestContentService _contentService = new TestContentService(); - //private readonly TestMediaService _mediaService = new TestMediaService(); - //private static UmbracoExamineSearcher _searcher; - //private static UmbracoContentIndexer _indexer; - //private Lucene.Net.Store.Directory _luceneDir; + } } } diff --git a/src/UmbracoExamine/BaseUmbracoIndexer.cs b/src/UmbracoExamine/BaseUmbracoIndexer.cs index ebb718898b..9ff68b8685 100644 --- a/src/UmbracoExamine/BaseUmbracoIndexer.cs +++ b/src/UmbracoExamine/BaseUmbracoIndexer.cs @@ -95,7 +95,7 @@ namespace UmbracoExamine /// Determines if the manager will call the indexing methods when content is saved or deleted as /// opposed to cache being updated. /// - public bool SupportUnpublishedContent { get; protected set; } + public bool SupportUnpublishedContent { get; protected internal set; } /// /// The data service used for retreiving and submitting data to the cms From 4e7d2f668bfdd594dc8a56293eee2a42744cb7d4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 13 Jan 2017 10:26:43 +0100 Subject: [PATCH 12/88] add node preview component --- .../components/umbnodepreview.directive.js | 35 +++++++++ src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../src/less/components/umb-node-preview.less | 71 +++++++++++++++++++ .../views/components/umb-node-preview.html | 13 ++++ 4 files changed, 120 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js new file mode 100644 index 0000000000..51e9b265f5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js @@ -0,0 +1,35 @@ +(function () { + 'use strict'; + + function NodePreviewDirective() { + + function link(scope, el, attr, ctrl) { + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-node-preview.html', + scope: { + icon: "=?", + name: "=", + description: "=?", + sortable: "=?", + allowEdit: "=?", + allowOpen: "=?", + allowRemove: "=?", + onEdit: "&?", + onOpen: "&?", + onRemove: "&?" + }, + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbNodePreview', NodePreviewDirective); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 2b53fbddfc..49a98ee219 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -122,6 +122,7 @@ @import "components/notifications/umb-notifications.less"; @import "components/umb-file-dropzone.less"; +@import "components/umb-node-preview.less"; // Utilities @import "utilities/_flexbox.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less new file mode 100644 index 0000000000..952a49f692 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -0,0 +1,71 @@ +.umb-node-preview { + padding: 5px 15px; + margin-bottom: 5px; + border: 2px solid #f8f8f8; + border-radius: 3px; + display: flex; + align-items: center; + background: @white; +} + +.umb-node-preview--sortable { + cursor: move; +} + +.umb-node-preview--sortable:hover { + border-color: #d9d9d9; +} + +.umb-node-preview__icon { + display: flex; + width: 25px; + height: 25px; + justify-content: center; + align-items: center; + font-size: 20px; + margin-right: 10px; + flex: 0 0 auto; +} + +.umb-node-preview__content { + flex: 1 1 auto; +} + +.umb-node-preview__name { + font-size: 13px; + font-weight: bold; + color: @black; +} + +.umb-node-preview__description { + font-size: 12px; +} + +.umb-node-preview__actions { + flex: 0 0 auto; +} + +.umb-node-preview__action { + margin-left: 5px; + margin-right: 5px; + font-size: 16px; +} + +.umb-node-preview__action:hover { + color: @blue; + text-decoration: none; +} + +.umb-node-preview-add { + display: flex; + align-items: center; + justify-content: center; + border: 1px dashed #d9d9d9; + color: @blue; + font-weight: bold; + padding: 5px 15px; +} + +.umb-node-preview-add:hover { + color: @blue; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html new file mode 100644 index 0000000000..4bcd1f39e2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html @@ -0,0 +1,13 @@ +
+ +
+
{{ name }}
+
{{ description }}
+
+
+ + + +
+ +
\ No newline at end of file From 0c709d144b3d805db1b67d66fc637f0eb0882148 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 13 Jan 2017 10:40:03 +0100 Subject: [PATCH 13/88] move mini editor launch logic to helper service so we can open it without the directive - it is needed for the content picker --- .../umblaunchminieditor.directive.js | 65 +-------------- .../services/minieditorhelper.service.js | 79 +++++++++++++++++++ 2 files changed, 81 insertions(+), 63 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/services/minieditorhelper.service.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblaunchminieditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblaunchminieditor.directive.js index 91212a82f8..11b934ce96 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblaunchminieditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblaunchminieditor.directive.js @@ -7,7 +7,7 @@ * Used on a button to launch a mini content editor editor dialog **/ angular.module("umbraco.directives") - .directive('umbLaunchMiniEditor', function (dialogService, editorState, fileManager, contentEditingHelper) { + .directive('umbLaunchMiniEditor', function (miniEditorHelper) { return { restrict: 'A', replace: false, @@ -16,69 +16,8 @@ angular.module("umbraco.directives") }, link: function(scope, element, attrs) { - var launched = false; - element.click(function() { - - if (launched === true) { - return; - } - - launched = true; - - //We need to store the current files selected in the file manager locally because the fileManager - // is a singleton and is shared globally. The mini dialog will also be referencing the fileManager - // and we don't want it to be sharing the same files as the main editor. So we'll store the current files locally here, - // clear them out and then launch the dialog. When the dialog closes, we'll reset the fileManager to it's previous state. - var currFiles = _.groupBy(fileManager.getFiles(), "alias"); - fileManager.clearFiles(); - - //We need to store the original editorState entity because it will need to change when the mini editor is loaded so that - // any property editors that are working with editorState get given the correct entity, otherwise strange things will - // start happening. - var currEditorState = editorState.getCurrent(); - - dialogService.open({ - template: "views/common/dialogs/content/edit.html", - id: scope.node.id, - closeOnSave: true, - tabFilter: ["Generic properties"], - callback: function (data) { - - //set the node name back - scope.node.name = data.name; - - //reset the fileManager to what it was - fileManager.clearFiles(); - _.each(currFiles, function (val, key) { - fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; })); - }); - - //reset the editor state - editorState.set(currEditorState); - - //Now we need to check if the content item that was edited was actually the same content item - // as the main content editor and if so, update all property data - if (data.id === currEditorState.id) { - var changed = contentEditingHelper.reBindChangedProperties(currEditorState, data); - } - - launched = false; - }, - closeCallback: function () { - //reset the fileManager to what it was - fileManager.clearFiles(); - _.each(currFiles, function (val, key) { - fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; })); - }); - - //reset the editor state - editorState.set(currEditorState); - - launched = false; - } - }); - + miniEditorHelper.launchMiniEditor(scope.node); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/minieditorhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/minieditorhelper.service.js new file mode 100644 index 0000000000..9c974f9ca9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/minieditorhelper.service.js @@ -0,0 +1,79 @@ +(function () { + 'use strict'; + + function miniEditorHelper(dialogService, editorState, fileManager, contentEditingHelper) { + + var launched = false; + + function launchMiniEditor(node) { + + launched = true; + + //We need to store the current files selected in the file manager locally because the fileManager + // is a singleton and is shared globally. The mini dialog will also be referencing the fileManager + // and we don't want it to be sharing the same files as the main editor. So we'll store the current files locally here, + // clear them out and then launch the dialog. When the dialog closes, we'll reset the fileManager to it's previous state. + var currFiles = _.groupBy(fileManager.getFiles(), "alias"); + fileManager.clearFiles(); + + //We need to store the original editorState entity because it will need to change when the mini editor is loaded so that + // any property editors that are working with editorState get given the correct entity, otherwise strange things will + // start happening. + var currEditorState = editorState.getCurrent(); + + dialogService.open({ + template: "views/common/dialogs/content/edit.html", + id: node.id, + closeOnSave: true, + tabFilter: ["Generic properties"], + callback: function (data) { + + //set the node name back + node.name = data.name; + + //reset the fileManager to what it was + fileManager.clearFiles(); + _.each(currFiles, function (val, key) { + fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; })); + }); + + //reset the editor state + editorState.set(currEditorState); + + //Now we need to check if the content item that was edited was actually the same content item + // as the main content editor and if so, update all property data + if (data.id === currEditorState.id) { + var changed = contentEditingHelper.reBindChangedProperties(currEditorState, data); + } + + launched = false; + }, + closeCallback: function () { + //reset the fileManager to what it was + fileManager.clearFiles(); + _.each(currFiles, function (val, key) { + fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; })); + }); + + //reset the editor state + editorState.set(currEditorState); + + launched = false; + } + }); + + } + + var service = { + launchMiniEditor: launchMiniEditor + }; + + return service; + + } + + + angular.module('umbraco.services').factory('miniEditorHelper', miniEditorHelper); + + +})(); From 8e79e15c71c69ddb7c882ee0f0afde897006dd73 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 13 Jan 2017 11:16:15 +0100 Subject: [PATCH 14/88] use new umb node preview component in content picker --- .../contentpicker/contentpicker.controller.js | 8 ++- .../contentpicker/contentpicker.html | 51 +++++++++---------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 667dfd0f22..c02ab7a588 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -1,7 +1,7 @@ //this controller simply tells the dialogs service to open a mediaPicker window //with a specified callback, this callback will receive an object with a selection on it -function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location) { +function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location, $timeout, miniEditorHelper) { function trim(str, chr) { var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); @@ -77,6 +77,8 @@ function contentPickerController($scope, dialogService, entityResource, editorSt : "Document"; $scope.allowOpenButton = entityType === "Document" || entityType === "Media"; $scope.allowEditButton = entityType === "Document"; + $scope.allowRemoveButton = true; + $scope.sortable = true; //the dialog options for the picker var dialogOptions = { @@ -200,6 +202,10 @@ function contentPickerController($scope, dialogService, entityResource, editorSt $scope.clear = function () { $scope.renderModel = []; }; + + $scope.openMiniEditor = function(node) { + miniEditorHelper.launchMiniEditor(node); + }; var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { var currIds = _.map($scope.renderModel, function (i) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index 9f5f3fb60f..760063b94b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -1,33 +1,30 @@
- - - - + +
+ + +
+ + + Add + From 21714f0eacdbd14822212c91faa758c0ed230fe4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 13 Jan 2017 11:16:59 +0100 Subject: [PATCH 15/88] use new umb node preview component in member group picker --- .../membergrouppicker.controller.js | 1 + .../membergrouppicker/membergrouppicker.html | 37 ++++++++----------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js index 5ebbd37217..aa20a9c43b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js @@ -8,6 +8,7 @@ function memberGroupPicker($scope, dialogService){ } $scope.renderModel = []; + $scope.allowRemove = true; if ($scope.model.value) { var modelIds = $scope.model.value.split(','); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html index 5258968c00..60ae9cb202 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html @@ -1,27 +1,22 @@
+
+ + +
- - - + + Add + Date: Fri, 13 Jan 2017 11:17:28 +0100 Subject: [PATCH 16/88] use new umb node preview component in member picker --- .../memberpicker/memberpicker.controller.js | 1 + .../memberpicker/memberpicker.html | 37 ++++++++----------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js index 9f20e121d9..0d4b2817d0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js @@ -8,6 +8,7 @@ function memberPickerController($scope, dialogService, entityResource, $log, ico } $scope.renderModel = []; + $scope.allowRemove = true; var dialogOptions = { multiPicker: false, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.html index b537a80113..a7a4127b18 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.html @@ -1,27 +1,22 @@
- - - + + Add + Date: Fri, 13 Jan 2017 11:45:49 +0100 Subject: [PATCH 17/88] U4-9377 It is possible to create/save entities with a blank names by using the services directly ensuring that when trying to save an entity with an empty name, directly through a service, an exception will be thrown. --- src/Umbraco.Core/Services/ContentService.cs | 5 +++ .../Services/ContentTypeService.cs | 9 ++++- src/Umbraco.Core/Services/DataTypeService.cs | 9 ++++- src/Umbraco.Core/Services/MacroService.cs | 27 ++++++++------ src/Umbraco.Core/Services/MediaService.cs | 4 ++ src/Umbraco.Core/Services/MemberService.cs | 5 +++ .../Services/MemberTypeService.cs | 5 +++ src/Umbraco.Core/Services/UserService.cs | 22 +++++++++++ .../Services/ContentServiceTests.cs | 11 ++++++ .../Services/ContentTypeServiceTests.cs | 10 +++++ .../Services/DataTypeServiceTests.cs | 13 +++++++ .../Services/MacroServiceTests.cs | 14 ++++++- .../Services/MediaServiceTests.cs | 13 +++++++ .../Services/MemberServiceTests.cs | 12 ++++++ .../Services/MemberTypeServiceTests.cs | 10 +++++ .../Services/UserServiceTests.cs | 37 +++++++++++++++++++ 16 files changed, 190 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index e0edaa38c4..0838a0eb85 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2192,6 +2192,11 @@ namespace Umbraco.Core.Services } } + if (string.IsNullOrWhiteSpace(content.Name)) + { + throw new ArgumentException("Cannot save content with empty name."); + } + using (new WriteLock(Locker)) { var uow = UowProvider.GetUnitOfWork(); diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 31e86cd128..9f625c27b8 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -719,8 +719,13 @@ namespace Umbraco.Core.Services /// Optional id of the user saving the ContentType public void Save(IContentType contentType, int userId = 0) { - if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs(contentType), this)) - return; + if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs(contentType), this)) + return; + + if (string.IsNullOrWhiteSpace(contentType.Name)) + { + throw new ArgumentException("Cannot save content type with empty name."); + } using (new WriteLock(Locker)) { diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 2cdcfc76d5..b3fca10798 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -361,8 +361,13 @@ namespace Umbraco.Core.Services /// Id of the user issueing the save public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0) { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) - return; + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) + return; + + if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name)) + { + throw new ArgumentException("Cannot save datatype with empty name."); + } var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) diff --git a/src/Umbraco.Core/Services/MacroService.cs b/src/Umbraco.Core/Services/MacroService.cs index 49853eab81..eca5f7e954 100644 --- a/src/Umbraco.Core/Services/MacroService.cs +++ b/src/Umbraco.Core/Services/MacroService.cs @@ -142,19 +142,24 @@ namespace Umbraco.Core.Services /// Optional Id of the user deleting the macro public void Save(IMacro macro, int userId = 0) { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(macro), this)) - return; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMacroRepository(uow)) - { - repository.AddOrUpdate(macro); - uow.Commit(); + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(macro), this)) + return; - Saved.RaiseEvent(new SaveEventArgs(macro, false), this); - } + if (string.IsNullOrWhiteSpace(macro.Name)) + { + throw new ArgumentException("Cannot save macro with empty name."); + } - Audit(AuditType.Save, "Save Macro performed by user", userId, -1); + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMacroRepository(uow)) + { + repository.AddOrUpdate(macro); + uow.Commit(); + + Saved.RaiseEvent(new SaveEventArgs(macro, false), this); + } + + Audit(AuditType.Save, "Save Macro performed by user", userId, -1); } ///// diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index bd373f1ec0..50b323ed0a 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -837,7 +837,11 @@ namespace Umbraco.Core.Services { return OperationStatus.Cancelled(evtMsgs); } + } + if (string.IsNullOrWhiteSpace(media.Name)) + { + throw new ArgumentException("Cannot save media with empty name."); } var uow = UowProvider.GetUnitOfWork(); diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 094539d66e..2ea4f1f023 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -978,6 +978,11 @@ namespace Umbraco.Core.Services } } + if (string.IsNullOrWhiteSpace(entity.Name)) + { + throw new ArgumentException("Cannot save member with empty name."); + } + var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMemberRepository(uow)) { diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs index c6bc9dd24a..164bbc8243 100644 --- a/src/Umbraco.Core/Services/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -78,6 +78,11 @@ namespace Umbraco.Core.Services if (Saving.IsRaisedEventCancelled(new SaveEventArgs(memberType), this)) return; + if (string.IsNullOrWhiteSpace(memberType.Name)) + { + throw new ArgumentException("Cannot save MemberType with empty name."); + } + using (new WriteLock(Locker)) { var uow = UowProvider.GetUnitOfWork(); diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 8e984d1e5d..e36e454987 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -125,6 +125,11 @@ namespace Umbraco.Core.Services { if (userType == null) throw new ArgumentNullException("userType"); + if (string.IsNullOrWhiteSpace(username)) + { + throw new ArgumentException("Cannot create user with empty username."); + } + //TODO: PUT lock here!! var uow = UowProvider.GetUnitOfWork(); @@ -312,6 +317,15 @@ namespace Umbraco.Core.Services return; } + if (string.IsNullOrWhiteSpace(entity.Username)) + { + throw new ArgumentException("Cannot save user with empty username."); + } + if (string.IsNullOrWhiteSpace(entity.Name)) + { + throw new ArgumentException("Cannot save user with empty name."); + } + var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateUserRepository(uow)) { @@ -353,6 +367,14 @@ namespace Umbraco.Core.Services { foreach (var member in entities) { + if (string.IsNullOrWhiteSpace(member.Username)) + { + throw new ArgumentException("Cannot save user with empty username."); + } + if (string.IsNullOrWhiteSpace(member.Name)) + { + throw new ArgumentException("Cannot save user with empty name."); + } repository.AddOrUpdate(member); } //commit the whole lot in one go diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 72a58861e2..0078d3e83e 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -668,6 +668,17 @@ namespace Umbraco.Tests.Services Assert.Throws(() => contentService.CreateContent("Test", -1, "umbAliasDoesntExist")); } + [Test] + public void Cannot_Save_Content_With_Empty_Name() + { + // Arrange + var contentService = ServiceContext.ContentService; + var content = new Content(string.Empty, -1, ServiceContext.ContentTypeService.GetContentType("umbTextpage")); + + // Act & Assert + Assert.Throws(() => contentService.Save(content)); + } + [Test] public void Can_Get_Content_By_Id() { diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index bc9ec8a9ad..3fd0e1f6cd 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -717,6 +717,16 @@ namespace Umbraco.Tests.Services Assert.DoesNotThrow(() => service.GetContentType("advancedPage")); } + [Test] + public void Cannot_Save_ContentType_With_Empty_Name() + { + // Arrange + var contentType = MockedContentTypes.CreateSimpleContentType("contentType", string.Empty); + + // Act & Assert + Assert.Throws(() => ServiceContext.ContentTypeService.Save(contentType)); + } + [Test] public void Cannot_Rename_PropertyType_Alias_On_Composition_Which_Would_Cause_Conflict_In_Other_Composition() { diff --git a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs index 073d7af406..ed9fc9a7e7 100644 --- a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs @@ -221,5 +221,18 @@ namespace Umbraco.Tests.Services Assert.AreEqual("preVal1", preVals.PreValuesAsArray.First().Value); Assert.AreEqual("preVal2", preVals.PreValuesAsArray.Last().Value); } + + [Test] + public void Cannot_Save_DataType_With_Empty_Name() + { + // Arrange + var dataTypeService = ServiceContext.DataTypeService; + + // Act + var dataTypeDefinition = new DataTypeDefinition(-1, "Test.TestEditor") { Name = string.Empty, DatabaseType = DataTypeDatabaseType.Ntext }; + + // Act & Assert + Assert.Throws(() => dataTypeService.Save(dataTypeDefinition)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/MacroServiceTests.cs b/src/Umbraco.Tests/Services/MacroServiceTests.cs index 5e52140bb5..770cc76fa6 100644 --- a/src/Umbraco.Tests/Services/MacroServiceTests.cs +++ b/src/Umbraco.Tests/Services/MacroServiceTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -217,6 +218,17 @@ namespace Umbraco.Tests.Services } + [Test] + public void Cannot_Save_Macro_With_Empty_Name() + { + // Arrange + var macroService = ServiceContext.MacroService; + var macro = new Macro("test", string.Empty, scriptPath: "~/Views/MacroPartials/Test.cshtml", cacheDuration: 1234); + + // Act & Assert + Assert.Throws(() => macroService.Save(macro)); + } + //[Test] //public void Can_Get_Many_By_Alias() //{ diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index a53257b26a..8193df911c 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -81,6 +81,19 @@ namespace Umbraco.Tests.Services Assert.That(mediaChild.Trashed, Is.False); } + [Test] + public void Cannot_Save_Media_With_Empty_Name() + { + // Arrange + var mediaService = ServiceContext.MediaService; + var mediaType = MockedContentTypes.CreateVideoMediaType(); + ServiceContext.ContentTypeService.Save(mediaType); + var media = mediaService.CreateMedia(string.Empty, -1, "video"); + + // Act & Assert + Assert.Throws(() => mediaService.Save(media)); + } + [Test] public void Ensure_Content_Xml_Created() { diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index c9cf4e6d5b..522ecc4a8c 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -185,6 +185,18 @@ namespace Umbraco.Tests.Services Assert.AreEqual(2, membersInRole.Count()); } + [Test] + public void Cannot_Save_Member_With_Empty_Name() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, string.Empty, "test@test.com", "pass", "test"); + + // Act & Assert + Assert.Throws(() => ServiceContext.MemberService.Save(member)); + + } + [TestCase("MyTestRole1", "test1", StringPropertyMatchType.StartsWith, 1)] [TestCase("MyTestRole1", "test", StringPropertyMatchType.StartsWith, 3)] [TestCase("MyTestRole1", "test1", StringPropertyMatchType.Exact, 1)] diff --git a/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs b/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs index c81d8c3f4e..8dd18b8b50 100644 --- a/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs @@ -173,6 +173,16 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Cannot_Save_MemberType_With_Empty_Name() + { + // Arrange + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType("memberTypeAlias", string.Empty); + + // Act & Assert + Assert.Throws(() => ServiceContext.MemberTypeService.Save(memberType)); + } + //[Test] //public void Can_Save_MemberType_Structure_And_Create_A_Member_Based_On_It() //{ diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index c7002ce79d..833ed1771b 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -490,6 +490,43 @@ namespace Umbraco.Tests.Services Assert.IsTrue(result4.AllowedSections.Contains("test")); } + [Test] + public void Cannot_Create_User_With_Empty_Username() + { + // Arrange + var userService = ServiceContext.UserService; + var userType = userService.GetUserTypeByAlias("admin"); + + // Act & Assert + Assert.Throws(() => userService.CreateUserWithIdentity(string.Empty, "john@umbraco.io", userType)); + } + + [Test] + public void Cannot_Save_User_With_Empty_Username() + { + // Arrange + var userService = ServiceContext.UserService; + var userType = userService.GetUserTypeByAlias("admin"); + var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io", userType); + user.Username = string.Empty; + + // Act & Assert + Assert.Throws(() => userService.Save(user)); + } + + [Test] + public void Cannot_Save_User_With_Empty_Name() + { + // Arrange + var userService = ServiceContext.UserService; + var userType = userService.GetUserTypeByAlias("admin"); + var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io", userType); + user.Name = string.Empty; + + // Act & Assert + Assert.Throws(() => userService.Save(user)); + } + [Test] public void Get_By_Profile_Username() { From 4fa36cd4247ccab1115869dcb3beba3e60be2c09 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 13 Jan 2017 13:16:47 +0100 Subject: [PATCH 18/88] fine tuning --- .../components/umbnodepreview.directive.js | 2 -- .../src/less/components/umb-node-preview.less | 15 ++++++--- .../views/components/umb-node-preview.html | 5 ++- .../contentpicker/contentpicker.controller.js | 32 +++++++++++-------- .../contentpicker/contentpicker.html | 9 +++--- 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js index 51e9b265f5..62a8bc766e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js @@ -16,10 +16,8 @@ name: "=", description: "=?", sortable: "=?", - allowEdit: "=?", allowOpen: "=?", allowRemove: "=?", - onEdit: "&?", onOpen: "&?", onRemove: "&?" }, diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index 952a49f692..b69d4b4b22 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -1,11 +1,11 @@ .umb-node-preview { padding: 5px 15px; margin-bottom: 5px; - border: 2px solid #f8f8f8; + background: @grayLighter; border-radius: 3px; display: flex; align-items: center; - background: @white; + max-width: 66.6%; } .umb-node-preview--sortable { @@ -38,22 +38,28 @@ } .umb-node-preview__description { - font-size: 12px; + font-size: 11px; + line-height: 1.5em; } .umb-node-preview__actions { flex: 0 0 auto; + display: flex; + align-items: center; } .umb-node-preview__action { margin-left: 5px; margin-right: 5px; - font-size: 16px; + font-size: 13px; + font-weight: bold; + opacity: 0.5; } .umb-node-preview__action:hover { color: @blue; text-decoration: none; + opacity: 1; } .umb-node-preview-add { @@ -64,6 +70,7 @@ color: @blue; font-weight: bold; padding: 5px 15px; + max-width: 66.6%; } .umb-node-preview-add:hover { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html index 4bcd1f39e2..b811ae8eec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html @@ -5,9 +5,8 @@
{{ description }}
- - - + Open + Remove
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index c02ab7a588..ccf9d865bc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -1,7 +1,7 @@ //this controller simply tells the dialogs service to open a mediaPicker window //with a specified callback, this callback will receive an object with a selection on it -function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location, $timeout, miniEditorHelper) { +function contentPickerController($scope, dialogService, entityResource, contentResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location, $timeout, miniEditorHelper) { function trim(str, chr) { var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); @@ -221,26 +221,32 @@ function contentPickerController($scope, dialogService, entityResource, editorSt //load current data var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - entityResource.getByIds(modelIds, entityType).then(function (data) { + var nodePromise = (entityType === "Document") ? contentResource.getByIds(modelIds) : entityResource.getByIds(modelIds, entityType); - //Ensure we populate the render model in the same order that the ids were stored! - _.each(modelIds, function (id, i) { - var entity = _.find(data, function (d) { - return d.id == id; - }); - - if (entity) { - entity.icon = iconHelper.convertFromLegacyIcon(entity.icon); - $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: entity.path }); - } + nodePromise.then(function (data) { + + _.each(modelIds, function (id, i) { + var entity = _.find(data, function (d) { + return d.id == id; + }); + + if (entity) { + entity.icon = iconHelper.convertFromLegacyIcon(entity.icon); + + var url = (entity.urls && entity.urls.length > 0) ? entity.urls[0] : ""; + var path = ($scope.model.config.showPathOnHover) ? entity.path : ""; + + $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: path, url: url }); + } - }); + }); //everything is loaded, start the watch on the model startWatch(); }); + } angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index 760063b94b..abcb7f20c0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -1,20 +1,19 @@
- +
+ on-open="openMiniEditor(node)">
From 9b9db23461b18d9b5b971f389a800d0fd41b1a34 Mon Sep 17 00:00:00 2001 From: Per Ploug Date: Fri, 13 Jan 2017 13:20:38 +0100 Subject: [PATCH 19/88] Replaces the last static strings with language keys --- .../overlays/insert/insert.controller.js | 17 ++++---- .../views/common/overlays/insert/insert.html | 2 +- .../overlays/querybuilder/querybuilder.html | 2 +- .../templatesections/templatesections.html | 40 +++++++------------ .../src/views/templates/edit.controller.js | 15 +++---- .../src/views/templates/edit.html | 2 +- .../Models/ContentEditing/TemplateDisplay.cs | 3 ++ 7 files changed, 37 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.controller.js index 59a00a5797..b99dd289b1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.controller.js @@ -1,16 +1,16 @@ (function () { "use strict"; - function InsertOverlayController($scope) { + function InsertOverlayController($scope, localizationService) { var vm = this; if(!$scope.model.title) { - $scope.model.title = "Insert"; + $scope.model.title = localizationService.localize("template_insert"); } if(!$scope.model.subtitle) { - $scope.model.subtitle = "Choose what to insert into your template"; + $scope.model.subtitle = localizationService.localize("template_insertDesc"); } vm.openMacroPicker = openMacroPicker; @@ -22,7 +22,7 @@ vm.macroPickerOverlay = { view: "macropicker", - title: "Insert macro", + title: localizationService.localize("template_insertMacro"), dialogData: {}, show: true, submit: function(model) { @@ -45,8 +45,8 @@ function openPageFieldOverlay() { vm.pageFieldOverlay = { - title: "Insert value", - description: "Select a value from the currentpage", + title: localizationService.localize("template_insertPageField"), + description: localizationService.localize("template_insertPageFieldDesc"), submitButtonLabel: "Insert", closeButtonlabel: "Cancel", view: "insertfield", @@ -78,7 +78,8 @@ treeAlias: "dictionary", entityType: "dictionary", multiPicker: false, - title: "Insert dictionary item", + title: localizationService.localize("template_insertDictionaryItem"), + description: localizationService.localize("template_insertDictionaryItemDesc"), show: true, select: function(node){ @@ -108,7 +109,7 @@ entityType: "partialView", multiPicker: false, show: true, - title: "Insert partial view", + title: localizationService.localize("template_insertPartialView"), select: function(node){ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.html index 4b92ea86ad..c2926f6acc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.html @@ -24,7 +24,7 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html index e9be97b7df..678733f1c8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html @@ -22,7 +22,7 @@
- from + from {{vm.query.source.name}} diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/templatesections/templatesections.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/templatesections/templatesections.html index 1a11d6a46e..ee62c3426d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/templatesections/templatesections.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/templatesections/templatesections.html @@ -1,71 +1,59 @@
-
+
-
Render child template
+
- Renders the contents of a child template, by inserting a - @RenderBody() placeholder. +
-
+
-
Render a named section
+
- Renders a named area of a child template, by insert a @RenderSection(name) placeholder. - This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. +
- + -
- Set the name of the section to render in this area of the template -
+
- If mandatory, the child template must contain a @section definition, otherwise an error is shown. +
-
+
-
Define a named section
+
- Defines a part of your template as a named section by wrapping it in - a @section { ... }. This can be rendered in a - specific area of the master of this template, by using @RenderSection. +
- + -
- Give the section a name -
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index 8e2b8337ad..97aa50cbbf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -220,7 +220,7 @@ view: "macropicker", dialogData: {}, show: true, - title: "Insert macro", + title: localizationService.localize("template_insertMacro"), submit: function (model) { var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); @@ -247,6 +247,7 @@ closeButtonlabel: "Cancel", view: "insertfield", show: true, + title: localizationService.localize("template_insertPageField"), submit: function (model) { insert(model.umbracoField); vm.pageFieldOverlay.show = false; @@ -271,7 +272,7 @@ entityType: "dictionary", multiPicker: false, show: true, - title: "Insert dictionary item", + title: localizationService.localize("template_insertDictionaryItem"), select: function(node){ //crappy hack due to dictionary items not in umbracoNode table var code = "@Umbraco.GetDictionaryValue(\"" + node.name + "\")"; @@ -298,7 +299,7 @@ entityType: "partialView", multiPicker: false, show: true, - title: "Insert Partial view", + title: localizationService.localize("template_insertPartialView"), select: function(node){ //crappy hack due to dictionary items not in umbracoNode table var nodeNameWithPath = node.id.replace(".cshtml", ""); @@ -322,7 +323,7 @@ vm.queryBuilderOverlay = { view: "querybuilder", show: true, - title: "Query for content", + title: localizationService.localize("template_queryBuilder"), submit: function (model) { @@ -356,7 +357,7 @@ vm.sectionsOverlay = { view: "templatesections", - hasMaster: vm.template.masterTemplateAlias, + isMaster: vm.template.isMasterTemplate, submitButtonLabel: "Insert", show: true, submit: function(model) { @@ -401,7 +402,7 @@ vm.masterTemplateOverlay = { view: "itempicker", - title: "Choose master template", + title: localizationService.localize("template_mastertemplate"), availableItems: availableMasterTemplates, show: true, submit: function(model) { @@ -457,7 +458,7 @@ return templateName; } else { - return "No master"; + return "no master"; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index b0e15e8ad7..2e5593894c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -62,7 +62,7 @@
  • Value
  • Partial view
  • Dictionary
  • -
  • Macro
  • +
  • Macro
  • diff --git a/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs index 8e813bd3d3..91c1aefdb0 100644 --- a/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs @@ -36,6 +36,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "masterTemplateAlias")] public string MasterTemplateAlias { get; set; } + [DataMember(Name = "isMasterTemplate")] + public bool IsMasterTemplate { get; set; } + /// /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. /// From b413a6eeaedf77ae631e6e28afd7363414f9bcf3 Mon Sep 17 00:00:00 2001 From: Per Ploug Date: Fri, 13 Jan 2017 13:20:49 +0100 Subject: [PATCH 20/88] English translations --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 2021be1d9d..3a7a0fda7c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1032,6 +1032,9 @@ To manage your website, simply open the Umbraco back office and start adding con Sections Insert content area Insert content area placeholder + + Insert + Choose what to insert into your template Dictionary item A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites. @@ -1053,15 +1056,50 @@ To manage your website, simply open the Umbraco back office and start adding con Master template + No master template + + Render child template + + @RenderBody() placeholder. + ]]> + + + + Define a named section + + @section { ... }. This can be rendered in a + specific area of the parent of this template, by using @RenderSection. + ]]> + + + Render a named section + + @RenderSection(name) placeholder. + This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. + ]]> + + + Section Name + Section is mandatory + + If mandatory, the child template must contain a @section definition, otherwise an error is shown. + + Query builder items returned, in I want + everything from where and - everything + Template From 99f66fe6a30c1542bcd2084e55e411ef2e9f91a1 Mon Sep 17 00:00:00 2001 From: Per Ploug Date: Fri, 13 Jan 2017 13:21:37 +0100 Subject: [PATCH 21/88] The danish language files - WIP The danish language has such elements when used to discusssed semi-technical-english terms --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 86 +++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index b0452fb631..3c52d7f1a1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1004,17 +1004,89 @@ Mange hilsner fra Umbraco robotten Vis prøve Styles + - Rediger skabelon - Indsæt indholdsområde - Indsæt indholdsområdemarkering - Indsæt ordbogselement - Indsæt makro - Indsæt Umbraco sidefelt + Rediger skabelong + + Sektioner + Insert content area + Insert content area placeholder + + Indsæt + Vælg hvad du vil indsætte + + Oversættelse + Indsætter en oversætbar tekst som skifter efter det sprog som websitet vises i. + + Makro + + En makro er et element som kan have forskellige indstillinger når det indsættes. + Det er glimrende som en genbrugelig del af dit design såsom gallerier, formularer + og lister. + + + Value + Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values. + + Partial view + + A partial view is a separate template file which can be rendered inside another + template, it's great for reusing markup or for separating complex templates into separate files. + + Master skabelon - Lynguide til Umbracos skabelontags + Ingen master skabelon + + Indsæt en underliggende skabelon + + @RenderBody() element. + ]]> + + + + Definer en sektion + + @section { ... }. Herefter kan denne sektion flettes ind i + overliggende skabelon ved at indsætte et @RenderSection element. + ]]> + + + Indsæt en sektion + + @RenderSection(name) element. Den underliggende skabelon skal have + defineret en sektion via et @section [name]{ ... } element. + ]]> + + + Sektion navn + Sektionen er oblikatorisk + + + Hvis oblikatorisk, skal under-skabelonen indeholde en @section definition. + + + + Query builder + sider returneret, på + + Returner + alt + fra + filtre + og + + + Skabelon + + Alternativt felt Alternativ tekst From 361f5093c7ea1d0a855ff1ccd7d2218e2bcc79e2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 13 Jan 2017 14:42:36 +0100 Subject: [PATCH 22/88] align width with input fields --- .../src/less/components/umb-node-preview.less | 2 ++ src/Umbraco.Web.UI.Client/src/less/forms.less | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index b69d4b4b22..3f51b09154 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -6,6 +6,7 @@ display: flex; align-items: center; max-width: 66.6%; + box-sizing: border-box; } .umb-node-preview--sortable { @@ -71,6 +72,7 @@ font-weight: bold; padding: 5px 15px; max-width: 66.6%; + box-sizing: border-box; } .umb-node-preview-add:hover { diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index d046ac7104..5b6e97ad71 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -190,7 +190,7 @@ input[type="tel"], input[type="color"], .uneditable-input { display: inline-block; - height: @baseLineHeight; + height: 30px; padding: 4px 6px; margin-bottom: @baseLineHeight / 2; font-size: @baseFontSize; @@ -198,6 +198,7 @@ input[type="color"], color: @gray; .border-radius(@inputBorderRadius); vertical-align: middle; + box-sizing: border-box; } input.-full-width-input { From a15bdba5d751c80ab756f1b2991c9b19c642366a Mon Sep 17 00:00:00 2001 From: jamiepollock Date: Sun, 15 Jan 2017 18:26:20 -0500 Subject: [PATCH 23/88] View fix for icons not appearing in the installed package list --- .../src/views/packager/views/installed.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html index 5da3c33a76..a41a7e72c0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html @@ -12,8 +12,8 @@
    - - + +
    From ede342febad2a6777185c64b300c03809490b5e0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Jan 2017 16:38:23 +1100 Subject: [PATCH 24/88] Finds all other instances where a reader would be executed inside another reader's loop and fixes --- src/Umbraco.Core/Cache/CacheKeys.cs | 3 +- src/Umbraco.Core/DatabaseContext.cs | 13 +- .../Cache/ContentTypeCacheRefresher.cs | 10 +- .../umbraco/controls/ContentTypeControl.cs | 3 + .../umbraco/users/EditUser.aspx.cs | 16 +- src/umbraco.cms/businesslogic/Tags/Tag.cs | 2 +- .../propertytype/propertytype.cs | 143 +++++++----------- src/umbraco.cms/businesslogic/web/Document.cs | 23 ++- .../PickerRelationsEventHandler.cs | 28 ++-- 9 files changed, 97 insertions(+), 144 deletions(-) diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index 0c1a202b66..3b9ee9c63a 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -64,7 +64,8 @@ namespace Umbraco.Core.Cache [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:"; - [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] + [Obsolete("No longer used and will be removed in v8")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string PropertyTypeCacheKey = "UmbracoPropertyTypeCache"; [Obsolete("This is no longer used and will be removed from the codebase in the future")] diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index a6d6ca9e6e..d4a5a309af 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -203,15 +203,10 @@ namespace Umbraco.Core var path = Path.Combine(GlobalSettings.FullpathToRoot, "App_Data", "Umbraco.sdf"); if (File.Exists(path) == false) { - var engine = new SqlCeEngine(connectionString); - engine.CreateDatabase(); - - // SD: Pretty sure this should be in a using clause but i don't want to cause unknown side-effects here - // since it's been like this for quite some time - //using (var engine = new SqlCeEngine(connectionString)) - //{ - // engine.CreateDatabase(); - //} + using (var engine = new SqlCeEngine(connectionString)) + { + engine.CreateDatabase(); + } } Initialize(providerName); diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 246571d479..1340545621 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -131,9 +131,7 @@ namespace Umbraco.Web.Cache ClearAllIsolatedCacheByEntityType(); ClearAllIsolatedCacheByEntityType(); ClearAllIsolatedCacheByEntityType(); - - //all property type cache - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.PropertyTypeCacheKey); + //all content type property cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.ContentTypePropertiesCacheKey); //all content type cache @@ -266,12 +264,6 @@ namespace Umbraco.Web.Cache /// private static void ClearContentTypeCache(JsonPayload payload) { - //clears the cache for each property type associated with the content type - foreach (var pid in payload.PropertyTypeIds) - { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.PropertyTypeCacheKey + pid); - } - //clears the cache associated with the Content type itself ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(string.Format("{0}{1}", CacheKeys.ContentTypeCacheKey, payload.Id)); //clears the cache associated with the content type properties collection diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControl.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControl.cs index 5a8b0b616e..c912db701d 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControl.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControl.cs @@ -2,6 +2,7 @@ using System; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Collections; +using System.ComponentModel; using System.IO; using Umbraco.Core.IO; using System.Linq; @@ -11,6 +12,8 @@ namespace umbraco.controls /// /// Summary description for ContentTypeControl. /// + [Obsolete("No longer used, will be removed in v8")] + [EditorBrowsable(EditorBrowsableState.Never)] public class ContentTypeControl : uicontrols.TabView { public event System.EventHandler OnSave; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index 9734401d95..91a8677c81 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Configuration.Provider; using System.Globalization; using System.IO; +using System.Linq; using System.Web; using System.Web.Security; using System.Web.UI; @@ -320,13 +321,14 @@ namespace umbraco.cms.presentation.user } // Populate dropdowns - foreach (DocumentType dt in DocumentType.GetAllAsList()) - cDocumentType.Items.Add( - new ListItem(dt.Text, dt.Alias) - ); + var allContentTypes = Services.ContentTypeService.GetAllContentTypes().ToList(); + foreach (var dt in allContentTypes) + { + cDocumentType.Items.Add(new ListItem(dt.Name, dt.Alias)); + } // populate fields - ArrayList fields = new ArrayList(); + var fields = new ArrayList(); cDescription.ID = "cDescription"; cCategories.ID = "cCategories"; cExcerpt.ID = "cExcerpt"; @@ -334,9 +336,9 @@ namespace umbraco.cms.presentation.user cCategories.Items.Add(new ListItem(ui.Text("choose"), "")); cExcerpt.Items.Add(new ListItem(ui.Text("choose"), "")); - foreach (PropertyType pt in PropertyType.GetAll()) + foreach (var pt in allContentTypes.SelectMany(x => x.PropertyTypes).OrderBy(x => x.Name)) { - if (!fields.Contains(pt.Alias)) + if (fields.Contains(pt.Alias) == false) { cDescription.Items.Add(new ListItem(string.Format("{0} ({1})", pt.Name, pt.Alias), pt.Alias)); cCategories.Items.Add(new ListItem(string.Format("{0} ({1})", pt.Name, pt.Alias), pt.Alias)); diff --git a/src/umbraco.cms/businesslogic/Tags/Tag.cs b/src/umbraco.cms/businesslogic/Tags/Tag.cs index 8534d02d26..d4e0c90d00 100644 --- a/src/umbraco.cms/businesslogic/Tags/Tag.cs +++ b/src/umbraco.cms/businesslogic/Tags/Tag.cs @@ -351,7 +351,7 @@ namespace umbraco.cms.businesslogic.Tags { Document cnode = new Document(rr.GetInt("nodeid")); - if (cnode != null && cnode.Published) + if (cnode.Published) docs.Add(cnode); } } diff --git a/src/umbraco.cms/businesslogic/propertytype/propertytype.cs b/src/umbraco.cms/businesslogic/propertytype/propertytype.cs index b9622d7c91..e041306ba3 100644 --- a/src/umbraco.cms/businesslogic/propertytype/propertytype.cs +++ b/src/umbraco.cms/businesslogic/propertytype/propertytype.cs @@ -54,32 +54,31 @@ namespace umbraco.cms.businesslogic.propertytype public PropertyType(int id) { - using (var sqlHelper = Application.SqlHelper) - using (IRecordsReader dr = sqlHelper.ExecuteReader( - "Select mandatory, DataTypeId, propertyTypeGroupId, ContentTypeId, sortOrder, alias, name, validationRegExp, description from cmsPropertyType where id=@id", - sqlHelper.CreateParameter("@id", id))) + var found = ApplicationContext.Current.DatabaseContext.Database + .SingleOrDefault( + "Select mandatory, DataTypeId, propertyTypeGroupId, contentTypeId, sortOrder, alias, name, validationRegExp, description from cmsPropertyType where id=@id", + new {id = id}); + + if (found == null) + throw new ArgumentException("Propertytype with id: " + id + " doesnt exist!"); + + _mandatory = found.mandatory; + _id = id; + + if (found.propertyTypeGroupId != null) { - if (!dr.Read()) - throw new ArgumentException("Propertytype with id: " + id + " doesnt exist!"); - - _mandatory = dr.GetBoolean("mandatory"); - _id = id; - - if (!dr.IsNull("propertyTypeGroupId")) - { - _propertyTypeGroup = dr.GetInt("propertyTypeGroupId"); - //TODO: Remove after refactoring! - _tabId = _propertyTypeGroup; - } - - _sortOrder = dr.GetInt("sortOrder"); - _alias = dr.GetString("alias"); - _name = dr.GetString("Name"); - _validationRegExp = dr.GetString("validationRegExp"); - _DataTypeId = dr.GetInt("DataTypeId"); - _contenttypeid = dr.GetInt("contentTypeId"); - _description = dr.GetString("description"); + _propertyTypeGroup = found.propertyTypeGroupId; + //TODO: Remove after refactoring! + _tabId = _propertyTypeGroup; } + + _sortOrder = found.sortOrder; + _alias = found.alias; + _name = found.name; + _validationRegExp = found.validationRegExp; + _DataTypeId = found.DataTypeId; + _contenttypeid = found.contentTypeId; + _description = found.description; } #endregion @@ -92,7 +91,6 @@ namespace umbraco.cms.businesslogic.propertytype set { _DataTypeId = value.Id; - InvalidateCache(); using (var sqlHelper = Application.SqlHelper) sqlHelper.ExecuteNonQuery( "Update cmsPropertyType set DataTypeId = " + value.Id + " where id=" + Id); @@ -119,7 +117,6 @@ namespace umbraco.cms.businesslogic.propertytype { _tabId = value; PropertyTypeGroup = value; - InvalidateCache(); } } @@ -148,7 +145,6 @@ namespace umbraco.cms.businesslogic.propertytype set { _mandatory = value; - InvalidateCache(); using (var sqlHelper = Application.SqlHelper) sqlHelper.ExecuteNonQuery("Update cmsPropertyType set mandatory = @mandatory where id = @id", sqlHelper.CreateParameter("@mandatory", value), @@ -162,7 +158,6 @@ namespace umbraco.cms.businesslogic.propertytype set { _validationRegExp = value; - InvalidateCache(); using (var sqlHelper = Application.SqlHelper) sqlHelper.ExecuteNonQuery("Update cmsPropertyType set validationRegExp = @validationRegExp where id = @id", sqlHelper.CreateParameter("@validationRegExp", value), sqlHelper.CreateParameter("@id", Id)); @@ -199,7 +194,6 @@ namespace umbraco.cms.businesslogic.propertytype set { _description = value; - InvalidateCache(); using (var sqlHelper = Application.SqlHelper) sqlHelper.ExecuteNonQuery("Update cmsPropertyType set description = @description where id = @id", sqlHelper.CreateParameter("@description", value), @@ -213,7 +207,6 @@ namespace umbraco.cms.businesslogic.propertytype set { _sortOrder = value; - InvalidateCache(); using (var sqlHelper = Application.SqlHelper) sqlHelper.ExecuteNonQuery("Update cmsPropertyType set sortOrder = @sortOrder where id = @id", sqlHelper.CreateParameter("@sortOrder", value), @@ -227,7 +220,6 @@ namespace umbraco.cms.businesslogic.propertytype set { _alias = value; - InvalidateCache(); using (var sqlHelper = Application.SqlHelper) sqlHelper.ExecuteNonQuery("Update cmsPropertyType set alias = @alias where id= @id", sqlHelper.CreateParameter("@alias", Casing.SafeAliasWithForcingCheck(_alias)), @@ -264,7 +256,6 @@ namespace umbraco.cms.businesslogic.propertytype set { _name = value; - InvalidateCache(); using (var sqlHelper = Application.SqlHelper) sqlHelper.ExecuteNonQuery( "UPDATE cmsPropertyType SET name=@name WHERE id=@id", @@ -331,17 +322,17 @@ namespace umbraco.cms.businesslogic.propertytype public static IEnumerable GetPropertyTypes() { var result = new List(); - using (var sqlHelper = Application.SqlHelper) - using (IRecordsReader dr = - sqlHelper.ExecuteReader("select id from cmsPropertyType order by Name")) + + var propertyTypeIds = ApplicationContext.Current.DatabaseContext.Database.Fetch( + "select id from cmsPropertyType order by Name"); + + foreach (var propertyTypeId in propertyTypeIds) { - while (dr.Read()) - { - PropertyType pt = GetPropertyType(dr.GetInt("id")); - if (pt != null) - result.Add(pt); - } + PropertyType pt = GetPropertyType(propertyTypeId); + if (pt != null) + result.Add(pt); } + return result; } @@ -353,18 +344,17 @@ namespace umbraco.cms.businesslogic.propertytype public static IEnumerable GetPropertyTypesByGroup(int groupId) { var result = new List(); - using (var sqlHelper = Application.SqlHelper) - using (IRecordsReader dr = - sqlHelper.ExecuteReader("SELECT id FROM cmsPropertyType WHERE propertyTypeGroupId = @groupId order by SortOrder", - sqlHelper.CreateParameter("@groupId", groupId))) + + var propertyTypeIds = ApplicationContext.Current.DatabaseContext.Database.Fetch( + "SELECT id FROM cmsPropertyType WHERE propertyTypeGroupId = @groupId order by SortOrder", new {groupId = groupId}); + + foreach (var propertyTypeId in propertyTypeIds) { - while (dr.Read()) - { - PropertyType pt = GetPropertyType(dr.GetInt("id")); - if (pt != null) - result.Add(pt); - } + PropertyType pt = GetPropertyType(propertyTypeId); + if (pt != null) + result.Add(pt); } + return result; } @@ -376,20 +366,18 @@ namespace umbraco.cms.businesslogic.propertytype public static IEnumerable GetByDataTypeDefinition(int dataTypeDefId) { var result = new List(); - using (var sqlHelper = Application.SqlHelper) - using (IRecordsReader dr = - sqlHelper.ExecuteReader( - "select id, Name from cmsPropertyType where dataTypeId=@dataTypeId order by Name", - sqlHelper.CreateParameter("@dataTypeId", dataTypeDefId))) + + var propertyTypeIds = ApplicationContext.Current.DatabaseContext.Database.Fetch( + "select id from cmsPropertyType where dataTypeId=@dataTypeId order by Name", new {dataTypeId = dataTypeDefId}); + + foreach (var propertyTypeId in propertyTypeIds) { - while (dr.Read()) - { - PropertyType pt = GetPropertyType(dr.GetInt("id")); - if (pt != null) - result.Add(pt); - } + PropertyType pt = GetPropertyType(propertyTypeId); + if (pt != null) + result.Add(pt); } - return result.ToList(); + + return result; } public void delete() @@ -411,7 +399,6 @@ namespace umbraco.cms.businesslogic.propertytype // delete cache from either master (via tabid) or current contentype FlushCacheBasedOnTab(); - InvalidateCache(); } public void FlushCacheBasedOnTab() @@ -478,8 +465,6 @@ namespace umbraco.cms.businesslogic.propertytype protected virtual void FlushCache() { - // clear local cache - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(GetCacheKey(Id)); // clear cache in contentype ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ContentTypePropertiesCacheKey + _contenttypeid); @@ -496,31 +481,9 @@ namespace umbraco.cms.businesslogic.propertytype public static PropertyType GetPropertyType(int id) { - return ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( - GetCacheKey(id), - timeout: TimeSpan.FromMinutes(30), - getCacheItem: () => - { - try - { - return new PropertyType(id); - } - catch - { - return null; - } - }); - } - - private void InvalidateCache() - { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(GetCacheKey(Id)); - } - - private static string GetCacheKey(int id) - { - return CacheKeys.PropertyTypeCacheKey + id; + return new PropertyType(id); } + #endregion } diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 70775f5c71..bd21517d80 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -433,21 +433,20 @@ namespace umbraco.cms.businesslogic.web { XmlDocument xd = new XmlDocument(); - using (var sqlHelper = Application.SqlHelper) - using (IRecordsReader dr = sqlHelper.ExecuteReader("select nodeId from cmsDocument")) + var nodeIds = ApplicationContext.Current.DatabaseContext.Database.Fetch( + "select nodeId from cmsDocument"); + + foreach (var nodeId in nodeIds) { - while (dr.Read()) + try { - try - { - new Document(dr.GetInt("nodeId")).SaveXmlPreview(xd); - } - catch (Exception ee) - { - LogHelper.Error("Error generating preview xml", ee); - } + new Document(nodeId).SaveXmlPreview(xd); } - } + catch (Exception ee) + { + LogHelper.Error("Error generating preview xml", ee); + } + } } /// diff --git a/src/umbraco.editorControls/PickerRelations/PickerRelationsEventHandler.cs b/src/umbraco.editorControls/PickerRelations/PickerRelationsEventHandler.cs index 13f786359b..844d9920e0 100644 --- a/src/umbraco.editorControls/PickerRelations/PickerRelationsEventHandler.cs +++ b/src/umbraco.editorControls/PickerRelations/PickerRelationsEventHandler.cs @@ -2,8 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; - -using umbraco.BusinessLogic; // ApplicationBase +// ApplicationBase using umbraco.businesslogic; using umbraco.cms.businesslogic; // SaveEventArgs using umbraco.cms.businesslogic.media; // Media @@ -12,6 +11,8 @@ using umbraco.cms.businesslogic.web; // Documentusing umbraco.cms.businesslogic. using umbraco.cms.businesslogic.property; using umbraco.cms.businesslogic.relation; using umbraco.DataLayer; +using Umbraco.Core; +using Application = umbraco.BusinessLogic.Application; namespace umbraco.editorControls.PickerRelations { @@ -212,7 +213,7 @@ namespace umbraco.editorControls.PickerRelations private static void DeleteRelations(RelationType relationType, int contentNodeId, bool reverseIndexing, string instanceIdentifier) { //if relationType is bi-directional or a reverse index then we can't get at the relations via the API, so using SQL - string getRelationsSql = "SELECT id FROM umbracoRelation WHERE relType = " + relationType.Id.ToString() + " AND "; + string getRelationsSql = "SELECT id FROM umbracoRelation WHERE relType = " + relationType.Id + " AND "; if (reverseIndexing || relationType.Dual) { @@ -229,19 +230,16 @@ namespace umbraco.editorControls.PickerRelations getRelationsSql += " AND comment = '" + instanceIdentifier + "'"; - using (var sqlHelper = Application.SqlHelper) - using (IRecordsReader relations = sqlHelper.ExecuteReader(getRelationsSql)) - { - //clear data - Relation relation; - while (relations.Read()) - { - relation = new Relation(relations.GetInt("id")); + var relationIds = ApplicationContext.Current.DatabaseContext.Database.Fetch( + getRelationsSql); + foreach (var relationId in relationIds) + { + var relation = new Relation(relationId); - // TODO: [HR] check to see if an instance identifier is used - relation.Delete(); - } - } + // TODO: [HR] check to see if an instance identifier is used + relation.Delete(); + } + } /// From d2837b9649438f170244df2957516e5a3e878a93 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Jan 2017 20:03:59 +1100 Subject: [PATCH 25/88] Initial commit of all UDI related things and tests, now to import the remaining items tagged for Core --- src/Umbraco.Core/Constants-Conventions.cs | 131 +++++++- src/Umbraco.Core/Deploy/ArtifactDependency.cs | 40 +++ .../Deploy/ArtifactDependencyCollection.cs | 69 ++++ .../Deploy/ArtifactDependencyMode.cs | 18 ++ .../Deploy/ArtifactDeployState.cs | 50 +++ .../ArtifactDeployStateOfTArtifactTEntity.cs | 48 +++ src/Umbraco.Core/Deploy/Difference.cs | 28 ++ src/Umbraco.Core/Deploy/Direction.cs | 8 + src/Umbraco.Core/Deploy/IArtifact.cs | 8 + src/Umbraco.Core/Deploy/IArtifactSignature.cs | 41 +++ src/Umbraco.Core/Deploy/IDeployContext.cs | 42 +++ src/Umbraco.Core/Deploy/IFileSource.cs | 60 ++++ src/Umbraco.Core/Deploy/IServiceConnector.cs | 86 +++++ src/Umbraco.Core/GuidUdi.cs | 83 +++++ src/Umbraco.Core/NamedUdiRange.cs | 34 ++ .../Serialization/UdiJsonConverter.cs | 47 +++ src/Umbraco.Core/StringUdi.cs | 71 +++++ src/Umbraco.Core/Udi.cs | 266 ++++++++++++++++ src/Umbraco.Core/UdiDefinitionAttribute.cs | 20 ++ src/Umbraco.Core/UdiGetterExtensions.cs | 300 ++++++++++++++++++ src/Umbraco.Core/UdiRange.cs | 104 ++++++ src/Umbraco.Core/UdiType.cs | 12 + src/Umbraco.Core/Umbraco.Core.csproj | 21 ++ src/Umbraco.Tests/UdiTests.cs | 155 +++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 25 files changed, 1742 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Deploy/ArtifactDependency.cs create mode 100644 src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs create mode 100644 src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs create mode 100644 src/Umbraco.Core/Deploy/ArtifactDeployState.cs create mode 100644 src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs create mode 100644 src/Umbraco.Core/Deploy/Difference.cs create mode 100644 src/Umbraco.Core/Deploy/Direction.cs create mode 100644 src/Umbraco.Core/Deploy/IArtifact.cs create mode 100644 src/Umbraco.Core/Deploy/IArtifactSignature.cs create mode 100644 src/Umbraco.Core/Deploy/IDeployContext.cs create mode 100644 src/Umbraco.Core/Deploy/IFileSource.cs create mode 100644 src/Umbraco.Core/Deploy/IServiceConnector.cs create mode 100644 src/Umbraco.Core/GuidUdi.cs create mode 100644 src/Umbraco.Core/NamedUdiRange.cs create mode 100644 src/Umbraco.Core/Serialization/UdiJsonConverter.cs create mode 100644 src/Umbraco.Core/StringUdi.cs create mode 100644 src/Umbraco.Core/Udi.cs create mode 100644 src/Umbraco.Core/UdiDefinitionAttribute.cs create mode 100644 src/Umbraco.Core/UdiGetterExtensions.cs create mode 100644 src/Umbraco.Core/UdiRange.cs create mode 100644 src/Umbraco.Core/UdiType.cs create mode 100644 src/Umbraco.Tests/UdiTests.cs diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index d7f4576137..69e0f7c68e 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -4,7 +4,136 @@ using Umbraco.Core.Models; namespace Umbraco.Core { - public static partial class Constants + /// + /// Contains the valid selector values. + /// + internal static class DeploySelector + { + public const string This = "this"; + public const string ThisAndChildren = "this-and-children"; + public const string ThisAndDescendants = "this-and-descendants"; + public const string ChildrenOfThis = "children"; + public const string DescendantsOfThis = "descendants"; + } + + /// + /// Defines well-known entity types. + /// + /// Well-known entity types are those that Deploy already knows about, + /// but entity types are strings and so can be extended beyond what is defined here. + internal static class DeployEntityType + { + // guid entity types + + public const string AnyGuid = "any-guid"; // that one is for tests + + public const string Document = "document"; + public const string Media = "media"; + public const string Member = "member"; + + public const string DictionaryItem = "dictionary-item"; + public const string Macro = "macro"; + public const string Template = "template"; + + public const string DocumentType = "document-type"; + public const string DocumentTypeContainer = "document-type-container"; + public const string MediaType = "media-type"; + public const string MediaTypeContainer = "media-type-container"; + public const string DataType = "data-type"; + public const string DataTypeContainer = "data-type-container"; + public const string MemberType = "member-type"; + public const string MemberGroup = "member-group"; + + public const string RelationType = "relation-type"; + + // string entity types + + public const string AnyString = "any-string"; // that one is for tests + + public const string MediaFile = "media-file"; + public const string TemplateFile = "template-file"; + public const string Script = "script"; + public const string Stylesheet = "stylesheet"; + public const string PartialView = "partial-view"; + public const string PartialViewMacro = "partial-view-macro"; + public const string Xslt = "xslt"; + + public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType) + { + switch (umbracoObjectType) + { + case UmbracoObjectTypes.Document: + return Document; + case UmbracoObjectTypes.Media: + return Media; + case UmbracoObjectTypes.Member: + return Member; + case UmbracoObjectTypes.Template: + return Template; + case UmbracoObjectTypes.DocumentType: + return DocumentType; + case UmbracoObjectTypes.DocumentTypeContainer: + return DocumentTypeContainer; + case UmbracoObjectTypes.MediaType: + return MediaType; + case UmbracoObjectTypes.MediaTypeContainer: + return MediaTypeContainer; + case UmbracoObjectTypes.DataType: + return DataType; + case UmbracoObjectTypes.DataTypeContainer: + return DataTypeContainer; + case UmbracoObjectTypes.MemberType: + return MemberType; + case UmbracoObjectTypes.MemberGroup: + return MemberGroup; + case UmbracoObjectTypes.Stylesheet: + return Stylesheet; + case UmbracoObjectTypes.RelationType: + return RelationType; + } + throw new NotSupportedException(string.Format("UmbracoObjectType \"{0}\" does not have a matching EntityType.", umbracoObjectType)); + } + + public static UmbracoObjectTypes ToUmbracoObjectType(string entityType) + { + switch (entityType) + { + case Document: + return UmbracoObjectTypes.Document; + case Media: + return UmbracoObjectTypes.Media; + case Member: + return UmbracoObjectTypes.Member; + case Template: + return UmbracoObjectTypes.Template; + case DocumentType: + return UmbracoObjectTypes.DocumentType; + case DocumentTypeContainer: + return UmbracoObjectTypes.DocumentTypeContainer; + case MediaType: + return UmbracoObjectTypes.MediaType; + case MediaTypeContainer: + return UmbracoObjectTypes.MediaTypeContainer; + case DataType: + return UmbracoObjectTypes.DataType; + case DataTypeContainer: + return UmbracoObjectTypes.DataTypeContainer; + case MemberType: + return UmbracoObjectTypes.MemberType; + case MemberGroup: + return UmbracoObjectTypes.MemberGroup; + case Stylesheet: + return UmbracoObjectTypes.Stylesheet; + case RelationType: + return UmbracoObjectTypes.RelationType; + } + throw new NotSupportedException( + string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType)); + } + } + + + public static partial class Constants { /// /// Defines the identifiers for property-type alias conventions that are used within the Umbraco core. diff --git a/src/Umbraco.Core/Deploy/ArtifactDependency.cs b/src/Umbraco.Core/Deploy/ArtifactDependency.cs new file mode 100644 index 0000000000..41e349d636 --- /dev/null +++ b/src/Umbraco.Core/Deploy/ArtifactDependency.cs @@ -0,0 +1,40 @@ +namespace Umbraco.Core.Deploy +{ + /// + /// Represents an artifact dependency. + /// + /// + /// Dependencies have an order property which indicates whether it must be respected when ordering artifacts. + /// Dependencies have a mode which can be Match or Exist depending on whether the checksum should match. + /// + public class ArtifactDependency + { + /// + /// Initializes a new instance of the ArtifactDependency class with an entity identifier and a mode. + /// + /// The entity identifier of the artifact that is a dependency. + /// A value indicating whether the dependency is ordering. + /// The dependency mode. + public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode) + { + Udi = udi; + Ordering = ordering; + Mode = mode; + } + + /// + /// Gets the entity id of the artifact that is a dependency. + /// + public Udi Udi { get; private set; } + + /// + /// Gets a value indicating whether the dependency is ordering. + /// + public bool Ordering { get; private set; } + + /// + /// Gets the dependency mode. + /// + public ArtifactDependencyMode Mode { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs new file mode 100644 index 0000000000..fd036f4628 --- /dev/null +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Umbraco.Core.Deploy +{ + /// + /// Represents a collection of distinct . + /// + /// The collection cannot contain duplicates and modes are properly managed. + public class ArtifactDependencyCollection : ICollection + { + private readonly Dictionary _dependencies + = new Dictionary(); + + public IEnumerator GetEnumerator() + { + return _dependencies.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(ArtifactDependency item) + { + if (_dependencies.ContainsKey(item.Udi)) + { + var exist = _dependencies[item.Udi]; + if (item.Mode == ArtifactDependencyMode.Exist || item.Mode == exist.Mode) + return; + } + + _dependencies[item.Udi] = item; + } + + public void Clear() + { + _dependencies.Clear(); + } + + public bool Contains(ArtifactDependency item) + { + return _dependencies.ContainsKey(item.Udi) && + (_dependencies[item.Udi].Mode == item.Mode || _dependencies[item.Udi].Mode == ArtifactDependencyMode.Match); + } + + public void CopyTo(ArtifactDependency[] array, int arrayIndex) + { + _dependencies.Values.CopyTo(array, arrayIndex); + } + + public bool Remove(ArtifactDependency item) + { + throw new NotSupportedException(); + } + + public int Count + { + get { return _dependencies.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs new file mode 100644 index 0000000000..7ee5c2f220 --- /dev/null +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Deploy +{ + /// + /// Indicates the mode of the dependency. + /// + public enum ArtifactDependencyMode + { + /// + /// The dependency must match exactly. + /// + Match, + + /// + /// The dependency must exist. + /// + Exist + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs new file mode 100644 index 0000000000..3723e483cb --- /dev/null +++ b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs @@ -0,0 +1,50 @@ +namespace Umbraco.Core.Deploy +{ + /// + /// Represent the state of an artifact being deployed. + /// + public abstract class ArtifactDeployState + { + /// + /// Creates a new instance of the class from an artifact and an entity. + /// + /// The type of the artifact. + /// The type of the entity. + /// The artifact. + /// The entity. + /// The service connector deploying the artifact. + /// The next pass number. + /// A deploying artifact. + public static ArtifactDeployState Create(TArtifact art, TEntity entity, IServiceConnector connector, int nextPass) + where TArtifact : IArtifact + { + return new ArtifactDeployState(art, entity, connector, nextPass); + } + + /// + /// Gets the artifact. + /// + public IArtifact Artifact + { + get { return GetArtifactAsIArtifact(); } + } + + /// + /// Gets the artifact as an . + /// + /// The artifact, as an . + /// This is because classes that inherit from this class cannot override the Artifact property + /// with a property that specializes the return type, and so they need to 'new' the property. + protected abstract IArtifact GetArtifactAsIArtifact(); + + /// + /// Gets or sets the service connector in charge of deploying the artifact. + /// + public IServiceConnector Connector { get; set; } + + /// + /// Gets or sets the next pass number. + /// + public int NextPass { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs new file mode 100644 index 0000000000..b4d2be23cd --- /dev/null +++ b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs @@ -0,0 +1,48 @@ +namespace Umbraco.Core.Deploy +{ + /// + /// Represent the state of an artifact being deployed. + /// + /// The type of the artifact. + /// The type of the entity. + public class ArtifactDeployState : ArtifactDeployState + where TArtifact : IArtifact + { + /// + /// Initializes a new instance of the class. + /// + public ArtifactDeployState() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The artifact. + /// The entity. + /// The service connector deploying the artifact. + /// The next pass number. + public ArtifactDeployState(TArtifact art, TEntity entity, IServiceConnector connector, int nextPass) + { + Artifact = art; + Entity = entity; + Connector = connector; + NextPass = nextPass; + } + + /// + /// Gets or sets the artifact. + /// + public new TArtifact Artifact { get; set; } + + /// + /// Gets or sets the entity. + /// + public TEntity Entity { get; set; } + + /// + protected sealed override IArtifact GetArtifactAsIArtifact() + { + return Artifact; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/Difference.cs b/src/Umbraco.Core/Deploy/Difference.cs new file mode 100644 index 0000000000..90329afdd6 --- /dev/null +++ b/src/Umbraco.Core/Deploy/Difference.cs @@ -0,0 +1,28 @@ +namespace Umbraco.Core.Deploy +{ + public class Difference + { + public Difference(string title, string text = null, string category = null) + { + Title = title; + Text = text; + Category = category; + } + + public string Title { get; set; } + public string Text { get; set; } + public string Category { get; set; } + + public override string ToString() + { + var s = Title; + if (!string.IsNullOrWhiteSpace(Category)) s += string.Format("[{0}]", Category); + if (!string.IsNullOrWhiteSpace(Text)) + { + if (s.Length > 0) s += ":"; + s += Text; + } + return s; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/Direction.cs b/src/Umbraco.Core/Deploy/Direction.cs new file mode 100644 index 0000000000..b0e1c1dc0a --- /dev/null +++ b/src/Umbraco.Core/Deploy/Direction.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Deploy +{ + public enum Direction + { + ToArtifact, + FromArtifact + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IArtifact.cs b/src/Umbraco.Core/Deploy/IArtifact.cs new file mode 100644 index 0000000000..f39b7b433f --- /dev/null +++ b/src/Umbraco.Core/Deploy/IArtifact.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Deploy +{ + /// + /// Represents an artifact ie an object that can be transfered between environments. + /// + public interface IArtifact : IArtifactSignature + { } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IArtifactSignature.cs b/src/Umbraco.Core/Deploy/IArtifactSignature.cs new file mode 100644 index 0000000000..83b112586b --- /dev/null +++ b/src/Umbraco.Core/Deploy/IArtifactSignature.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Deploy +{ + /// + /// Represents the signature of an artifact. + /// + public interface IArtifactSignature + { + /// + /// Gets the entity unique identifier of this artifact. + /// + /// + /// The project identifier is independent from the state of the artifact, its data + /// values, dependencies, anything. It never changes and fully identifies the artifact. + /// What an entity uses as a unique identifier will influence what we can transfer + /// between environments. Eg content type "Foo" on one environment is not necessarily the + /// same as "Foo" on another environment, if guids are used as unique identifiers. What is + /// used should be documented for each entity, along with the consequences of the choice. + /// + Udi Udi { get; } + + /// + /// Gets the checksum of this artifact. + /// + /// + /// The checksum depends on the artifact's properties, and on the identifiers of all its dependencies, + /// but not on their checksums. So the checksum changes when any of the artifact's properties changes, + /// or when the list of dependencies changes. But not if one of these dependencies change. + /// It is assumed that checksum collisions cannot happen ie that no two different artifact's + /// states will ever produce the same checksum, so that if two artifacts have the same checksum then + /// they are identical. + /// + string Checksum { get; } + + /// + /// Gets the dependencies of this artifact. + /// + IEnumerable Dependencies { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IDeployContext.cs b/src/Umbraco.Core/Deploy/IDeployContext.cs new file mode 100644 index 0000000000..7d4066e015 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IDeployContext.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Deploy +{ + /// + /// Represents a deployment context. + /// + public interface IDeployContext + { + /// + /// Gets the unique identifier of the deployment. + /// + Guid SessionId { get; } + + /// + /// Gets the file source. + /// + /// The file source is used to obtain files from the source environment. + IFileSource FileSource { get; } + + /// + /// Gets the next number in a numerical sequence. + /// + /// The next sequence number. + /// Can be used to uniquely number things during a deployment. + int NextSeq(); + + /// + /// Gets items. + /// + IDictionary Items { get; } + + /// + /// Gets item. + /// + /// The type of the item. + /// The key of the item. + /// The item with the specified key and type, if any, else null. + T Item(string key) where T : class; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IFileSource.cs b/src/Umbraco.Core/Deploy/IFileSource.cs new file mode 100644 index 0000000000..3baa9069f0 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IFileSource.cs @@ -0,0 +1,60 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Core.Deploy +{ + /// + /// Represents a file source, ie a mean for a target environment involved in a + /// deployment to obtain the content of files being deployed. + /// + public interface IFileSource + { + /// + /// Gets the content of a file as a stream. + /// + /// A file entity identifier. + /// A stream with read access to the file content. + /// + /// Returns null if no content could be read. + /// The caller should ensure that the stream is properly closed/disposed. + /// + Stream GetFileStream(StringUdi udi); + + /// + /// Gets the content of a file as a stream. + /// + /// A file entity identifier. + /// A cancellation token. + /// A stream with read access to the file content. + /// + /// Returns null if no content could be read. + /// The caller should ensure that the stream is properly closed/disposed. + /// + Task GetFileStreamAsync(StringUdi udi, CancellationToken token); + + /// + /// Gets the content of a file as a string. + /// + /// A file entity identifier. + /// A string containing the file content. + /// Returns null if no content could be read. + string GetFileContent(StringUdi udi); + + /// + /// Gets the content of a file as a string. + /// + /// A file entity identifier. + /// A cancellation token. + /// A string containing the file content. + /// Returns null if no content could be read. + Task GetFileContentAsync(StringUdi udi, CancellationToken token); + + ///// + ///// Gets the content of a file as a bytes array. + ///// + ///// A file entity identifier. + ///// A byte array containing the file content. + //byte[] GetFileBytes(StringUdi Udi); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs new file mode 100644 index 0000000000..d1c0bf61de --- /dev/null +++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Umbraco.Core; + +namespace Umbraco.Core.Deploy +{ + /// + /// Connects to an Umbraco service. + /// + public interface IServiceConnector + { + /// + /// Gets an artifact. + /// + /// The entity identifier of the artifact. + /// The corresponding artifact, or null. + IArtifact GetArtifact(Udi udi); + + /// + /// Gets an artifact. + /// + /// The entity. + /// The corresponding artifact. + IArtifact GetArtifact(object entity); + + /// + /// Initializes processing for an artifact. + /// + /// The artifact. + /// The deploy context. + /// The mapped artifact. + ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context); + + /// + /// Processes an artifact. + /// + /// The mapped artifact. + /// The deploy context. + /// The processing pass number. + void Process(ArtifactDeployState dart, IDeployContext context, int pass); + + /// + /// Explodes a range into udis. + /// + /// The range. + /// The list of udis where to add the new udis. + /// Also, it's cool to have a method named Explode. Kaboom! + void Explode(UdiRange range, List udis); + + /// + /// Gets a named range for a specified udi and selector. + /// + /// The udi. + /// The selector. + /// The named range for the specified udi and selector. + NamedUdiRange GetRange(Udi udi, string selector); + + /// + /// Gets a named range for specified entity type, identifier and selector. + /// + /// The entity type. + /// The identifier. + /// The selector. + /// The named range for the specified entity type, identifier and selector. + /// + /// This is temporary. At least we thought it would be, in sept. 2016. What day is it now? + /// At the moment our UI has a hard time returning proper udis, mainly because Core's tree do + /// not manage guids but only ints... so we have to provide a way to support it. The string id here + /// can be either a real string (for string udis) or an "integer as a string", using the value "-1" to + /// indicate the "root" i.e. an open udi. + /// + NamedUdiRange GetRange(string entityType, string sid, string selector); + + /// + /// Compares two artifacts. + /// + /// The first artifact. + /// The second artifact. + /// A collection of differences to append to, if not null. + /// A boolean value indicating whether the artifacts are identical. + /// ServiceConnectorBase{TArtifact} provides a very basic default implementation. + bool Compare(IArtifact art1, IArtifact art2, ICollection differences = null); + } + +} diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Core/GuidUdi.cs new file mode 100644 index 0000000000..fabdf46c93 --- /dev/null +++ b/src/Umbraco.Core/GuidUdi.cs @@ -0,0 +1,83 @@ +using System; + +namespace Umbraco.Core +{ + /// + /// Represents a guid-based entity identifier. + /// + public class GuidUdi : Udi + { + /// + /// The guid part of the identifier. + /// + public Guid Guid { get; private set; } + + /// + /// Initializes a new instance of the GuidUdi class with an entity type and a guid. + /// + /// The entity type part of the udi. + /// The guid part of the udi. + public GuidUdi(string entityType, Guid guid) + : base(entityType, "umb://" + entityType + "/" + guid.ToString("N")) + { + Guid = guid; + } + + /// + /// Initializes a new instance of the GuidUdi class with an uri value. + /// + /// The uri value of the udi. + public GuidUdi(Uri uriValue) + : base(uriValue) + { + Guid = Guid.Parse(uriValue.AbsolutePath.TrimStart('/')); + } + + /// + /// Converts the string representation of an entity identifier into the equivalent GuidUdi instance. + /// + /// The string to convert. + /// A GuidUdi instance that contains the value that was parsed. + public new static GuidUdi Parse(string s) + { + var udi = Udi.Parse(s); + if (!(udi is GuidUdi)) + throw new FormatException("String \"" + s + "\" is not a guid entity id."); + return (GuidUdi)udi; + } + + public static bool TryParse(string s, out GuidUdi udi) + { + Udi tmp; + udi = null; + if (!TryParse(s, out tmp)) return false; + udi = tmp as GuidUdi; + return udi != null; + } + + public override bool Equals(object obj) + { + var other = obj as GuidUdi; + if (other == null) return false; + return EntityType == other.EntityType && Guid == other.Guid; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + public override bool IsRoot + { + get { return Guid == Guid.Empty; } + } + + /// + public GuidUdi EnsureClosed() + { + base.EnsureNotRoot(); + return this; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/NamedUdiRange.cs b/src/Umbraco.Core/NamedUdiRange.cs new file mode 100644 index 0000000000..4e81631e03 --- /dev/null +++ b/src/Umbraco.Core/NamedUdiRange.cs @@ -0,0 +1,34 @@ +namespace Umbraco.Core +{ + /// + /// Represents a complemented with a name. + /// + public class NamedUdiRange : UdiRange + { + /// + /// Initializes a new instance of the class with a and an optional selector. + /// + /// A . + /// An optional selector. + public NamedUdiRange(Udi udi, string selector = DeploySelector.This) + : base(udi, selector) + { } + + /// + /// Initializes a new instance of the class with a , a name, and an optional selector. + /// + /// A . + /// A name. + /// An optional selector. + public NamedUdiRange(Udi udi, string name, string selector = DeploySelector.This) + : base(udi, selector) + { + Name = name; + } + + /// + /// Gets or sets the name of the range. + /// + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Serialization/UdiJsonConverter.cs b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs new file mode 100644 index 0000000000..7d2db92e81 --- /dev/null +++ b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs @@ -0,0 +1,47 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Umbraco.Core.Serialization +{ + public class UdiRangeJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return typeof(UdiRange).IsAssignableFrom(objectType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(value.ToString()); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jo = JToken.ReadFrom(reader); + var val = jo.ToObject(); + return val == null ? null : UdiRange.Parse(val); + } + } + + + public class UdiJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return typeof(Udi).IsAssignableFrom(objectType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(value.ToString()); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jo = JToken.ReadFrom(reader); + var val = jo.ToObject(); + return val == null ? null : Udi.Parse(val); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Core/StringUdi.cs new file mode 100644 index 0000000000..2fcb53f263 --- /dev/null +++ b/src/Umbraco.Core/StringUdi.cs @@ -0,0 +1,71 @@ +using System; + +namespace Umbraco.Core +{ + /// + /// Represents a string-based entity identifier. + /// + public class StringUdi : Udi + { + /// + /// The string part of the identifier. + /// + public string Id { get; private set; } + + /// + /// Initializes a new instance of the StringUdi class with an entity type and a string id. + /// + /// The entity type part of the udi. + /// The string id part of the udi. + public StringUdi(string entityType, string id) + : base(entityType, "umb://" + entityType + "/" + id) + { + Id = id; + } + + /// + /// Initializes a new instance of the StringUdi class with a uri value. + /// + /// The uri value of the udi. + public StringUdi(Uri uriValue) + : base(uriValue) + { + Id = uriValue.AbsolutePath.TrimStart('/'); + } + + /// + /// Converts the string representation of an entity identifier into the equivalent StringUdi instance. + /// + /// The string to convert. + /// A StringUdi instance that contains the value that was parsed. + public new static StringUdi Parse(string s) + { + var udi = Udi.Parse(s); + if (!(udi is StringUdi)) + throw new FormatException("String \"" + s + "\" is not a string entity id."); + return (StringUdi)udi; + } + + public static bool TryParse(string s, out StringUdi udi) + { + udi = null; + Udi tmp; + if (!TryParse(s, out tmp) || !(tmp is StringUdi)) return false; + udi = (StringUdi)tmp; + return true; + } + + /// + public override bool IsRoot + { + get { return Id == string.Empty; } + } + + /// + public StringUdi EnsureClosed() + { + base.EnsureNotRoot(); + return this; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs new file mode 100644 index 0000000000..aef0fc9274 --- /dev/null +++ b/src/Umbraco.Core/Udi.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data.Metadata.Edm; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Deploy; +using EntityContainer = System.Data.Metadata.Edm.EntityContainer; + +namespace Umbraco.Core +{ + /// + /// Represents an entity identifier. + /// + /// An Udi can be fully qualified or "closed" eg umb://document/{guid} or "open" eg umb://document. + public abstract class Udi + { + private static readonly Dictionary UdiTypes = new Dictionary(); + private static readonly ConcurrentDictionary RootUdis = new ConcurrentDictionary(); + internal readonly Uri UriValue; // internal for UdiRange + + /// + /// Initializes a new instance of the Udi class. + /// + /// The entity type part of the identifier. + /// The string value of the identifier. + protected Udi(string entityType, string stringValue) + { + EntityType = entityType; + UriValue = new Uri(stringValue); + } + + /// + /// Initializes a new instance of the Udi class. + /// + /// The uri value of the identifier. + protected Udi(Uri uriValue) + { + EntityType = uriValue.Host; + UriValue = uriValue; + } + + static Udi() + { + // for tests etc. + UdiTypes[DeployEntityType.AnyGuid] = UdiType.GuidUdi; + UdiTypes[DeployEntityType.AnyString] = UdiType.StringUdi; + + // we don't have connectors for these... + UdiTypes[DeployEntityType.Member] = UdiType.GuidUdi; + UdiTypes[DeployEntityType.MemberGroup] = UdiType.GuidUdi; + + // fixme - or inject from...? + // there is no way we can get the "registered" service connectors, as registration + // happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we + // just pick every service connectors - just making sure that not two of them + // would register the same entity type, with different udi types (would not make + // much sense anyways). + var connectors = PluginManager.Current.ResolveTypes(); + foreach (var connector in connectors) + { + var attrs = connector.GetCustomAttributes(false); + foreach (var attr in attrs) + { + UdiType udiType; + if (UdiTypes.TryGetValue(attr.EntityType, out udiType) && udiType != attr.UdiType) + throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType)); + UdiTypes[attr.EntityType] = attr.UdiType; + } + } + } + + /// + /// Gets the entity type part of the identifier. + /// + public string EntityType { get; private set; } + + public override string ToString() + { + // UriValue is created in the ctor and is never null + return UriValue.ToString(); + } + + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// An Udi instance that contains the value that was parsed. + public static Udi Parse(string s) + { + Udi udi; + ParseInternal(s, false, out udi); + return udi; + } + + public static bool TryParse(string s, out Udi udi) + { + return ParseInternal(s, true, out udi); + } + + private static bool ParseInternal(string s, bool tryParse, out Udi udi) + { + udi = null; + Uri uri; + + if (!Uri.IsWellFormedUriString(s, UriKind.Absolute) + || !Uri.TryCreate(s, UriKind.Absolute, out uri)) + { + if (tryParse) return false; + throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); + } + + var entityType = uri.Host; + UdiType udiType; + if (!UdiTypes.TryGetValue(entityType, out udiType)) + { + if (tryParse) return false; + throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType)); + } + var path = uri.AbsolutePath.TrimStart('/'); + if (udiType == UdiType.GuidUdi) + { + if (path == string.Empty) + { + udi = GetRootUdi(uri.Host); + return true; + } + Guid guid; + if (!Guid.TryParse(path, out guid)) + { + if (tryParse) return false; + throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); + } + udi = new GuidUdi(uri.Host, guid); + return true; + } + if (udiType == UdiType.StringUdi) + { + udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, path); + return true; + } + if (tryParse) return false; + throw new InvalidOperationException("Internal error."); + } + + private static Udi GetRootUdi(string entityType) + { + return RootUdis.GetOrAdd(entityType, x => + { + UdiType udiType; + if (!UdiTypes.TryGetValue(x, out udiType)) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType)); + return udiType == UdiType.StringUdi + ? (Udi)new StringUdi(entityType, string.Empty) + : new GuidUdi(entityType, Guid.Empty); + }); + } + + /// + /// Creates a root Udi for an entity type. + /// + /// The entity type. + /// The root Udi for the entity type. + public static Udi Create(string entityType) + { + return GetRootUdi(entityType); + } + + /// + /// Creates a string Udi. + /// + /// The entity type. + /// The identifier. + /// The string Udi for the entity type and identifier. + public static Udi Create(string entityType, string id) + { + UdiType udiType; + if (!UdiTypes.TryGetValue(entityType, out udiType)) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentException("Value cannot be null or whitespace.", "id"); + if (udiType != UdiType.StringUdi) + throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have string udis.", entityType)); + + return new StringUdi(entityType, id); + } + + /// + /// Creates a Guid Udi. + /// + /// The entity type. + /// The identifier. + /// The Guid Udi for the entity type and identifier. + public static Udi Create(string entityType, Guid id) + { + UdiType udiType; + if (!UdiTypes.TryGetValue(entityType, out udiType)) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); + if (udiType != UdiType.GuidUdi) + throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", entityType)); + if (id == default(Guid)) + throw new ArgumentException("Cannot be an empty guid.", "id"); + return new GuidUdi(entityType, id); + } + + internal static Udi Create(Uri uri) + { + UdiType udiType; + if (!UdiTypes.TryGetValue(uri.Host, out udiType)) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri"); + if (udiType == UdiType.GuidUdi) + return new GuidUdi(uri); + if (udiType == UdiType.GuidUdi) + return new StringUdi(uri); + throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri)); + } + + public void EnsureType(params string[] validTypes) + { + if (!validTypes.Contains(EntityType)) + throw new Exception(string.Format("Unexpected entity type \"{0}\".", EntityType)); + } + + /// + /// Gets a value indicating whether this Udi is a root Udi. + /// + /// A root Udi points to the "root of all things" for a given entity type, e.g. the content tree root. + public abstract bool IsRoot { get; } + + /// + /// Ensures that this Udi is not a root Udi. + /// + /// This Udi. + /// When this Udi is a Root Udi. + public Udi EnsureNotRoot() + { + if (IsRoot) throw new Exception("Root Udi."); + return this; + } + + public override bool Equals(object obj) + { + var other = obj as Udi; + return other != null && GetType() == other.GetType() && UriValue == other.UriValue; + } + + public override int GetHashCode() + { + return UriValue.GetHashCode(); + } + + public static bool operator ==(Udi udi1, Udi udi2) + { + if (ReferenceEquals(udi1, udi2)) return true; + if ((object)udi1 == null || (object)udi2 == null) return false; + return udi1.Equals(udi2); + } + + public static bool operator !=(Udi udi1, Udi udi2) + { + return !(udi1 == udi2); + } + } + +} diff --git a/src/Umbraco.Core/UdiDefinitionAttribute.cs b/src/Umbraco.Core/UdiDefinitionAttribute.cs new file mode 100644 index 0000000000..cc52bb6039 --- /dev/null +++ b/src/Umbraco.Core/UdiDefinitionAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace Umbraco.Core +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public sealed class UdiDefinitionAttribute : Attribute + { + public UdiDefinitionAttribute(string entityType, UdiType udiType) + { + if (string.IsNullOrWhiteSpace(entityType)) throw new ArgumentNullException("entityType"); + if (udiType != UdiType.GuidUdi && udiType != UdiType.StringUdi) throw new ArgumentException("Invalid value.", "udiType"); + EntityType = entityType; + UdiType = udiType; + } + + public string EntityType { get; private set; } + + public UdiType UdiType { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/UdiGetterExtensions.cs b/src/Umbraco.Core/UdiGetterExtensions.cs new file mode 100644 index 0000000000..aebb8873a6 --- /dev/null +++ b/src/Umbraco.Core/UdiGetterExtensions.cs @@ -0,0 +1,300 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core +{ + /// + /// Provides extension methods that return udis for Umbraco entities. + /// + public static class UdiGetterExtensions + { + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this ITemplate entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.Template, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IContentType entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.DocumentType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMediaType entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.MediaType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMemberType entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.MemberType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMemberGroup entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.MemberGroup, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IContentTypeComposition entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + + string type; + if (entity is IContentType) type = DeployEntityType.DocumentType; + else if (entity is IMediaType) type = DeployEntityType.MediaType; + else if (entity is IMemberType) type = DeployEntityType.MemberType; + else throw new NotSupportedException(string.Format("Composition type {0} is not supported.", entity.GetType().FullName)); + return new GuidUdi(type, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IDataTypeDefinition entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.DataType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this Umbraco.Core.Models.EntityContainer entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + + string entityType; + if (entity.ContainedObjectType == Constants.ObjectTypes.DataTypeGuid) + entityType = DeployEntityType.DataTypeContainer; + else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentTypeGuid) + entityType = DeployEntityType.DocumentTypeContainer; + else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaTypeGuid) + entityType = DeployEntityType.MediaTypeContainer; + else + throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", entity.ContainedObjectType)); + return new GuidUdi(entityType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMedia entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.Media, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IContent entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.Document, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMember entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.Member, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static StringUdi GetUdi(this Stylesheet entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new StringUdi(DeployEntityType.Stylesheet, entity.Path.TrimStart('/')).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static StringUdi GetUdi(this Script entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new StringUdi(DeployEntityType.Script, entity.Path.TrimStart('/')).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IDictionaryItem entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.DictionaryItem, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMacro entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.Macro, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static StringUdi GetUdi(this IPartialView entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new StringUdi(DeployEntityType.PartialView, entity.Path.TrimStart('/')).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static StringUdi GetUdi(this IXsltFile entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new StringUdi(DeployEntityType.Xslt, entity.Path.TrimStart('/')).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IContentBase entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + + string type; + if (entity is IContent) type = DeployEntityType.Document; + else if (entity is IMedia) type = DeployEntityType.Media; + else if (entity is IMember) type = DeployEntityType.Member; + else throw new NotSupportedException(string.Format("ContentBase type {0} is not supported.", entity.GetType().FullName)); + return new GuidUdi(type, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IRelationType entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(DeployEntityType.RelationType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static Udi GetUdi(this IEntity entity) + { + if (entity == null) throw new ArgumentNullException("entity"); + + // entity could eg be anything implementing IThing + // so we have to go through casts here + + var template = entity as ITemplate; + if (template != null) return template.GetUdi(); + + var contentType = entity as IContentType; + if (contentType != null) return contentType.GetUdi(); + + var mediaType = entity as IMediaType; + if (mediaType != null) return mediaType.GetUdi(); + + var memberType = entity as IMemberType; + if (memberType != null) return memberType.GetUdi(); + + var memberGroup = entity as IMemberGroup; + if (memberGroup != null) return memberGroup.GetUdi(); + + var contentTypeComposition = entity as IContentTypeComposition; + if (contentTypeComposition != null) return contentTypeComposition.GetUdi(); + + var dataTypeComposition = entity as IDataTypeDefinition; + if (dataTypeComposition != null) return dataTypeComposition.GetUdi(); + + var container = entity as Umbraco.Core.Models.EntityContainer; + if (container != null) return container.GetUdi(); + + var media = entity as IMedia; + if (media != null) return media.GetUdi(); + + var content = entity as IContent; + if (content != null) return content.GetUdi(); + + var member = entity as IMember; + if (member != null) return member.GetUdi(); + + var contentBase = entity as IContentBase; + if (contentBase != null) return contentBase.GetUdi(); + + var macro = entity as IMacro; + if (macro != null) return macro.GetUdi(); + + var relationType = entity as IRelationType; + if (relationType != null) return relationType.GetUdi(); + + throw new NotSupportedException(string.Format("Entity type {0} is not supported.", entity.GetType().FullName)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/UdiRange.cs b/src/Umbraco.Core/UdiRange.cs new file mode 100644 index 0000000000..e1aa49e079 --- /dev/null +++ b/src/Umbraco.Core/UdiRange.cs @@ -0,0 +1,104 @@ +using System; + +namespace Umbraco.Core +{ + /// + /// Represents a range. + /// + /// + /// A Udi range is composed of a which represents the base of the range, + /// plus a selector that can be "." (the Udi), ".*" (the Udi and its children), ".**" (the udi and + /// its descendants, "*" (the children of the Udi), and "**" (the descendants of the Udi). + /// The Udi here can be a closed entity, or an open entity. + public class UdiRange + { + private readonly Uri _uriValue; + + /// + /// Initializes a new instance of the class with a and an optional selector. + /// + /// A . + /// An optional selector. + public UdiRange(Udi udi, string selector = DeploySelector.This) + { + Udi = udi; + switch (selector) + { + case DeploySelector.This: + Selector = selector; + _uriValue = udi.UriValue; + break; + case DeploySelector.ChildrenOfThis: + case DeploySelector.DescendantsOfThis: + case DeploySelector.ThisAndChildren: + case DeploySelector.ThisAndDescendants: + Selector = selector; + _uriValue = new Uri(Udi + "?" + selector); + break; + default: + throw new ArgumentException(string.Format("Invalid selector \"{0}\".", selector)); + } + } + + /// + /// Gets the for this range. + /// + public Udi Udi { get; private set; } + + /// + /// Gets or sets the selector for this range. + /// + public string Selector { get; private set; } + + /// + /// Gets the entity type of the for this range. + /// + public string EntityType + { + get { return Udi.EntityType; } + } + + public static UdiRange Parse(string s) + { + Uri uri; + + if (!Uri.IsWellFormedUriString(s, UriKind.Absolute) + || !Uri.TryCreate(s, UriKind.Absolute, out uri)) + { + //if (tryParse) return false; + throw new FormatException(string.Format("String \"{0}\" is not a valid udi range.", s)); + } + + var udiUri = uri.Query == string.Empty ? uri : new UriBuilder(uri) { Query = string.Empty }.Uri; + return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart('?')); + } + + public override string ToString() + { + return _uriValue.ToString(); + } + + public override bool Equals(object obj) + { + var other = obj as UdiRange; + return other != null && GetType() == other.GetType() && _uriValue == other._uriValue; + } + + public override int GetHashCode() + { + return _uriValue.GetHashCode(); + } + + public static bool operator ==(UdiRange range1, UdiRange range2) + { + if (ReferenceEquals(range1, range2)) return true; + if ((object)range1 == null || (object)range2 == null) return false; + return range1.Equals(range2); + } + + public static bool operator !=(UdiRange range1, UdiRange range2) + { + return !(range1 == range2); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/UdiType.cs b/src/Umbraco.Core/UdiType.cs new file mode 100644 index 0000000000..d15987033d --- /dev/null +++ b/src/Umbraco.Core/UdiType.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core +{ + /// + /// Defines Udi types. + /// + public enum UdiType + { + Unknown, + GuidUdi, + StringUdi + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 950a44502f..38f2920f7d 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -299,6 +299,18 @@ + + + + + + + + + + + + @@ -340,6 +352,7 @@ True Files.resx + @@ -410,6 +423,7 @@ + @@ -527,6 +541,7 @@ + @@ -1326,6 +1341,7 @@ + @@ -1370,6 +1386,11 @@ + + + + + diff --git a/src/Umbraco.Tests/UdiTests.cs b/src/Umbraco.Tests/UdiTests.cs new file mode 100644 index 0000000000..8654f4b458 --- /dev/null +++ b/src/Umbraco.Tests/UdiTests.cs @@ -0,0 +1,155 @@ +using System; +using System.Linq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests +{ + [TestFixture] + public class UdiTests + { + [Test] + public void StringEntityCtorTest() + { + var udi = new StringUdi(DeployEntityType.AnyString, "test-id"); + Assert.AreEqual(DeployEntityType.AnyString, udi.EntityType); + Assert.AreEqual("test-id", udi.Id); + Assert.AreEqual("umb://" + DeployEntityType.AnyString + "/test-id", udi.ToString()); + } + + [Test] + public void StringEntityParseTest() + { + var udi = Udi.Parse("umb://" + DeployEntityType.AnyString + "/test-id"); + Assert.AreEqual(DeployEntityType.AnyString, udi.EntityType); + Assert.IsInstanceOf(udi); + var stringEntityId = udi as StringUdi; + Assert.IsNotNull(stringEntityId); + Assert.AreEqual("test-id", stringEntityId.Id); + Assert.AreEqual("umb://" + DeployEntityType.AnyString + "/test-id", udi.ToString()); + } + + [Test] + public void GuidEntityCtorTest() + { + var guid = Guid.NewGuid(); + var udi = new GuidUdi(DeployEntityType.AnyGuid, guid); + Assert.AreEqual(DeployEntityType.AnyGuid, udi.EntityType); + Assert.AreEqual(guid, udi.Guid); + Assert.AreEqual("umb://" + DeployEntityType.AnyGuid + "/" + guid.ToString("N"), udi.ToString()); + } + + [Test] + public void GuidEntityParseTest() + { + var guid = Guid.NewGuid(); + var s = "umb://" + DeployEntityType.AnyGuid + "/" + guid.ToString("N"); + var udi = Udi.Parse(s); + Assert.AreEqual(DeployEntityType.AnyGuid, udi.EntityType); + Assert.IsInstanceOf(udi); + var gudi = udi as GuidUdi; + Assert.IsNotNull(gudi); + Assert.AreEqual(guid, gudi.Guid); + Assert.AreEqual(s, udi.ToString()); + } + + [Test] + public void EqualityTest() + { + var guid1 = Guid.NewGuid(); + var guid2 = Guid.NewGuid(); + + Assert.IsTrue(new GuidUdi("type", guid1).Equals(new GuidUdi("type", guid1))); + Assert.IsTrue(new GuidUdi("type", guid1) == new GuidUdi("type", guid1)); + + Assert.IsTrue(((Udi)new GuidUdi("type", guid1)).Equals((Udi)new GuidUdi("type", guid1))); + Assert.IsTrue((Udi)new GuidUdi("type", guid1) == (Udi)new GuidUdi("type", guid1)); + + Assert.IsFalse(new GuidUdi("type", guid1).Equals(new GuidUdi("typex", guid1))); + Assert.IsFalse(new GuidUdi("type", guid1) == new GuidUdi("typex", guid1)); + Assert.IsFalse(new GuidUdi("type", guid1).Equals(new GuidUdi("type", guid2))); + Assert.IsFalse(new GuidUdi("type", guid1) == new GuidUdi("type", guid2)); + + Assert.IsTrue(new GuidUdi("type", guid1).ToString() == new StringUdi("type", guid1.ToString("N")).ToString()); + Assert.IsFalse(new GuidUdi("type", guid1) == new StringUdi("type", guid1.ToString("N"))); + } + + [Test] + public void DistinctTest() + { + var guid1 = Guid.NewGuid(); + var entities = new[] + { + new GuidUdi(DeployEntityType.AnyGuid, guid1), + new GuidUdi(DeployEntityType.AnyGuid, guid1), + new GuidUdi(DeployEntityType.AnyGuid, guid1), + }; + Assert.AreEqual(1, entities.Distinct().Count()); + } + + [Test] + public void CreateTest() + { + var guid = Guid.NewGuid(); + var udi = Udi.Create(DeployEntityType.AnyGuid, guid); + Assert.AreEqual(DeployEntityType.AnyGuid, udi.EntityType); + Assert.AreEqual(guid, ((GuidUdi)udi).Guid); + + Assert.Throws(() => Udi.Create(DeployEntityType.AnyString, guid)); + Assert.Throws(() => Udi.Create(DeployEntityType.AnyGuid, "foo")); + Assert.Throws(() => Udi.Create("barf", "foo")); + } + + [Test] + public void RangeTest() + { + // can parse open string udi + const string stringUdiString = "umb://stylesheet"; + Udi stringUdi; + Assert.IsTrue(Udi.TryParse(stringUdiString, out stringUdi)); + Assert.AreEqual(string.Empty, ((StringUdi)stringUdi).Id); + + // can parse open guid udi + const string guidUdiString = "umb://document"; + Udi guidUdi; + Assert.IsTrue(Udi.TryParse(guidUdiString, out guidUdi)); + Assert.AreEqual(Guid.Empty, ((GuidUdi)guidUdi).Guid); + + // can create a range + var range = new UdiRange(stringUdi, DeploySelector.ChildrenOfThis); + + // cannot create invalid ranges + Assert.Throws(() => new UdiRange(guidUdi, "x")); + } + + [Test] + public void SerializationTest() + { + var settings = new JsonSerializerSettings + { + Converters = new JsonConverter[] { new UdiJsonConverter(), new UdiRangeJsonConverter() } + }; + + + var guid = Guid.NewGuid(); + var udi = new GuidUdi(DeployEntityType.AnyGuid, guid); + var json = JsonConvert.SerializeObject(udi, settings); + Assert.AreEqual(string.Format("\"umb://any-guid/{0:N}\"", guid), json); + + var dudi = JsonConvert.DeserializeObject(json, settings); + Assert.AreEqual(DeployEntityType.AnyGuid, dudi.EntityType); + Assert.AreEqual(guid, ((GuidUdi)dudi).Guid); + + var range = new UdiRange(udi, DeploySelector.ChildrenOfThis); + json = JsonConvert.SerializeObject(range, settings); + Assert.AreEqual(string.Format("\"umb://any-guid/{0:N}?children\"", guid), json); + + var drange = JsonConvert.DeserializeObject(json, settings); + Assert.AreEqual(udi, drange.Udi); + Assert.AreEqual(string.Format("umb://any-guid/{0:N}", guid), drange.Udi.UriValue.ToString()); + Assert.AreEqual(DeploySelector.ChildrenOfThis, drange.Selector); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 1f5e324142..0f0a7385c5 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -170,6 +170,7 @@ + From 198edec4f4f9f72bf3ebea1dbdbd4ebfc37ee282 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 16 Jan 2017 10:09:53 +0100 Subject: [PATCH 26/88] add fallback icons --- .../contentpicker/contentpicker.controller.js | 21 ++++++++++++++++++- .../memberpicker/memberpicker.controller.js | 3 ++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index ccf9d865bc..e77e743f2b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -231,7 +231,26 @@ function contentPickerController($scope, dialogService, entityResource, contentR }); if (entity) { - entity.icon = iconHelper.convertFromLegacyIcon(entity.icon); + + // set icon + if(entity.icon) { + entity.icon = iconHelper.convertFromLegacyIcon(entity.icon); + } + + // set default icon + if (!entity.icon) { + switch (entityType) { + case "Document": + entity.icon = "icon-document"; + break; + case "Media": + entity.icon = "icon-picture"; + break; + case "Member": + entity.icon = "icon-user"; + break; + } + } var url = (entity.urls && entity.urls.length > 0) ? entity.urls[0] : ""; var path = ($scope.model.config.showPathOnHover) ? entity.path : ""; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js index 0d4b2817d0..0029332f64 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js @@ -97,7 +97,8 @@ function memberPickerController($scope, dialogService, entityResource, $log, ico var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; entityResource.getByIds(modelIds, "Member").then(function (data) { _.each(data, function (item, i) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); + // set default icon if it's missing + item.icon = (item.icon) ? iconHelper.convertFromLegacyIcon(item.icon) : "icon-user"; $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon }); }); }); From 7a0ac625ff9342d403e67e2279f71ad9a350261d Mon Sep 17 00:00:00 2001 From: Per Ploug Date: Mon, 16 Jan 2017 10:13:32 +0100 Subject: [PATCH 27/88] Last remaining dk translation strings This needs a review by the dynamic writing duo vera and martin. --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 3c52d7f1a1..e14a0f9109 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1009,11 +1009,11 @@ Mange hilsner fra Umbraco robotten Rediger skabelong Sektioner - Insert content area - Insert content area placeholder + Indsæt indholdsområde + Indsæt indholdsområde placeholder Indsæt - Vælg hvad du vil indsætte + Hvad vil du indsætte ? Oversættelse Indsætter en oversætbar tekst som skifter efter det sprog som websitet vises i. @@ -1021,17 +1021,20 @@ Mange hilsner fra Umbraco robotten Makro En makro er et element som kan have forskellige indstillinger når det indsættes. - Det er glimrende som en genbrugelig del af dit design såsom gallerier, formularer + Brug det som en genbrugelig del af dit design såsom gallerier, formularer og lister. - Value - Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values. + Side værdi + + Viser værdien af et felt fra den nuværende side. Kan indstilles til at bruge rekursive værdier eller + vise en standard værdi i tilfælde af at feltet er tomt. + Partial view - A partial view is a separate template file which can be rendered inside another - template, it's great for reusing markup or for separating complex templates into separate files. + Et Partial View er et skabelon element som kan indsættes i andre skabeloner og derved + genbruges og deles på tværs af side-skabelonerne. Master skabelon @@ -1081,8 +1084,6 @@ Mange hilsner fra Umbraco robotten filtre og - - Skabelon From d24541fce41699a75a99ea47f96a68a641fec946 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Jan 2017 20:45:08 +1100 Subject: [PATCH 28/88] second commit of everything that we want to add to core for Deploy, have moved stuff to where they should go, probably need to review if the GridValue should be in models though or if we already have this. --- src/Umbraco.Core/Constants-Conventions.cs | 12 -- .../Constants-DatabaseProviders.cs | 12 ++ .../Constants-DeployEntityType.cs | 129 ++++++++++++++++++ src/Umbraco.Core/Constants-DeploySelector.cs | 21 +++ src/Umbraco.Core/Constants-Security.cs | 33 +++++ src/Umbraco.Core/Constants-System.cs | 8 +- src/Umbraco.Core/Constants-Web.cs | 26 +--- src/Umbraco.Core/Deploy/ArtifactBase.cs | 47 +++++++ src/Umbraco.Core/Deploy/ArtifactSignature.cs | 21 +++ src/Umbraco.Core/Deploy/GridValue.cs | 82 +++++++++++ .../Deploy/IGridCellValueConnector.cs | 41 ++++++ src/Umbraco.Core/Deploy/IImageSourceParser.cs | 27 ++++ src/Umbraco.Core/Deploy/ILocalLinkParser.cs | 27 ++++ src/Umbraco.Core/Deploy/IMacroParser.cs | 32 +++++ src/Umbraco.Core/Deploy/IPreValueConnector.cs | 32 +++++ src/Umbraco.Core/Deploy/IPrettyArtifact.cs | 8 ++ src/Umbraco.Core/Deploy/IValueConnector.cs | 35 +++++ src/Umbraco.Core/NamedUdiRange.cs | 4 +- .../Serialization/StreamResultExtensions.cs | 22 +++ .../Serialization/StreamedResult.cs | 18 --- .../Serialization/UdiJsonConverter.cs | 20 --- .../Serialization/UdiRangeJsonConverter.cs | 26 ++++ src/Umbraco.Core/UdiRange.cs | 12 +- src/Umbraco.Core/Umbraco.Core.csproj | 16 +++ src/Umbraco.Tests/UdiTests.cs | 6 +- 25 files changed, 624 insertions(+), 93 deletions(-) create mode 100644 src/Umbraco.Core/Constants-DatabaseProviders.cs create mode 100644 src/Umbraco.Core/Constants-DeployEntityType.cs create mode 100644 src/Umbraco.Core/Constants-DeploySelector.cs create mode 100644 src/Umbraco.Core/Constants-Security.cs create mode 100644 src/Umbraco.Core/Deploy/ArtifactBase.cs create mode 100644 src/Umbraco.Core/Deploy/ArtifactSignature.cs create mode 100644 src/Umbraco.Core/Deploy/GridValue.cs create mode 100644 src/Umbraco.Core/Deploy/IGridCellValueConnector.cs create mode 100644 src/Umbraco.Core/Deploy/IImageSourceParser.cs create mode 100644 src/Umbraco.Core/Deploy/ILocalLinkParser.cs create mode 100644 src/Umbraco.Core/Deploy/IMacroParser.cs create mode 100644 src/Umbraco.Core/Deploy/IPreValueConnector.cs create mode 100644 src/Umbraco.Core/Deploy/IPrettyArtifact.cs create mode 100644 src/Umbraco.Core/Deploy/IValueConnector.cs create mode 100644 src/Umbraco.Core/Serialization/StreamResultExtensions.cs create mode 100644 src/Umbraco.Core/Serialization/UdiRangeJsonConverter.cs diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 69e0f7c68e..d6c4003fcf 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -4,18 +4,6 @@ using Umbraco.Core.Models; namespace Umbraco.Core { - /// - /// Contains the valid selector values. - /// - internal static class DeploySelector - { - public const string This = "this"; - public const string ThisAndChildren = "this-and-children"; - public const string ThisAndDescendants = "this-and-descendants"; - public const string ChildrenOfThis = "children"; - public const string DescendantsOfThis = "descendants"; - } - /// /// Defines well-known entity types. /// diff --git a/src/Umbraco.Core/Constants-DatabaseProviders.cs b/src/Umbraco.Core/Constants-DatabaseProviders.cs new file mode 100644 index 0000000000..2a64ade081 --- /dev/null +++ b/src/Umbraco.Core/Constants-DatabaseProviders.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core +{ + public static partial class Constants + { + public static class DatabaseProviders + { + public const string SqlCe = "System.Data.SqlServerCe.4.0"; + public const string SqlServer = "System.Data.SqlClient"; + public const string MySql = "MySql.Data.MySqlClient"; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-DeployEntityType.cs b/src/Umbraco.Core/Constants-DeployEntityType.cs new file mode 100644 index 0000000000..2c0ea81be3 --- /dev/null +++ b/src/Umbraco.Core/Constants-DeployEntityType.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core +{ + + public static partial class Constants + { + + /// + /// Defines well-known entity types. + /// + /// Well-known entity types are those that Deploy already knows about, + /// but entity types are strings and so can be extended beyond what is defined here. + internal static class DeployEntityType + { + // guid entity types + + public const string AnyGuid = "any-guid"; // that one is for tests + + public const string Document = "document"; + public const string Media = "media"; + public const string Member = "member"; + + public const string DictionaryItem = "dictionary-item"; + public const string Macro = "macro"; + public const string Template = "template"; + + public const string DocumentType = "document-type"; + public const string DocumentTypeContainer = "document-type-container"; + public const string MediaType = "media-type"; + public const string MediaTypeContainer = "media-type-container"; + public const string DataType = "data-type"; + public const string DataTypeContainer = "data-type-container"; + public const string MemberType = "member-type"; + public const string MemberGroup = "member-group"; + + public const string RelationType = "relation-type"; + + // string entity types + + public const string AnyString = "any-string"; // that one is for tests + + public const string MediaFile = "media-file"; + public const string TemplateFile = "template-file"; + public const string Script = "script"; + public const string Stylesheet = "stylesheet"; + public const string PartialView = "partial-view"; + public const string PartialViewMacro = "partial-view-macro"; + public const string Xslt = "xslt"; + + public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType) + { + switch (umbracoObjectType) + { + case UmbracoObjectTypes.Document: + return Document; + case UmbracoObjectTypes.Media: + return Media; + case UmbracoObjectTypes.Member: + return Member; + case UmbracoObjectTypes.Template: + return Template; + case UmbracoObjectTypes.DocumentType: + return DocumentType; + case UmbracoObjectTypes.DocumentTypeContainer: + return DocumentTypeContainer; + case UmbracoObjectTypes.MediaType: + return MediaType; + case UmbracoObjectTypes.MediaTypeContainer: + return MediaTypeContainer; + case UmbracoObjectTypes.DataType: + return DataType; + case UmbracoObjectTypes.DataTypeContainer: + return DataTypeContainer; + case UmbracoObjectTypes.MemberType: + return MemberType; + case UmbracoObjectTypes.MemberGroup: + return MemberGroup; + case UmbracoObjectTypes.Stylesheet: + return Stylesheet; + case UmbracoObjectTypes.RelationType: + return RelationType; + } + throw new NotSupportedException(string.Format("UmbracoObjectType \"{0}\" does not have a matching EntityType.", umbracoObjectType)); + } + + public static UmbracoObjectTypes ToUmbracoObjectType(string entityType) + { + switch (entityType) + { + case Document: + return UmbracoObjectTypes.Document; + case Media: + return UmbracoObjectTypes.Media; + case Member: + return UmbracoObjectTypes.Member; + case Template: + return UmbracoObjectTypes.Template; + case DocumentType: + return UmbracoObjectTypes.DocumentType; + case DocumentTypeContainer: + return UmbracoObjectTypes.DocumentTypeContainer; + case MediaType: + return UmbracoObjectTypes.MediaType; + case MediaTypeContainer: + return UmbracoObjectTypes.MediaTypeContainer; + case DataType: + return UmbracoObjectTypes.DataType; + case DataTypeContainer: + return UmbracoObjectTypes.DataTypeContainer; + case MemberType: + return UmbracoObjectTypes.MemberType; + case MemberGroup: + return UmbracoObjectTypes.MemberGroup; + case Stylesheet: + return UmbracoObjectTypes.Stylesheet; + case RelationType: + return UmbracoObjectTypes.RelationType; + } + throw new NotSupportedException( + string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType)); + } + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-DeploySelector.cs b/src/Umbraco.Core/Constants-DeploySelector.cs new file mode 100644 index 0000000000..b2415fb37f --- /dev/null +++ b/src/Umbraco.Core/Constants-DeploySelector.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Contains the valid selector values. + /// + internal static class DeploySelector + { + public const string This = "this"; + public const string ThisAndChildren = "this-and-children"; + public const string ThisAndDescendants = "this-and-descendants"; + public const string ChildrenOfThis = "children"; + public const string DescendantsOfThis = "descendants"; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs new file mode 100644 index 0000000000..bd2e1c5acf --- /dev/null +++ b/src/Umbraco.Core/Constants-Security.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel; + +namespace Umbraco.Core +{ + public static partial class Constants + { + public static class Security + { + + public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; + public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie"; + public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN"; + public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken"; + public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie"; + + /// + /// The prefix used for external identity providers for their authentication type + /// + /// + /// By default we don't want to interfere with front-end external providers and their default setup, for back office the + /// providers need to be setup differently and each auth type for the back office will be prefixed with this value + /// + public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco."; + + public const string StartContentNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode"; + public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode"; + public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp"; + public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid"; + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index d1ac6fbb38..b38417c39b 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -29,12 +29,6 @@ public const string UmbracoConnectionName = "umbracoDbDSN"; public const string UmbracoMigrationName = "Umbraco"; } - - public static class DatabaseProviders - { - public const string SqlCe = "System.Data.SqlServerCe.4.0"; - public const string SqlServer = "System.Data.SqlClient"; - public const string MySql = "MySql.Data.MySqlClient"; - } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index f596820506..67b5698610 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -29,30 +29,6 @@ namespace Umbraco.Core public const string AuthCookieName = "UMB_UCONTEXT"; } - - public static class Security - { - - public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; - public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie"; - public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN"; - public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken"; - public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie"; - - /// - /// The prefix used for external identity providers for their authentication type - /// - /// - /// By default we don't want to interfere with front-end external providers and their default setup, for back office the - /// providers need to be setup differently and each auth type for the back office will be prefixed with this value - /// - public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco."; - - public const string StartContentNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode"; - public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode"; - public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp"; - public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid"; - - } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs new file mode 100644 index 0000000000..4d6bc687cb --- /dev/null +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace Umbraco.Core.Deploy +{ + /// + /// Provides a base class to all artifacts. + /// + public abstract class ArtifactBase : IArtifact + where TUdi : Udi + { + protected ArtifactBase(TUdi udi, IEnumerable dependencies = null) + { + if (udi == null) + throw new ArgumentNullException("udi"); + Udi = udi; + + Dependencies = dependencies ?? Enumerable.Empty(); + _checksum = new Lazy(GetChecksum); + } + + private readonly Lazy _checksum; + + protected abstract string GetChecksum(); + + #region Abstract implementation of IArtifactSignature + + Udi IArtifactSignature.Udi + { + get { return Udi; } + } + + public TUdi Udi { get; set; } + + [JsonIgnore] + public string Checksum + { + get { return _checksum.Value; } + } + + public IEnumerable Dependencies { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/ArtifactSignature.cs b/src/Umbraco.Core/Deploy/ArtifactSignature.cs new file mode 100644 index 0000000000..4bea6a5ce8 --- /dev/null +++ b/src/Umbraco.Core/Deploy/ArtifactSignature.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Deploy +{ + public sealed class ArtifactSignature : IArtifactSignature + { + public ArtifactSignature(Udi udi, string checksum, IEnumerable dependencies = null) + { + Udi = udi; + Checksum = checksum; + Dependencies = dependencies ?? Enumerable.Empty(); + } + + public Udi Udi { get; private set; } + + public string Checksum { get; private set; } + + public IEnumerable Dependencies { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/GridValue.cs b/src/Umbraco.Core/Deploy/GridValue.cs new file mode 100644 index 0000000000..39a38a665c --- /dev/null +++ b/src/Umbraco.Core/Deploy/GridValue.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Umbraco.Core.Deploy +{ + public class GridValue + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("sections")] + public IEnumerable
    Sections { get; set; } + + public class Section + { + [JsonProperty("grid")] + public string Grid { get; set; } + + [JsonProperty("rows")] + public IEnumerable Rows { get; set; } + } + + public class Row + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("id")] + public Guid Id { get; set; } + + [JsonProperty("areas")] + public IEnumerable Areas { get; set; } + + [JsonProperty("styles")] + public JToken Styles { get; set; } + + [JsonProperty("config")] + public JToken Config { get; set; } + } + + public class Area + { + [JsonProperty("grid")] + public string Grid { get; set; } + + [JsonProperty("controls")] + public IEnumerable Controls { get; set; } + + [JsonProperty("styles")] + public JToken Styles { get; set; } + + [JsonProperty("config")] + public JToken Config { get; set; } + } + + public class Control + { + [JsonProperty("value")] + public JToken Value { get; set; } + + [JsonProperty("editor")] + public Editor Editor { get; set; } + + [JsonProperty("styles")] + public JToken Styles { get; set; } + + [JsonProperty("config")] + public JToken Config { get; set; } + } + + public class Editor + { + [JsonProperty("alias")] + public string Alias { get; set; } + + [JsonProperty("view")] + public string View { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs new file mode 100644 index 0000000000..f95a4e7cf1 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Deploy +{ + /// + /// Defines methods that can convert a grid cell value to / from an environment-agnostic string. + /// + /// Grid cell values may contain values such as content identifiers, that would be local + /// to one environment, and need to be converted in order to be deployed. + public interface IGridCellValueConnector + { + /// + /// Gets a value indicating whether the connector supports a specified grid editor alias. + /// + /// The grid editor alias. + /// A value indicating whether the connector supports the grid editor alias. + /// Note that can be string.Empty to indicate the "default" connector. + bool IsConnector(string alias); + + /// + /// Gets the value to be deployed from the control value as a string. + /// + /// The control containing the value. + /// The property where the control is located. Do not modify - only used for context + /// The dependencies of the property. + /// The grid cell value to be deployed. + /// Note that + string GetValue(GridValue.Control control, Property property, ICollection dependencies); + + /// + /// Allows you to modify the value of a control being deployed. + /// + /// The control being deployed. + /// The property where the is located. Do not modify - only used for context. + /// Follows the pattern of the property value connectors (). The SetValue method is used to modify the value of the . + /// Note that only the value should be modified - not the . + /// The should only be used to assist with context data relevant when setting the value. + void SetValue(GridValue.Control control, Property property); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IImageSourceParser.cs b/src/Umbraco.Core/Deploy/IImageSourceParser.cs new file mode 100644 index 0000000000..d8e8d860ac --- /dev/null +++ b/src/Umbraco.Core/Deploy/IImageSourceParser.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Deploy +{ + /// + /// Provides methods to parse image tag sources in property values. + /// + public interface IImageSourceParser + { + /// + /// Parses an Umbraco property value and produces an artifact property value. + /// + /// The property value. + /// A list of dependencies. + /// The parsed value. + /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. + string ToArtifact(string value, ICollection dependencies); + + /// + /// Parses an artifact property value and produces an Umbraco property value. + /// + /// The artifact property value. + /// The parsed value. + /// Turns umb://media/... into /media/.... + string FromArtifact(string value); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs new file mode 100644 index 0000000000..c5906c2060 --- /dev/null +++ b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Deploy +{ + /// + /// Provides methods to parse local link tags in property values. + /// + public interface ILocalLinkParser + { + /// + /// Parses an Umbraco property value and produces an artifact property value. + /// + /// The property value. + /// A list of dependencies. + /// The parsed value. + /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the dependencies. + string ToArtifact(string value, ICollection dependencies); + + /// + /// Parses an artifact property value and produces an Umbraco property value. + /// + /// The artifact property value. + /// The parsed value. + /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. + string FromArtifact(string value); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IMacroParser.cs b/src/Umbraco.Core/Deploy/IMacroParser.cs new file mode 100644 index 0000000000..9cde8ef8b6 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IMacroParser.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Deploy +{ + public interface IMacroParser + { + /// + /// Parses an Umbraco property value and produces an artifact property value. + /// + /// Property value. + /// A list of dependencies. + /// Parsed value. + string ToArtifact(string value, ICollection dependencies); + + /// + /// Parses an artifact property value and produces an Umbraco property value. + /// + /// Artifact property value. + /// Parsed value. + string FromArtifact(string value); + + /// + /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. + /// + /// Value to attempt to convert + /// Alias of the editor used for the parameter + /// Collection to add dependencies to when performing ToArtifact + /// Indicates which action is being performed (to or from artifact) + /// Value with converted identifiers + string ReplaceAttributeValue(string value, string editorAlias, ICollection dependencies, Direction direction); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IPreValueConnector.cs b/src/Umbraco.Core/Deploy/IPreValueConnector.cs new file mode 100644 index 0000000000..4ef898cc74 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IPreValueConnector.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Deploy +{ + /// + /// Defines methods that can convert a preValue to / from an environment-agnostic string. + /// + /// PreValues may contain values such as content identifiers, that would be local + /// to one environment, and need to be converted in order to be deployed. + public interface IPreValueConnector + { + /// + /// Gets the property editor aliases that the value converter supports by default. + /// + IEnumerable PropertyEditorAliases { get; } + + /// + /// Gets the environment-agnostic preValues corresponding to environment-specific preValues. + /// + /// The environment-specific preValues. + /// The dependencies. + /// + IDictionary ConvertToDeploy(IDictionary preValues, ICollection dependencies); + + /// + /// Gets the environment-specific preValues corresponding to environment-agnostic preValues. + /// + /// The environment-agnostic preValues. + /// + IDictionary ConvertToLocalEnvironment(IDictionary preValues); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IPrettyArtifact.cs b/src/Umbraco.Core/Deploy/IPrettyArtifact.cs new file mode 100644 index 0000000000..38c0ddee4c --- /dev/null +++ b/src/Umbraco.Core/Deploy/IPrettyArtifact.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Deploy +{ + public interface IPrettyArtifact + { + string Name { get; } + string Alias { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/IValueConnector.cs b/src/Umbraco.Core/Deploy/IValueConnector.cs new file mode 100644 index 0000000000..a93e5a05a4 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IValueConnector.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Deploy +{ + /// + /// Defines methods that can convert a property value to / from an environment-agnostic string. + /// + /// Property values may contain values such as content identifiers, that would be local + /// to one environment, and need to be converted in order to be deployed. Connectors also deal + /// with serializing to / from string. + public interface IValueConnector + { + /// + /// Gets the property editor aliases that the value converter supports by default. + /// + IEnumerable PropertyEditorAliases { get; } + + /// + /// Gets the deploy property corresponding to a content property. + /// + /// The content property. + /// The content dependencies. + /// The deploy property value. + string GetValue(Property property, ICollection dependencies); + + /// + /// Sets a content property value using a deploy property. + /// + /// The content item. + /// The property alias. + /// The deploy property value. + void SetValue(IContentBase content, string alias, string value); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/NamedUdiRange.cs b/src/Umbraco.Core/NamedUdiRange.cs index 4e81631e03..c87e5db82e 100644 --- a/src/Umbraco.Core/NamedUdiRange.cs +++ b/src/Umbraco.Core/NamedUdiRange.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core ///
    /// A . /// An optional selector. - public NamedUdiRange(Udi udi, string selector = DeploySelector.This) + public NamedUdiRange(Udi udi, string selector = Constants.DeploySelector.This) : base(udi, selector) { } @@ -20,7 +20,7 @@ namespace Umbraco.Core /// A . /// A name. /// An optional selector. - public NamedUdiRange(Udi udi, string name, string selector = DeploySelector.This) + public NamedUdiRange(Udi udi, string name, string selector = Constants.DeploySelector.This) : base(udi, selector) { Name = name; diff --git a/src/Umbraco.Core/Serialization/StreamResultExtensions.cs b/src/Umbraco.Core/Serialization/StreamResultExtensions.cs new file mode 100644 index 0000000000..96490a933c --- /dev/null +++ b/src/Umbraco.Core/Serialization/StreamResultExtensions.cs @@ -0,0 +1,22 @@ +using System.IO; +using System.Text; +using System.Xml.Linq; + +namespace Umbraco.Core.Serialization +{ + public static class StreamResultExtensions + { + public static string ToJsonString(this Stream stream) + { + byte[] bytes = new byte[stream.Length]; + stream.Position = 0; + stream.Read(bytes, 0, (int)stream.Length); + return Encoding.UTF8.GetString(bytes); + } + + public static XDocument ToXDoc(this Stream stream) + { + return XDocument.Load(stream); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Serialization/StreamedResult.cs b/src/Umbraco.Core/Serialization/StreamedResult.cs index 9b73fd06bf..0a50751229 100644 --- a/src/Umbraco.Core/Serialization/StreamedResult.cs +++ b/src/Umbraco.Core/Serialization/StreamedResult.cs @@ -1,6 +1,4 @@ using System.IO; -using System.Text; -using System.Xml.Linq; namespace Umbraco.Core.Serialization { @@ -20,20 +18,4 @@ namespace Umbraco.Core.Serialization #endregion } - - public static class StreamResultExtensions - { - public static string ToJsonString(this Stream stream) - { - byte[] bytes = new byte[stream.Length]; - stream.Position = 0; - stream.Read(bytes, 0, (int)stream.Length); - return Encoding.UTF8.GetString(bytes); - } - - public static XDocument ToXDoc(this Stream stream) - { - return XDocument.Load(stream); - } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Serialization/UdiJsonConverter.cs b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs index 7d2db92e81..ff62535825 100644 --- a/src/Umbraco.Core/Serialization/UdiJsonConverter.cs +++ b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs @@ -4,26 +4,6 @@ using Newtonsoft.Json.Linq; namespace Umbraco.Core.Serialization { - public class UdiRangeJsonConverter : JsonConverter - { - public override bool CanConvert(Type objectType) - { - return typeof(UdiRange).IsAssignableFrom(objectType); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteValue(value.ToString()); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var jo = JToken.ReadFrom(reader); - var val = jo.ToObject(); - return val == null ? null : UdiRange.Parse(val); - } - } - public class UdiJsonConverter : JsonConverter { diff --git a/src/Umbraco.Core/Serialization/UdiRangeJsonConverter.cs b/src/Umbraco.Core/Serialization/UdiRangeJsonConverter.cs new file mode 100644 index 0000000000..099c46f29d --- /dev/null +++ b/src/Umbraco.Core/Serialization/UdiRangeJsonConverter.cs @@ -0,0 +1,26 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Umbraco.Core.Serialization +{ + public class UdiRangeJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return typeof(UdiRange).IsAssignableFrom(objectType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(value.ToString()); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jo = JToken.ReadFrom(reader); + var val = jo.ToObject(); + return val == null ? null : UdiRange.Parse(val); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/UdiRange.cs b/src/Umbraco.Core/UdiRange.cs index e1aa49e079..a708220066 100644 --- a/src/Umbraco.Core/UdiRange.cs +++ b/src/Umbraco.Core/UdiRange.cs @@ -19,19 +19,19 @@ namespace Umbraco.Core ///
    /// A . /// An optional selector. - public UdiRange(Udi udi, string selector = DeploySelector.This) + public UdiRange(Udi udi, string selector = Constants.DeploySelector.This) { Udi = udi; switch (selector) { - case DeploySelector.This: + case Constants.DeploySelector.This: Selector = selector; _uriValue = udi.UriValue; break; - case DeploySelector.ChildrenOfThis: - case DeploySelector.DescendantsOfThis: - case DeploySelector.ThisAndChildren: - case DeploySelector.ThisAndDescendants: + case Constants.DeploySelector.ChildrenOfThis: + case Constants.DeploySelector.DescendantsOfThis: + case Constants.DeploySelector.ThisAndChildren: + case Constants.DeploySelector.ThisAndDescendants: Selector = selector; _uriValue = new Uri(Udi + "?" + selector); break; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 38f2920f7d..d06547ff19 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -290,27 +290,41 @@ + + + + + + + + + + + + + + @@ -541,7 +555,9 @@ + + diff --git a/src/Umbraco.Tests/UdiTests.cs b/src/Umbraco.Tests/UdiTests.cs index 8654f4b458..2587e245aa 100644 --- a/src/Umbraco.Tests/UdiTests.cs +++ b/src/Umbraco.Tests/UdiTests.cs @@ -118,7 +118,7 @@ namespace Umbraco.Tests Assert.AreEqual(Guid.Empty, ((GuidUdi)guidUdi).Guid); // can create a range - var range = new UdiRange(stringUdi, DeploySelector.ChildrenOfThis); + var range = new UdiRange(stringUdi, Constants.DeploySelector.ChildrenOfThis); // cannot create invalid ranges Assert.Throws(() => new UdiRange(guidUdi, "x")); @@ -142,14 +142,14 @@ namespace Umbraco.Tests Assert.AreEqual(DeployEntityType.AnyGuid, dudi.EntityType); Assert.AreEqual(guid, ((GuidUdi)dudi).Guid); - var range = new UdiRange(udi, DeploySelector.ChildrenOfThis); + var range = new UdiRange(udi, Constants.DeploySelector.ChildrenOfThis); json = JsonConvert.SerializeObject(range, settings); Assert.AreEqual(string.Format("\"umb://any-guid/{0:N}?children\"", guid), json); var drange = JsonConvert.DeserializeObject(json, settings); Assert.AreEqual(udi, drange.Udi); Assert.AreEqual(string.Format("umb://any-guid/{0:N}", guid), drange.Udi.UriValue.ToString()); - Assert.AreEqual(DeploySelector.ChildrenOfThis, drange.Selector); + Assert.AreEqual(Constants.DeploySelector.ChildrenOfThis, drange.Selector); } } } \ No newline at end of file From b7d37af933f615deb96cfb1dbdca4b9ae678b884 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 16 Jan 2017 11:04:28 +0100 Subject: [PATCH 29/88] Bumps version --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 78653d30ea..7b0909cd23 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,2 +1,2 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.5.7 \ No newline at end of file +7.5.8 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 7ed0a29d79..9889522e34 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.5.7")] -[assembly: AssemblyInformationalVersion("7.5.7")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.5.8")] +[assembly: AssemblyInformationalVersion("7.5.8")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 86c64872ff..0bc8eee404 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.5.7"); + private static readonly Version Version = new Version("7.5.8"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 7b2dd06c89..c4134d6850 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2421,9 +2421,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" True True - 7570 + 7580 / - http://localhost:7570 + http://localhost:7580 False False From 3bdc2e63abda90cb03f1d62d0bc935a78119a0a3 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 16 Jan 2017 14:45:50 +0100 Subject: [PATCH 30/88] get url for items + set published/unpublished state --- .../components/umbnodepreview.directive.js | 1 + .../src/common/resources/entity.resource.js | 35 +++++- .../src/less/components/umb-node-preview.less | 8 ++ .../views/components/umb-node-preview.html | 2 +- .../contentpicker/contentpicker.controller.js | 100 ++++++++++++------ .../contentpicker/contentpicker.html | 1 + src/Umbraco.Web/Editors/EntityController.cs | 31 ++++++ 7 files changed, 140 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js index 62a8bc766e..8d63623efb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js @@ -15,6 +15,7 @@ icon: "=?", name: "=", description: "=?", + published: "=?", sortable: "=?", allowOpen: "=?", allowRemove: "=?", diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 914b601249..5e0f5deada 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -56,7 +56,7 @@ function entityResource($q, $http, umbRequestHelper) { * * ##usage *
    -         * entityResource.getPath(id)
    +         * entityResource.getPath(id, type)
              *    .then(function(pathArray) {
              *        alert('its here!');
              *    });
    @@ -77,6 +77,37 @@ function entityResource($q, $http, umbRequestHelper) {
                    'Failed to retrieve path for id:' + id);
             },
     
    +        /**
    +         * @ngdoc method
    +         * @name umbraco.resources.entityResource#getUrl
    +         * @methodOf umbraco.resources.entityResource
    +         *
    +         * @description
    +         * Returns a url, given a node ID and type
    +         *
    +         * ##usage
    +         * 
    +         * entityResource.getUrl(id, type)
    +         *    .then(function(url) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id Id of node to return the public url to + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the url. + * + */ + getUrl: function(id, type) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrl", + [{ id: id }, {type: type }])), + 'Failed to retrieve url for id:' + id); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getById @@ -140,7 +171,7 @@ function entityResource($q, $http, umbRequestHelper) { query += "ids=" + item + "&"; }); - // if ids array is empty we need a empty variable in the querystring otherwise the service returns a error + // if ids array is empty we need a empty variable in the querystring otherwise the service returns a error if (ids.length === 0) { query += "ids=&"; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index 3f51b09154..5ef9cc7e74 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -17,6 +17,14 @@ border-color: #d9d9d9; } +.umb-node-preview--unpublished { + .umb-node-preview__icon, + .umb-node-preview__name, + .umb-node-preview__description { + opacity: 0.6; + } +} + .umb-node-preview__icon { display: flex; width: 25px; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html index b811ae8eec..f31f595a88 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html @@ -1,4 +1,4 @@ -
    +
    {{ name }}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index e77e743f2b..f97aa2bf62 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -194,8 +194,19 @@ function contentPickerController($scope, dialogService, entityResource, contentR }); if (currIds.indexOf(item.id) < 0) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, path: item.path }); + + // get url for content and media items + if(entityType !== "Member") { + entityResource.getUrl(item.id, entityType).then(function(data){ + // update url + item.url = data.url; + // push item to render model + addSelectedItem(item); + }); + } else { + addSelectedItem(item); + } + } }; @@ -221,43 +232,28 @@ function contentPickerController($scope, dialogService, entityResource, contentR //load current data var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - var nodePromise = (entityType === "Document") ? contentResource.getByIds(modelIds) : entityResource.getByIds(modelIds, entityType); + var nodePromise = entityResource.getByIds(modelIds, entityType); nodePromise.then(function (data) { - _.each(modelIds, function (id, i) { - var entity = _.find(data, function (d) { - return d.id == id; - }); - - if (entity) { - - // set icon - if(entity.icon) { - entity.icon = iconHelper.convertFromLegacyIcon(entity.icon); - } - - // set default icon - if (!entity.icon) { - switch (entityType) { - case "Document": - entity.icon = "icon-document"; - break; - case "Media": - entity.icon = "icon-picture"; - break; - case "Member": - entity.icon = "icon-user"; - break; - } - } - - var url = (entity.urls && entity.urls.length > 0) ? entity.urls[0] : ""; - var path = ($scope.model.config.showPathOnHover) ? entity.path : ""; - - $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: path, url: url }); + _.each(modelIds, function (id, i) { + var entity = _.find(data, function (d) { + return d.id == id; + }); + + if (entity) { + // get url for content and media items + if(entityType !== "Member") { + entityResource.getUrl(entity.id, entityType).then(function(data){ + // update url + entity.url = data.url; + // push item to render model + addSelectedItem(entity); + }); + } else { + addSelectedItem(entity); } - + } }); @@ -266,6 +262,40 @@ function contentPickerController($scope, dialogService, entityResource, contentR }); + function addSelectedItem(item) { + + // set icon + if(item.icon) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + } + + // set default icon + if (!item.icon) { + switch (entityType) { + case "Document": + item.icon = "icon-document"; + break; + case "Media": + item.icon = "icon-picture"; + break; + case "Member": + item.icon = "icon-user"; + break; + } + } + + $scope.renderModel.push({ + "name": item.name, + "id": item.id, + "icon": item.icon, + "path": item.path, + "url": item.url, + "published": (item.metaData.IsPublished === false && entityType === "Document") ? false : true + // only content supports published/unpublished content so we set everything else to published so the UI looks correct + }); + + } + } angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index abcb7f20c0..a8076ff987 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -8,6 +8,7 @@ ng-repeat="node in renderModel" icon="node.icon" name="node.name" + published="node.published" description="node.url" sortable="sortable" allow-remove="allowRemoveButton" diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 596e27e3a5..cfc04a7f20 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -144,6 +144,37 @@ namespace Umbraco.Web.Editors return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); } + + + + public dynamic GetUrl(int id, UmbracoEntityTypes type) + { + dynamic result = new System.Dynamic.ExpandoObject(); + + + if(type == UmbracoEntityTypes.Document) + { + var foundUrl = Umbraco.Url(id); + if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#") + { + result.url = foundUrl; + return result; + } + } + + var ancestors = GetAncestors(id, type); + + //if content, skip the first node for replicating NiceUrl defaults + if(type == UmbracoEntityTypes.Document) { + ancestors = ancestors.Skip(1); + } + + result.url = "/" + string.Join("/", ancestors.Select(x => x.Name) ); + + + return result; + } + /// /// Gets an entity by it's unique id if the entity supports that /// From c0a2a78f3d28cf56838e33a0d23699fd1b78f596 Mon Sep 17 00:00:00 2001 From: Niels Hartvig Date: Mon, 16 Jan 2017 19:13:05 +0100 Subject: [PATCH 31/88] Fixes issue with entities not being returned from Keys (Guids) --- src/Umbraco.Web/Editors/EntityController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 596e27e3a5..fa3e21a1eb 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -614,7 +614,7 @@ namespace Umbraco.Web.Editors .Select(Mapper.Map); // entities are in "some" order, put them back in order - var xref = entities.ToDictionary(x => x.Id); + var xref = entities.ToDictionary(x => x.Key); var result = keysArray.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); return result; From bbe68cc22436917df70de0549045e45051888cfc Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 16 Jan 2017 20:13:17 +0100 Subject: [PATCH 32/88] better styling of mini editor --- .../components/tabs/umbtabsnav.directive.js | 3 +- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../src/less/components/umb-mini-editor.less | 33 ++++++++++++++++ .../src/less/components/umb-node-preview.less | 10 +++++ .../views/common/dialogs/content/edit.html | 39 ++++++++++++++----- .../views/components/tabs/umb-tabs-nav.html | 2 +- 6 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umb-mini-editor.less diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js index 890acf3d6f..50c4775ef9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js @@ -43,7 +43,8 @@ templateUrl: "views/components/tabs/umb-tabs-nav.html", scope: { model: "=", - tabdrop: "=" + tabdrop: "=", + idSuffix: "@" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 49a98ee219..79a3ecdf8f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -123,6 +123,7 @@ @import "components/notifications/umb-notifications.less"; @import "components/umb-file-dropzone.less"; @import "components/umb-node-preview.less"; +@import "components/umb-mini-editor.less"; // Utilities @import "utilities/_flexbox.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-editor.less new file mode 100644 index 0000000000..de10805aef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-editor.less @@ -0,0 +1,33 @@ +.umb-modal .umb-mini-editor { + + .umb-panel-header { + padding: 20px 10px; + background: @grayLighter; + border-bottom: 1px solid @grayLight; + height: 59px; + + .umb-headline { + margin-left: 0; + margin-right: 0; + margin-bottom: 0; + margin-top: 3px; + } + } + + .umb-panel-body { + padding-left: 0; + padding-right: 0; + } + + .umb-panel-body.with-footer { + bottom: 52px; + } + + .umb-panel-footer { + background: @grayLighter; + border-top: 1px solid @grayLight; + height: 51px; + padding: 0 20px; + } + +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index 5ef9cc7e74..5b96492b48 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -85,4 +85,14 @@ .umb-node-preview-add:hover { color: @blue; +} + +.umb-overlay, +.umb-modal { + .umb-node-preview { + max-width: none; + } + .umb-node-preview-add { + max-width: none; + } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.html index 55e0cad2e7..eef28aefa1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.html @@ -2,7 +2,8 @@ ng-controller="Umbraco.Dialogs.Content.EditController" ng-show="loaded" ng-submit="save()" - val-form-manager> + val-form-manager + class="umb-mini-editor">
    @@ -12,15 +13,33 @@
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html index 8522bc3b6a..808822e138 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html @@ -1,5 +1,5 @@ From cca0f4c293fdf95420e9b74cf024468531ca5c74 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 16 Jan 2017 20:13:43 +0100 Subject: [PATCH 33/88] clean up --- .../propertyeditors/contentpicker/contentpicker.controller.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index f97aa2bf62..b587231220 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -232,9 +232,8 @@ function contentPickerController($scope, dialogService, entityResource, contentR //load current data var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - var nodePromise = entityResource.getByIds(modelIds, entityType); - nodePromise.then(function (data) { + entityResource.getByIds(modelIds, entityType).then(function (data) { _.each(modelIds, function (id, i) { var entity = _.find(data, function (d) { From 7b616cf719ce8eedfc78cc182de5e78174052b82 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 16 Jan 2017 20:50:56 +0100 Subject: [PATCH 34/88] add a bit padding on the sides --- .../src/less/components/umb-mini-editor.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-editor.less index de10805aef..53e0dcd6b3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-editor.less @@ -1,7 +1,7 @@ .umb-modal .umb-mini-editor { .umb-panel-header { - padding: 20px 10px; + padding: 20px; background: @grayLighter; border-bottom: 1px solid @grayLight; height: 59px; From c58957dc7b1a7e8e885562a5b134f333e7b32de2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 16 Jan 2017 20:52:07 +0100 Subject: [PATCH 35/88] fix unit tests --- .../common/mocks/resources/entity.mocks.js | 17 ++++++++ .../contentpicker/contentpicker.controller.js | 2 +- .../content-picker-controller.spec.js | 40 +++++++++++++------ 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js index fc60781375..a98240111e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js @@ -33,6 +33,19 @@ angular.module('umbraco.mocks'). return [200, nodes, null]; } + function returnEntityUrl() { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var urlOrbject = { + "url": "url" + }; + + return [200, urlOrbject, null]; + + } return { register: function () { @@ -48,6 +61,10 @@ angular.module('umbraco.mocks'). $httpBackend .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetById?')) .respond(returnEntitybyId); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetUrl?')) + .respond(returnEntityUrl); } }; }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index b587231220..e5852c29fd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -289,7 +289,7 @@ function contentPickerController($scope, dialogService, entityResource, contentR "icon": item.icon, "path": item.path, "url": item.url, - "published": (item.metaData.IsPublished === false && entityType === "Document") ? false : true + "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true // only content supports published/unpublished content so we set everything else to published so the UI looks correct }); diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js index e6d1312109..f34f8088df 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js @@ -17,7 +17,11 @@ describe('Content picker controller tests', function () { value:"1233,1231,23121", label: "My content picker", description: "desc", - config: {} + config: { + startNode: { + type: "content" + } + } }; //this controller requires an angular form controller applied to it @@ -47,6 +51,12 @@ describe('Content picker controller tests', function () { })); describe('content edit controller save and publish', function () { + + var item = { + name: "meh", + id: 666, + icon: "woop" + }; it('should define the default properties on construction', function () { expect(scope.model.value).toNotBe(undefined); @@ -65,7 +75,6 @@ describe('Content picker controller tests', function () { }); it("Removing an item should update renderModel, ids and model.value", function(){ - scope.remove(1); scope.$apply(); expect(scope.renderModel.length).toBe(2); @@ -73,24 +82,29 @@ describe('Content picker controller tests', function () { }); it("Adding an item should update renderModel, ids and model.value", function(){ - - scope.add({name: "meh", id: 666, icon: "woop"}); + scope.add(item); scope.$apply(); - expect(scope.renderModel.length).toBe(4); - expect(scope.model.value).toBe("1233,1231,23121,666"); + setTimeout(function(){ + expect(scope.renderModel.length).toBe(4); + expect(scope.model.value).toBe("1233,1231,23121,666"); + }, 1000); }); it("Adding a dublicate item should note update renderModel, ids and model.value", function(){ - - scope.add({ name: "meh", id: 666, icon: "woop" }); + scope.add(item); scope.$apply(); - expect(scope.renderModel.length).toBe(4); - expect(scope.model.value).toBe("1233,1231,23121,666"); + setTimeout(function(){ + expect(scope.renderModel.length).toBe(4); + expect(scope.model.value).toBe("1233,1231,23121,666"); + }, 1000); - scope.add({ name: "meh 2", id: 666, icon: "woop 2" }); + scope.add(item); scope.$apply(); - expect(scope.renderModel.length).toBe(4); - expect(scope.model.value).toBe("1233,1231,23121,666"); + setTimeout(function(){ + expect(scope.renderModel.length).toBe(4); + expect(scope.model.value).toBe("1233,1231,23121,666"); + }, 1000); + }); }); }); \ No newline at end of file From bf4ad25cadaa97b5783ef178187e07b8987d4b91 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 16 Jan 2017 21:16:31 +0100 Subject: [PATCH 36/88] remove ng-title --- .../src/views/propertyeditors/contentpicker/contentpicker.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index a8076ff987..ca6f9ac826 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -4,7 +4,6 @@
    Date: Tue, 17 Jan 2017 12:38:54 +1100 Subject: [PATCH 37/88] Moves GridValue to Models --- .../Deploy/IGridCellValueConnector.cs | 16 ++++++------ .../{Deploy => Models}/GridValue.cs | 25 +++++++++++-------- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../PropertyEditors/GridPropertyEditor.cs | 2 ++ 4 files changed, 25 insertions(+), 20 deletions(-) rename src/Umbraco.Core/{Deploy => Models}/GridValue.cs (72%) diff --git a/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs index f95a4e7cf1..65fde1ec0b 100644 --- a/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs @@ -21,21 +21,21 @@ namespace Umbraco.Core.Deploy /// /// Gets the value to be deployed from the control value as a string. /// - /// The control containing the value. + /// The control containing the value. /// The property where the control is located. Do not modify - only used for context /// The dependencies of the property. /// The grid cell value to be deployed. /// Note that - string GetValue(GridValue.Control control, Property property, ICollection dependencies); + string GetValue(GridValue.GridControl gridControl, Property property, ICollection dependencies); /// /// Allows you to modify the value of a control being deployed. /// - /// The control being deployed. - /// The property where the is located. Do not modify - only used for context. - /// Follows the pattern of the property value connectors (). The SetValue method is used to modify the value of the . - /// Note that only the value should be modified - not the . - /// The should only be used to assist with context data relevant when setting the value. - void SetValue(GridValue.Control control, Property property); + /// The control being deployed. + /// The property where the is located. Do not modify - only used for context. + /// Follows the pattern of the property value connectors (). The SetValue method is used to modify the value of the . + /// Note that only the value should be modified - not the . + /// The should only be used to assist with context data relevant when setting the value. + void SetValue(GridValue.GridControl gridControl, Property property); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/GridValue.cs b/src/Umbraco.Core/Models/GridValue.cs similarity index 72% rename from src/Umbraco.Core/Deploy/GridValue.cs rename to src/Umbraco.Core/Models/GridValue.cs index 39a38a665c..87f6146af6 100644 --- a/src/Umbraco.Core/Deploy/GridValue.cs +++ b/src/Umbraco.Core/Models/GridValue.cs @@ -3,26 +3,29 @@ using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Umbraco.Core.Deploy +namespace Umbraco.Core.Models { + /// + /// A model representing the value saved for the grid + /// public class GridValue { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("sections")] - public IEnumerable
    Sections { get; set; } + public IEnumerable Sections { get; set; } - public class Section + public class GridSection { [JsonProperty("grid")] public string Grid { get; set; } [JsonProperty("rows")] - public IEnumerable Rows { get; set; } + public IEnumerable Rows { get; set; } } - public class Row + public class GridRow { [JsonProperty("name")] public string Name { get; set; } @@ -31,7 +34,7 @@ namespace Umbraco.Core.Deploy public Guid Id { get; set; } [JsonProperty("areas")] - public IEnumerable Areas { get; set; } + public IEnumerable Areas { get; set; } [JsonProperty("styles")] public JToken Styles { get; set; } @@ -40,13 +43,13 @@ namespace Umbraco.Core.Deploy public JToken Config { get; set; } } - public class Area + public class GridArea { [JsonProperty("grid")] public string Grid { get; set; } [JsonProperty("controls")] - public IEnumerable Controls { get; set; } + public IEnumerable Controls { get; set; } [JsonProperty("styles")] public JToken Styles { get; set; } @@ -55,13 +58,13 @@ namespace Umbraco.Core.Deploy public JToken Config { get; set; } } - public class Control + public class GridControl { [JsonProperty("value")] public JToken Value { get; set; } [JsonProperty("editor")] - public Editor Editor { get; set; } + public GridEditor Editor { get; set; } [JsonProperty("styles")] public JToken Styles { get; set; } @@ -70,7 +73,7 @@ namespace Umbraco.Core.Deploy public JToken Config { get; set; } } - public class Editor + public class GridEditor { [JsonProperty("alias")] public string Alias { get; set; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d06547ff19..e17a142376 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -312,7 +312,7 @@ - + diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index b40cf3bbf3..e22748096e 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -31,6 +31,8 @@ namespace Umbraco.Web.PropertyEditors { try { + //TODO: We should deserialize this to Umbraco.Core.Models.GridValue instead of doing the below + var json = JsonConvert.DeserializeObject(e.Fields[field.Name]); //check if this is formatted for grid json From 038b155aeabb28896f61467eb190eca49a9f3c1d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Jan 2017 14:42:30 +1100 Subject: [PATCH 38/88] Fixes constants and internalsvisibleto --- src/Umbraco.Core/Constants-Conventions.cs | 118 +----------------- .../Constants-DeployEntityType.cs | 2 +- src/Umbraco.Core/Constants-DeploySelector.cs | 2 +- src/Umbraco.Core/Properties/AssemblyInfo.cs | 7 +- src/Umbraco.Core/Udi.cs | 8 +- src/Umbraco.Core/UdiGetterExtensions.cs | 50 ++++---- src/Umbraco.Tests/UdiTests.cs | 40 +++--- src/Umbraco.Web/Properties/AssemblyInfo.cs | 3 + 8 files changed, 60 insertions(+), 170 deletions(-) diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index d6c4003fcf..2e3d652d7e 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -4,123 +4,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core { - /// - /// Defines well-known entity types. - /// - /// Well-known entity types are those that Deploy already knows about, - /// but entity types are strings and so can be extended beyond what is defined here. - internal static class DeployEntityType - { - // guid entity types - - public const string AnyGuid = "any-guid"; // that one is for tests - - public const string Document = "document"; - public const string Media = "media"; - public const string Member = "member"; - - public const string DictionaryItem = "dictionary-item"; - public const string Macro = "macro"; - public const string Template = "template"; - - public const string DocumentType = "document-type"; - public const string DocumentTypeContainer = "document-type-container"; - public const string MediaType = "media-type"; - public const string MediaTypeContainer = "media-type-container"; - public const string DataType = "data-type"; - public const string DataTypeContainer = "data-type-container"; - public const string MemberType = "member-type"; - public const string MemberGroup = "member-group"; - - public const string RelationType = "relation-type"; - - // string entity types - - public const string AnyString = "any-string"; // that one is for tests - - public const string MediaFile = "media-file"; - public const string TemplateFile = "template-file"; - public const string Script = "script"; - public const string Stylesheet = "stylesheet"; - public const string PartialView = "partial-view"; - public const string PartialViewMacro = "partial-view-macro"; - public const string Xslt = "xslt"; - - public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType) - { - switch (umbracoObjectType) - { - case UmbracoObjectTypes.Document: - return Document; - case UmbracoObjectTypes.Media: - return Media; - case UmbracoObjectTypes.Member: - return Member; - case UmbracoObjectTypes.Template: - return Template; - case UmbracoObjectTypes.DocumentType: - return DocumentType; - case UmbracoObjectTypes.DocumentTypeContainer: - return DocumentTypeContainer; - case UmbracoObjectTypes.MediaType: - return MediaType; - case UmbracoObjectTypes.MediaTypeContainer: - return MediaTypeContainer; - case UmbracoObjectTypes.DataType: - return DataType; - case UmbracoObjectTypes.DataTypeContainer: - return DataTypeContainer; - case UmbracoObjectTypes.MemberType: - return MemberType; - case UmbracoObjectTypes.MemberGroup: - return MemberGroup; - case UmbracoObjectTypes.Stylesheet: - return Stylesheet; - case UmbracoObjectTypes.RelationType: - return RelationType; - } - throw new NotSupportedException(string.Format("UmbracoObjectType \"{0}\" does not have a matching EntityType.", umbracoObjectType)); - } - - public static UmbracoObjectTypes ToUmbracoObjectType(string entityType) - { - switch (entityType) - { - case Document: - return UmbracoObjectTypes.Document; - case Media: - return UmbracoObjectTypes.Media; - case Member: - return UmbracoObjectTypes.Member; - case Template: - return UmbracoObjectTypes.Template; - case DocumentType: - return UmbracoObjectTypes.DocumentType; - case DocumentTypeContainer: - return UmbracoObjectTypes.DocumentTypeContainer; - case MediaType: - return UmbracoObjectTypes.MediaType; - case MediaTypeContainer: - return UmbracoObjectTypes.MediaTypeContainer; - case DataType: - return UmbracoObjectTypes.DataType; - case DataTypeContainer: - return UmbracoObjectTypes.DataTypeContainer; - case MemberType: - return UmbracoObjectTypes.MemberType; - case MemberGroup: - return UmbracoObjectTypes.MemberGroup; - case Stylesheet: - return UmbracoObjectTypes.Stylesheet; - case RelationType: - return UmbracoObjectTypes.RelationType; - } - throw new NotSupportedException( - string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType)); - } - } - - + public static partial class Constants { /// diff --git a/src/Umbraco.Core/Constants-DeployEntityType.cs b/src/Umbraco.Core/Constants-DeployEntityType.cs index 2c0ea81be3..f622661dff 100644 --- a/src/Umbraco.Core/Constants-DeployEntityType.cs +++ b/src/Umbraco.Core/Constants-DeployEntityType.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core /// /// Well-known entity types are those that Deploy already knows about, /// but entity types are strings and so can be extended beyond what is defined here. - internal static class DeployEntityType + public static class DeployEntityType { // guid entity types diff --git a/src/Umbraco.Core/Constants-DeploySelector.cs b/src/Umbraco.Core/Constants-DeploySelector.cs index b2415fb37f..b7b45fd666 100644 --- a/src/Umbraco.Core/Constants-DeploySelector.cs +++ b/src/Umbraco.Core/Constants-DeploySelector.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core /// /// Contains the valid selector values. /// - internal static class DeploySelector + public static class DeploySelector { public const string This = "this"; public const string ThisAndChildren = "this-and-children"; diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs index dbc1ab6c93..a74ebb8fec 100644 --- a/src/Umbraco.Core/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs @@ -29,7 +29,7 @@ using System.Security.Permissions; [assembly: InternalsVisibleTo("umbraco.webservices")] [assembly: InternalsVisibleTo("umbraco.datalayer")] [assembly: InternalsVisibleTo("umbraco.MacroEngines")] - +[assembly: InternalsVisibleTo("umbraco.providers")] [assembly: InternalsVisibleTo("umbraco.editorControls")] [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] @@ -42,7 +42,10 @@ using System.Security.Permissions; [assembly: InternalsVisibleTo("Umbraco.VisualStudio")] [assembly: InternalsVisibleTo("Umbraco.Courier.Core")] [assembly: InternalsVisibleTo("Umbraco.Courier.Persistence")] -[assembly: InternalsVisibleTo("umbraco.providers")] + +[assembly: InternalsVisibleTo("Umbraco.Deploy")] +[assembly: InternalsVisibleTo("Umbraco.Deploy.UI")] +[assembly: InternalsVisibleTo("Umbraco.Deploy.Cloud")] //allow this to be mocked in our unit tests [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index aef0fc9274..95bd9f90b1 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -44,12 +44,12 @@ namespace Umbraco.Core static Udi() { // for tests etc. - UdiTypes[DeployEntityType.AnyGuid] = UdiType.GuidUdi; - UdiTypes[DeployEntityType.AnyString] = UdiType.StringUdi; + UdiTypes[Constants.DeployEntityType.AnyGuid] = UdiType.GuidUdi; + UdiTypes[Constants.DeployEntityType.AnyString] = UdiType.StringUdi; // we don't have connectors for these... - UdiTypes[DeployEntityType.Member] = UdiType.GuidUdi; - UdiTypes[DeployEntityType.MemberGroup] = UdiType.GuidUdi; + UdiTypes[Constants.DeployEntityType.Member] = UdiType.GuidUdi; + UdiTypes[Constants.DeployEntityType.MemberGroup] = UdiType.GuidUdi; // fixme - or inject from...? // there is no way we can get the "registered" service connectors, as registration diff --git a/src/Umbraco.Core/UdiGetterExtensions.cs b/src/Umbraco.Core/UdiGetterExtensions.cs index aebb8873a6..d10f730aef 100644 --- a/src/Umbraco.Core/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/UdiGetterExtensions.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this ITemplate entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.Template, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.Template, entity.Key).EnsureClosed(); } /// @@ -28,7 +28,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IContentType entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.DocumentType, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.DocumentType, entity.Key).EnsureClosed(); } /// @@ -39,7 +39,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMediaType entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.MediaType, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.MediaType, entity.Key).EnsureClosed(); } /// @@ -50,7 +50,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMemberType entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.MemberType, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.MemberType, entity.Key).EnsureClosed(); } /// @@ -61,7 +61,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMemberGroup entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.MemberGroup, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.MemberGroup, entity.Key).EnsureClosed(); } /// @@ -74,9 +74,9 @@ namespace Umbraco.Core if (entity == null) throw new ArgumentNullException("entity"); string type; - if (entity is IContentType) type = DeployEntityType.DocumentType; - else if (entity is IMediaType) type = DeployEntityType.MediaType; - else if (entity is IMemberType) type = DeployEntityType.MemberType; + if (entity is IContentType) type = Constants.DeployEntityType.DocumentType; + else if (entity is IMediaType) type = Constants.DeployEntityType.MediaType; + else if (entity is IMemberType) type = Constants.DeployEntityType.MemberType; else throw new NotSupportedException(string.Format("Composition type {0} is not supported.", entity.GetType().FullName)); return new GuidUdi(type, entity.Key).EnsureClosed(); } @@ -89,7 +89,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IDataTypeDefinition entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.DataType, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.DataType, entity.Key).EnsureClosed(); } /// @@ -103,11 +103,11 @@ namespace Umbraco.Core string entityType; if (entity.ContainedObjectType == Constants.ObjectTypes.DataTypeGuid) - entityType = DeployEntityType.DataTypeContainer; + entityType = Constants.DeployEntityType.DataTypeContainer; else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentTypeGuid) - entityType = DeployEntityType.DocumentTypeContainer; + entityType = Constants.DeployEntityType.DocumentTypeContainer; else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaTypeGuid) - entityType = DeployEntityType.MediaTypeContainer; + entityType = Constants.DeployEntityType.MediaTypeContainer; else throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", entity.ContainedObjectType)); return new GuidUdi(entityType, entity.Key).EnsureClosed(); @@ -121,7 +121,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMedia entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.Media, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.Media, entity.Key).EnsureClosed(); } /// @@ -132,7 +132,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IContent entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.Document, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.Document, entity.Key).EnsureClosed(); } /// @@ -143,7 +143,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMember entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.Member, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.Member, entity.Key).EnsureClosed(); } /// @@ -154,7 +154,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this Stylesheet entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(DeployEntityType.Stylesheet, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.DeployEntityType.Stylesheet, entity.Path.TrimStart('/')).EnsureClosed(); } /// @@ -165,7 +165,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this Script entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(DeployEntityType.Script, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.DeployEntityType.Script, entity.Path.TrimStart('/')).EnsureClosed(); } /// @@ -176,7 +176,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IDictionaryItem entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.DictionaryItem, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.DictionaryItem, entity.Key).EnsureClosed(); } /// @@ -187,7 +187,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMacro entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.Macro, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.Macro, entity.Key).EnsureClosed(); } /// @@ -198,7 +198,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this IPartialView entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(DeployEntityType.PartialView, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.DeployEntityType.PartialView, entity.Path.TrimStart('/')).EnsureClosed(); } /// @@ -209,7 +209,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this IXsltFile entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(DeployEntityType.Xslt, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.DeployEntityType.Xslt, entity.Path.TrimStart('/')).EnsureClosed(); } /// @@ -222,9 +222,9 @@ namespace Umbraco.Core if (entity == null) throw new ArgumentNullException("entity"); string type; - if (entity is IContent) type = DeployEntityType.Document; - else if (entity is IMedia) type = DeployEntityType.Media; - else if (entity is IMember) type = DeployEntityType.Member; + if (entity is IContent) type = Constants.DeployEntityType.Document; + else if (entity is IMedia) type = Constants.DeployEntityType.Media; + else if (entity is IMember) type = Constants.DeployEntityType.Member; else throw new NotSupportedException(string.Format("ContentBase type {0} is not supported.", entity.GetType().FullName)); return new GuidUdi(type, entity.Key).EnsureClosed(); } @@ -237,7 +237,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IRelationType entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(DeployEntityType.RelationType, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.DeployEntityType.RelationType, entity.Key).EnsureClosed(); } /// diff --git a/src/Umbraco.Tests/UdiTests.cs b/src/Umbraco.Tests/UdiTests.cs index 2587e245aa..1a7b30a770 100644 --- a/src/Umbraco.Tests/UdiTests.cs +++ b/src/Umbraco.Tests/UdiTests.cs @@ -13,41 +13,41 @@ namespace Umbraco.Tests [Test] public void StringEntityCtorTest() { - var udi = new StringUdi(DeployEntityType.AnyString, "test-id"); - Assert.AreEqual(DeployEntityType.AnyString, udi.EntityType); + var udi = new StringUdi(Constants.DeployEntityType.AnyString, "test-id"); + Assert.AreEqual(Constants.DeployEntityType.AnyString, udi.EntityType); Assert.AreEqual("test-id", udi.Id); - Assert.AreEqual("umb://" + DeployEntityType.AnyString + "/test-id", udi.ToString()); + Assert.AreEqual("umb://" + Constants.DeployEntityType.AnyString + "/test-id", udi.ToString()); } [Test] public void StringEntityParseTest() { - var udi = Udi.Parse("umb://" + DeployEntityType.AnyString + "/test-id"); - Assert.AreEqual(DeployEntityType.AnyString, udi.EntityType); + var udi = Udi.Parse("umb://" + Constants.DeployEntityType.AnyString + "/test-id"); + Assert.AreEqual(Constants.DeployEntityType.AnyString, udi.EntityType); Assert.IsInstanceOf(udi); var stringEntityId = udi as StringUdi; Assert.IsNotNull(stringEntityId); Assert.AreEqual("test-id", stringEntityId.Id); - Assert.AreEqual("umb://" + DeployEntityType.AnyString + "/test-id", udi.ToString()); + Assert.AreEqual("umb://" + Constants.DeployEntityType.AnyString + "/test-id", udi.ToString()); } [Test] public void GuidEntityCtorTest() { var guid = Guid.NewGuid(); - var udi = new GuidUdi(DeployEntityType.AnyGuid, guid); - Assert.AreEqual(DeployEntityType.AnyGuid, udi.EntityType); + var udi = new GuidUdi(Constants.DeployEntityType.AnyGuid, guid); + Assert.AreEqual(Constants.DeployEntityType.AnyGuid, udi.EntityType); Assert.AreEqual(guid, udi.Guid); - Assert.AreEqual("umb://" + DeployEntityType.AnyGuid + "/" + guid.ToString("N"), udi.ToString()); + Assert.AreEqual("umb://" + Constants.DeployEntityType.AnyGuid + "/" + guid.ToString("N"), udi.ToString()); } [Test] public void GuidEntityParseTest() { var guid = Guid.NewGuid(); - var s = "umb://" + DeployEntityType.AnyGuid + "/" + guid.ToString("N"); + var s = "umb://" + Constants.DeployEntityType.AnyGuid + "/" + guid.ToString("N"); var udi = Udi.Parse(s); - Assert.AreEqual(DeployEntityType.AnyGuid, udi.EntityType); + Assert.AreEqual(Constants.DeployEntityType.AnyGuid, udi.EntityType); Assert.IsInstanceOf(udi); var gudi = udi as GuidUdi; Assert.IsNotNull(gudi); @@ -82,9 +82,9 @@ namespace Umbraco.Tests var guid1 = Guid.NewGuid(); var entities = new[] { - new GuidUdi(DeployEntityType.AnyGuid, guid1), - new GuidUdi(DeployEntityType.AnyGuid, guid1), - new GuidUdi(DeployEntityType.AnyGuid, guid1), + new GuidUdi(Constants.DeployEntityType.AnyGuid, guid1), + new GuidUdi(Constants.DeployEntityType.AnyGuid, guid1), + new GuidUdi(Constants.DeployEntityType.AnyGuid, guid1), }; Assert.AreEqual(1, entities.Distinct().Count()); } @@ -93,12 +93,12 @@ namespace Umbraco.Tests public void CreateTest() { var guid = Guid.NewGuid(); - var udi = Udi.Create(DeployEntityType.AnyGuid, guid); - Assert.AreEqual(DeployEntityType.AnyGuid, udi.EntityType); + var udi = Udi.Create(Constants.DeployEntityType.AnyGuid, guid); + Assert.AreEqual(Constants.DeployEntityType.AnyGuid, udi.EntityType); Assert.AreEqual(guid, ((GuidUdi)udi).Guid); - Assert.Throws(() => Udi.Create(DeployEntityType.AnyString, guid)); - Assert.Throws(() => Udi.Create(DeployEntityType.AnyGuid, "foo")); + Assert.Throws(() => Udi.Create(Constants.DeployEntityType.AnyString, guid)); + Assert.Throws(() => Udi.Create(Constants.DeployEntityType.AnyGuid, "foo")); Assert.Throws(() => Udi.Create("barf", "foo")); } @@ -134,12 +134,12 @@ namespace Umbraco.Tests var guid = Guid.NewGuid(); - var udi = new GuidUdi(DeployEntityType.AnyGuid, guid); + var udi = new GuidUdi(Constants.DeployEntityType.AnyGuid, guid); var json = JsonConvert.SerializeObject(udi, settings); Assert.AreEqual(string.Format("\"umb://any-guid/{0:N}\"", guid), json); var dudi = JsonConvert.DeserializeObject(json, settings); - Assert.AreEqual(DeployEntityType.AnyGuid, dudi.EntityType); + Assert.AreEqual(Constants.DeployEntityType.AnyGuid, dudi.EntityType); Assert.AreEqual(guid, ((GuidUdi)dudi).Guid); var range = new UdiRange(udi, Constants.DeploySelector.ChildrenOfThis); diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index 89499b833e..7b30b1f4b3 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -34,6 +34,9 @@ using System.Security; [assembly: InternalsVisibleTo("Concorde.Sync")] [assembly: InternalsVisibleTo("Umbraco.Courier.Core")] [assembly: InternalsVisibleTo("Umbraco.Courier.Persistence")] +[assembly: InternalsVisibleTo("Umbraco.Deploy")] +[assembly: InternalsVisibleTo("Umbraco.Deploy.UI")] +[assembly: InternalsVisibleTo("Umbraco.Deploy.Cloud")] [assembly: InternalsVisibleTo("Umbraco.VisualStudio")] [assembly: InternalsVisibleTo("Umbraco.ModelsBuilder")] [assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.AspNet")] From 256ebe7c14b5f06134df4d9bac5802c6e228beba Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Jan 2017 15:29:58 +1100 Subject: [PATCH 39/88] bumps version for MyGet --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 1672ad53d0..62a06d8bcc 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.6.0 -alpha048 +alpha051 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index b092457e25..837339ea56 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha048")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha051")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index cd759cba82..6cbbcd82a4 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "alpha048"; } } + public static string CurrentComment { get { return "alpha051"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx From 28debbda1b55e071e09911597beb5ffa27a3b1d0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Jan 2017 16:21:43 +1100 Subject: [PATCH 40/88] adds more logging for when indexes are rebuilt --- src/UmbracoExamine/UmbracoContentIndexer.cs | 6 +++++- src/UmbracoExamine/UmbracoMemberIndexer.cs | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index efc3e4a214..37e1188612 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -371,6 +371,8 @@ namespace UmbracoExamine const int pageSize = 10000; var pageIndex = 0; + DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - Start data queries - {0}", type)); + switch (type) { case IndexTypes.Content: @@ -471,6 +473,8 @@ namespace UmbracoExamine break; } + + DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}", type)); } private IEnumerable GetSerializedContent(IEnumerable content, ISet notPublished) @@ -520,7 +524,7 @@ namespace UmbracoExamine public override void RebuildIndex() { - DataService.LogService.AddVerboseLog(-1, "Rebuilding index"); + DataService.LogService.AddInfoLog(-1, "Rebuilding index"); base.RebuildIndex(); } diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index 64a574822f..bc7dd2bda6 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -132,6 +132,8 @@ namespace UmbracoExamine const int pageSize = 1000; var pageIndex = 0; + DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - Start data queries - {0}", type)); + IMember[] members; if (IndexerData.IncludeNodeTypes.Any()) @@ -163,6 +165,8 @@ namespace UmbracoExamine pageIndex++; } while (members.Length == pageSize); } + + DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}", type)); } private IEnumerable GetSerializedMembers(IEnumerable members) From b2acb97639ada0a8f18ccdcb5df4d2ce61c8823d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Jan 2017 16:29:30 +1100 Subject: [PATCH 41/88] adds stopwatch timer to logging --- src/UmbracoExamine/UmbracoContentIndexer.cs | 6 +++++- src/UmbracoExamine/UmbracoMemberIndexer.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index 37e1188612..d23f14cd60 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.IO; using System.Linq; using System.Xml.Linq; @@ -372,6 +373,8 @@ namespace UmbracoExamine var pageIndex = 0; DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - Start data queries - {0}", type)); + var stopwatch = new Stopwatch(); + stopwatch.Start(); switch (type) { @@ -474,7 +477,8 @@ namespace UmbracoExamine break; } - DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}", type)); + stopwatch.Stop(); + DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}, took {1}ms", type, stopwatch.ElapsedMilliseconds)); } private IEnumerable GetSerializedContent(IEnumerable content, ISet notPublished) diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index bc7dd2bda6..e3833da317 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Services; using UmbracoExamine.Config; using System.Collections.Generic; +using System.Diagnostics; using Examine; using System.IO; using UmbracoExamine.DataServices; @@ -133,6 +134,8 @@ namespace UmbracoExamine var pageIndex = 0; DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - Start data queries - {0}", type)); + var stopwatch = new Stopwatch(); + stopwatch.Start(); IMember[] members; @@ -166,7 +169,8 @@ namespace UmbracoExamine } while (members.Length == pageSize); } - DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}", type)); + stopwatch.Stop(); + DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}, took {1}ms", type, stopwatch.ElapsedMilliseconds)); } private IEnumerable GetSerializedMembers(IEnumerable members) From 24c7dee5d05be6d6370a55bc92682341e72babd3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Jan 2017 17:57:49 +1100 Subject: [PATCH 42/88] U4-9395 When rebuilding content indexes that don't support unpublished content and member indexes, use the cmsContentXml table as the data source --- src/Umbraco.Core/Models/IMediaType.cs | 2 +- .../Interfaces/IContentRepository.cs | 1 + .../Interfaces/IMediaRepository.cs | 11 +- .../Interfaces/IMemberRepository.cs | 2 +- .../Interfaces/IRepositoryVersionable.cs | 10 + .../Repositories/MediaRepository.cs | 24 -- .../Repositories/MemberRepository.cs | 2 +- .../Repositories/VersionableRepositoryBase.cs | 25 ++ src/Umbraco.Core/Services/ContentService.cs | 21 ++ src/Umbraco.Core/Services/IContentService.cs | 14 + src/Umbraco.Core/Services/IMemberService.cs | 10 + src/Umbraco.Core/Services/MemberService.cs | 20 ++ src/UmbracoExamine/UmbracoContentIndexer.cs | 263 +++++++++++------- src/UmbracoExamine/UmbracoMemberIndexer.cs | 105 +++++-- 14 files changed, 351 insertions(+), 159 deletions(-) diff --git a/src/Umbraco.Core/Models/IMediaType.cs b/src/Umbraco.Core/Models/IMediaType.cs index 29e4b665ba..e8f7c16190 100644 --- a/src/Umbraco.Core/Models/IMediaType.cs +++ b/src/Umbraco.Core/Models/IMediaType.cs @@ -4,7 +4,7 @@ namespace Umbraco.Core.Models { /// /// Defines a ContentType, which Media is based on - /// public interface IMediaType : IContentTypeComposition { diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index e2555995d2..28e4fbf199 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -96,5 +96,6 @@ namespace Umbraco.Core.Persistence.Repositories /// An Enumerable list of objects IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index 64989f9269..9e8890a37b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -38,15 +38,6 @@ namespace Umbraco.Core.Persistence.Repositories /// An Enumerable list of objects IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); - - /// - /// Gets paged media descendants as XML by path - /// - /// Path starts with - /// Page number - /// Page size - /// Total records the query would return without paging - /// A paged enumerable of XML entries of media items - IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index a24116f0e2..9528fd3d76 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -73,6 +73,6 @@ namespace Umbraco.Core.Persistence.Repositories /// /// void AddOrUpdatePreviewXml(IMember content, Func xml); - + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs index 3e05d1feaf..256ef36c05 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs @@ -67,5 +67,15 @@ namespace Umbraco.Core.Persistence.Repositories /// Id of the object to delete versions from /// Latest version date void DeleteVersions(int id, DateTime versionDate); + + /// + /// Gets paged content descendants as XML by path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of content items + IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 08c2f7e0be..2687848fa5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -502,30 +502,6 @@ namespace Umbraco.Core.Persistence.Repositories } - /// - /// Gets paged media descendants as XML by path - /// - /// Path starts with - /// Page number - /// Page size - /// Total records the query would return without paging - /// A paged enumerable of XML entries of media items - public IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords) - { - Sql query; - if (path == "-1") - { - query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE nodeObjectType = @0)", Guid.Parse(Constants.ObjectTypes.Media)).OrderBy("nodeId"); - } - else - { - query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE path LIKE @0)", path.EnsureEndsWith(",%")).OrderBy("nodeId"); - } - var pagedResult = Database.Page(pageIndex+1, pageSize, query); - totalRecords = pagedResult.TotalItems; - return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml)); - } - /// /// Private method to create a media object from a ContentDto /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index dcab898685..a92794775f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -381,7 +381,7 @@ namespace Umbraco.Core.Persistence.Repositories .Where(GetBaseWhereClause(), new { Id = id }) .OrderByDescending(x => x.VersionDate, SqlSyntax); return ProcessQuery(sql, true); - } + } public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) { diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index ec1623d16a..df15ab049c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Xml.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -137,6 +138,30 @@ namespace Umbraco.Core.Persistence.Repositories #endregion + /// + /// Gets paged content descendants as XML by path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of content items + public IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords) + { + Sql query; + if (path == "-1") + { + query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE nodeObjectType = @0)", NodeObjectTypeId).OrderBy("nodeId"); + } + else + { + query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE path LIKE (@0))", path.EnsureEndsWith(",%")).OrderBy("nodeId"); + } + var pagedResult = Database.Page(pageIndex + 1, pageSize, query); + totalRecords = pagedResult.TotalItems; + return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml)); + } + public int CountDescendants(int parentId, string contentTypeAlias = null) { var pathMatch = parentId == -1 diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 0838a0eb85..fff0ac61c8 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1764,6 +1764,27 @@ namespace Umbraco.Core.Services return true; } + /// + /// Gets paged content descendants as XML by path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of content items + public IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, out totalRecords); + return contents; + } + } + /// /// This builds the Xml document used for the XML cache /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index ec5db8c4fb..7722bf9c65 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Xml; +using System.Xml.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -95,6 +96,19 @@ namespace Umbraco.Core.Services /// public interface IContentService : IService { + /// + /// Gets all XML entries found in the cmsContentXml table based on the given path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of content items + /// + /// If -1 is passed, then this will return all content xml entries, otherwise will return all descendents from the path + /// + IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords); + /// /// This builds the Xml document used for the XML cache /// diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index a893d89feb..38f53f2d68 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Xml.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -13,6 +14,15 @@ namespace Umbraco.Core.Services /// public interface IMemberService : IMembershipMemberService { + /// + /// Gets all XML entries found in the cmsContentXml table + /// + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of content items + IEnumerable GetPagedXmlEntries(long pageIndex, int pageSize, out long totalRecords); + /// /// Rebuilds all xml content in the cmsContentXml table for all documents /// diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 2ea4f1f023..cf833dcd28 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -612,6 +612,26 @@ namespace Umbraco.Core.Services Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); } + /// + /// Gets paged member descendants as XML by path + /// + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of member items + public IEnumerable GetPagedXmlEntries(long pageIndex, int pageSize, out long totalRecords) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var contents = repository.GetPagedXmlEntriesByPath("-1", pageIndex, pageSize, out totalRecords); + return contents; + } + } + #endregion #region IMembershipMemberService Implementation diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index 447fd62f4c..c2dbecf83d 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Linq; +using System.Xml; using System.Xml.Linq; using Examine; using Lucene.Net.Documents; @@ -230,7 +231,13 @@ namespace UmbracoExamine SupportProtectedContent = supportProtected; else SupportProtectedContent = false; - + + bool disableXmlDocLookup; + if (config["disableXmlDocLookup"] != null && bool.TryParse(config["disableXmlDocLookup"], out disableXmlDocLookup)) + DisableXmlDocumentLookup = disableXmlDocLookup; + else + DisableXmlDocumentLookup = false; + base.Initialize(name, config); } @@ -238,6 +245,11 @@ namespace UmbracoExamine #region Properties + /// + /// Whether to use the cmsContentXml data to re-index when possible (i.e. for published content, media and members) + /// + public bool DisableXmlDocumentLookup { get; private set; } + /// /// By default this is false, if set to true then the indexer will include indexing content that is flagged as publicly protected. /// This property is ignored if SupportUnpublishedContent is set to true. @@ -364,6 +376,9 @@ namespace UmbracoExamine protected override void PerformIndexAll(string type) { + if (SupportedTypes.Contains(type) == false) + return; + const int pageSize = 10000; var pageIndex = 0; @@ -371,111 +386,171 @@ namespace UmbracoExamine var stopwatch = new Stopwatch(); stopwatch.Start(); - switch (type) + try { - case IndexTypes.Content: - var contentParentId = -1; - if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0) - { - contentParentId = IndexerData.ParentNodeId.Value; - } - IContent[] content; - - //used to track non-published entities so we can determine what items are implicitly not published - var notPublished = new HashSet(); - - do - { - long total; - - IEnumerable descendants; - if (SupportUnpublishedContent) + switch (type) + { + case IndexTypes.Content: + var contentParentId = -1; + if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0) { - descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total); - } - else - { - //get all paged records but order by level ascending, we need to do this because we need to track which nodes are not published so that we can determine - // which descendent nodes are implicitly not published - descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "level", Direction.Ascending, true, (string)null); - } - - //if specific types are declared we need to post filter them - //TODO: Update the service layer to join the cmsContentType table so we can query by content type too - if (IndexerData.IncludeNodeTypes.Any()) - { - content = descendants.Where(x => IndexerData.IncludeNodeTypes.Contains(x.ContentType.Alias)).ToArray(); - } - else - { - content = descendants.ToArray(); - } - - AddNodesToIndex(GetSerializedContent( - SupportUnpublishedContent, - c => _serializer.Serialize(_contentService, _dataTypeService, _userService, c), - content, notPublished).WhereNotNull(), type); - - pageIndex++; - } while (content.Length == pageSize); - - notPublished.Clear(); - - break; - case IndexTypes.Media: - var mediaParentId = -1; - - if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0) - { - mediaParentId = IndexerData.ParentNodeId.Value; - } - - XElement[] mediaXElements; - - var mediaTypes = _contentTypeService.GetAllMediaTypes().ToArray(); - var icons = mediaTypes.ToDictionary(x => x.Id, y => y.Icon); - - do - { - long total; - if (mediaParentId == -1) - { - mediaXElements = _mediaService.GetPagedXmlEntries("-1", pageIndex, pageSize, out total).ToArray(); - } - else - { - //Get the parent - var parent = _mediaService.GetById(mediaParentId); - if (parent == null) - mediaXElements = new XElement[0]; - else - mediaXElements = _mediaService.GetPagedXmlEntries(parent.Path, pageIndex, pageSize, out total).ToArray(); - } - - //if specific types are declared we need to post filter them - //TODO: Update the service layer to join the cmsContentType table so we can query by content type too - if (IndexerData.IncludeNodeTypes.Any()) - { - var includeNodeTypeIds = mediaTypes.Where(x => IndexerData.IncludeNodeTypes.Contains(x.Alias)).Select(x => x.Id); - mediaXElements = mediaXElements.Where(elm => includeNodeTypeIds.Contains(elm.AttributeValue("nodeType"))).ToArray(); + contentParentId = IndexerData.ParentNodeId.Value; } - foreach (var element in mediaXElements) + if (SupportUnpublishedContent == false && DisableXmlDocumentLookup == false) { - element.Add(new XAttribute("icon", icons[element.AttributeValue("nodeType")])); + ReindexWithXmlEntries(type, contentParentId, + () => _contentTypeService.GetAllContentTypes().ToArray(), + (path, pIndex, pSize) => + { + long totalContent; + var result = _contentService.GetPagedXmlEntries(path, pIndex, pSize, out totalContent).ToArray(); + return new Tuple(totalContent, result); + }, + i => _contentService.GetById(i)); + } + else + { + //used to track non-published entities so we can determine what items are implicitly not published + //currently this is not in use apart form in tests + var notPublished = new HashSet(); + + IContent[] content; + do + { + long total; + + IEnumerable descendants; + if (SupportUnpublishedContent) + { + descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total); + } + else + { + //get all paged records but order by level ascending, we need to do this because we need to track which nodes are not published so that we can determine + // which descendent nodes are implicitly not published + descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "level", Direction.Ascending, true, (string)null); + } + + //if specific types are declared we need to post filter them + //TODO: Update the service layer to join the cmsContentType table so we can query by content type too + if (IndexerData.IncludeNodeTypes.Any()) + { + content = descendants.Where(x => IndexerData.IncludeNodeTypes.Contains(x.ContentType.Alias)).ToArray(); + } + else + { + content = descendants.ToArray(); + } + + AddNodesToIndex(GetSerializedContent( + SupportUnpublishedContent, + c => _serializer.Serialize(_contentService, _dataTypeService, _userService, c), + content, notPublished).WhereNotNull(), type); + + pageIndex++; + } while (content.Length == pageSize); } - AddNodesToIndex(mediaXElements, type); - pageIndex++; - } while (mediaXElements.Length == pageSize); + break; + case IndexTypes.Media: + var mediaParentId = -1; - break; + if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0) + { + mediaParentId = IndexerData.ParentNodeId.Value; + } + + ReindexWithXmlEntries(type, mediaParentId, + () => _contentTypeService.GetAllMediaTypes().ToArray(), + (path, pIndex, pSize) => + { + long totalMedia; + var result = _mediaService.GetPagedXmlEntries(path, pIndex, pSize, out totalMedia).ToArray(); + return new Tuple(totalMedia, result); + }, + i => _mediaService.GetById(i)); + + break; + } } - - stopwatch.Stop(); + finally + { + stopwatch.Stop(); + } + DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}, took {1}ms", type, stopwatch.ElapsedMilliseconds)); } + /// + /// Performs a reindex of a type based on looking up entries from the cmsContentXml table - but using callbacks to get this data since + /// we don't have a common underlying service interface for the media/content stuff + /// + /// + /// + /// + /// + /// + internal void ReindexWithXmlEntries( + string type, + int parentId, + Func getContentTypes, + Func> getPagedXmlEntries, + Func getContent) + where TContentType: IContentTypeComposition + { + const int pageSize = 10000; + var pageIndex = 0; + + XElement[] xElements; + + var contentTypes = getContentTypes(); + var icons = contentTypes.ToDictionary(x => x.Id, y => y.Icon); + + do + { + long total; + if (parentId == -1) + { + var pagedElements = getPagedXmlEntries("-1", pageIndex, pageSize); + total = pagedElements.Item1; + xElements = pagedElements.Item2; + } + else + { + //Get the parent + var parent = getContent(parentId); + if (parent == null) + xElements = new XElement[0]; + else + { + var pagedElements = getPagedXmlEntries(parent.Path, pageIndex, pageSize); + total = pagedElements.Item1; + xElements = pagedElements.Item2; + } + } + + //if specific types are declared we need to post filter them + //TODO: Update the service layer to join the cmsContentType table so we can query by content type too + if (IndexerData.IncludeNodeTypes.Any()) + { + var includeNodeTypeIds = contentTypes.Where(x => IndexerData.IncludeNodeTypes.Contains(x.Alias)).Select(x => x.Id); + xElements = xElements.Where(elm => includeNodeTypeIds.Contains(elm.AttributeValue("nodeType"))).ToArray(); + } + + foreach (var element in xElements) + { + if (element.Attribute("icon") == null) + { + element.Add(new XAttribute("icon", icons[element.AttributeValue("nodeType")])); + } + } + + AddNodesToIndex(xElements, type); + pageIndex++; + } while (xElements.Length == pageSize); + } + internal static IEnumerable GetSerializedContent( bool supportUnpublishdContent, Func serializer, diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index e3833da317..33000a9f19 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Services; using UmbracoExamine.Config; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using Examine; using System.IO; @@ -25,6 +26,7 @@ namespace UmbracoExamine { private readonly IMemberService _memberService; + private readonly IMemberTypeService _memberTypeService; private readonly IDataTypeService _dataTypeService; /// @@ -34,6 +36,7 @@ namespace UmbracoExamine { _dataTypeService = ApplicationContext.Current.Services.DataTypeService; _memberService = ApplicationContext.Current.Services.MemberService; + _memberTypeService = ApplicationContext.Current.Services.MemberTypeService; } /// @@ -49,6 +52,7 @@ namespace UmbracoExamine { _dataTypeService = ApplicationContext.Current.Services.DataTypeService; _memberService = ApplicationContext.Current.Services.MemberService; + _memberTypeService = ApplicationContext.Current.Services.MemberTypeService; } /// @@ -61,6 +65,8 @@ namespace UmbracoExamine /// /// /// + [Obsolete("Use the ctor specifying all dependencies instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, IDataTypeService dataTypeService, IMemberService memberService, @@ -69,9 +75,31 @@ namespace UmbracoExamine { _dataTypeService = dataTypeService; _memberService = memberService; + _memberTypeService = ApplicationContext.Current.Services.MemberTypeService; } - + /// + /// Constructor to allow for creating an indexer at runtime + /// + /// + /// + /// + /// + /// + /// + /// + /// + public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, + IDataTypeService dataTypeService, + IMemberService memberService, + IMemberTypeService memberTypeService, + Analyzer analyzer, bool async) + : base(indexerData, indexPath, dataService, analyzer, async) + { + _dataTypeService = dataTypeService; + _memberService = memberService; + _memberTypeService = memberTypeService; + } /// /// Ensures that the'_searchEmail' is added to the user fields so that it is indexed - without having to modify the config @@ -130,46 +158,67 @@ namespace UmbracoExamine if (SupportedTypes.Contains(type) == false) return; - const int pageSize = 1000; - var pageIndex = 0; - DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - Start data queries - {0}", type)); var stopwatch = new Stopwatch(); stopwatch.Start(); - IMember[] members; - - if (IndexerData.IncludeNodeTypes.Any()) + try { - //if there are specific node types then just index those - foreach (var nodeType in IndexerData.IncludeNodeTypes) + if (DisableXmlDocumentLookup == false) { - do + ReindexWithXmlEntries(type, -1, + () => _memberTypeService.GetAll().ToArray(), + (path, pIndex, pSize) => + { + long totalContent; + var result = _memberService.GetPagedXmlEntries(pIndex, pSize, out totalContent).ToArray(); + return new Tuple(totalContent, result); + }, + i => _memberService.GetById(i)); + } + else + { + const int pageSize = 1000; + var pageIndex = 0; + + IMember[] members; + + if (IndexerData.IncludeNodeTypes.Any()) { - long total; - members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, true, null, nodeType).ToArray(); + //if there are specific node types then just index those + foreach (var nodeType in IndexerData.IncludeNodeTypes) + { + do + { + long total; + members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, true, null, nodeType).ToArray(); - AddNodesToIndex(GetSerializedMembers(members), type); + AddNodesToIndex(GetSerializedMembers(members), type); - pageIndex++; - } while (members.Length == pageSize); + pageIndex++; + } while (members.Length == pageSize); + } + } + else + { + //no node types specified, do all members + do + { + int total; + members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray(); + + AddNodesToIndex(GetSerializedMembers(members), type); + + pageIndex++; + } while (members.Length == pageSize); + } } } - else + finally { - //no node types specified, do all members - do - { - int total; - members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray(); - - AddNodesToIndex(GetSerializedMembers(members), type); - - pageIndex++; - } while (members.Length == pageSize); + stopwatch.Stop(); } - - stopwatch.Stop(); + DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}, took {1}ms", type, stopwatch.ElapsedMilliseconds)); } From 907afa11ad9797470c4496bf110be5f5d592cb18 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 17 Jan 2017 11:11:20 +0100 Subject: [PATCH 43/88] combine "open" end "edit" prevalues into one setting (open in dialog) + remove "show path on hover" setting because it is not used anymore --- .../PropertyEditors/ContentPickerPropertyEditor.cs | 7 +------ .../PropertyEditors/MultiNodeTreePickerPropertyEditor.cs | 9 +-------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index 4d58060164..be10cd83e2 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -33,17 +33,12 @@ namespace Umbraco.Web.PropertyEditors internal class ContentPickerPreValueEditor : PreValueEditor { - [PreValueField("showOpenButton", "Show open button", "boolean")] + [PreValueField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = " Opens the node in a dialog")] public string ShowOpenButton { get; set; } - - [PreValueField("showEditButton", "Show edit button (this feature is in preview!)", "boolean")] - public string ShowEditButton { get; set; } [PreValueField("startNodeId", "Start node", "treepicker")] public int StartNodeId { get; set; } - [PreValueField("showPathOnHover", "Show path when hovering items", "boolean")] - public bool ShowPathOnHover { get; set; } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index b3d3f02448..82daf80e34 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -45,16 +45,9 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("maxNumber", "Maximum number of items", "number")] public string MaxNumber { get; set; } - - [PreValueField("showOpenButton", "Show open button", "boolean")] + [PreValueField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = " Opens the node in a dialog")] public string ShowOpenButton { get; set; } - [PreValueField("showEditButton", "Show edit button (this feature is in preview!)", "boolean")] - public string ShowEditButton { get; set; } - - [PreValueField("showPathOnHover", "Show path when hovering items", "boolean")] - public bool ShowPathOnHover { get; set; } - /// /// This ensures the multiPicker pre-val is set based on the maxNumber of nodes set /// From 01f50a243b3fcec4a3bc49dc6b1c86dfb97efa3b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 17 Jan 2017 14:13:20 +0100 Subject: [PATCH 44/88] translate master template and no macros --- .../views/common/overlays/macropicker/macropicker.html | 8 ++++---- .../src/views/templates/edit.controller.js | 8 -------- src/Umbraco.Web.UI.Client/src/views/templates/edit.html | 6 +++++- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 ++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 ++ 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html index 418e6b5ca6..6bcbaad50c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html @@ -27,12 +27,12 @@ - - + + There are no macros available to insert -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index 97aa50cbbf..4cf55f4768 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -444,23 +444,15 @@ } function getMasterTemplateName(masterTemplateAlias, templates) { - if(masterTemplateAlias) { - var templateName = ""; - angular.forEach(templates, function(template){ if(template.alias === masterTemplateAlias) { templateName = template.name; } }); - return templateName; - - } else { - return "no master"; } - } function removeMasterTemplate() { diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index 2e5593894c..bdfb9517f3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -33,7 +33,11 @@ class="umb-era-button umb-button--s" ng-click="vm.openMasterTemplateOverlay()"> - Master template: {{ vm.getMasterTemplateName(vm.template.masterTemplateAlias, vm.templates) }} + Master template: + + {{ vm.getMasterTemplateName(vm.template.masterTemplateAlias, vm.templates) }} + No master + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index e14a0f9109..32b1ef780e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -291,6 +291,7 @@ Vælg medlem Vælg medlemsgruppe Der er ingen parametre for denne makro + Der er ikke tilføjet nogle makroer Link dit Fjern link fra dit konto @@ -1039,6 +1040,7 @@ Mange hilsner fra Umbraco robotten Master skabelon Ingen master skabelon + Ingen master Indsæt en underliggende skabelon diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 3a7a0fda7c..7243a34490 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -305,6 +305,7 @@ Select member group No icons were found There are no parameters for this macro + There are no macros available to insert External login providers Exception Details Stacktrace @@ -1057,6 +1058,7 @@ To manage your website, simply open the Umbraco back office and start adding con Master template No master template + No master Render child template From 931db6f0b177cd381d78dd9005b13c971dffc2a3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 17 Jan 2017 16:47:54 +0100 Subject: [PATCH 45/88] U4-9395 - Handle errors with non-existing fields and invalide JSON data --- src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs | 5 +++++ src/UmbracoExamine/UmbracoMemberIndexer.cs | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index b40cf3bbf3..db9792572f 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -92,7 +92,12 @@ namespace Umbraco.Web.PropertyEditors //swallow...on purpose, there's a chance that this isn't json and we don't want that to affect // the website. } + catch (ArgumentException) + { + //swallow on purpose to prevent this error: + // Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject. + } } } } diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index 33000a9f19..2e48dff64e 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -238,7 +238,9 @@ namespace UmbracoExamine var fields = base.GetSpecialFieldsToIndex(allValuesForIndexing); //adds the special path property to the index - fields.Add("__key", allValuesForIndexing["__key"]); + string valuesForIndexing; + if (allValuesForIndexing.TryGetValue("__key", out valuesForIndexing)) + fields.Add("__key", valuesForIndexing); return fields; From a54f4aeffdf139533f356f541ab53ea1bb8427cd Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 17 Jan 2017 19:10:22 +0100 Subject: [PATCH 46/88] added documentation for umb-node-preview component --- .../components/umbnodepreview.directive.js | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js index 8d63623efb..77f6f06a30 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js @@ -1,3 +1,91 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbNodePreview +@restrict E +@scope + +@description +Added in Umbraco v. 7.6: Use this directive to render a node preview. + +

    Markup example

    +
    +    
    + +
    + + +
    + +
    +
    + +

    Controller example

    +
    +    (function () {
    +        "use strict";
    +    
    +        function Controller() {
    +    
    +            var vm = this;
    +    
    +            vm.allowRemove = true;
    +            vm.allowOpen = true;
    +            vm.sortable = true;
    +    
    +            vm.nodes = [
    +                {
    +                    "icon": "icon-document",
    +                    "name": "My node 1",
    +                    "published": true,
    +                    "description": "A short description of my node"
    +                },
    +                {
    +                    "icon": "icon-document",
    +                    "name": "My node 2",
    +                    "published": true,
    +                    "description": "A short description of my node"
    +                }
    +            ];
    +    
    +            vm.remove = remove;
    +            vm.open = open;
    +    
    +            function remove(index, nodes) {
    +                alert("remove node");
    +            }
    +    
    +            function open(node) {
    +                alert("open node");
    +            }
    +    
    +        }
    +    
    +        angular.module("umbraco").controller("My.NodePreviewController", Controller);
    +    
    +    })();
    +
    + +@param {string} icon (binding): The node icon. +@param {string} name (binding): The node name. +@param {boolean} published (binding): The node pusblished state. +@param {string} description (binding): A short description. +@param {boolean} sortable (binding): Will add a move cursor on the node preview. Can used in combination with ui-sortable. +@param {boolean} allowRemove (binding): Show/Hide the remove button. +@param {boolean} allowOpen (binding): Show/Hide the open button. +@param {function} onRemove (expression): Callback function when the remove button is clicked. +@param {function} onOpen (expression): Callback function when the open button is clicked. +**/ + (function () { 'use strict'; From 4fe12d893044478a4b2f275b50ad15542e7fa16c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 17 Jan 2017 19:26:00 +0100 Subject: [PATCH 47/88] clean up dependencies in content picker controller --- .../propertyeditors/contentpicker/contentpicker.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index e5852c29fd..1ebfe81905 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -1,7 +1,7 @@ //this controller simply tells the dialogs service to open a mediaPicker window //with a specified callback, this callback will receive an object with a selection on it -function contentPickerController($scope, dialogService, entityResource, contentResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location, $timeout, miniEditorHelper) { +function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper) { function trim(str, chr) { var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); From 4612183b13f8a15c6154e8c470137306e1a6f44f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 17 Jan 2017 19:27:39 +0100 Subject: [PATCH 48/88] fix unit test typo --- .../unit/app/propertyeditors/content-picker-controller.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js index f34f8088df..d5e3a7a496 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js @@ -90,7 +90,7 @@ describe('Content picker controller tests', function () { }, 1000); }); - it("Adding a dublicate item should note update renderModel, ids and model.value", function(){ + it("Adding a duplicate item should note update renderModel, ids and model.value", function(){ scope.add(item); scope.$apply(); setTimeout(function(){ From 2964f9a1c7596ecc4796d715dd0ef2257018e67a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 17 Jan 2017 20:48:00 +0100 Subject: [PATCH 49/88] clean up redundant code --- .../contentpicker/contentpicker.controller.js | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 1ebfe81905..c191ee5518 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -194,19 +194,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper }); if (currIds.indexOf(item.id) < 0) { - - // get url for content and media items - if(entityType !== "Member") { - entityResource.getUrl(item.id, entityType).then(function(data){ - // update url - item.url = data.url; - // push item to render model - addSelectedItem(item); - }); - } else { - addSelectedItem(item); - } - + setEntityUrl(item); } }; @@ -241,17 +229,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper }); if (entity) { - // get url for content and media items - if(entityType !== "Member") { - entityResource.getUrl(entity.id, entityType).then(function(data){ - // update url - entity.url = data.url; - // push item to render model - addSelectedItem(entity); - }); - } else { - addSelectedItem(entity); - } + setEntityUrl(entity); } }); @@ -261,6 +239,20 @@ function contentPickerController($scope, entityResource, editorState, iconHelper }); + function setEntityUrl(entity) { + // get url for content and media items + if(entityType !== "Member") { + entityResource.getUrl(entity.id, entityType).then(function(data){ + // update url + entity.url = data.url; + // push item to render model + addSelectedItem(entity); + }); + } else { + addSelectedItem(entity); + } + } + function addSelectedItem(item) { // set icon From c6ed4eaff695a3ad20a198e4229a7e07c78c993d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 17 Jan 2017 21:27:24 +0100 Subject: [PATCH 50/88] only sort lists with more than one item --- .../contentpicker/contentpicker.controller.js | 21 ++++++++++++++++++- .../contentpicker/contentpicker.html | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index c191ee5518..3a238e141f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -39,6 +39,9 @@ function contentPickerController($scope, entityResource, editorState, iconHelper else { $scope.contentPickerForm.maxCount.$setValidity("maxCount", true); } + + setSortingState($scope.renderModel); + }); } @@ -59,6 +62,14 @@ function contentPickerController($scope, entityResource, editorState, iconHelper } }; + // sortable options + $scope.sortableOptions = { + distance: 10, + tolerance: "pointer", + scroll: true, + zIndex: 6000 + }; + if ($scope.model.config) { //merge the server config on top of the default config, then set the server config to use the result $scope.model.config = angular.extend(defaultConfig, $scope.model.config); @@ -78,7 +89,6 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.allowOpenButton = entityType === "Document" || entityType === "Media"; $scope.allowEditButton = entityType === "Document"; $scope.allowRemoveButton = true; - $scope.sortable = true; //the dialog options for the picker var dialogOptions = { @@ -287,6 +297,15 @@ function contentPickerController($scope, entityResource, editorState, iconHelper } + function setSortingState(items) { + // disable sorting if the list only consist of one item + if(items.length > 1) { + $scope.sortableOptions.disabled = false; + } else { + $scope.sortableOptions.disabled = true; + } + } + } angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index ca6f9ac826..900cf6b416 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -2,14 +2,14 @@ -
    +
    Date: Tue, 17 Jan 2017 23:01:40 +0100 Subject: [PATCH 51/88] remove option to open media items in mini editor --- .../propertyeditors/contentpicker/contentpicker.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 3a238e141f..22312511ce 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -86,7 +86,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper : $scope.model.config.startNode.type === "media" ? "Media" : "Document"; - $scope.allowOpenButton = entityType === "Document" || entityType === "Media"; + $scope.allowOpenButton = entityType === "Document"; $scope.allowEditButton = entityType === "Document"; $scope.allowRemoveButton = true; From fd1da492cc3e49d698230cf5edd6df07de38b545 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Jan 2017 19:18:19 +1100 Subject: [PATCH 52/88] fixes range test --- src/Umbraco.Tests/UdiTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/UdiTests.cs b/src/Umbraco.Tests/UdiTests.cs index 1a7b30a770..36242bab13 100644 --- a/src/Umbraco.Tests/UdiTests.cs +++ b/src/Umbraco.Tests/UdiTests.cs @@ -106,13 +106,13 @@ namespace Umbraco.Tests public void RangeTest() { // can parse open string udi - const string stringUdiString = "umb://stylesheet"; + var stringUdiString = "umb://" + Constants.DeployEntityType.AnyString; Udi stringUdi; Assert.IsTrue(Udi.TryParse(stringUdiString, out stringUdi)); Assert.AreEqual(string.Empty, ((StringUdi)stringUdi).Id); // can parse open guid udi - const string guidUdiString = "umb://document"; + var guidUdiString = "umb://" + Constants.DeployEntityType.AnyGuid; Udi guidUdi; Assert.IsTrue(Udi.TryParse(guidUdiString, out guidUdi)); Assert.AreEqual(Guid.Empty, ((GuidUdi)guidUdi).Guid); From 1f98b76da0cf944c9e6f610c35e5a580b0400822 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 18 Jan 2017 09:34:02 +0000 Subject: [PATCH 53/88] On app.authenticated JS event - we clear out LocalStorage values in case a different user is logging in --- src/Umbraco.Web.UI.Client/src/init.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index ded5ba08e4..3233974cee 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -1,6 +1,6 @@ /** Executed when the application starts, binds to events and set global state */ -app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', - function (userService, $log, $rootScope, $location, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache) { +app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService', + function (userService, $log, $rootScope, $location, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService) { //This sets the default jquery ajax headers to include our csrf token, we // need to user the beforeSend method because our token changes per user/login so @@ -13,6 +13,11 @@ app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', /** Listens for authentication and checks if our required assets are loaded, if/once they are we'll broadcast a ready event */ eventsService.on("app.authenticated", function(evt, data) { + + //Removes all stored LocalStorage browser items - that may contain sensitive data + //So if a machine or computer is shared and a new user logs in, we clear out the previous persons localStorage items + localStorageService.clearAll(); + assetsService._loadInitAssets().then(function() { appState.setGlobalState("isReady", true); From 6710ac8c85f075b447bdfb03727bfe39780ec0c5 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 18 Jan 2017 09:43:21 +0000 Subject: [PATCH 54/88] Fix up camelCase method name to GetChildren --- src/Umbraco.Web/Editors/MediaController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 5dd0c3073f..391931cb91 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -191,7 +191,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(idGuid); if (entity != null) { - return getChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } else { @@ -200,13 +200,13 @@ namespace Umbraco.Web.Editors } else if (int.TryParse(id, out idInt)) { - return getChildren(idInt, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(idInt, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } throw new HttpResponseException(HttpStatusCode.NotFound); } - private PagedResult> getChildren(int id, + private PagedResult> GetChildren(int id, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", From ba64768d8d4a6a939d8d04a9a3035ef99ba149e4 Mon Sep 17 00:00:00 2001 From: Aaron Sturmfels Date: Wed, 18 Jan 2017 21:12:30 +1100 Subject: [PATCH 55/88] Fixed UmbracoContentIndexer's PerformIndexAll SupportUnpublishedContent = true paging breaking out of the loop early if filtering by node types. --- src/UmbracoExamine/UmbracoContentIndexer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index c2dbecf83d..70d4f8242a 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -416,6 +416,7 @@ namespace UmbracoExamine var notPublished = new HashSet(); IContent[] content; + int currentPageSize; do { long total; @@ -432,6 +433,9 @@ namespace UmbracoExamine descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "level", Direction.Ascending, true, (string)null); } + // need to store decendants count before filtering, in order for loop to work correctly + currentPageSize = descendants.Count(); + //if specific types are declared we need to post filter them //TODO: Update the service layer to join the cmsContentType table so we can query by content type too if (IndexerData.IncludeNodeTypes.Any()) @@ -449,7 +453,7 @@ namespace UmbracoExamine content, notPublished).WhereNotNull(), type); pageIndex++; - } while (content.Length == pageSize); + } while (currentPageSize == pageSize); } break; From 54e460cf5bc06f640b7e8166fc2f9cadf75af35e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 18 Jan 2017 11:47:23 +0100 Subject: [PATCH 56/88] U4-9392 Upgrade ImageProcessor with cache header and high CPU fix U4-9393 Upgrade to Examine 0.1.80 --- build/NuSpecs/UmbracoCms.Core.nuspec | 4 ++-- src/Umbraco.Tests/Umbraco.Tests.csproj | 5 +++-- src/Umbraco.Tests/packages.config | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 9 +++++---- src/Umbraco.Web.UI/packages.config | 4 ++-- src/Umbraco.Web/Umbraco.Web.csproj | 5 +++-- src/Umbraco.Web/packages.config | 2 +- src/UmbracoExamine/UmbracoExamine.csproj | 5 +++-- src/UmbracoExamine/packages.config | 2 +- src/umbraco.MacroEngines/packages.config | 2 +- src/umbraco.MacroEngines/umbraco.MacroEngines.csproj | 5 +++-- 11 files changed, 25 insertions(+), 20 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 219437d5e0..3c65f6f145 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -32,9 +32,9 @@ - + - + diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index dbb877ee64..6e20740bcb 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -57,8 +57,9 @@ ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - - ..\packages\Examine.0.1.70.0\lib\Examine.dll + + ..\packages\Examine.0.1.80\lib\net45\Examine.dll + True ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index 6428eebadf..b71ba7a170 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index c4134d6850..af11140701 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -127,8 +127,8 @@ False ..\packages\dotless.1.4.1.0\lib\dotless.Core.dll - - ..\packages\Examine.0.1.70.0\lib\Examine.dll + + ..\packages\Examine.0.1.80\lib\net45\Examine.dll True @@ -138,8 +138,9 @@ ..\packages\ImageProcessor.2.5.1\lib\net45\ImageProcessor.dll - - ..\packages\ImageProcessor.Web.4.7.2\lib\net45\ImageProcessor.Web.dll + + ..\packages\ImageProcessor.Web.4.8.0\lib\net45\ImageProcessor.Web.dll + True False diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index 6919e10f64..629438762f 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -4,9 +4,9 @@ - + - + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 1db9416f86..31aa38b1bf 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -112,8 +112,9 @@ ..\packages\dotless.1.4.1.0\lib\dotless.Core.dll - - ..\packages\Examine.0.1.70.0\lib\Examine.dll + + ..\packages\Examine.0.1.80\lib\net45\Examine.dll + True ..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index f8d737194e..9490486e9a 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -3,7 +3,7 @@ - + diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj index 1bc438edd7..8720849744 100644 --- a/src/UmbracoExamine/UmbracoExamine.csproj +++ b/src/UmbracoExamine/UmbracoExamine.csproj @@ -82,8 +82,9 @@ ..\Solution Items\TheFARM-Public.snk - - ..\packages\Examine.0.1.70.0\lib\Examine.dll + + ..\packages\Examine.0.1.80\lib\net45\Examine.dll + True ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll diff --git a/src/UmbracoExamine/packages.config b/src/UmbracoExamine/packages.config index 04734b9fb8..0c85a9c3ca 100644 --- a/src/UmbracoExamine/packages.config +++ b/src/UmbracoExamine/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/umbraco.MacroEngines/packages.config b/src/umbraco.MacroEngines/packages.config index 9a785e35c3..930b7adbb0 100644 --- a/src/umbraco.MacroEngines/packages.config +++ b/src/umbraco.MacroEngines/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj index c9ee70e52c..198867a5d6 100644 --- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj @@ -45,8 +45,9 @@ false - - ..\packages\Examine.0.1.70.0\lib\Examine.dll + + ..\packages\Examine.0.1.80\lib\net45\Examine.dll + True ..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll From fc5bf81991fa79f10fd8753da22de8bec7f3abf6 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 18 Jan 2017 12:33:43 +0000 Subject: [PATCH 57/88] Returns a string as opposed to a Dynamic object - however cannot return a string object from this method as the result is wrapped in quotes hence we use a HttpResponseMessage & StringContent to return a raw string from the API endpoint --- src/Umbraco.Web/Editors/EntityController.cs | 31 ++++++++++++++------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index cfc04a7f20..9f8b15449c 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using System.Linq; +using System.Net.Http; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; using Umbraco.Web.WebApi.Filters; @@ -146,19 +147,27 @@ namespace Umbraco.Web.Editors - - public dynamic GetUrl(int id, UmbracoEntityTypes type) + /// + /// Gets the url of an entity + /// + /// Int id of the entity to fetch URL for + /// The tpye of entity such as Document, Media, Member + /// The URL or path to the item + public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) { - dynamic result = new System.Dynamic.ExpandoObject(); - + var returnUrl = string.Empty; - if(type == UmbracoEntityTypes.Document) + if (type == UmbracoEntityTypes.Document) { var foundUrl = Umbraco.Url(id); if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#") { - result.url = foundUrl; - return result; + returnUrl = foundUrl; + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(returnUrl) + }; } } @@ -169,10 +178,12 @@ namespace Umbraco.Web.Editors ancestors = ancestors.Skip(1); } - result.url = "/" + string.Join("/", ancestors.Select(x => x.Name) ); + returnUrl = "/" + string.Join("/", ancestors.Select(x => x.Name)); - - return result; + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(returnUrl) + }; } /// From 40ea54007fab4e08e540cbf3444064da703a22d1 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 18 Jan 2017 12:34:58 +0000 Subject: [PATCH 58/88] Update the usage of GetUrl - to just use the raw string value as opposed to a JSON object. Updated related JS object mock as well --- .../src/common/mocks/resources/entity.mocks.js | 8 ++------ .../contentpicker/contentpicker.controller.js | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js index a98240111e..8c03679183 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js @@ -38,12 +38,8 @@ angular.module('umbraco.mocks'). if (!mocksUtils.checkAuth()) { return [401, null, null]; } - - var urlOrbject = { - "url": "url" - }; - return [200, urlOrbject, null]; + return [200, "url", null]; } @@ -61,7 +57,7 @@ angular.module('umbraco.mocks'). $httpBackend .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetById?')) .respond(returnEntitybyId); - + $httpBackend .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetUrl?')) .respond(returnEntityUrl); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 22312511ce..d39027e014 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -254,7 +254,8 @@ function contentPickerController($scope, entityResource, editorState, iconHelper if(entityType !== "Member") { entityResource.getUrl(entity.id, entityType).then(function(data){ // update url - entity.url = data.url; + entity.url = data; + // push item to render model addSelectedItem(entity); }); From 1fd98dba30aff6183ec6c82613a7a2ac8b2f72ea Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 18 Jan 2017 14:50:51 +0100 Subject: [PATCH 59/88] make launch mini editor service return a promise so we can update a node when it is saved --- .../src/common/services/minieditorhelper.service.js | 12 +++++++++++- .../contentpicker/contentpicker.controller.js | 11 ++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/minieditorhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/minieditorhelper.service.js index 9c974f9ca9..693457c7e8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/minieditorhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/minieditorhelper.service.js @@ -1,12 +1,14 @@ (function () { 'use strict'; - function miniEditorHelper(dialogService, editorState, fileManager, contentEditingHelper) { + function miniEditorHelper(dialogService, editorState, fileManager, contentEditingHelper, $q) { var launched = false; function launchMiniEditor(node) { + var deferred = $q.defer(); + launched = true; //We need to store the current files selected in the file manager locally because the fileManager @@ -47,6 +49,9 @@ } launched = false; + + deferred.resolve(data); + }, closeCallback: function () { //reset the fileManager to what it was @@ -59,9 +64,14 @@ editorState.set(currEditorState); launched = false; + + deferred.reject(); + } }); + return deferred.promise; + } var service = { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index d39027e014..ba29b92477 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -213,7 +213,16 @@ function contentPickerController($scope, entityResource, editorState, iconHelper }; $scope.openMiniEditor = function(node) { - miniEditorHelper.launchMiniEditor(node); + miniEditorHelper.launchMiniEditor(node).then(function(updatedNode){ + // update the node + node.name = updatedNode.name; + node.published = updatedNode.hasPublishedVersion; + if(entityType !== "Member") { + entityResource.getUrl(updatedNode.id, entityType).then(function(data){ + node.url = data; + }); + } + }); }; var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { From db414e8045d0a455dae568a4f67675eab6c3ccef Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Jan 2017 12:51:56 +1100 Subject: [PATCH 60/88] Fixes issue with implicitly non-published items getting into the index, fixes issue with sql syntax columns (i.e for mysql) for queries that query the cmsContentXml table --- .../Repositories/ContentRepository.cs | 12 +++-- .../Interfaces/IRepositoryVersionable.cs | 3 +- .../Repositories/VersionableRepositoryBase.cs | 20 ++++++-- src/Umbraco.Core/Services/ContentService.cs | 6 ++- src/Umbraco.Core/Services/MediaService.cs | 2 +- src/Umbraco.Core/Services/MemberService.cs | 2 +- src/UmbracoExamine/UmbracoContentIndexer.cs | 47 ++++++++++++++++++- 7 files changed, 78 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 14e6c0e9a6..294f869c3f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -624,13 +624,13 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .Where(x => x.Published) + .Where(x => x.Published, SqlSyntax) .OrderBy(x => x.Level, SqlSyntax) .OrderBy(x => x.SortOrder, SqlSyntax); return ProcessQuery(sql, true); } - + /// /// This builds the Xml document used for the XML cache /// @@ -662,10 +662,14 @@ namespace Umbraco.Core.Persistence.Repositories parent.Attributes.Append(pIdAtt); xmlDoc.AppendChild(parent); - const string sql = @"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.xml, umbracoNode.level from umbracoNode + //Ensure that only nodes that have published versions are selected + var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.{0}, umbracoNode.{1} from umbracoNode inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1) -order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; +order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", + SqlSyntax.GetQuotedColumnName("xml"), + SqlSyntax.GetQuotedColumnName("level"), + SqlSyntax.GetQuotedColumnName("level")); XmlElement last = null; diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs index 256ef36c05..b318223ca7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs @@ -74,8 +74,9 @@ namespace Umbraco.Core.Persistence.Repositories /// Path starts with /// Page number /// Page size + /// /// Total records the query would return without paging /// A paged enumerable of XML entries of content items - IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords); + IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, string[] orderBy, out long totalRecords); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index df15ab049c..19f4b8fdfb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -139,24 +139,34 @@ namespace Umbraco.Core.Persistence.Repositories #endregion /// - /// Gets paged content descendants as XML by path + /// Gets paged document descendants as XML by path /// /// Path starts with /// Page number /// Page size + /// /// Total records the query would return without paging /// A paged enumerable of XML entries of content items - public IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords) + public virtual IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, string[] orderBy, out long totalRecords) { - Sql query; + var query = new Sql().Select(string.Format("umbracoNode.id, cmsContentXml.{0}", SqlSyntax.GetQuotedColumnName("xml"))) + .From("umbracoNode") + .InnerJoin("cmsContentXml").On("cmsContentXml.nodeId = umbracoNode.id"); + if (path == "-1") { - query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE nodeObjectType = @0)", NodeObjectTypeId).OrderBy("nodeId"); + query.Where("umbracoNode.nodeObjectType = @type", new { type = NodeObjectTypeId }); } else { - query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE path LIKE (@0))", path.EnsureEndsWith(",%")).OrderBy("nodeId"); + query.Where(string.Format("umbracoNode.{0} LIKE (@0)", SqlSyntax.GetQuotedColumnName("path")), path.EnsureEndsWith(",%")); } + + //each order by param needs to be in a bracket! see: https://github.com/toptensoftware/PetaPoco/issues/177 + query.OrderBy(orderBy == null + ? "(umbracoNode.id)" + : string.Join(",", orderBy.Select(x => string.Format("({0})", SqlSyntax.GetQuotedColumnName(x))))); + var pagedResult = Database.Page(pageIndex + 1, pageSize, query); totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml)); diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index fff0ac61c8..6f2007912f 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1780,7 +1780,11 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateContentRepository(uow)) { - var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, out totalRecords); + var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, + //This order by is VERY important! This allows us to figure out what is implicitly not published, see ContentRepository.BuildXmlCache and + // UmbracoContentIndexer.PerformIndexAll which uses the logic based on this sort order + new[] {"level", "parentID", "sortOrder"}, + out totalRecords); return contents; } } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 50b323ed0a..9a3a30c8bd 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -1228,7 +1228,7 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { - var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, out totalRecords); + var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, null, out totalRecords); return contents; } } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index cf833dcd28..70d45926fc 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -627,7 +627,7 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMemberRepository(uow)) { - var contents = repository.GetPagedXmlEntriesByPath("-1", pageIndex, pageSize, out totalRecords); + var contents = repository.GetPagedXmlEntriesByPath("-1", pageIndex, pageSize, null, out totalRecords); return contents; } } diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index c2dbecf83d..32fa2607ac 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -399,13 +399,58 @@ namespace UmbracoExamine if (SupportUnpublishedContent == false && DisableXmlDocumentLookup == false) { + //get all node Ids that have a published version - this is a fail safe check, in theory + // only document nodes that have a published version would exist in the cmsContentXml table + var allNodesWithPublishedVersions = ApplicationContext.Current.DatabaseContext.Database.Fetch( + "select DISTINCT cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1"); + + XElement last = null; + var trackedIds = new HashSet(); + ReindexWithXmlEntries(type, contentParentId, () => _contentTypeService.GetAllContentTypes().ToArray(), (path, pIndex, pSize) => { long totalContent; + + //sorted by: umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder var result = _contentService.GetPagedXmlEntries(path, pIndex, pSize, out totalContent).ToArray(); - return new Tuple(totalContent, result); + + //then like we do in the ContentRepository.BuildXmlCache we need to track what Parents have been processed + // already so that we can then exclude implicitly unpublished content items + var filtered = new List(); + + foreach (var xml in result) + { + var id = xml.AttributeValue("id"); + + //don't include this if it doesn't have a published version + if (allNodesWithPublishedVersions.Contains(id) == false) + continue; + + var parentId = xml.AttributeValue("parentID"); + + if (parentId == null) continue; //this shouldn't happen + + //if the parentid is changing + if (last != null && last.AttributeValue("parentID") != parentId) + { + var found = trackedIds.Contains(parentId); + if (found == false) + { + //Need to short circuit here, if the parent is not there it means that the parent is unpublished + // and therefore the child is not published either so cannot be included in the xml cache + continue; + } + } + + last = xml; + trackedIds.Add(xml.AttributeValue("id")); + + filtered.Add(xml); + } + + return new Tuple(totalContent, filtered.ToArray()); }, i => _contentService.GetById(i)); } From 4f596d20a9bbe2638d1252ace88c2d1136cb77d5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Jan 2017 13:13:49 +1100 Subject: [PATCH 61/88] refactors the fix for U4-9398 so it's not iterating multiple times over the descendants Enumerable --- src/UmbracoExamine/UmbracoContentIndexer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index c4045936a7..c36ad37adb 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -460,36 +460,36 @@ namespace UmbracoExamine //currently this is not in use apart form in tests var notPublished = new HashSet(); - IContent[] content; int currentPageSize; do { long total; - IEnumerable descendants; + IContent[] descendants; if (SupportUnpublishedContent) { - descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total); + descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total).ToArray(); } else { //get all paged records but order by level ascending, we need to do this because we need to track which nodes are not published so that we can determine // which descendent nodes are implicitly not published - descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "level", Direction.Ascending, true, (string)null); + descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "level", Direction.Ascending, true, (string)null).ToArray(); } // need to store decendants count before filtering, in order for loop to work correctly - currentPageSize = descendants.Count(); + currentPageSize = descendants.Length; //if specific types are declared we need to post filter them //TODO: Update the service layer to join the cmsContentType table so we can query by content type too + IEnumerable content; if (IndexerData.IncludeNodeTypes.Any()) { - content = descendants.Where(x => IndexerData.IncludeNodeTypes.Contains(x.ContentType.Alias)).ToArray(); + content = descendants.Where(x => IndexerData.IncludeNodeTypes.Contains(x.ContentType.Alias)); } else { - content = descendants.ToArray(); + content = descendants; } AddNodesToIndex(GetSerializedContent( From c96e017e65b905d37e1ccc27eb632af1ffeaa89b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Jan 2017 14:00:33 +1100 Subject: [PATCH 62/88] Fixes PocoToSqlExpressionVisitor (oops), fixes examine tests by using the correct usings and NRT indexer/searcher to avoid waiting on thread timers --- .../Querying/ModelToSqlExpressionVisitor.cs | 1 + .../Querying/PocoToSqlExpressionVisitor.cs | 7 +- .../LegacyExamineBackedMediaTests.cs | 20 ++++-- .../PublishedContent/PublishedMediaTests.cs | 64 +++++++++++++------ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../UmbracoExamine/EventsTest.cs | 62 ++++++++---------- .../UmbracoExamine/IndexInitializer.cs | 25 ++++---- src/Umbraco.Tests/UmbracoExamine/IndexTest.cs | 55 ++++++++-------- .../UmbracoExamine/RandomIdRAMDirectory.cs | 14 ++++ .../UmbracoExamine/SearchTests.cs | 21 +++--- src/UmbracoExamine/BaseUmbracoIndexer.cs | 14 ++++ src/UmbracoExamine/UmbracoContentIndexer.cs | 29 +++++++++ src/UmbracoExamine/UmbracoExamineSearcher.cs | 61 ++++++++++-------- 13 files changed, 230 insertions(+), 144 deletions(-) create mode 100644 src/Umbraco.Tests/UmbracoExamine/RandomIdRAMDirectory.cs diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs index b265a5b587..7f5e479af6 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs @@ -20,6 +20,7 @@ namespace Umbraco.Core.Persistence.Querying _mapper = mapper; } + [Obsolete("Use the overload the specifies a SqlSyntaxProvider")] public ModelToSqlExpressionVisitor() : this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T))) { } diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs index e0a4f07e48..4569b95853 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs @@ -18,14 +18,13 @@ namespace Umbraco.Core.Persistence.Querying public PocoToSqlExpressionVisitor(ISqlSyntaxProvider syntaxProvider) : base(syntaxProvider) { - + _pd = new Database.PocoData(typeof(T)); } [Obsolete("Use the overload the specifies a SqlSyntaxProvider")] public PocoToSqlExpressionVisitor() - : base(SqlSyntaxContext.SqlSyntaxProvider) - { - _pd = new Database.PocoData(typeof(T)); + : this(SqlSyntaxContext.SqlSyntaxProvider) + { } protected override string VisitMemberAccess(MemberExpression m) diff --git a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs index 5bf6a4edc5..f48887498e 100644 --- a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs @@ -1,6 +1,8 @@ using System; using System.Linq; +using Lucene.Net.Analysis.Standard; using Lucene.Net.Documents; +using Lucene.Net.Index; using Lucene.Net.Store; using Moq; using NUnit.Framework; @@ -31,12 +33,15 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ensure_Children_Are_Sorted() { - using (var luceneDir = new RAMDirectory()) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); + //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var result = searcher.Search(searcher.CreateSearchCriteria().Id(1111).Compile()); Assert.IsNotNull(result); Assert.AreEqual(1, result.TotalItemCount); @@ -60,12 +65,15 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ensure_Result_Has_All_Values() { - using (var luceneDir = new RAMDirectory()) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); + //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var result = searcher.Search(searcher.CreateSearchCriteria().Id(1111).Compile()); Assert.IsNotNull(result); Assert.AreEqual(1, result.TotalItemCount); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index 6ec347cc0f..eadc08d834 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -27,6 +27,7 @@ using UmbracoExamine; using UmbracoExamine.DataServices; using umbraco.BusinessLogic; using System.Linq; +using Lucene.Net.Index; namespace Umbraco.Tests.PublishedContent { @@ -106,11 +107,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ensure_Children_Sorted_With_Examine() { - using (var luceneDir = new RAMDirectory()) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); + //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var ctx = GetUmbracoContext("/test", 1234); var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); @@ -135,11 +139,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Find_In_Recycle_Bin() { - using (var luceneDir = new RAMDirectory()) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); + //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var ctx = GetUmbracoContext("/test", 1234); var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); @@ -175,11 +182,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_With_Examine() { - using (var luceneDir = new RAMDirectory()) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); + //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var ctx = GetUmbracoContext("/test", 1234); var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); @@ -197,11 +207,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Descendants_With_Examine() { - using (var luceneDir = new RAMDirectory()) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); + //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var ctx = GetUmbracoContext("/test", 1234); var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); @@ -219,11 +232,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void DescendantsOrSelf_With_Examine() { - using (var luceneDir = new RAMDirectory()) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); + //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var ctx = GetUmbracoContext("/test", 1234); var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); @@ -241,12 +257,15 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ancestors_With_Examine() { - using (var luceneDir = new RAMDirectory()) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); + //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); indexer.RebuildIndex(); var ctx = GetUmbracoContext("/test", 1234); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace @@ -260,12 +279,15 @@ namespace Umbraco.Tests.PublishedContent [Test] public void AncestorsOrSelf_With_Examine() { - using (var luceneDir = new RAMDirectory()) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); + //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); indexer.RebuildIndex(); var ctx = GetUmbracoContext("/test", 1234); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 6e20740bcb..f0fb6f5921 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -164,6 +164,7 @@ + diff --git a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs index 3e7377f3b6..34c7a1f6ac 100644 --- a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using Examine; +using Lucene.Net.Analysis.Standard; +using Lucene.Net.Index; using Lucene.Net.Store; using NUnit.Framework; using Umbraco.Tests.TestHelpers; @@ -15,53 +17,39 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Events_Ignoring_Node() { - //change the parent id so that they are all ignored - var existingCriteria = _indexer.IndexerData; - _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, - 999); //change to 999 + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) + { + //change the parent id so that they are all ignored + var existingCriteria = indexer.IndexerData; + indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, + 999); //change to 999 - var isIgnored = false; + var isIgnored = false; - EventHandler ignoringNode = (s, e) => - { - isIgnored = true; - }; + EventHandler ignoringNode = (s, e) => + { + isIgnored = true; + }; - _indexer.IgnoringNode += ignoringNode; + indexer.IgnoringNode += ignoringNode; - //get a node from the data repo - var node = _contentService.GetPublishedContentByXPath("//*[string-length(@id)>0 and number(@id)>0]") - .Root - .Elements() - .First(); + //get a node from the data repo + var node = _contentService.GetPublishedContentByXPath("//*[string-length(@id)>0 and number(@id)>0]") + .Root + .Elements() + .First(); - _indexer.ReIndexNode(node, IndexTypes.Content); + indexer.ReIndexNode(node, IndexTypes.Content); - Assert.IsTrue(isIgnored); - + Assert.IsTrue(isIgnored); + } } private readonly TestContentService _contentService = new TestContentService(); - private static UmbracoExamineSearcher _searcher; - private static UmbracoContentIndexer _indexer; - private Lucene.Net.Store.Directory _luceneDir; - - public override void Initialize() - { - base.Initialize(); - - _luceneDir = new RAMDirectory(); - _indexer = IndexInitializer.GetUmbracoIndexer(_luceneDir); - _indexer.RebuildIndex(); - _searcher = IndexInitializer.GetUmbracoSearcher(_luceneDir); - } - - public override void TearDown() - { - base.TearDown(); - _luceneDir.Dispose(); - } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index f57b1af213..c7922b9d58 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -7,6 +7,7 @@ using Examine.LuceneEngine.Config; using Examine.LuceneEngine.Providers; using Lucene.Net.Analysis; using Lucene.Net.Analysis.Standard; +using Lucene.Net.Index; using Lucene.Net.Store; using Moq; using Umbraco.Core; @@ -33,7 +34,7 @@ namespace Umbraco.Tests.UmbracoExamine internal static class IndexInitializer { public static UmbracoContentIndexer GetUmbracoIndexer( - Directory luceneDir, + IndexWriter writer, Analyzer analyzer = null, IDataService dataService = null, IContentService contentService = null, @@ -178,15 +179,14 @@ namespace Umbraco.Tests.UmbracoExamine var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies); var i = new UmbracoContentIndexer(indexCriteria, - luceneDir, //custom lucene directory - dataService, - contentService, - mediaService, - dataTypeService, - userService, - contentTypeService, - analyzer, - false) + writer, + dataService, + contentService, + mediaService, + dataTypeService, + userService, + contentTypeService, + false) { SupportUnpublishedContent = supportUnpublishedContent }; @@ -197,13 +197,14 @@ namespace Umbraco.Tests.UmbracoExamine return i; } - public static UmbracoExamineSearcher GetUmbracoSearcher(Directory luceneDir, Analyzer analyzer = null) + + public static UmbracoExamineSearcher GetUmbracoSearcher(IndexWriter writer, Analyzer analyzer = null) { if (analyzer == null) { analyzer = new StandardAnalyzer(Version.LUCENE_29); } - return new UmbracoExamineSearcher(luceneDir, analyzer); + return new UmbracoExamineSearcher(writer, analyzer); } public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 5f3f5525c1..3f324bb2ed 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -1,11 +1,11 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Examine; using Examine.LuceneEngine; using Examine.LuceneEngine.Providers; using Examine.LuceneEngine.SearchCriteria; using Examine.SearchCriteria; +using Lucene.Net.Analysis.Standard; using Lucene.Net.Index; using Lucene.Net.Search; using Lucene.Net.Store; @@ -15,7 +15,6 @@ using UmbracoExamine; namespace Umbraco.Tests.UmbracoExamine { - /// /// Tests the standard indexing capabilities /// @@ -30,9 +29,10 @@ namespace Umbraco.Tests.UmbracoExamine public void Index_Protected_Content_Not_Indexed() { - using (var luceneDir = new RAMDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir)) - using (var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir)) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { indexer.RebuildIndex(); @@ -59,9 +59,10 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID() { - using (var luceneDir = new RAMDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir)) - using (var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir)) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { indexer.RebuildIndex(); @@ -77,7 +78,7 @@ namespace Umbraco.Tests.UmbracoExamine //ensure that node 2112 doesn't exist var results = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile()); - Assert.AreEqual(0, results.Count()); + Assert.AreEqual(0, results.TotalItemCount); //get a node from the data repo (this one exists underneath 2222) var node = mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]") @@ -103,19 +104,19 @@ namespace Umbraco.Tests.UmbracoExamine //now ensure it's deleted var newResults = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile()); - Assert.AreEqual(1, newResults.Count()); + Assert.AreEqual(1, newResults.TotalItemCount); } } [Test] - [Ignore] public void Index_Move_Media_To_Non_Indexable_ParentID() { - using (var luceneDir = new RAMDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir)) - using (var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir)) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { indexer.RebuildIndex(); @@ -152,7 +153,7 @@ namespace Umbraco.Tests.UmbracoExamine //now ensure it's deleted var results = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile()); - Assert.AreEqual(0, results.Count()); + Assert.AreEqual(0, results.TotalItemCount); } } @@ -164,9 +165,10 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Index_Reindex_Content() { - using (var luceneDir = new RAMDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir, supportUnpublishedContent:true)) - using (var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir)) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer, supportUnpublishedContent: true)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { indexer.RebuildIndex(); @@ -175,10 +177,9 @@ namespace Umbraco.Tests.UmbracoExamine //first delete all 'Content' (not media). This is done by directly manipulating the index with the Lucene API, not examine! var contentTerm = new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content); - var writer = indexer.GetIndexWriter(); writer.DeleteDocuments(contentTerm); writer.Commit(); - + //make sure the content is gone. This is done with lucene APIs, not examine! var collector = new AllHitsCollector(false, true); var query = new TermQuery(contentTerm); @@ -205,13 +206,13 @@ namespace Umbraco.Tests.UmbracoExamine /// This will delete an item from the index and ensure that all children of the node are deleted too! /// [Test] - [Ignore] public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed() { - using (var luceneDir = new RAMDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir)) - using (var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir)) + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) { indexer.RebuildIndex(); @@ -221,10 +222,10 @@ namespace Umbraco.Tests.UmbracoExamine //this node had children: 1141 & 1142, let's ensure they are also removed var results = searcher.Search(searcher.CreateSearchCriteria().Id(1141).Compile()); - Assert.AreEqual(0, results.Count()); + Assert.AreEqual(0, results.TotalItemCount); results = searcher.Search(searcher.CreateSearchCriteria().Id(1142).Compile()); - Assert.AreEqual(0, results.Count()); + Assert.AreEqual(0, results.TotalItemCount); } } diff --git a/src/Umbraco.Tests/UmbracoExamine/RandomIdRAMDirectory.cs b/src/Umbraco.Tests/UmbracoExamine/RandomIdRAMDirectory.cs new file mode 100644 index 0000000000..34b69274c8 --- /dev/null +++ b/src/Umbraco.Tests/UmbracoExamine/RandomIdRAMDirectory.cs @@ -0,0 +1,14 @@ +using System; +using Lucene.Net.Store; + +namespace Umbraco.Tests.UmbracoExamine +{ + public class RandomIdRAMDirectory : RAMDirectory + { + private readonly string _lockId = Guid.NewGuid().ToString(); + public override string GetLockID() + { + return _lockId; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index 7eb92ad49d..f739286f98 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -8,6 +8,8 @@ using Examine.LuceneEngine.Providers; using Lucene.Net.Store; using NUnit.Framework; using Examine.LuceneEngine.SearchCriteria; +using Lucene.Net.Analysis.Standard; +using Lucene.Net.Index; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.UmbracoExamine @@ -20,16 +22,17 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Test_Sort_Order_Sorting() { - using (var luceneDir = new RAMDirectory()) - { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir, null, + using (var luceneDir = new RandomIdRAMDirectory()) + using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED)) + using (var indexer = IndexInitializer.GetUmbracoIndexer(writer, null, new TestDataService() - { - ContentService = new TestContentService(TestFiles.umbraco_sort) - }, - supportUnpublishedContent:true); - indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + { + ContentService = new TestContentService(TestFiles.umbraco_sort) + }, + supportUnpublishedContent: true)) + using (var searcher = IndexInitializer.GetUmbracoSearcher(writer)) + { + indexer.RebuildIndex(); var s = (LuceneSearcher)searcher; var luceneSearcher = s.GetSearcher(); diff --git a/src/UmbracoExamine/BaseUmbracoIndexer.cs b/src/UmbracoExamine/BaseUmbracoIndexer.cs index 9ff68b8685..ed9271c1c0 100644 --- a/src/UmbracoExamine/BaseUmbracoIndexer.cs +++ b/src/UmbracoExamine/BaseUmbracoIndexer.cs @@ -49,6 +49,7 @@ namespace UmbracoExamine /// /// /// + /// protected BaseUmbracoIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async) : base(indexerData, indexPath, analyzer, async) { @@ -61,6 +62,19 @@ namespace UmbracoExamine DataService = dataService; } + /// + /// Creates an NRT indexer + /// + /// + /// + /// + /// + protected BaseUmbracoIndexer(IIndexCriteria indexerData, IndexWriter writer, IDataService dataService, bool async) + : base(indexerData, writer, async) + { + DataService = dataService; + } + #endregion /// diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index c36ad37adb..f7e4c45f50 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -17,6 +17,7 @@ using Examine.LuceneEngine; using Examine.LuceneEngine.Config; using UmbracoExamine.Config; using Lucene.Net.Analysis; +using Lucene.Net.Index; using Umbraco.Core.Persistence.Querying; using IContentService = Umbraco.Core.Services.IContentService; using IMediaService = Umbraco.Core.Services.IMediaService; @@ -146,6 +147,34 @@ namespace UmbracoExamine _contentTypeService = contentTypeService; } + /// + /// Creates an NRT indexer + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public UmbracoContentIndexer(IIndexCriteria indexerData, IndexWriter writer, IDataService dataService, + IContentService contentService, + IMediaService mediaService, + IDataTypeService dataTypeService, + IUserService userService, + IContentTypeService contentTypeService, + bool async) + : base(indexerData, writer, dataService, async) + { + _contentService = contentService; + _mediaService = mediaService; + _dataTypeService = dataTypeService; + _userService = userService; + _contentTypeService = contentTypeService; + } + #endregion #region Constants & Fields diff --git a/src/UmbracoExamine/UmbracoExamineSearcher.cs b/src/UmbracoExamine/UmbracoExamineSearcher.cs index 7a2a80c3fe..26b02c904e 100644 --- a/src/UmbracoExamine/UmbracoExamineSearcher.cs +++ b/src/UmbracoExamine/UmbracoExamineSearcher.cs @@ -51,12 +51,41 @@ namespace UmbracoExamine /// public override string Name { - get - { - return _name; - } + get { return _name; } + } + + /// + /// Constructor to allow for creating an indexer at runtime + /// + /// + /// + + public UmbracoExamineSearcher(DirectoryInfo indexPath, Analyzer analyzer) + : base(indexPath, analyzer) + { } + /// + /// Constructor to allow for creating an indexer at runtime + /// + /// + /// + public UmbracoExamineSearcher(Lucene.Net.Store.Directory luceneDirectory, Analyzer analyzer) + : base(luceneDirectory, analyzer) + { + } + + /// + /// Creates an NRT searcher + /// + /// + /// + public UmbracoExamineSearcher(IndexWriter writer, Analyzer analyzer) + : base(writer, analyzer) + { + } + + #endregion public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { @@ -115,30 +144,6 @@ namespace UmbracoExamine } } - /// - /// Constructor to allow for creating an indexer at runtime - /// - /// - /// - - public UmbracoExamineSearcher(DirectoryInfo indexPath, Analyzer analyzer) - : base(indexPath, analyzer) - { - } - - /// - /// Constructor to allow for creating an indexer at runtime - /// - /// - /// - - public UmbracoExamineSearcher(Lucene.Net.Store.Directory luceneDirectory, Analyzer analyzer) - : base(luceneDirectory, analyzer) - { - } - - #endregion - /// /// Used for unit tests /// From 86ffd66c8a0ff983331b3716a3f020fa218cc6c9 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 19 Jan 2017 09:50:18 +0100 Subject: [PATCH 63/88] update danish translations --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 32b1ef780e..b7d178ef41 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -291,7 +291,7 @@ Vælg medlem Vælg medlemsgruppe Der er ingen parametre for denne makro - Der er ikke tilføjet nogle makroer + Der er ikke tilføjet nogen makroer Link dit Fjern link fra dit konto @@ -1007,39 +1007,38 @@ Mange hilsner fra Umbraco robotten - Rediger skabelong + Rediger skabelon Sektioner Indsæt indholdsområde - Indsæt indholdsområde placeholder + Indsæt pladsholder for indholdsområde Indsæt Hvad vil du indsætte ? Oversættelse - Indsætter en oversætbar tekst som skifter efter det sprog som websitet vises i. + Indsætter en oversætbar tekst, som skifter efter det sprog, som websitet vises i. Makro - En makro er et element som kan have forskellige indstillinger når det indsættes. - Brug det som en genbrugelig del af dit design såsom gallerier, formularer - og lister. + En makro er et element, som kan have forskellige indstillinger, når det indsættes. + Brug det som en genbrugelig del af dit design såsom gallerier, formularer og lister. - Side værdi + Sideværdi - Viser værdien af et felt fra den nuværende side. Kan indstilles til at bruge rekursive værdier eller - vise en standard værdi i tilfælde af at feltet er tomt. + Viser værdien af et felt fra den nuværende side. Kan indstilles til at bruge rekursive værdier eller + vise en standardværdi i tilfælde af, at feltet er tomt. Partial view - Et Partial View er et skabelon element som kan indsættes i andre skabeloner og derved - genbruges og deles på tværs af side-skabelonerne. + Et Partial View er et skabelonelement, som kan indsættes i andre skabeloner og derved + genbruges og deles på tværs af sideskabelonerne. Master skabelon - Ingen master skabelon + Ingen masterskabelon Ingen master Indsæt en underliggende skabelon @@ -1069,11 +1068,11 @@ Mange hilsner fra Umbraco robotten ]]> - Sektion navn - Sektionen er oblikatorisk + Sektionsnavn + Sektionen er obligatorisk - Hvis oblikatorisk, skal under-skabelonen indeholde en @section definition. + Hvis obligatorisk, skal underskabelonen indeholde en @section -definition. From a57abce96ef8bd4253683900805a769746e64146 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 19 Jan 2017 11:30:14 +0100 Subject: [PATCH 64/88] Manually applying https://github.com/umbraco/Umbraco-CMS/pull/1688 --- .../views/propertyeditors/googlemaps/googlemaps.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/googlemaps/googlemaps.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/googlemaps/googlemaps.controller.js index f8e89d7200..1d6261c10a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/googlemaps/googlemaps.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/googlemaps/googlemaps.controller.js @@ -2,7 +2,7 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.GoogleMapsController", function ($element, $rootScope, $scope, notificationsService, dialogService, assetsService, $log, $timeout) { - assetsService.loadJs('http://www.google.com/jsapi') + assetsService.loadJs('https://www.google.com/jsapi') .then(function () { google.load("maps", "3", { From 4119380af517e886d34d9f3aeb37b1573ec62e0c Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 19 Jan 2017 11:39:09 +0100 Subject: [PATCH 65/88] U4-9414 EntityContainer repository PerformGetAll query returns nothing --- .../Repositories/EntityContainerRepository.cs | 24 +++++++++---- .../Repositories/ContentTypeRepositoryTest.cs | 32 +++++++++++++++++ .../Services/ContentTypeServiceTests.cs | 36 +++++++++++++++++++ 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index cc13275798..b76abf4db6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -62,17 +62,27 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformGetAll(params int[] ids) { - //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit - return ids.InGroupsOf(2000).SelectMany(@group => + if (ids.Any()) + { + //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit + return ids.InGroupsOf(2000).SelectMany(@group => + { + var sql = GetBaseQuery(false) + .Where("nodeObjectType=@umbracoObjectTypeId", new {umbracoObjectTypeId = NodeObjectTypeId}) + .Where(string.Format("{0} IN (@ids)", SqlSyntax.GetQuotedColumnName("id")), new {ids = @group}); + + sql.OrderBy(x => x.Level, SqlSyntax); + + return Database.Fetch(sql).Select(CreateEntity); + }); + } + else { var sql = GetBaseQuery(false) - .Where("nodeObjectType=@umbracoObjectTypeId", new { umbracoObjectTypeId = NodeObjectTypeId }) - .Where(string.Format("{0} IN (@ids)", SqlSyntax.GetQuotedColumnName("id")), new { ids = @group }); - + .Where("nodeObjectType=@umbracoObjectTypeId", new {umbracoObjectTypeId = NodeObjectTypeId}); sql.OrderBy(x => x.Level, SqlSyntax); - return Database.Fetch(sql).Select(CreateEntity); - }); + } } protected override IEnumerable PerformGetByQuery(IQuery query) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 84cdef73e1..699e563407 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -172,6 +172,38 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Get_All_Containers() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container1, container2, container3; + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) + { + container1 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container1" }; + containerRepository.AddOrUpdate(container1); + container2 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container2" }; + containerRepository.AddOrUpdate(container2); + container3 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container3" }; + containerRepository.AddOrUpdate(container3); + unitOfWork.Commit(); + Assert.That(container1.Id, Is.GreaterThan(0)); + Assert.That(container2.Id, Is.GreaterThan(0)); + Assert.That(container3.Id, Is.GreaterThan(0)); + } + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) + { + var found1 = containerRepository.Get(container1.Id); + Assert.IsNotNull(found1); + var found2 = containerRepository.Get(container2.Id); + Assert.IsNotNull(found2); + var found3 = containerRepository.Get(container3.Id); + Assert.IsNotNull(found3); + var allContainers = containerRepository.GetAll(); + Assert.AreEqual(3, allContainers.Count()); + } + } + [Test] public void Can_Delete_Container() { diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 3fd0e1f6cd..78f4a5dad9 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -304,6 +304,42 @@ namespace Umbraco.Tests.Services Assert.IsNull(deletedContentType); } + [Test] + public void Can_Create_Container() + { + // Arrange + var cts = ServiceContext.ContentTypeService; + + // Act + var container = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid); + container.Name = "container1"; + cts.SaveContentTypeContainer(container); + + // Assert + var createdContainer = cts.GetContentTypeContainer(container.Id); + Assert.IsNotNull(createdContainer); + } + + [Test] + public void Can_Get_All_Containers() + { + // Arrange + var cts = ServiceContext.ContentTypeService; + + // Act + var container1 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid); + container1.Name = "container1"; + cts.SaveContentTypeContainer(container1); + + var container2 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid); + container2.Name = "container2"; + cts.SaveContentTypeContainer(container2); + + // Assert + var containers = cts.GetContentTypeContainers(new int[0]); + Assert.AreEqual(2, containers.Count()); + } + [Test] public void Deleting_ContentType_Sends_Correct_Number_Of_DeletedEntities_In_Events() { From eefd13547c82f63d90479cab4f296f6f3b8f697e Mon Sep 17 00:00:00 2001 From: Per Ploug Date: Thu, 19 Jan 2017 12:53:39 +0100 Subject: [PATCH 66/88] More localization of querybuilder --- .../querybuilder/querybuilder.controller.js | 15 ++++++++++++--- .../overlays/querybuilder/querybuilder.html | 4 ++-- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 5 ++++- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 8 +++++--- .../Editors/TemplateQueryController.cs | 6 ++++-- 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js index 3939882969..8f28bd3674 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js @@ -1,7 +1,10 @@ (function () { "use strict"; - function QueryBuilderOverlayController($scope, templateQueryResource) { + function QueryBuilderOverlayController($scope, templateQueryResource, localizationService) { + + var everything = localizationService.localize("template_allContent"); + var myWebsite = localizationService.localize("template_myWebsite"); var vm = this; @@ -17,10 +20,10 @@ vm.query = { contentType: { - name: "Everything" + name: everything }, source: { - name: "My website" + name: myWebsite }, filters: [ { @@ -113,6 +116,12 @@ function trashFilter(query) { query.filters.splice(query, 1); + + //if we remove the last one, add a new one to generate ui for it. + if (query.filters.length == 0) { + query.filters.push({}); + } + } function changeSortOrder(query) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html index 678733f1c8..542b7a3c6f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html @@ -5,7 +5,7 @@
    - I Want + I want
    @@ -91,7 +91,7 @@ - + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index b7d178ef41..6e4905cc7a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1080,8 +1080,11 @@ Mange hilsner fra Umbraco robotten sider returneret, på Returner - alt + alt indhold + indhold af typen "%0%" + fra + mit website filtre og diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 7243a34490..24391288bd 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1096,15 +1096,17 @@ To manage your website, simply open the Umbraco back office and start adding con Query builder items returned, in - I want - everything + I want + all content + content of type "%0%" from + my website where and - Template + diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index e3c63dd22e..8693350c74 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -9,6 +9,7 @@ using System; using System.Diagnostics; using Umbraco.Web.Dynamics; using Umbraco.Web.Models.TemplateQuery; +using Umbraco.Core.Services; namespace Umbraco.Web.Editors { @@ -297,9 +298,10 @@ namespace Umbraco.Web.Editors { var contentTypes = ApplicationContext.Services.ContentTypeService.GetAllContentTypes() - .Select(x => new ContentTypeModel() { Alias = x.Alias, Name = x.Name }) + .Select(x => new ContentTypeModel() { Alias = x.Alias, Name = Services.TextService.Localize("template/contentOfType", tokens: new string[] { x.Name } ) }) .OrderBy(x => x.Name).ToList(); - contentTypes.Insert(0, new ContentTypeModel() { Alias = string.Empty, Name = "Everything" }); + + contentTypes.Insert(0, new ContentTypeModel() { Alias = string.Empty, Name = Services.TextService.Localize("template/allContent") }); return contentTypes; } From b8734e5eead43872be0884edeb7e9c2e33cb3e7c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 19 Jan 2017 15:11:37 +0100 Subject: [PATCH 67/88] fixes: U4-9419 The new content picker adds picked items in a random order --- .../contentpicker/contentpicker.controller.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index ba29b92477..947603794a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -259,18 +259,24 @@ function contentPickerController($scope, entityResource, editorState, iconHelper }); function setEntityUrl(entity) { + // get url for content and media items if(entityType !== "Member") { entityResource.getUrl(entity.id, entityType).then(function(data){ - // update url - entity.url = data; - - // push item to render model - addSelectedItem(entity); + // update url + angular.forEach($scope.renderModel, function(item){ + if(item.id === entity.id) { + item.url = data; + } + }); }); - } else { - addSelectedItem(entity); } + + // add the selected item to the renderModel + // if it needs to show a url the item will get + // updated when the url comes back from server + addSelectedItem(entity); + } function addSelectedItem(item) { From 85f054e3c1a70bc76279d8cbec9d6bc1dfc91ef0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 20 Jan 2017 09:54:59 +0100 Subject: [PATCH 68/88] No need to warn if attribute is not present Adds some logging about indexing being done.. the number of committed items seems to differ a little from the number of items in the index, not sure wh --- .../WebServices/ExamineManagementApiController.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs index 9c50468465..79e7fe8f53 100644 --- a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs +++ b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs @@ -166,6 +166,8 @@ namespace Umbraco.Web.WebServices var msg = ValidateLuceneIndexer(indexerName, out indexer); if (msg.IsSuccessStatusCode) { + LogHelper.Info(string.Format("Rebuilding index '{0}'", indexerName)); + //remove it in case there's a handler there alraedy indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; //now add a single handler @@ -201,6 +203,8 @@ namespace Umbraco.Web.WebServices //ensure it's not listening anymore indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; + LogHelper.Info(string.Format("Rebuilding index '{0}' done, {1} items committed (can differ from the number of items in the index)", indexer.Name, indexer.CommitCount)); + var cacheKey = "temp_indexing_op_" + indexer.Name; ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(cacheKey); } @@ -266,7 +270,10 @@ namespace Umbraco.Web.WebServices var val = p.GetValue(indexer, null); if (val == null) { - LogHelper.Warn("Property value was null when setting up property on indexer: " + indexer.Name + " property: " + p.Name); + // Do not warn for new new attribute that is optional + if(string.Equals(p.Name, "DirectoryFactory", StringComparison.InvariantCultureIgnoreCase) == false) + LogHelper.Warn("Property value was null when setting up property on indexer: " + indexer.Name + " property: " + p.Name); + val = string.Empty; } indexerModel.ProviderProperties.Add(p.Name, val.ToString()); From 12922f841361c6884d266401d0cb548a89d84504 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 20 Jan 2017 09:54:59 +0100 Subject: [PATCH 69/88] No need to warn if attribute is not present Adds some logging about indexing being done.. the number of committed items seems to differ a little from the number of items in the index, not sure wh --- .../WebServices/ExamineManagementApiController.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs index 9c50468465..79e7fe8f53 100644 --- a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs +++ b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs @@ -166,6 +166,8 @@ namespace Umbraco.Web.WebServices var msg = ValidateLuceneIndexer(indexerName, out indexer); if (msg.IsSuccessStatusCode) { + LogHelper.Info(string.Format("Rebuilding index '{0}'", indexerName)); + //remove it in case there's a handler there alraedy indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; //now add a single handler @@ -201,6 +203,8 @@ namespace Umbraco.Web.WebServices //ensure it's not listening anymore indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; + LogHelper.Info(string.Format("Rebuilding index '{0}' done, {1} items committed (can differ from the number of items in the index)", indexer.Name, indexer.CommitCount)); + var cacheKey = "temp_indexing_op_" + indexer.Name; ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(cacheKey); } @@ -266,7 +270,10 @@ namespace Umbraco.Web.WebServices var val = p.GetValue(indexer, null); if (val == null) { - LogHelper.Warn("Property value was null when setting up property on indexer: " + indexer.Name + " property: " + p.Name); + // Do not warn for new new attribute that is optional + if(string.Equals(p.Name, "DirectoryFactory", StringComparison.InvariantCultureIgnoreCase) == false) + LogHelper.Warn("Property value was null when setting up property on indexer: " + indexer.Name + " property: " + p.Name); + val = string.Empty; } indexerModel.ProviderProperties.Add(p.Name, val.ToString()); From 2a4e73c65021cf9c22e0b68001030d559ac159cd Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 23 Jan 2017 00:40:24 +1100 Subject: [PATCH 70/88] Reduced allocations for the Media/Content/Property Factories since we don't need to create new objects every time we want to map values. Changes default sort order for paging from Path to umbracoNode.id since we have no index on Path and it doesn't make a lot of sense. Fixes obsolete warnings for various Sql usages. Reduces the amount of DeepClone calls required when looking up a content type for a content item, even though these are cached they are still deep cloned out of the cache. Fixes the main issue of having nearly 100,000 rows of unsorted property data and then having to query those rows for every document being built, the re-iteration of these rows causes a lot of overhead and is unecessary, instead we ensure the property data set and the document data set is sorted by node id, then use a stored index to continue looking up the property data for the next content item found. --- .../Persistence/Factories/ContentFactory.cs | 17 +- .../Persistence/Factories/MediaFactory.cs | 13 +- .../Persistence/Factories/PropertyFactory.cs | 16 +- .../Repositories/ContentRepository.cs | 52 +++--- .../Repositories/MediaRepository.cs | 60 +++---- .../Repositories/VersionableRepositoryBase.cs | 150 +++++++++++------- src/Umbraco.Core/Services/ContentService.cs | 4 +- src/Umbraco.Core/Services/IContentService.cs | 4 +- src/Umbraco.Core/Services/IMediaService.cs | 4 +- src/Umbraco.Core/Services/MediaService.cs | 4 +- 10 files changed, 199 insertions(+), 125 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs index 5dcec8fed0..88de5a1d6c 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -27,15 +27,15 @@ namespace Umbraco.Core.Persistence.Factories #region Implementation of IEntityFactory - public IContent BuildEntity(DocumentDto dto) + public static IContent BuildEntity(DocumentDto dto, IContentType contentType) { - var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType); + var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, contentType); try { content.DisableChangeTracking(); - content.Id = _id; + content.Id = dto.NodeId; content.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId; content.Name = dto.Text; content.NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text; @@ -49,8 +49,8 @@ namespace Umbraco.Core.Persistence.Factories content.Published = dto.Published; content.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate; content.UpdateDate = dto.ContentVersionDto.VersionDate; - content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?) null; - content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?) null; + content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null; + content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null; content.Version = dto.ContentVersionDto.VersionId; content.PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished; content.PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId; @@ -64,6 +64,13 @@ namespace Umbraco.Core.Persistence.Factories { content.EnableChangeTracking(); } + + } + + [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")] + public IContent BuildEntity(DocumentDto dto) + { + return BuildEntity(dto, _contentType); } public DocumentDto BuildDto(IContent entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs index 0fcb654cb7..5729bb125e 100644 --- a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs @@ -27,15 +27,15 @@ namespace Umbraco.Core.Persistence.Factories #region Implementation of IEntityFactory - public IMedia BuildEntity(ContentVersionDto dto) + public static IMedia BuildEntity(ContentVersionDto dto, IMediaType contentType) { - var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, _contentType); + var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, contentType); try { media.DisableChangeTracking(); - media.Id = _id; + media.Id = dto.NodeId; media.Key = dto.ContentDto.NodeDto.UniqueId; media.Path = dto.ContentDto.NodeDto.Path; media.CreatorId = dto.ContentDto.NodeDto.UserId.Value; @@ -55,6 +55,13 @@ namespace Umbraco.Core.Persistence.Factories { media.EnableChangeTracking(); } + + } + + [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")] + public IMedia BuildEntity(ContentVersionDto dto) + { + return BuildEntity(dto, _contentType); } public ContentVersionDto BuildDto(IMedia entity) diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index 446bd426ad..f202d8c321 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -30,11 +30,11 @@ namespace Umbraco.Core.Persistence.Factories _updateDate = updateDate; } - public IEnumerable BuildEntity(PropertyDataDto[] dtos) + public static IEnumerable BuildEntity(IReadOnlyCollection dtos, PropertyType[] compositionTypeProperties, DateTime createDate, DateTime updateDate) { var properties = new List(); - foreach (var propertyType in _compositionTypeProperties) + foreach (var propertyType in compositionTypeProperties) { var propertyDataDto = dtos.LastOrDefault(x => x.PropertyTypeId == propertyType.Id); var property = propertyDataDto == null @@ -47,8 +47,8 @@ namespace Umbraco.Core.Persistence.Factories //on initial construction we don't want to have dirty properties tracked property.DisableChangeTracking(); - property.CreateDate = _createDate; - property.UpdateDate = _updateDate; + property.CreateDate = createDate; + property.UpdateDate = updateDate; // http://issues.umbraco.org/issue/U4-1946 property.ResetDirtyProperties(false); properties.Add(property); @@ -57,12 +57,18 @@ namespace Umbraco.Core.Persistence.Factories { property.EnableChangeTracking(); } - + } return properties; } + [Obsolete("Use the static method instead, there's no reason to allocate one of these classes everytime we want to map values")] + public IEnumerable BuildEntity(PropertyDataDto[] dtos) + { + return BuildEntity(dtos, _compositionTypeProperties, _createDate, _updateDate); + } + public IEnumerable BuildDto(IEnumerable properties) { var propertyDataDtos = new List(); diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 294f869c3f..b25db8afb2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -55,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) - .Where(x => x.Newest) + .Where(x => x.Newest, SqlSyntax) .OrderByDescending(x => x.VersionDate, SqlSyntax); var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); @@ -77,7 +77,7 @@ namespace Umbraco.Core.Persistence.Repositories } //we only want the newest ones with this method - sql.Where(x => x.Newest); + sql.Where(x => x.Newest, SqlSyntax); return ProcessQuery(sql); } @@ -87,9 +87,9 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate) - .OrderBy(x => x.SortOrder); + .Where(x => x.Newest, SqlSyntax) + .OrderByDescending(x => x.VersionDate, SqlSyntax) + .OrderBy(x => x.SortOrder, SqlSyntax); return ProcessQuery(sql); } @@ -183,8 +183,8 @@ namespace Umbraco.Core.Persistence.Repositories query = query .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); query = query - .Where(x => x.NodeId > baseId && x.Trashed == false) - .Where(x => x.Published) + .Where(x => x.NodeId > baseId && x.Trashed == false, SqlSyntax) + .Where(x => x.Published, SqlSyntax) .OrderBy(x => x.NodeId, SqlSyntax); var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) @@ -222,7 +222,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); var dto = Database.Fetch(sql).FirstOrDefault(); @@ -238,10 +238,10 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql() .Select("*") - .From() - .InnerJoin().On(left => left.VersionId, right => right.VersionId) - .Where(x => x.VersionId == versionId) - .Where(x => x.Newest != true); + .From(SqlSyntax) + .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.VersionId, right => right.VersionId) + .Where(x => x.VersionId == versionId, SqlSyntax) + .Where(x => x.Newest != true, SqlSyntax); var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return; @@ -848,7 +848,12 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", var content = new IContent[dtos.Count]; var defs = new List(); var templateIds = new List(); - + + //track the looked up content types, even though the content types are cached + // they still need to be deep cloned out of the cache and we don't want to add + // the overhead of deep cloning them on every item in this loop + var contentTypes = new Dictionary(); + for (var i = 0; i < dtos.Count; i++) { var dto = dtos[i]; @@ -867,9 +872,19 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", // else, need to fetch from the database // content type repository is full-cache so OK to get each one independently - var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - content[i] = factory.BuildEntity(dto); + + IContentType contentType; + if (contentTypes.ContainsKey(dto.ContentVersionDto.ContentDto.ContentTypeId)) + { + contentType = contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId]; + } + else + { + contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType; + } + + content[i] = ContentFactory.BuildEntity(dto, contentType); // need template if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) @@ -910,7 +925,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 - ((Entity) cc).ResetDirtyProperties(false); + cc.ResetDirtyProperties(false); } return content; @@ -927,8 +942,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", { var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - var content = factory.BuildEntity(dto); + var content = ContentFactory.BuildEntity(dto, contentType); //Check if template id is set on DocumentDto, and get ITemplate if it is. if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 2687848fa5..bf034bd8ff 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -76,7 +76,7 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .OrderBy(x => x.SortOrder); + .OrderBy(x => x.SortOrder, SqlSyntax); return ProcessQuery(sql); } @@ -89,12 +89,12 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql(); sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId, SqlSyntax) + .Where(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax); return sql; } @@ -148,6 +148,11 @@ namespace Umbraco.Core.Persistence.Repositories var content = new IMedia[dtos.Count]; var defs = new List(); + //track the looked up content types, even though the content types are cached + // they still need to be deep cloned out of the cache and we don't want to add + // the overhead of deep cloning them on every item in this loop + var contentTypes = new Dictionary(); + for (var i = 0; i < dtos.Count; i++) { var dto = dtos[i]; @@ -165,9 +170,19 @@ namespace Umbraco.Core.Persistence.Repositories // else, need to fetch from the database // content type repository is full-cache so OK to get each one independently - var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); - var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); - content[i] = factory.BuildEntity(dto); + + IMediaType contentType; + if (contentTypes.ContainsKey(dto.ContentDto.ContentTypeId)) + { + contentType = contentTypes[dto.ContentDto.ContentTypeId]; + } + else + { + contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + contentTypes[dto.ContentDto.ContentTypeId] = contentType; + } + + content[i] = MediaFactory.BuildEntity(dto, contentType); // need properties defs.Add(new DocumentDefinition( @@ -195,7 +210,7 @@ namespace Umbraco.Core.Persistence.Repositories //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 - ((Entity) cc).ResetDirtyProperties(false); + cc.ResetDirtyProperties(false); } return content; @@ -205,26 +220,16 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; - var mediaType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + var content = CreateMediaFromDto(dto, versionId, sql); - var factory = new MediaFactory(mediaType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) }); - - media.Properties = properties[dto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - return media; + return content; } public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) @@ -245,7 +250,7 @@ namespace Umbraco.Core.Persistence.Repositories query = query .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); query = query - .Where(x => x.NodeId > baseId) + .Where(x => x.NodeId > baseId, SqlSyntax) .OrderBy(x => x.NodeId, SqlSyntax); var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) @@ -512,9 +517,8 @@ namespace Umbraco.Core.Persistence.Repositories private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId, Sql docSql) { var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); - - var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); + + var media = MediaFactory.BuildEntity(dto, contentType); var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType); diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 19f4b8fdfb..16bad74612 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Text; @@ -405,11 +406,15 @@ namespace Umbraco.Core.Persistence.Repositories } } - //no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique. - // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column - // is empty for many nodes) - // see: http://issues.umbraco.org/issue/U4-8831 - sortedSql.OrderBy("umbracoNode.id"); + if (orderBySystemField && orderBy != "umbracoNode.id") + { + //no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique. + // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column + // is empty for many nodes) + // see: http://issues.umbraco.org/issue/U4-8831 + sortedSql.OrderBy("umbracoNode.id"); + } + return sortedSql; @@ -511,9 +516,9 @@ namespace Umbraco.Core.Persistence.Repositories protected IDictionary GetPropertyCollection( Sql docSql, - IEnumerable documentDefs) + IReadOnlyCollection documentDefs) { - if (documentDefs.Any() == false) return new Dictionary(); + if (documentDefs.Count == 0) return new Dictionary(); //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use // the statement to go get the property data for all of the items by using an inner join @@ -524,6 +529,15 @@ namespace Umbraco.Core.Persistence.Repositories parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); } + //It's Important with the sort order here! We require this to be sorted by node id, + // this is required because this data set can be huge depending on the page size. Due + // to it's size we need to be smart about iterating over the property values to build + // the document. Before we used to use Linq to get the property data for a given content node + // and perform a Distinct() call. This kills performance because that would mean if we had 7000 nodes + // and on each iteration we will perform a lookup on potentially 100,000 property rows against the node + // id which turns out to be a crazy amount of iterations. Instead we know it's sorted by this value we'll + // keep an index stored of the rows being read so we never have to re-iterate the entire data set + // on each document iteration. var propSql = new Sql(@"SELECT cmsPropertyData.* FROM cmsPropertyData INNER JOIN cmsPropertyType @@ -531,8 +545,8 @@ ON cmsPropertyData.propertytypeid = cmsPropertyType.id INNER JOIN (" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId -LEFT OUTER JOIN cmsDataTypePreValues -ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments); +ORDER BY contentNodeId, propertytypeid +", docSql.Arguments); var allPropertyData = Database.Fetch(propSql); @@ -556,59 +570,81 @@ WHERE EXISTS( }); var result = new Dictionary(); - var propertiesWithTagSupport = new Dictionary(); + //used to track the resolved composition property types per content type so we don't have to re-resolve (ToArray) the list every time + var resolvedCompositionProperties = new Dictionary(); + var propertyDataSetIndex = 0; - //iterate each definition grouped by it's content type - this will mean less property type iterations while building - // up the property collections - foreach (var compositionGroup in documentDefs.GroupBy(x => x.Composition)) + //This must be sorted by node id because this is how we are sorting the query to lookup property types above, + // which allows us to more efficiently iterate over the large data set of property values + foreach (var def in documentDefs.OrderBy(x => x.Id)) { - var compositionProperties = compositionGroup.Key.CompositionPropertyTypes.ToArray(); - - foreach (var def in compositionGroup) + //get the resolved proeprties from our local cache, or resolve them and put them in cache + PropertyType[] compositionProperties; + if (resolvedCompositionProperties.ContainsKey(def.Composition.Id)) { - var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct(); - - var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate); - var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); - - foreach (var property in properties) - { - //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck - var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); - - var tagSupport = propertiesWithTagSupport.ContainsKey(property.PropertyType.PropertyEditorAlias) - ? propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] - : TagExtractor.GetAttribute(editor); - - if (tagSupport != null) - { - //add to local cache so we don't need to reflect next time for this property editor alias - propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport; - - //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up - var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId) - .Distinct() - .ToArray(); - - var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); - - var preVals = new PreValueCollection(asDictionary); - - var contentPropData = new ContentPropertyData(property.Value, - preVals, - new Dictionary()); - - TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); - } - } - - if (result.ContainsKey(def.Id)) - { - Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); - } - result[def.Id] = new PropertyCollection(properties); + compositionProperties = resolvedCompositionProperties[def.Composition.Id]; } + else + { + compositionProperties = def.Composition.CompositionPropertyTypes.ToArray(); + resolvedCompositionProperties[def.Composition.Id] = compositionProperties; + } + + var propertyDataDtos = new List(); + + for (var i = propertyDataSetIndex; i < allPropertyData.Count; i++) + { + if (allPropertyData[i].NodeId == def.Id) + { + propertyDataDtos.Add(allPropertyData[i]); + } + else + { + //the node id has changed so we need to exit the loop and store the index + propertyDataSetIndex = i; + break; + } + } + + var properties = PropertyFactory.BuildEntity(propertyDataDtos, compositionProperties, def.CreateDate, def.VersionDate).ToArray(); + + foreach (var property in properties) + { + //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck + var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + + var tagSupport = propertiesWithTagSupport.ContainsKey(property.PropertyType.PropertyEditorAlias) + ? propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] + : TagExtractor.GetAttribute(editor); + + if (tagSupport != null) + { + //add to local cache so we don't need to reflect next time for this property editor alias + propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport; + + //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up + var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId) + .Distinct() + .ToArray(); + + var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); + + var preVals = new PreValueCollection(asDictionary); + + var contentPropData = new ContentPropertyData(property.Value, + preVals, + new Dictionary()); + + TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); + } + } + + if (result.ContainsKey(def.Id)) + { + Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); + } + result[def.Id] = new PropertyCollection(properties); } return result; diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 6f2007912f..d5658b9f9a 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -585,7 +585,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") { long total; var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); @@ -604,7 +604,7 @@ namespace Umbraco.Core.Services /// Direction to order by /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") { return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 7722bf9c65..6d73546571 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -259,7 +259,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -273,7 +273,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index d25ddf7f58..a02ac43b93 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -169,7 +169,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -183,7 +183,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 9a3a30c8bd..f4e963c658 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -451,7 +451,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") { long total; var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); @@ -470,7 +470,7 @@ namespace Umbraco.Core.Services /// Direction to order by /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") { return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); } From 44fc8be49ef773d8af4d22ba2668452c51635692 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 24 Jan 2017 00:35:17 +1100 Subject: [PATCH 71/88] Changes back to "path" default sorting but updates the UmbracoContentIndexer to ensure it's sorted by umbracoNode.id --- src/Umbraco.Core/Services/ContentService.cs | 4 ++-- src/Umbraco.Core/Services/IContentService.cs | 4 ++-- src/Umbraco.Core/Services/IMediaService.cs | 4 ++-- src/Umbraco.Core/Services/MediaService.cs | 4 ++-- src/UmbracoExamine/UmbracoContentIndexer.cs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index d5658b9f9a..32665910ad 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -585,7 +585,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") { long total; var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); @@ -604,7 +604,7 @@ namespace Umbraco.Core.Services /// Direction to order by /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") { return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 6d73546571..ff515169d9 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -259,7 +259,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -273,7 +273,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index a02ac43b93..d4218764b7 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -169,7 +169,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -183,7 +183,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index f4e963c658..a67ee8285d 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -451,7 +451,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") { long total; var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); @@ -470,7 +470,7 @@ namespace Umbraco.Core.Services /// Direction to order by /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") { return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); } diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index f7e4c45f50..e7df641122 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -497,7 +497,7 @@ namespace UmbracoExamine IContent[] descendants; if (SupportUnpublishedContent) { - descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total).ToArray(); + descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "umbracoNode.id").ToArray(); } else { From 24c053d6713a081a17bbc95fa1e9bbcb2501e954 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 23 Jan 2017 16:11:09 +0100 Subject: [PATCH 72/88] Fixes: U4-9427 Unable to install local packages in various browsers --- .../views/install-local.controller.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js index e34bc48ecd..9ce2506e76 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js @@ -34,18 +34,24 @@ fields: {}, file: file }).progress(function (evt) { + + // hack: in some browsers the progress event is called after success + // this prevents the UI from going back to a uploading state + if(vm.zipFile.uploadStatus !== "done" && vm.zipFile.uploadStatus !== "error") { - // set view state to uploading - vm.state = 'uploading'; + // set view state to uploading + vm.state = 'uploading'; - // calculate progress in percentage - var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); + // calculate progress in percentage + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); - // set percentage property on file - vm.zipFile.uploadProgress = progressPercentage; + // set percentage property on file + vm.zipFile.uploadProgress = progressPercentage; - // set uploading status on file - vm.zipFile.uploadStatus = "uploading"; + // set uploading status on file + vm.zipFile.uploadStatus = "uploading"; + + } }).success(function (data, status, headers, config) { From 072f0d15204a12459a33076fa6976fd2abd54b0b Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 23 Jan 2017 15:53:18 +0000 Subject: [PATCH 73/88] Adds in some extra propeties on JSON to store localization label for ascending & descending & use correct key for MyWebstie/WebSite Root that was already in lang file --- .../querybuilder/querybuilder.controller.js | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js index 8f28bd3674..3c0199d7bd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js @@ -4,8 +4,11 @@ function QueryBuilderOverlayController($scope, templateQueryResource, localizationService) { var everything = localizationService.localize("template_allContent"); - var myWebsite = localizationService.localize("template_myWebsite"); + var myWebsite = localizationService.localize("template_websiteRoot"); + var ascendingTranslation = localizationService.localize("template_ascending"); + var descendingTranslation = localizationService.localize("template_descending"); + var vm = this; vm.properties = []; @@ -36,7 +39,13 @@ alias: "", name: "", }, - direction: "ascending" + direction: "ascending", //This is the value for sorting sent to server + translation: { + currentLabel: ascendingTranslation, //This is the localized UI value in the the dialog + ascending: ascendingTranslation, + descending: descendingTranslation + } + } }; @@ -77,7 +86,6 @@ vm.contentPickerOverlay = { view: "contentpicker", show: true, - submitButtonLabel: "Insert", submit: function(model) { var selectedNodeId = model.selection[0].id; @@ -86,7 +94,7 @@ if (selectedNodeId > 0) { query.source = { id: selectedNodeId, name: selectedNodeName }; } else { - query.source.name = "My website"; + query.source.name = myWebsite; delete query.source.id; } @@ -127,8 +135,10 @@ function changeSortOrder(query) { if (query.sort.direction === "ascending") { query.sort.direction = "descending"; + query.sort.translation.currentLabel = query.sort.translation.descending; } else { query.sort.direction = "ascending"; + query.sort.translation.currentLabel = query.sort.translation.ascending; } throttledFunc(); } @@ -137,8 +147,10 @@ query.sort.property = property; if (property.type === "datetime") { query.sort.direction = "descending"; + query.sort.translation.currentLabel = query.sort.translation.descending; } else { query.sort.direction = "ascending"; + query.sort.translation.currentLabel = query.sort.translation.ascending; } throttledFunc(); } From 25822a7564e0c3e881ac46398bc0c50c2be660b2 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 23 Jan 2017 16:01:05 +0000 Subject: [PATCH 74/88] Fix up orderby translation in view and use our new JSON properties for display the tranlsated value for asc & desc --- .../src/views/common/overlays/querybuilder/querybuilder.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html index 542b7a3c6f..d3a4835470 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html @@ -100,7 +100,7 @@ From 47e4c123959c24ba2059595466215ea8a2f99243 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 23 Jan 2017 16:04:14 +0000 Subject: [PATCH 75/88] Adds in translations into the JSON objects we send back from the API as the display name (not the aliases for querying) --- .../Editors/TemplateQueryController.cs | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 8693350c74..cd004fe926 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -30,33 +30,44 @@ namespace Umbraco.Web.Editors { } - private static readonly IEnumerable Terms = new List() + private IEnumerable Terms + { + get { - new OperathorTerm("is", Operathor.Equals, new [] {"string"}), - new OperathorTerm("is not", Operathor.NotEquals, new [] {"string"}), - new OperathorTerm("before", Operathor.LessThan, new [] {"datetime"}), - new OperathorTerm("before (including selected date)", Operathor.LessThanEqualTo, new [] {"datetime"}), - new OperathorTerm("after", Operathor.GreaterThan, new [] {"datetime"}), - new OperathorTerm("after (including selected date)", Operathor.GreaterThanEqualTo, new [] {"datetime"}), - new OperathorTerm("equals", Operathor.Equals, new [] {"int"}), - new OperathorTerm("does not equal", Operathor.NotEquals, new [] {"int"}), - new OperathorTerm("contains", Operathor.Contains, new [] {"string"}), - new OperathorTerm("does not contain", Operathor.NotContains, new [] {"string"}), - new OperathorTerm("greater than", Operathor.GreaterThan, new [] {"int"}), - new OperathorTerm("greater than or equal to", Operathor.GreaterThanEqualTo, new [] {"int"}), - new OperathorTerm("less than", Operathor.LessThan, new [] {"int"}), - new OperathorTerm("less than or equal to", Operathor.LessThanEqualTo, new [] {"int"}) - }; + 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"}) + }; + } + } - private static readonly IEnumerable Properties = new List() + private IEnumerable Properties + { + get { - new PropertyModel() { Name = "Id", Alias = "Id", Type = "int" }, - new PropertyModel() { Name = "Name", Alias = "Name", Type = "string" }, - //new PropertyModel() { Name = "Url", Alias = "url", Type = "string" }, - new PropertyModel() { Name = "Created Date", Alias = "CreateDate", Type = "datetime" }, - new PropertyModel() { Name = "Last Updated Date", Alias = "UpdateDate", Type = "datetime" } - - }; + return new List() + { + new PropertyModel() {Name = Services.TextService.Localize("template/id"), Alias = "Id", Type = "int"}, + new PropertyModel() {Name = Services.TextService.Localize("template/name"), Alias = "Name", Type = "string"}, + //new PropertyModel() { Name = "Url", Alias = "url", Type = "string" }, + new PropertyModel() {Name = Services.TextService.Localize("template/createdDate"), Alias = "CreateDate", Type = "datetime"}, + new PropertyModel() {Name = Services.TextService.Localize("template/lastUpdatedDate"), Alias = "UpdateDate", Type = "datetime"} + }; + } + } public QueryResultModel PostTemplateQuery(QueryModel model) { From 0bba68a7f5e6df069454bf0eae97b2c5071a1fe8 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 23 Jan 2017 16:06:37 +0000 Subject: [PATCH 76/88] Added keys needed in EN lang files --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 23 ++++ .../umbraco/config/lang/en_us.xml | 100 +++++++++++++++++- 2 files changed, 119 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 24391288bd..444ecc2e3d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1104,6 +1104,29 @@ To manage your website, simply open the Umbraco back office and start adding con where and + is + is not + before + before (including selected date) + after + after (including selected date) + equals + does not equal + contains + does not contain + greater than + greater than or equal to + less than + less than or equal to + + Id + Name + Created Date + Last Updated Date + + order by + ascending + descending Template 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 173da6cc9b..a9c4e751f9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1027,13 +1027,105 @@ To manage your website, simply open the Umbraco back office and start adding con Edit template + + Sections Insert content area Insert content area placeholder - Insert dictionary item - Insert Macro - Insert Umbraco page field + + Insert + Choose what to insert into your template + + Dictionary item + A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites. + + Macro + + A Macro is a configurable component which is great for + reusable parts of your design, where you need the option to provide parameters, + such as galleries, forms and lists. + + + Value + Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values. + + Partial view + + A partial view is a separate template file which can be rendered inside another + template, it's great for reusing markup or for separating complex templates into separate files. + + Master template - Quick Guide to Umbraco template tags + No master template + No master + + Render child template + + @RenderBody() placeholder. + ]]> + + + + Define a named section + + @section { ... }. This can be rendered in a + specific area of the parent of this template, by using @RenderSection. + ]]> + + + Render a named section + + @RenderSection(name) placeholder. + This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. + ]]> + + + Section Name + Section is mandatory + + If mandatory, the child template must contain a @section definition, otherwise an error is shown. + + + + Query builder + items returned, in + + I want + all content + content of type "%0%" + from + my website + where + and + + is + is not + before + before (including selected date) + after + after (including selected date) + equals + does not equal + contains + does not contain + greater than + greater than or equal to + less than + less than or equal to + + Id + Name + Created Date + Last Updated Date + + order by + ascending + descending + Template From c1fcb37642d9a25be274b9c0ce45f55bee904b8e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 23 Jan 2017 16:08:38 +0000 Subject: [PATCH 77/88] Adds keys for DA file but appends them with DA to try & make it clear they need some translation love as my Danish is to be desired :) --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 6e4905cc7a..8528cb851a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1088,6 +1088,30 @@ Mange hilsner fra Umbraco robotten filtre og + is DA + is not DA + before DA + before (including selected date) DA + after DA + after (including selected date) DA + equals DA + does not equal DA + contains DA + does not contain DA + greater than DA + greater than or equal to DA + less than DA + less than or equal to DA + + Id DA + Name DA + Created Date DA + Last Updated Date DA + + order by DA + ascending DA + descending DA + Skabelon From c7b505fd908d926c194b78907b02e93c7b043c88 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 24 Jan 2017 16:04:26 +1100 Subject: [PATCH 78/88] Updates the paging logic so that the ContentRepository uses 2x queries: A full query and a query for getting Ids. This is important because the query for fetching ids is used for paging and for getting property data and it can be much much faster than the full query which was previously used for both. There's not really any changes to the media/members respositories since their full queries don't have certain outer joins that make them run really slow --- .../Persistence/Repositories/BaseQueryType.cs | 9 + .../Repositories/ContentRepository.cs | 174 +++++++++---- .../Repositories/MediaRepository.cs | 15 +- .../Repositories/MemberRepository.cs | 31 ++- .../Repositories/VersionableRepositoryBase.cs | 246 +++++++++--------- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 6 files changed, 285 insertions(+), 191 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs b/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs new file mode 100644 index 0000000000..4579fd98fb --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Persistence.Repositories +{ + internal enum BaseQueryType + { + Full, + Ids, + Count + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index b25db8afb2..b02f5304e6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -70,63 +70,90 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformGetAll(params int[] ids) { - var sql = GetBaseQuery(false); - if (ids.Any()) + Func translate = s => { - sql.Where("umbracoNode.id in (@ids)", new { ids }); - } + if (ids.Any()) + { + s.Where("umbracoNode.id in (@ids)", new { ids }); + } + //we only want the newest ones with this method + s.Where(x => x.Newest, SqlSyntax); + return s; + }; + + var sqlBaseFull = GetBaseQuery(BaseQueryType.Full); + var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids); - //we only want the newest ones with this method - sql.Where(x => x.Newest, SqlSyntax); - - return ProcessQuery(sql); + return ProcessQuery(translate(sqlBaseFull), translate(sqlBaseIds)); } protected override IEnumerable PerformGetByQuery(IQuery query) { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .Where(x => x.Newest, SqlSyntax) - .OrderByDescending(x => x.VersionDate, SqlSyntax) - .OrderBy(x => x.SortOrder, SqlSyntax); + var sqlBaseFull = GetBaseQuery(BaseQueryType.Full); + var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids); - return ProcessQuery(sql); + Func, Sql> translate = (translator) => + { + return translator.Translate() + .Where(x => x.Newest, SqlSyntax) + .OrderByDescending(x => x.VersionDate, SqlSyntax) + .OrderBy(x => x.SortOrder, SqlSyntax); + }; + + var translatorFull = new SqlTranslator(sqlBaseFull, query); + var translatorIds = new SqlTranslator(sqlBaseIds, query); + + return ProcessQuery(translate(translatorFull), translate(translatorIds)); } #endregion #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(BaseQueryType queryType) { - var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)", + var sql = new Sql(); + sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsDocument.nodeId" : "*")) + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.VersionId, right => right.VersionId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId); + + if (queryType == BaseQueryType.Full) + { + //The only reason we apply this left outer join is to be able to pull back the DocumentPublishedReadOnlyDto + //information with the entire data set, so basically this will get both the latest document and also it's published + //version if it has one. When performing a count or when just retrieving Ids like in paging, this is unecessary + //and causes huge performance overhead for the SQL server, especially when sorting the result. + //To fix this perf overhead we'd need another index on : + // CREATE NON CLUSTERED INDEX ON cmsDocument.node + cmsDocument.published + + var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)", SqlSyntax.GetQuotedTableName("cmsDocument"), SqlSyntax.GetQuotedTableName("cmsDocument2"), SqlSyntax.GetQuotedColumnName("nodeId"), SqlSyntax.GetQuotedColumnName("published")); - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - // cannot do this because PetaPoco does not know how to alias the table //.LeftOuterJoin() //.On(left => left.NodeId, right => right.NodeId) // so have to rely on writing our own SQL - .Append(sqlx/*, new { @published = true }*/) + sql.Append(sqlx /*, new { @published = true }*/); + } + + sql.Where(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax); - .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } + protected override Sql GetBaseQuery(bool isCount) + { + return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.Full); + } + protected override string GetBaseWhereClause() { return "umbracoNode.id = @Id"; @@ -173,20 +200,32 @@ namespace Umbraco.Core.Persistence.Repositories // not bring much safety - so this reverts to updating each record individually, // and it may be slower in the end, but should be more resilient. - var baseId = 0; var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray(); + + Func translate = (bId, sql) => + { + if (contentTypeIdsA.Length > 0) + { + sql.WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); + } + + sql + .Where(x => x.NodeId > bId && x.Trashed == false, SqlSyntax) + .Where(x => x.Published, SqlSyntax) + .OrderBy(x => x.NodeId, SqlSyntax); + + return sql; + }; + + var baseId = 0; + while (true) { // get the next group of nodes - var query = GetBaseQuery(false); - if (contentTypeIdsA.Length > 0) - query = query - .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); - query = query - .Where(x => x.NodeId > baseId && x.Trashed == false, SqlSyntax) - .Where(x => x.Published, SqlSyntax) - .OrderBy(x => x.NodeId, SqlSyntax); - var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) + var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.Full)); + var sqlIds = translate(baseId, GetBaseQuery(BaseQueryType.Ids)); + + var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), SqlSyntax.SelectTop(sqlIds, groupSize)) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) .ToList(); @@ -212,10 +251,16 @@ namespace Umbraco.Core.Persistence.Repositories public override IEnumerable GetAllVersions(int id) { - var sql = GetBaseQuery(false) - .Where(GetBaseWhereClause(), new { Id = id }) - .OrderByDescending(x => x.VersionDate, SqlSyntax); - return ProcessQuery(sql, true); + Func translate = s => + { + return s.Where(GetBaseWhereClause(), new {Id = id}) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + }; + + var sqlFull = translate(GetBaseQuery(BaseQueryType.Full)); + var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids)); + + return ProcessQuery(sqlFull, sqlIds, true); } public override IContent GetByVersion(Guid versionId) @@ -616,19 +661,25 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetByPublishedVersion(IQuery query) { + Func, Sql> translate = t => + { + return t.Translate() + .Where(x => x.Published, SqlSyntax) + .OrderBy(x => x.Level, SqlSyntax) + .OrderBy(x => x.SortOrder, SqlSyntax); + }; + // we WANT to return contents in top-down order, ie parents should come before children // ideal would be pure xml "document order" which can be achieved with: // ORDER BY substring(path, 1, len(path) - charindex(',', reverse(path))), sortOrder // but that's probably an overkill - sorting by level,sortOrder should be enough - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .Where(x => x.Published, SqlSyntax) - .OrderBy(x => x.Level, SqlSyntax) - .OrderBy(x => x.SortOrder, SqlSyntax); + var sqlFull = GetBaseQuery(BaseQueryType.Full); + var translatorFull = new SqlTranslator(sqlFull, query); + var sqlIds = GetBaseQuery(BaseQueryType.Ids); + var translatorIds = new SqlTranslator(sqlIds, query); - return ProcessQuery(sql, true); + return ProcessQuery(translate(translatorFull), translate(translatorIds), true); } /// @@ -806,9 +857,9 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", Func> filterCallback = () => new Tuple(filterSql.SQL, filterSql.Arguments); - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsDocument", "nodeId"), - sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, + (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -839,10 +890,21 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", return base.GetDatabaseFieldNameForOrderBy(orderBy); } - private IEnumerable ProcessQuery(Sql sql, bool withCache = false) + /// + /// This is the underlying method that processes most queries for this repository + /// + /// + /// The full SQL with the outer join to return all data required to create an IContent + /// + /// + /// The Id SQL without the outer join to just return all document ids - used to process the properties for the content item + /// + /// + /// + private IEnumerable ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = false) { // fetch returns a list so it's ok to iterate it in this method - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sqlFull); if (dtos.Count == 0) return Enumerable.Empty(); var content = new IContent[dtos.Count]; @@ -905,7 +967,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", .ToDictionary(x => x.Id, x => x); // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sql, defs); + var propertyData = GetPropertyCollection(sqlIds, defs); // assign var dtoIndex = 0; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index bf034bd8ff..8cfb037c6c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -84,11 +84,11 @@ namespace Umbraco.Core.Persistence.Repositories #endregion #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) + + protected override Sql GetBaseQuery(BaseQueryType queryType) { var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") + sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsContentVersion.contentId" : "*")) .From(SqlSyntax) .InnerJoin(SqlSyntax) .On(SqlSyntax, left => left.NodeId, right => right.NodeId) @@ -98,6 +98,11 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } + protected override Sql GetBaseQuery(bool isCount) + { + return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.Full); + } + protected override string GetBaseWhereClause() { return "umbracoNode.id = @Id"; @@ -500,9 +505,9 @@ namespace Umbraco.Core.Persistence.Repositories filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); } - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsContentVersion", "contentId"), - sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, + (sqlFull, sqlIds) => ProcessQuery(sqlFull), orderBy, orderDirection, orderBySystemField, filterCallback); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index a92794775f..8b3bf4a471 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -107,24 +107,29 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of PetaPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(BaseQueryType queryType) { var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) + sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsMember.nodeId" : "*")) + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) //We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not? // the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content // types by default on the document and media repo's so we can query by content type there too. - .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax); return sql; + } + protected override Sql GetBaseQuery(bool isCount) + { + return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.Full); } protected override string GetBaseWhereClause() @@ -617,9 +622,9 @@ namespace Umbraco.Core.Persistence.Repositories filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); } - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsMember", "nodeId"), - sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, + (sqlFull, sqlIds) => ProcessQuery(sqlFull), orderBy, orderDirection, orderBySystemField, filterCallback); } diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 16bad74612..31dd75b3d1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -27,8 +27,6 @@ using Umbraco.Core.IO; namespace Umbraco.Core.Persistence.Repositories { - using SqlSyntax; - internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase where TEntity : class, IAggregateRoot { @@ -384,12 +382,8 @@ namespace Umbraco.Core.Persistence.Repositories ON CustomPropData.CustomPropValContentId = umbracoNode.id ", sortedInt, sortedDecimal, sortedDate, sortedString, nodeIdSelect.Item2, nodeIdSelect.Item1, versionQuery, sortedSql.Arguments.Length, newestQuery); - //insert this just above the first LEFT OUTER JOIN (for cmsDocument) or the last WHERE (everything else) - string newSql; - if (nodeIdSelect.Item1 == "cmsDocument") - newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), outerJoinTempTable); - else - newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), outerJoinTempTable); + //insert this just above the last WHERE + string newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), outerJoinTempTable); var newArgs = sortedSql.Arguments.ToList(); newArgs.Add(orderBy); @@ -414,7 +408,6 @@ namespace Umbraco.Core.Persistence.Repositories // see: http://issues.umbraco.org/issue/U4-8831 sortedSql.OrderBy("umbracoNode.id"); } - return sortedSql; @@ -424,7 +417,6 @@ namespace Umbraco.Core.Persistence.Repositories /// A helper method for inheritors to get the paged results by query in a way that minimizes queries /// /// The type of the d. - /// The 'true' entity type (i.e. Content, Member, etc...) /// The query. /// Index of the page. /// Size of the page. @@ -437,34 +429,30 @@ namespace Umbraco.Core.Persistence.Repositories /// Flag to indicate when ordering by system field /// /// orderBy - protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Tuple nodeIdSelect, - Func> processQuery, + Func> processQuery, string orderBy, Direction orderDirection, bool orderBySystemField, Func> defaultFilter = null) - where TContentBase : class, IAggregateRoot, TEntity { if (orderBy == null) throw new ArgumentNullException("orderBy"); - // Get base query - var sqlBase = GetBaseQuery(false); + // Get base query for returning IDs + var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids); + // Get base query for returning all data + var sqlBaseFull = GetBaseQuery(BaseQueryType.Full); if (query == null) query = new Query(); - var translator = new SqlTranslator(sqlBase, query); - var sqlQuery = translator.Translate(); - - // Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery, - // but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id. - // So we'll modify the SQL. - var sqlNodeIds = new Sql( - sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)), - sqlQuery.Arguments); + var translatorIds = new SqlTranslator(sqlBaseIds, query); + var sqlQueryIds = translatorIds.Translate(); + var translatorFull = new SqlTranslator(sqlBaseFull, query); + var sqlQueryFull = translatorFull.Translate(); //get sorted and filtered sql var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( - GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), + GetFilteredSqlForPagedResults(sqlQueryIds, defaultFilter), orderDirection, orderBy, orderBySystemField, nodeIdSelect); // Get page of results and total count @@ -480,31 +468,25 @@ namespace Umbraco.Core.Persistence.Repositories //Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query var args = sqlNodeIdsWithSort.Arguments; string sqlStringCount, sqlStringPage; - Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); + Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); - //if this is for sql server, the sqlPage will start with a SELECT * but we don't want that, we only want to return the nodeId - sqlStringPage = sqlStringPage - .Replace("SELECT *", - //This ensures we only take the field name of the node id select and not the table name - since the resulting select - // will ony work with the field name. - "SELECT " + nodeIdSelect.Item2); - - //We need to make this an inner join on the paged query - var splitQuery = sqlQuery.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None); - var withInnerJoinSql = new Sql(splitQuery[0]) + //We need to make this FULL query an inner join on the paged ID query + var splitQuery = sqlQueryFull.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None); + var fullQueryWithPagedInnerJoin = new Sql(splitQuery[0]) .Append("INNER JOIN (") //join the paged query with the paged query arguments .Append(sqlStringPage, args) .Append(") temp ") .Append(string.Format("ON {0}.{1} = temp.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)) //add the original where clause back with the original arguments - .Where(splitQuery[1], sqlQuery.Arguments); + .Where(splitQuery[1], sqlQueryIds.Arguments); //get sorted and filtered sql var fullQuery = GetSortedSqlForPagedResults( - GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), + GetFilteredSqlForPagedResults(fullQueryWithPagedInnerJoin, defaultFilter), orderDirection, orderBy, orderBySystemField, nodeIdSelect); - return processQuery(fullQuery); + + return processQuery(fullQuery, sqlNodeIdsWithSort); } else { @@ -529,6 +511,25 @@ namespace Umbraco.Core.Persistence.Repositories parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); } + //This retrieves all pre-values for all data types that are referenced for all property types + // that exist in the data set. + //Benchmarks show that eagerly loading these so that we can lazily read the property data + // below (with the use of Query intead of Fetch) go about 30% faster, so we'll eagerly load + // this now since we cannot execute another reader inside of reading the property data. + var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId +FROM cmsDataTypePreValues a +WHERE EXISTS( + SELECT DISTINCT b.id as preValIdInner + FROM cmsDataTypePreValues b + INNER JOIN cmsPropertyType + ON b.datatypeNodeId = cmsPropertyType.dataTypeId + INNER JOIN + (" + string.Format(parsedOriginalSql, "DISTINCT cmsContent.contentType") + @") as docData + ON cmsPropertyType.contentTypeId = docData.contentType + WHERE a.id = b.id)", docSql.Arguments); + + var allPreValues = Database.Fetch(preValsSql); + //It's Important with the sort order here! We require this to be sorted by node id, // this is required because this data set can be huge depending on the page size. Due // to it's size we need to be smart about iterating over the property values to build @@ -548,106 +549,110 @@ ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNode ORDER BY contentNodeId, propertytypeid ", docSql.Arguments); - var allPropertyData = Database.Fetch(propSql); - - //This is a lazy access call to get all prevalue data for the data types that make up all of these properties which we use - // below if any property requires tag support - var allPreValues = new Lazy>(() => - { - var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId -FROM cmsDataTypePreValues a -WHERE EXISTS( - SELECT DISTINCT b.id as preValIdInner - FROM cmsDataTypePreValues b - INNER JOIN cmsPropertyType - ON b.datatypeNodeId = cmsPropertyType.dataTypeId - INNER JOIN - (" + string.Format(parsedOriginalSql, "DISTINCT cmsContent.contentType") + @") as docData - ON cmsPropertyType.contentTypeId = docData.contentType - WHERE a.id = b.id)", docSql.Arguments); - - return Database.Fetch(preValsSql); - }); + //This does NOT fetch all data into memory in a list, this will read + // over the records as a data reader, this is much better for performance and memory, + // but it means that during the reading of this data set, nothing else can be read + // from SQL server otherwise we'll get an exception. + var allPropertyData = Database.Query(propSql); var result = new Dictionary(); var propertiesWithTagSupport = new Dictionary(); //used to track the resolved composition property types per content type so we don't have to re-resolve (ToArray) the list every time var resolvedCompositionProperties = new Dictionary(); - var propertyDataSetIndex = 0; - //This must be sorted by node id because this is how we are sorting the query to lookup property types above, - // which allows us to more efficiently iterate over the large data set of property values - foreach (var def in documentDefs.OrderBy(x => x.Id)) + //keep track of the current property data item being enumerated + var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); + + try { - //get the resolved proeprties from our local cache, or resolve them and put them in cache - PropertyType[] compositionProperties; - if (resolvedCompositionProperties.ContainsKey(def.Composition.Id)) + //This must be sorted by node id because this is how we are sorting the query to lookup property types above, + // which allows us to more efficiently iterate over the large data set of property values + foreach (var def in documentDefs.OrderBy(x => x.Id)) { - compositionProperties = resolvedCompositionProperties[def.Composition.Id]; - } - else - { - compositionProperties = def.Composition.CompositionPropertyTypes.ToArray(); - resolvedCompositionProperties[def.Composition.Id] = compositionProperties; - } - - var propertyDataDtos = new List(); - - for (var i = propertyDataSetIndex; i < allPropertyData.Count; i++) - { - if (allPropertyData[i].NodeId == def.Id) + //get the resolved proeprties from our local cache, or resolve them and put them in cache + PropertyType[] compositionProperties; + if (resolvedCompositionProperties.ContainsKey(def.Composition.Id)) { - propertyDataDtos.Add(allPropertyData[i]); + compositionProperties = resolvedCompositionProperties[def.Composition.Id]; } else { - //the node id has changed so we need to exit the loop and store the index - propertyDataSetIndex = i; - break; + compositionProperties = def.Composition.CompositionPropertyTypes.ToArray(); + resolvedCompositionProperties[def.Composition.Id] = compositionProperties; } - } - - var properties = PropertyFactory.BuildEntity(propertyDataDtos, compositionProperties, def.CreateDate, def.VersionDate).ToArray(); - foreach (var property in properties) - { - //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck - var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + var propertyDataDtos = new List(); - var tagSupport = propertiesWithTagSupport.ContainsKey(property.PropertyType.PropertyEditorAlias) - ? propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] - : TagExtractor.GetAttribute(editor); - - if (tagSupport != null) + //Check if there is a current enumerated item and check if we match and add it + if (propertyDataSetEnumerator.Current != null) { - //add to local cache so we don't need to reflect next time for this property editor alias - propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport; - - //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up - var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId) - .Distinct() - .ToArray(); - - var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); - - var preVals = new PreValueCollection(asDictionary); - - var contentPropData = new ContentPropertyData(property.Value, - preVals, - new Dictionary()); - - TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); + if (propertyDataSetEnumerator.Current.NodeId == def.Id) + { + propertyDataDtos.Add(propertyDataSetEnumerator.Current); + } + } + //Move to the next position, see if we match and add it, if not exit + while (propertyDataSetEnumerator.MoveNext()) + { + if (propertyDataSetEnumerator.Current.NodeId == def.Id) + { + propertyDataDtos.Add(propertyDataSetEnumerator.Current); + } + else + { + //the node id has changed so we need to exit the loop, + //the enumerator position will be maintained + break; + } } - } - if (result.ContainsKey(def.Id)) - { - Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); + var properties = PropertyFactory.BuildEntity(propertyDataDtos, compositionProperties, def.CreateDate, def.VersionDate).ToArray(); + + foreach (var property in properties) + { + //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck + var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + + var tagSupport = propertiesWithTagSupport.ContainsKey(property.PropertyType.PropertyEditorAlias) + ? propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] + : TagExtractor.GetAttribute(editor); + + if (tagSupport != null) + { + //add to local cache so we don't need to reflect next time for this property editor alias + propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport; + + //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up + var preValData = allPreValues.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId) + .Distinct() + .ToArray(); + + var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); + + var preVals = new PreValueCollection(asDictionary); + + var contentPropData = new ContentPropertyData(property.Value, + preVals, + new Dictionary()); + + TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); + } + } + + if (result.ContainsKey(def.Id)) + { + Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); + } + result[def.Id] = new PropertyCollection(properties); } - result[def.Id] = new PropertyCollection(properties); + } + finally + { + propertyDataSetEnumerator.Dispose(); } return result; + } public class DocumentDefinition @@ -759,5 +764,12 @@ WHERE EXISTS( return allsuccess; } + + /// + /// For Paging, repositories must support returning different query for the query type specified + /// + /// + /// + protected abstract Sql GetBaseQuery(BaseQueryType queryType); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 17be8e62df..8ba327c9c9 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -466,6 +466,7 @@ + From 408232c9ac5a1d6878966e6c6195a7dd5bdf3462 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 24 Jan 2017 11:11:50 +0100 Subject: [PATCH 79/88] moving over some interfaces that was lost in merge. --- src/Umbraco.Core/Constants-DeploySelector.cs | 6 +---- src/Umbraco.Core/Deploy/IFileSource.cs | 23 +++++++++++++++++++- src/Umbraco.Core/Deploy/IFileStore.cs | 9 ++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 4 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Core/Deploy/IFileStore.cs diff --git a/src/Umbraco.Core/Constants-DeploySelector.cs b/src/Umbraco.Core/Constants-DeploySelector.cs index b7b45fd666..cd9c48a6f5 100644 --- a/src/Umbraco.Core/Constants-DeploySelector.cs +++ b/src/Umbraco.Core/Constants-DeploySelector.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core +namespace Umbraco.Core { public static partial class Constants { diff --git a/src/Umbraco.Core/Deploy/IFileSource.cs b/src/Umbraco.Core/Deploy/IFileSource.cs index 3baa9069f0..ea73d2b571 100644 --- a/src/Umbraco.Core/Deploy/IFileSource.cs +++ b/src/Umbraco.Core/Deploy/IFileSource.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -50,6 +51,26 @@ namespace Umbraco.Core.Deploy /// Returns null if no content could be read. Task GetFileContentAsync(StringUdi udi, CancellationToken token); + /// + /// Gets the length of a file. + /// + /// A file entity identifier. + /// The length of the file, or -1 if the file does not exist. + long GetFileLength(StringUdi udi); + + /// + /// Gets the length of a file. + /// + /// A file entity identifier. + /// A cancellation token. + /// The length of the file, or -1 if the file does not exist. + Task GetFileLengthAsync(StringUdi udi, CancellationToken token); + + // fixme - doc + void GetFiles(IEnumerable udis, IFileStore fileStore); + + Task GetFilesAsync(IEnumerable udis, IFileStore fileStore, CancellationToken token); + ///// ///// Gets the content of a file as a bytes array. ///// diff --git a/src/Umbraco.Core/Deploy/IFileStore.cs b/src/Umbraco.Core/Deploy/IFileStore.cs new file mode 100644 index 0000000000..0cd7425eb9 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IFileStore.cs @@ -0,0 +1,9 @@ +using System.IO; + +namespace Umbraco.Core.Deploy +{ + public interface IFileStore + { + void SaveStream(StringUdi udi, Stream stream); + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7a4ba92b76..3a53f273f4 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -312,6 +312,7 @@ + From a3e7153bca8ecaa07f7aa5e71810f82b686b1d7c Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 24 Jan 2017 11:38:00 +0100 Subject: [PATCH 80/88] bumping version to alpha052. --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 62a06d8bcc..7403b5751a 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.6.0 -alpha051 \ No newline at end of file +alpha052 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 837339ea56..55d83a03c0 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha051")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha052")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 6cbbcd82a4..0ba8fc0602 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "alpha051"; } } + public static string CurrentComment { get { return "alpha052"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx From 9bdd080cfbc20d9a8d4e5a615a1ce2703d9d9262 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 24 Jan 2017 14:38:22 +0100 Subject: [PATCH 81/88] ensuring Udi's are comparable. --- src/Umbraco.Core/Deploy/ArtifactBase.cs | 2 +- src/Umbraco.Core/Udi.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs index 4d6bc687cb..d54086fc00 100644 --- a/src/Umbraco.Core/Deploy/ArtifactBase.cs +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Deploy throw new ArgumentNullException("udi"); Udi = udi; - Dependencies = dependencies ?? Enumerable.Empty(); + Dependencies = dependencies != null ? dependencies.OrderBy(x => x.Udi) : Enumerable.Empty(); _checksum = new Lazy(GetChecksum); } diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index 95bd9f90b1..a7298c6f89 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -1,12 +1,8 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Data.Metadata.Edm; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core.Deploy; -using EntityContainer = System.Data.Metadata.Edm.EntityContainer; namespace Umbraco.Core { @@ -14,7 +10,7 @@ namespace Umbraco.Core /// Represents an entity identifier. /// /// An Udi can be fully qualified or "closed" eg umb://document/{guid} or "open" eg umb://document. - public abstract class Udi + public abstract class Udi : IComparable { private static readonly Dictionary UdiTypes = new Dictionary(); private static readonly ConcurrentDictionary RootUdis = new ConcurrentDictionary(); @@ -76,6 +72,11 @@ namespace Umbraco.Core /// public string EntityType { get; private set; } + public int CompareTo(Udi other) + { + return string.Compare(UriValue.ToString(), other.UriValue.ToString(), StringComparison.InvariantCultureIgnoreCase); + } + public override string ToString() { // UriValue is created in the ctor and is never null From 16e9bf265d88fcb9535475689693464ed5fa007b Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 24 Jan 2017 14:46:20 +0100 Subject: [PATCH 82/88] bumping version. --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 7403b5751a..7baf3dcdbf 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.6.0 -alpha052 \ No newline at end of file +alpha053 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 55d83a03c0..5be3322448 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha052")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha053")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 0ba8fc0602..765f14647f 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "alpha052"; } } + public static string CurrentComment { get { return "alpha053"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx From 8b65968ac94a56acc33784274205db47ac59e9da Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 24 Jan 2017 15:14:44 +0100 Subject: [PATCH 83/88] when modifying dependencies after constructing an artifact they should also be ordered correctly. --- src/Umbraco.Core/Deploy/ArtifactBase.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs index d54086fc00..b507c54415 100644 --- a/src/Umbraco.Core/Deploy/ArtifactBase.cs +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -17,11 +17,12 @@ namespace Umbraco.Core.Deploy throw new ArgumentNullException("udi"); Udi = udi; - Dependencies = dependencies != null ? dependencies.OrderBy(x => x.Udi) : Enumerable.Empty(); + Dependencies = dependencies ?? Enumerable.Empty(); _checksum = new Lazy(GetChecksum); } private readonly Lazy _checksum; + private IEnumerable _dependencies; protected abstract string GetChecksum(); @@ -40,7 +41,11 @@ namespace Umbraco.Core.Deploy get { return _checksum.Value; } } - public IEnumerable Dependencies { get; set; } + public IEnumerable Dependencies + { + get { return _dependencies; } + set { _dependencies = value.OrderBy(x => x.Udi); } + } #endregion } From 501cef665fda5873bf262aee034ea9cf556f3fca Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 24 Jan 2017 15:18:31 +0100 Subject: [PATCH 84/88] version bump alpha054 --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 7baf3dcdbf..2fbc54ba3a 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.6.0 -alpha053 \ No newline at end of file +alpha054 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 5be3322448..d35ac3f682 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha053")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha054")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 765f14647f..5f35f67077 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "alpha053"; } } + public static string CurrentComment { get { return "alpha054"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx From 031340a2ac7c03378f34162d60279f81852adf67 Mon Sep 17 00:00:00 2001 From: mikkelhm Date: Thu, 26 Jan 2017 13:41:04 +0100 Subject: [PATCH 85/88] Adds case for IDictionaryItem when trying to get the Udi from an entity --- src/Umbraco.Core/UdiGetterExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Core/UdiGetterExtensions.cs b/src/Umbraco.Core/UdiGetterExtensions.cs index d10f730aef..1bd018a754 100644 --- a/src/Umbraco.Core/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/UdiGetterExtensions.cs @@ -294,6 +294,9 @@ namespace Umbraco.Core var relationType = entity as IRelationType; if (relationType != null) return relationType.GetUdi(); + var dictionaryItem = entity as IDictionaryItem; + if (dictionaryItem != null) return dictionaryItem.GetUdi(); + throw new NotSupportedException(string.Format("Entity type {0} is not supported.", entity.GetType().FullName)); } } From 6bcd1e44f97941095c6f278801b77215e96c5e8e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 26 Jan 2017 18:24:56 +0100 Subject: [PATCH 86/88] If translation is not done yet, better to leave it English so at least it kind of makes sense --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 8528cb851a..9ee79ce8d8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1088,29 +1088,29 @@ Mange hilsner fra Umbraco robotten filtre og - is DA - is not DA - before DA - before (including selected date) DA - after DA - after (including selected date) DA - equals DA - does not equal DA - contains DA - does not contain DA - greater than DA - greater than or equal to DA - less than DA - less than or equal to DA + is + is not + before + before (including selected date) + after + after (including selected date) + equals + does not equal + contains + does not contain + greater than + greater than or equal to + less than + less than or equal to - Id DA - Name DA - Created Date DA - Last Updated Date DA + Id + Name + Created Date + Last Updated Date - order by DA - ascending DA - descending DA + order by + ascending + descending Skabelon From b334e0ccb205031a3989b16bbe019822da5e2c0d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 26 Jan 2017 19:45:05 +0100 Subject: [PATCH 87/88] Fixes: U4-9440 Update the last few Danish translations for template editor --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 9ee79ce8d8..fb032a7f22 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1085,32 +1085,32 @@ Mange hilsner fra Umbraco robotten fra mit website - filtre + hvor og - is - is not - before - before (including selected date) - after - after (including selected date) - equals - does not equal - contains - does not contain - greater than - greater than or equal to - less than - less than or equal to + er + ikke er + er før + er før (inkl. valgte dato) + er efter + er efter (inkl. valgte dato) + er + ikke er + indeholder + ikke indeholder + er større end + er større end eller det samme som + er mindre end + er mindre end eller det samme som Id - Name - Created Date - Last Updated Date + Navn + Oprettelsesdato + Sidste opdatering - order by - ascending - descending + Sortér efter + stigende rækkefølge + faldende rækkefølge Skabelon From a5a42854efa487c8b4c346c15a37cf95c2c612b3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 26 Jan 2017 22:01:10 +0100 Subject: [PATCH 88/88] Bump version to alpha055 --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 2fbc54ba3a..07f33971ff 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.6.0 -alpha054 \ No newline at end of file +alpha055 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index d35ac3f682..40455f708c 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha054")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha055")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 5f35f67077..1637763e6e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "alpha054"; } } + public static string CurrentComment { get { return "alpha055"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx