From 3595dd33391a715b281ed20dc3ef914ddba1c2e2 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Wed, 19 Aug 2015 16:54:03 +0100 Subject: [PATCH 01/20] Adding checks for predefined crops that are still set to default and where a specific resize parameter has been passed into GetCropUrl so that output crop is in same ratio as the predefined crop --- .../PropertyEditors/ImageCropperTest.cs | 27 ++++++++++ src/Umbraco.Web/ImageCropperBaseExtensions.cs | 2 + .../ImageCropperTemplateExtensions.cs | 49 ++++++++++++------- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 75846fb404..0cbf09f782 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -124,5 +124,32 @@ namespace Umbraco.Tests.PropertyEditors var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 300, height: 150, preferFocalPoint:true); Assert.AreEqual(mediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString); } + + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth() + { + var cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; + + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", width: 200); + Assert.AreEqual(mediaPath + "?anchor=center&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + } + + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() + { + var cropperJson = "{\"focalPoint\": {\"left\": 0.4275,\"top\": 0.41},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; + + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", width: 200); + Assert.AreEqual(mediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + } + + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() + { + var cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; + + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", height: 200); + Assert.AreEqual(mediaPath + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/ImageCropperBaseExtensions.cs b/src/Umbraco.Web/ImageCropperBaseExtensions.cs index cceac8ab31..b870335c91 100644 --- a/src/Umbraco.Web/ImageCropperBaseExtensions.cs +++ b/src/Umbraco.Web/ImageCropperBaseExtensions.cs @@ -81,6 +81,7 @@ { return null; } + if ((preferFocalPoint && cropDataSet.HasFocalPoint()) || (crop != null && crop.Coordinates == null && cropDataSet.HasFocalPoint()) || (string.IsNullOrEmpty(cropAlias) && cropDataSet.HasFocalPoint())) { cropUrl.Append("?center=" + cropDataSet.FocalPoint.Top.ToString(CultureInfo.InvariantCulture) + "," + cropDataSet.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); @@ -100,6 +101,7 @@ cropUrl.Append("?anchor=center"); cropUrl.Append("&mode=crop"); } + return cropUrl.ToString(); } } diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index de0f8a225e..4bfdf6e12b 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -153,6 +153,12 @@ /// /// The height of the output image. /// + /// + /// The Json data from the Umbraco Core Image Cropper property editor + /// + /// + /// The crop alias. + /// /// /// Quality percentage of the output image. /// @@ -162,17 +168,11 @@ /// /// The image crop anchor. /// - /// - /// The Json data from the Umbraco Core Image Cropper property editor - /// - /// - /// The crop alias. - /// /// /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one /// /// - /// 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 @@ -182,10 +182,10 @@ /// /// /// Use a dimension as a ratio - /// + /// /// /// If the image should be upscaled to requested dimensions - /// + /// /// /// The . /// @@ -203,8 +203,7 @@ string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true - ) + bool upScale = true) { if (string.IsNullOrEmpty(imageUrl) == false) { @@ -229,11 +228,25 @@ return null; } - if (crop!= null & useCropDimensions) + if (crop != null & useCropDimensions) { width = crop.Width; height = crop.Height; } + + // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a width parameter has been passed we can get the crop ratio for the height + if (crop != null && crop.Coordinates == null && ratioMode == null && width != null && height == null) + { + var heightRatio = (decimal)crop.Height / (decimal)crop.Width; + imageResizerUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); + } + + // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a height parameter has been passed we can get the crop ratio for the width + if (crop != null && crop.Coordinates == null && ratioMode == null && width == null && height != null) + { + var widthRatio = (decimal)crop.Width / (decimal)crop.Height; + imageResizerUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); + } } } else @@ -270,23 +283,25 @@ if (ratioMode == ImageCropRatioMode.Width && height != null) { - //if only height specified then assume a sqaure + // if only height specified then assume a sqaure if (width == null) { width = height; } - var widthRatio = (decimal)width/(decimal)height; - imageResizerUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); + + var widthRatio = (decimal)width / (decimal)height; + imageResizerUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); } if (ratioMode == ImageCropRatioMode.Height && width != null) { - //if only width specified then assume a sqaure + // if only width specified then assume a sqaure if (height == null) { height = width; } - var heightRatio = (decimal)height/(decimal)width; + + var heightRatio = (decimal)height / (decimal)width; imageResizerUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); } From 6f76ee384ccb20b90bc6fcd5eee9c5ddf721da87 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Thu, 20 Aug 2015 09:31:20 +0100 Subject: [PATCH 02/20] Added some extra GetCropUrl tests as you can never have too many --- .../PropertyEditors/ImageCropperTest.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 0cbf09f782..4a398082ce 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -21,6 +21,16 @@ namespace Umbraco.Tests.PropertyEditors Assert.AreEqual(mediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } + /// + /// Test to ensure useCropDimensions is observed + /// + [Test] + public void GetCropUrl_CropAliasIgnoreWidthHeightTest() + { + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "Thumb", useCropDimensions: true, width: 50, height: 50); + Assert.AreEqual(mediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + } + [Test] public void GetCropUrl_WidthHeightTest() { @@ -125,6 +135,9 @@ namespace Umbraco.Tests.PropertyEditors Assert.AreEqual(mediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString); } + /// + /// Test to check if height ratio is returned for a predefined crop without coordinates and focal point in centre when a width parameter is passed + /// [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth() { @@ -134,6 +147,9 @@ namespace Umbraco.Tests.PropertyEditors Assert.AreEqual(mediaPath + "?anchor=center&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); } + /// + /// Test to check if height ratio is returned for a predefined crop without coordinates and focal point is custom when a width parameter is passed + /// [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() { @@ -143,6 +159,21 @@ namespace Umbraco.Tests.PropertyEditors Assert.AreEqual(mediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); } + /// + /// Test to check if crop ratio is ignored if useCropDimensions is true + /// + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPointIgnore() + { + var cropperJson = "{\"focalPoint\": {\"left\": 0.4275,\"top\": 0.41},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; + + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", width: 200, useCropDimensions: true); + Assert.AreEqual(mediaPath + "?center=0.41,0.4275&mode=crop&width=270&height=161", urlString); + } + + /// + /// Test to check if width ratio is returned for a predefined crop without coordinates and focal point in centre when a height parameter is passed + /// [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() { From a17865683cdf72beec26039e8caa443996735069 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Thu, 20 Aug 2015 09:33:12 +0100 Subject: [PATCH 03/20] Variable renaming --- .../ImageCropperTemplateExtensions.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index 4bfdf6e12b..e40fcfb297 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -207,7 +207,7 @@ { if (string.IsNullOrEmpty(imageUrl) == false) { - var imageResizerUrl = new StringBuilder(); + var imageProcessorUrl = new StringBuilder(); if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) { @@ -216,12 +216,12 @@ { var crop = cropDataSet.GetCrop(cropAlias); - imageResizerUrl.Append(cropDataSet.Src); + imageProcessorUrl.Append(cropDataSet.Src); var cropBaseUrl = cropDataSet.GetCropBaseUrl(cropAlias, preferFocalPoint); if (cropBaseUrl != null) { - imageResizerUrl.Append(cropBaseUrl); + imageProcessorUrl.Append(cropBaseUrl); } else { @@ -238,47 +238,47 @@ if (crop != null && crop.Coordinates == null && ratioMode == null && width != null && height == null) { var heightRatio = (decimal)crop.Height / (decimal)crop.Width; - imageResizerUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); } // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a height parameter has been passed we can get the crop ratio for the width if (crop != null && crop.Coordinates == null && ratioMode == null && width == null && height != null) { var widthRatio = (decimal)crop.Width / (decimal)crop.Height; - imageResizerUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); } } } else { - imageResizerUrl.Append(imageUrl); + imageProcessorUrl.Append(imageUrl); if (imageCropMode == null) { imageCropMode = ImageCropMode.Pad; } - imageResizerUrl.Append("?mode=" + imageCropMode.ToString().ToLower()); + imageProcessorUrl.Append("?mode=" + imageCropMode.ToString().ToLower()); if (imageCropAnchor != null) { - imageResizerUrl.Append("&anchor=" + imageCropAnchor.ToString().ToLower()); + imageProcessorUrl.Append("&anchor=" + imageCropAnchor.ToString().ToLower()); } } if (quality != null) { - imageResizerUrl.Append("&quality=" + quality); + imageProcessorUrl.Append("&quality=" + quality); } if (width != null && ratioMode != ImageCropRatioMode.Width) { - imageResizerUrl.Append("&width=" + width); + imageProcessorUrl.Append("&width=" + width); } if (height != null && ratioMode != ImageCropRatioMode.Height) { - imageResizerUrl.Append("&height=" + height); + imageProcessorUrl.Append("&height=" + height); } if (ratioMode == ImageCropRatioMode.Width && height != null) @@ -290,7 +290,7 @@ } var widthRatio = (decimal)width / (decimal)height; - imageResizerUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); } if (ratioMode == ImageCropRatioMode.Height && width != null) @@ -302,25 +302,25 @@ } var heightRatio = (decimal)height / (decimal)width; - imageResizerUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); } if (upScale == false) { - imageResizerUrl.Append("&upscale=false"); + imageProcessorUrl.Append("&upscale=false"); } if (furtherOptions != null) { - imageResizerUrl.Append(furtherOptions); + imageProcessorUrl.Append(furtherOptions); } if (cacheBusterValue != null) { - imageResizerUrl.Append("&rnd=").Append(cacheBusterValue); + imageProcessorUrl.Append("&rnd=").Append(cacheBusterValue); } - return imageResizerUrl.ToString(); + return imageProcessorUrl.ToString(); } return string.Empty; From bc20a48570f32f965346c7e78195226ee51ec679 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Thu, 20 Aug 2015 09:34:48 +0100 Subject: [PATCH 04/20] Tiny Stylecop bracket adjustment --- src/Umbraco.Web/ImageCropperTemplateExtensions.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index e40fcfb297..9d097b3a3e 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -110,9 +110,8 @@ bool useCropDimensions = false, bool cacheBuster = true, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true - ) + ImageCropRatioMode? ratioMode = null, + bool upScale = true) { string imageCropperValue = null; From 8ea99b9463443dffe8013c1f2840a93ac7b87dc8 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Thu, 20 Aug 2015 10:22:15 +0100 Subject: [PATCH 05/20] Adjust so that first crop isn't used for ratio when only a width or height parameter is passed into GetCropUrl --- .../PropertyEditors/ImageCropperTest.cs | 24 +++++++++++++++++++ .../ImageCropperTemplateExtensions.cs | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 4a398082ce..2877e3bb90 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -182,5 +182,29 @@ namespace Umbraco.Tests.PropertyEditors var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", height: 200); Assert.AreEqual(mediaPath + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); } + + /// + /// Test to check result when only a width parameter is passed, effectivly a resize only + /// + [Test] + public void GetCropUrl_WidthOnlyParameter() + { + var cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; + + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 200); + Assert.AreEqual(mediaPath + "?anchor=center&mode=crop&width=200", urlString); + } + + /// + /// Test to check result when only a height parameter is passed, effectivly a resize only + /// + [Test] + public void GetCropUrl_HeightOnlyParameter() + { + var cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; + + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, height: 200); + Assert.AreEqual(mediaPath + "?anchor=center&mode=crop&height=200", urlString); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index 9d097b3a3e..a76b39f187 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -234,14 +234,14 @@ } // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a width parameter has been passed we can get the crop ratio for the height - if (crop != null && crop.Coordinates == null && ratioMode == null && width != null && height == null) + if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null && width != null && height == null) { var heightRatio = (decimal)crop.Height / (decimal)crop.Width; imageProcessorUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); } // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a height parameter has been passed we can get the crop ratio for the width - if (crop != null && crop.Coordinates == null && ratioMode == null && width == null && height != null) + if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null && width == null && height != null) { var widthRatio = (decimal)crop.Width / (decimal)crop.Height; imageProcessorUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); From bd56117c80cc1ff7a8bf89233313b659ed5c7fa6 Mon Sep 17 00:00:00 2001 From: Dan Bramall Date: Thu, 20 Aug 2015 22:08:31 +0100 Subject: [PATCH 06/20] Rounds grid widths to 8 decimal places (like Bootstrap) to fix U4-6569 and U4-6357. --- .../propertyeditors/grid/dialogs/layoutconfig.controller.js | 2 +- .../views/propertyeditors/grid/dialogs/rowconfig.controller.js | 2 +- .../src/views/propertyeditors/grid/grid.controller.js | 2 +- .../src/views/propertyeditors/grid/grid.prevalues.controller.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js index edb7f768d1..628112903d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js @@ -21,7 +21,7 @@ angular.module("umbraco") }; $scope.percentage = function(spans){ - return ((spans / $scope.columns) * 100).toFixed(1); + return ((spans / $scope.columns) * 100).toFixed(8); }; $scope.toggleCollection = function(collection, toggle){ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js index 70f9951de5..7f7b817b8a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js @@ -19,7 +19,7 @@ function RowConfigController($scope) { }; $scope.percentage = function(spans) { - return ((spans / $scope.columns) * 100).toFixed(1); + return ((spans / $scope.columns) * 100).toFixed(8); }; $scope.toggleCollection = function(collection, toggle) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index 9428c64704..24aa722cc3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -424,7 +424,7 @@ angular.module("umbraco") }; $scope.percentage = function (spans) { - return ((spans / $scope.model.config.items.columns) * 100).toFixed(1); + return ((spans / $scope.model.config.items.columns) * 100).toFixed(8); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js index 229a7992f5..bfe5d1ba2e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js @@ -161,7 +161,7 @@ angular.module("umbraco") }; $scope.percentage = function(spans){ - return ((spans / $scope.model.value.columns) * 100).toFixed(1); + return ((spans / $scope.model.value.columns) * 100).toFixed(8); }; $scope.zeroWidthFilter = function (cell) { From 37986f0ed5b74aca75d191efc2a5347ceaebd855 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 1 Sep 2015 10:28:35 +0200 Subject: [PATCH 07/20] Fixes: U4-6193 Inserting an image into grid layout ignores alternative text AltText is used for alt tag instead of caption text which is used below the media. Ensured the altText set in the dialog is saved to the value object. --- .../src/views/propertyeditors/grid/editors/media.controller.js | 3 ++- src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 0e794c29d8..69a85957d9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -14,7 +14,8 @@ angular.module("umbraco") $scope.control.value = { focalPoint: data.focalPoint, id: data.id, - image: data.image + image: data.image, + altText: data.altText }; $scope.setUrl(); diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml index 09d04219f2..f5dfc6459c 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml @@ -14,7 +14,7 @@ } } - @Model.value.caption + @Model.value.altText if (Model.value.caption != null) { From dbe2257b47e801d1f35c4fe0aaa1c37753f9981a Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 1 Sep 2015 13:15:44 +0200 Subject: [PATCH 08/20] U4-7046 - fix DatabaseServerRegistrar issues --- .../ServerRegistrationRepository.cs | 74 +++++++++-------- .../Persistence/RepositoryFactory.cs | 2 +- .../Services/ServerRegistrationService.cs | 81 +++++++++++++------ 3 files changed, 98 insertions(+), 59 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs index fc16009e6c..46e1c139a5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; - using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; @@ -15,52 +15,38 @@ namespace Umbraco.Core.Persistence.Repositories { internal class ServerRegistrationRepository : PetaPocoRepositoryBase, IServerRegistrationRepository { + // create a new disabled cache helper, as we don't want runtime caching, + // and yet we want the static cache so we can statically cache all regs in PerformGetAll. + private readonly ICacheProvider _staticCache; + public ServerRegistrationRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) + : base(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax) { + _staticCache = cache.StaticCache; } protected override IServerRegistration PerformGet(int id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - - var serverDto = Database.First(sql); - if (serverDto == null) - return null; - - var factory = new ServerRegistrationFactory(); - var entity = factory.BuildEntity(serverDto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - entity.ResetDirtyProperties(false); - - return entity; + // use the underlying GetAll which force-caches all registrations + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) { - var factory = new ServerRegistrationFactory(); + // we do NOT want to populate the cache on-demand, because then it might happen + // during a ReadCommited transaction, and reading the registrations under ReadCommited + // is NOT safe because they could be updated in the middle of the read. + // + // the cache is populated by ReloadCache which should only be called from methods + // that ensure proper locking (at least, read-lock in ReadCommited) of the repo. - if (ids.Any()) - { - return Database.Fetch("WHERE id in (@ids)", new { ids = ids }) - .Select(x => factory.BuildEntity(x)); - } - - return Database.Fetch("WHERE id > 0") - .Select(x => factory.BuildEntity(x)); + var all = _staticCache.GetCacheItem>(CacheKey, Enumerable.Empty); + return ids.Length == 0 ? all : all.Where(x => ids.Contains(x.Id)); } protected override IEnumerable PerformGetByQuery(IQuery query) { - var factory = new ServerRegistrationFactory(); - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - - return Database.Fetch(sql).Select(x => factory.BuildEntity(x)); + throw new NotSupportedException("This repository does not support this method"); } protected override Sql GetBaseQuery(bool isCount) @@ -101,6 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories entity.Id = id; entity.ResetDirtyProperties(); + ReloadCache(); } protected override void PersistUpdatedItem(IServerRegistration entity) @@ -113,13 +100,34 @@ namespace Umbraco.Core.Persistence.Repositories Database.Update(dto); entity.ResetDirtyProperties(); + ReloadCache(); + } + + public override void PersistDeletedItem(IEntity entity) + { + base.PersistDeletedItem(entity); + ReloadCache(); + } + + private static readonly string CacheKey = GetCacheTypeKey() + "all"; + + public void ReloadCache() + { + var factory = new ServerRegistrationFactory(); + var all = Database.Fetch("WHERE id > 0") + .Select(x => factory.BuildEntity(x)) + .Cast() + .ToArray(); + _staticCache.ClearCacheItem(CacheKey); + _staticCache.GetCacheItem(CacheKey, () => all); } public void DeactiveStaleServers(TimeSpan staleTimeout) { var timeoutDate = DateTime.Now.Subtract(staleTimeout); - Database.Update("SET isActive=0, isMaster=0 WHERE lastNotifiedDate < @timeoutDate", new { timeoutDate = timeoutDate }); + Database.Update("SET isActive=0, isMaster=0 WHERE lastNotifiedDate < @timeoutDate", new { /*timeoutDate =*/ timeoutDate }); + ReloadCache(); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index bb213eb50f..011c122861 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -230,7 +230,7 @@ namespace Umbraco.Core.Persistence { return new ServerRegistrationRepository( uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + _cacheHelper, _logger, _sqlSyntax); } diff --git a/src/Umbraco.Core/Services/ServerRegistrationService.cs b/src/Umbraco.Core/Services/ServerRegistrationService.cs index 43329ebe5f..5a4a48b7aa 100644 --- a/src/Umbraco.Core/Services/ServerRegistrationService.cs +++ b/src/Umbraco.Core/Services/ServerRegistrationService.cs @@ -6,7 +6,6 @@ using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Sync; @@ -38,7 +37,6 @@ namespace Umbraco.Core.Services _lrepo = new LockingRepository(UowProvider, x => RepositoryFactory.CreateServerRegistrationRepository(x), LockingRepositoryIds, LockingRepositoryIds); - } /// @@ -51,10 +49,11 @@ namespace Umbraco.Core.Services { _lrepo.WithWriteLocked(xr => { - var regs = xr.Repository.GetAll().ToArray(); // faster to query only once + ((ServerRegistrationRepository) xr.Repository).ReloadCache(); // ensure we have up-to-date cache + + var regs = xr.Repository.GetAll().ToArray(); var hasMaster = regs.Any(x => ((ServerRegistration)x).IsMaster); var server = regs.FirstOrDefault(x => x.ServerIdentity.InvariantEquals(serverIdentity)); - var hasServer = server != null; if (server == null) { @@ -71,18 +70,17 @@ namespace Umbraco.Core.Services server.IsMaster = true; xr.Repository.AddOrUpdate(server); - xr.UnitOfWork.Commit(); - xr.Repository.DeactiveStaleServers(staleTimeout); + xr.UnitOfWork.Commit(); // triggers a cache reload + xr.Repository.DeactiveStaleServers(staleTimeout); // triggers a cache reload - // default role is single server - _currentServerRole = ServerRole.Single; + // reload - cheap, cached + regs = xr.Repository.GetAll().ToArray(); - // if registrations contain more than 0/1 server, role is master or slave - // compare to 0 or 1 depending on whether regs already contains the server - if (regs.Length > (hasServer ? 1 : 0)) - _currentServerRole = server.IsMaster - ? ServerRole.Master - : ServerRole.Slave; + // default role is single server, but if registrations contain more + // than one active server, then role is master or slave + _currentServerRole = regs.Count(x => x.IsActive) > 1 + ? (server.IsMaster ? ServerRole.Master : ServerRole.Slave) + : ServerRole.Single; }); } @@ -92,15 +90,27 @@ namespace Umbraco.Core.Services /// The server unique identity. public void DeactiveServer(string serverIdentity) { + //_lrepo.WithWriteLocked(xr => + //{ + // var query = Query.Builder.Where(x => x.ServerIdentity.ToUpper() == serverIdentity.ToUpper()); + // var server = xr.Repository.GetByQuery(query).FirstOrDefault(); + // if (server == null) return; + + // server.IsActive = false; + // server.IsMaster = false; + // xr.Repository.AddOrUpdate(server); + //}); + + // because the repository caches "all" and has queries disabled... + _lrepo.WithWriteLocked(xr => { - var query = Query.Builder.Where(x => x.ServerIdentity.ToUpper() == serverIdentity.ToUpper()); - var server = xr.Repository.GetByQuery(query).FirstOrDefault(); - if (server == null) return; + ((ServerRegistrationRepository)xr.Repository).ReloadCache(); // ensure we have up-to-date cache - server.IsActive = false; - server.IsMaster = false; - xr.Repository.AddOrUpdate(server); + var server = xr.Repository.GetAll().FirstOrDefault(x => x.ServerIdentity.InvariantEquals(serverIdentity)); + if (server == null) return; + server.IsActive = server.IsMaster = false; + xr.Repository.AddOrUpdate(server); // will trigger a cache reload }); } @@ -119,11 +129,32 @@ namespace Umbraco.Core.Services /// public IEnumerable GetActiveServers() { - return _lrepo.WithReadLocked(xr => - { - var query = Query.Builder.Where(x => x.IsActive); - return xr.Repository.GetByQuery(query).ToArray(); - }); + //return _lrepo.WithReadLocked(xr => + //{ + // var query = Query.Builder.Where(x => x.IsActive); + // return xr.Repository.GetByQuery(query).ToArray(); + //}); + + // because the repository caches "all" we should use the following code + // in order to ensure we use the cache and not hit the database each time + + //return _lrepo.WithReadLocked(xr => xr.Repository.GetAll().Where(x => x.IsActive).ToArray()); + + // however, WithReadLocked (as any other LockingRepository methods) will attempt + // to properly lock the repository using a database-level lock, which wants + // the transaction isolation level to be RepeatableRead, which it is not by default, + // and then, see U4-7046. + // + // in addition, LockingRepository methods need to hit the database in order to + // ensure proper locking, and so if we know that the repository might not need the + // database, we cannot use these methods - and then what? + // + // this raises a good number of questions, including whether caching anything in + // repositories works at all in a LB environment - TODO: figure it out + + var uow = UowProvider.GetUnitOfWork(); + var repo = RepositoryFactory.CreateServerRegistrationRepository(uow); + return repo.GetAll().Where(x => x.IsActive).ToArray(); // fast, cached } /// From 62afc06f56aa2b550b86f50d737f67321afe4180 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 1 Sep 2015 14:30:30 +0200 Subject: [PATCH 09/20] Fixes: COU-162 Revision Name is Lowercase After Transfer Removed lowercasing of urls routed through legacy iframe injection controller. Regex for javascript handlers changed to ignore casing. --- src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js index 8e58535c74..fcc8e8c2ed 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js @@ -9,7 +9,7 @@ */ function LegacyController($scope, $routeParams, $element) { - var url = decodeURIComponent($routeParams.url.toLowerCase().replace(/javascript\:/g, "")); + var url = decodeURIComponent($routeParams.url.replace(/javascript\:/gi, "")); //split into path and query var urlParts = url.split("?"); var extIndex = urlParts[0].lastIndexOf("."); From 7848ea5ec0e7b80ce748791bf54fcc3953564d1e Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 31 Aug 2015 18:59:51 +0200 Subject: [PATCH 10/20] U4-7038 - IPublishedContentWithKey for contents, members --- .../Models/IPublishedContentWithKey.cs | 14 ++++++++++++++ .../PublishedContentExtended.cs | 17 ++++++++++++++--- .../PublishedContentWithKeyExtended.cs | 14 ++++++++++++++ .../PublishedContentWithKeyModel.cs | 13 +++++++++++++ .../PublishedContentWithKeyWrapped.cs | 17 +++++++++++++++++ .../Services/EntityXmlSerializer.cs | 1 + src/Umbraco.Core/Umbraco.Core.csproj | 4 ++++ src/Umbraco.Web.UI/config/log4net.config | 2 +- .../Models/PublishedContentWithKeyBase.cs | 15 +++++++++++++++ .../PublishedCache/MemberPublishedContent.cs | 7 ++++++- .../XmlPublishedCache/XmlPublishedContent.cs | 15 ++++++++++++++- src/Umbraco.Web/PublishedContentExtensions.cs | 12 +++++++++++- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/umbraco.cms/businesslogic/CMSNode.cs | 1 + src/umbraco.cms/businesslogic/Content.cs | 1 + src/umbraco.cms/businesslogic/web/Document.cs | 1 + 16 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 src/Umbraco.Core/Models/IPublishedContentWithKey.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyExtended.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyModel.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyWrapped.cs create mode 100644 src/Umbraco.Web/Models/PublishedContentWithKeyBase.cs diff --git a/src/Umbraco.Core/Models/IPublishedContentWithKey.cs b/src/Umbraco.Core/Models/IPublishedContentWithKey.cs new file mode 100644 index 0000000000..b0e71221b2 --- /dev/null +++ b/src/Umbraco.Core/Models/IPublishedContentWithKey.cs @@ -0,0 +1,14 @@ +using System; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a cached content with a GUID key. + /// + /// This is temporary, because we cannot add the Key property to IPublishedContent without + /// breaking backward compatibility. With v8, it will be merged into IPublishedContent. + public interface IPublishedContentWithKey : IPublishedContent + { + Guid Key { get; } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs index 8c0eeef86f..fef066e0b1 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs @@ -62,9 +62,20 @@ namespace Umbraco.Core.Models.PublishedContent // model and therefore returned the original content unchanged. var model = content.CreateModel(); - var extended = model == content // == means the factory did not create a model - ? new PublishedContentExtended(content) // so we have to extend - : model; // else we can use what the factory returned + IPublishedContent extended; + if (model == content) // == means the factory did not create a model + { + // so we have to extend + var contentWithKey = content as IPublishedContentWithKey; + extended = contentWithKey == null + ? new PublishedContentExtended(content) + : new PublishedContentWithKeyExtended(contentWithKey); + } + else + { + // else we can use what the factory returned + extended = model; + } // so extended should always implement IPublishedContentExtended, however if // by mistake the factory returned a different object that does not implement diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyExtended.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyExtended.cs new file mode 100644 index 0000000000..492fd79796 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyExtended.cs @@ -0,0 +1,14 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + public class PublishedContentWithKeyExtended : PublishedContentExtended, IPublishedContentWithKey + { + // protected for models, internal for PublishedContentExtended static Extend method + protected internal PublishedContentWithKeyExtended(IPublishedContentWithKey content) + : base(content) + { } + + public Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyModel.cs new file mode 100644 index 0000000000..4761a52617 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyModel.cs @@ -0,0 +1,13 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + public abstract class PublishedContentWithKeyModel : PublishedContentModel, IPublishedContentWithKey + { + protected PublishedContentWithKeyModel(IPublishedContentWithKey content) + : base (content) + { } + + public Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyWrapped.cs new file mode 100644 index 0000000000..35d7dd6f1f --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyWrapped.cs @@ -0,0 +1,17 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides an abstract base class for IPublishedContentWithKey implementations that + /// wrap and extend another IPublishedContentWithKey. + /// + public class PublishedContentWithKeyWrapped : PublishedContentWrapped, IPublishedContentWithKey + { + protected PublishedContentWithKeyWrapped(IPublishedContentWithKey content) + : base(content) + { } + + public virtual Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } } + } +} diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index 949b4e61c7..d0c89c32a0 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -440,6 +440,7 @@ namespace Umbraco.Core.Services var xml = new XElement(nodeName, new XAttribute("id", contentBase.Id), + new XAttribute("key", contentBase.Key), new XAttribute("parentID", contentBase.Level > 1 ? contentBase.ParentId : -1), new XAttribute("level", contentBase.Level), new XAttribute("creatorID", contentBase.CreatorId), diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f3e6cac1f1..b838db5441 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -364,10 +364,14 @@ + + + + diff --git a/src/Umbraco.Web.UI/config/log4net.config b/src/Umbraco.Web.UI/config/log4net.config index aa96f5a26a..497fd4471f 100644 --- a/src/Umbraco.Web.UI/config/log4net.config +++ b/src/Umbraco.Web.UI/config/log4net.config @@ -2,7 +2,7 @@ - + diff --git a/src/Umbraco.Web/Models/PublishedContentWithKeyBase.cs b/src/Umbraco.Web/Models/PublishedContentWithKeyBase.cs new file mode 100644 index 0000000000..52bd8b2f59 --- /dev/null +++ b/src/Umbraco.Web/Models/PublishedContentWithKeyBase.cs @@ -0,0 +1,15 @@ +using System; +using System.Diagnostics; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models +{ + /// + /// Provide an abstract base class for IPublishedContent implementations. + /// + [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] + public abstract class PublishedContentWithKeyBase : PublishedContentBase, IPublishedContentWithKey + { + public abstract Guid Key { get; } + } +} diff --git a/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs b/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs index 5c0050c2a1..35b3d6ee62 100644 --- a/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.PublishedCache /// /// Exposes a member object as IPublishedContent /// - public sealed class MemberPublishedContent : PublishedContentBase + public sealed class MemberPublishedContent : PublishedContentWithKeyBase { private readonly IMember _member; @@ -150,6 +150,11 @@ namespace Umbraco.Web.PublishedCache get { return _member.Id; } } + public override Guid Key + { + get { return _member.Key; } + } + public override int TemplateId { get { throw new NotSupportedException(); } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index 5450d4063f..4bc2f2388a 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// [Serializable] [XmlType(Namespace = "http://umbraco.org/webservices/")] - internal class XmlPublishedContent : PublishedContentBase + internal class XmlPublishedContent : PublishedContentWithKeyBase { /// /// Initializes a new instance of the XmlPublishedContent class with an Xml node. @@ -64,6 +64,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private IPublishedContent _parent; private int _id; + private Guid _key; private int _template; private string _name; private string _docTypeAlias; @@ -150,6 +151,16 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } + public override Guid Key + { + get + { + if (_initialized == false) + Initialize(); + return _key; + } + } + public override int TemplateId { get @@ -348,6 +359,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (_xmlNode.Attributes != null) { _id = int.Parse(_xmlNode.Attributes.GetNamedItem("id").Value); + if (_xmlNode.Attributes.GetNamedItem("key") != null) // because, migration + _key = Guid.Parse(_xmlNode.Attributes.GetNamedItem("key").Value); if (_xmlNode.Attributes.GetNamedItem("template") != null) _template = int.Parse(_xmlNode.Attributes.GetNamedItem("template").Value); if (_xmlNode.Attributes.GetNamedItem("sortOrder") != null) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 8f7dbc31df..d6cf3b3141 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -19,7 +19,17 @@ namespace Umbraco.Web /// Provides extension methods for IPublishedContent. /// public static class PublishedContentExtensions - { + { + #region Key + + public static Guid GetKey(this IPublishedContent content) + { + var contentWithKey = content as IPublishedContentWithKey; + return contentWithKey == null ? Guid.Empty : contentWithKey.Key; + } + + #endregion + #region Urls /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b8ed24d5bb..1ad99b4315 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -303,6 +303,7 @@ + diff --git a/src/umbraco.cms/businesslogic/CMSNode.cs b/src/umbraco.cms/businesslogic/CMSNode.cs index 90091cc80e..661e620056 100644 --- a/src/umbraco.cms/businesslogic/CMSNode.cs +++ b/src/umbraco.cms/businesslogic/CMSNode.cs @@ -1181,6 +1181,7 @@ order by level,sortOrder"; { // attributes x.Attributes.Append(xmlHelper.addAttribute(xd, "id", this.Id.ToString())); + x.Attributes.Append(xmlHelper.addAttribute(xd, "key", this.UniqueId.ToString())); if (this.Level > 1) x.Attributes.Append(xmlHelper.addAttribute(xd, "parentID", this.Parent.Id.ToString())); else diff --git a/src/umbraco.cms/businesslogic/Content.cs b/src/umbraco.cms/businesslogic/Content.cs index f81ecce499..36eafb91a2 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -408,6 +408,7 @@ namespace umbraco.cms.businesslogic // attributes x.Attributes.Append(XmlHelper.AddAttribute(xd, "id", this.Id.ToString())); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "key", this.UniqueId.ToString())); x.Attributes.Append(XmlHelper.AddAttribute(xd, "version", this.Version.ToString())); if (this.Level > 1) x.Attributes.Append(XmlHelper.AddAttribute(xd, "parentID", this.Parent.Id.ToString())); diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 1df6dd7c40..0c5f2c3025 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -1288,6 +1288,7 @@ namespace umbraco.cms.businesslogic.web // attributes x.Attributes.Append(addAttribute(xd, "id", Id.ToString())); + x.Attributes.Append(addAttribute(xd, "key", UniqueId.ToString())); // x.Attributes.Append(addAttribute(xd, "version", Version.ToString())); if (Level > 1) x.Attributes.Append(addAttribute(xd, "parentID", Parent.Id.ToString())); From dfbdf99bbc066bbd39f69762b9137d3e09454ff2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 1 Sep 2015 14:49:50 +0200 Subject: [PATCH 11/20] U4-7038 - IPublishedContentWithKey for medias --- .../XmlPublishedCache/PublishedMediaCache.cs | 15 +++++++++++++-- src/UmbracoExamine/UmbracoContentIndexer.cs | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 2e437ccc97..61dd50e610 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -273,6 +273,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache values.Add("level", values["__Path"].Split(',').Length.ToString()); } + // because, migration + if (values.ContainsKey("key") == false) + values["key"] = Guid.Empty.ToString(); + return new CacheValues { Values = values, @@ -321,6 +325,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache result.Current.MoveToParent(); } } + // because, migration + if (values.ContainsKey("key") == false) + values["key"] = Guid.Empty.ToString(); //add the user props while (result.MoveNext()) { @@ -526,7 +533,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// This is a helper class and definitely not intended for public use, it expects that all of the values required /// to create an IPublishedContent exist in the dictionary by specific aliases. /// - internal class DictionaryPublishedContent : PublishedContentBase + internal class DictionaryPublishedContent : PublishedContentWithKeyBase { // note: I'm not sure this class fully complies with IPublishedContent rules especially // I'm not sure that _properties contains all properties including those without a value, @@ -534,7 +541,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // List of properties that will appear in the XML and do not match // anything in the ContentType, so they must be ignored. - private static readonly string[] IgnoredKeys = { "version", "isDoc", "key" }; + private static readonly string[] IgnoredKeys = { "version", "isDoc" }; public DictionaryPublishedContent( IDictionary valueDictionary, @@ -555,6 +562,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache LoadedFromExamine = fromExamine; ValidateAndSetProperty(valueDictionary, val => _id = int.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int! + ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key"); // wtf are we dealing with templates for medias?! ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); ValidateAndSetProperty(valueDictionary, val => _sortOrder = int.Parse(val), "sortOrder"); @@ -664,6 +672,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache get { return _id; } } + public override Guid Key { get { return _key; } } + public override int TemplateId { get @@ -803,6 +813,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private readonly List _keysAdded = new List(); private int _id; + private Guid _key; private int _templateId; private int _sortOrder; private string _name; diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index de76ab8e79..613304c4ff 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -145,6 +145,7 @@ namespace UmbracoExamine = new List { new StaticField("id", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), + new StaticField("key", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), new StaticField( "version", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), new StaticField( "parentID", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), new StaticField( "level", FieldIndexTypes.NOT_ANALYZED, true, "NUMBER"), From d4fb28607d71676856106407988bd91058c0072c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 1 Sep 2015 15:35:29 +0200 Subject: [PATCH 12/20] U4-7053 Update ImageProcessor dependency for core #U4-7053 Fixed --- build/NuSpecs/UmbracoCms.Core.nuspec | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 12 ++++++------ src/Umbraco.Web.UI/packages.config | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 1dfec6062c..0c7d51334e 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -33,8 +33,8 @@ - - + + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index e8d7ea6d99..39258f6b1a 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -135,13 +135,13 @@ False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - False - ..\packages\ImageProcessor.1.9.5.0\lib\ImageProcessor.dll + + ..\packages\ImageProcessor.2.2.8.0\lib\net45\ImageProcessor.dll + True - - False - ..\packages\ImageProcessor.Web.3.3.1.0\lib\net45\ImageProcessor.Web.dll + + ..\packages\ImageProcessor.Web.4.3.6.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 84ee708179..efab0db7da 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -5,8 +5,8 @@ - - + + From 42b689867ebe28ad496a11e3214f0c489a6257dd Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 1 Sep 2015 15:46:34 +0200 Subject: [PATCH 13/20] U4-6992,U4-7046 - more fixes & tests --- .../ServerRegistrationRepository.cs | 17 +++- .../Persistence/RepositoryFactory.cs | 2 +- .../ServerRegistrationRepositoryTest.cs | 90 ++++++++++--------- 3 files changed, 62 insertions(+), 47 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs index 46e1c139a5..20d3c38042 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs @@ -15,14 +15,23 @@ namespace Umbraco.Core.Persistence.Repositories { internal class ServerRegistrationRepository : PetaPocoRepositoryBase, IServerRegistrationRepository { - // create a new disabled cache helper, as we don't want runtime caching, - // and yet we want the static cache so we can statically cache all regs in PerformGetAll. private readonly ICacheProvider _staticCache; - public ServerRegistrationRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public ServerRegistrationRepository(IDatabaseUnitOfWork work, ICacheProvider staticCache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax) { - _staticCache = cache.StaticCache; + _staticCache = staticCache; + } + + protected override int PerformCount(IQuery query) + { + throw new NotSupportedException("This repository does not support this method"); + } + + protected override bool PerformExists(int id) + { + // use the underlying GetAll which force-caches all registrations + return GetAll().Any(x => x.Id == id); } protected override IServerRegistration PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 011c122861..f9367d8433 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -230,7 +230,7 @@ namespace Umbraco.Core.Persistence { return new ServerRegistrationRepository( uow, - _cacheHelper, + _cacheHelper.StaticCache, _logger, _sqlSyntax); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs index b94339970f..bcb49068fa 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs @@ -4,6 +4,7 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -18,17 +19,20 @@ namespace Umbraco.Tests.Persistence.Repositories [TestFixture] public class ServerRegistrationRepositoryTest : BaseDatabaseFactoryTest { + private ICacheProvider _staticCache; + [SetUp] public override void Initialize() { base.Initialize(); + _staticCache = new StaticCacheProvider(); CreateTestData(); } - private ServerRegistrationRepository CreateRepositor(IDatabaseUnitOfWork unitOfWork) + private ServerRegistrationRepository CreateRepository(IDatabaseUnitOfWork unitOfWork) { - return new ServerRegistrationRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); + return new ServerRegistrationRepository(unitOfWork, _staticCache, Mock.Of(), SqlSyntax); } [Test] @@ -39,7 +43,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); // Act - using (var repository = CreateRepositor(unitOfWork)) + using (var repository = CreateRepository(unitOfWork)) { var server = new ServerRegistration("http://shazwazza.com", "COMPUTER1", DateTime.Now); repository.AddOrUpdate(server); @@ -57,7 +61,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); // Act - using (var repository = CreateRepositor(unitOfWork)) + using (var repository = CreateRepository(unitOfWork)) { var server = repository.Get(1); server.ServerIdentity = "COMPUTER2"; @@ -75,7 +79,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); // Act - using (var repository = CreateRepositor(unitOfWork)) + using (var repository = CreateRepository(unitOfWork)) { // Assert Assert.That(repository, Is.Not.Null); @@ -88,7 +92,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - using (var repository = CreateRepositor(unitOfWork)) + using (var repository = CreateRepository(unitOfWork)) { // Act var server = repository.Get(1); @@ -108,7 +112,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - using (var repository = CreateRepositor(unitOfWork)) + using (var repository = CreateRepository(unitOfWork)) { // Act var servers = repository.GetAll(); @@ -119,39 +123,41 @@ namespace Umbraco.Tests.Persistence.Repositories } - [Test] - public void Can_Perform_GetByQuery_On_Repository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - using (var repository = CreateRepositor(unitOfWork)) - { - // Act - var query = Query.Builder.Where(x => x.ServerIdentity.ToUpper() == "COMPUTER3"); - var result = repository.GetByQuery(query); + // queries are not supported due to in-memory caching - // Assert - Assert.AreEqual(1, result.Count()); - } - } + //[Test] + //public void Can_Perform_GetByQuery_On_Repository() + //{ + // // Arrange + // var provider = new PetaPocoUnitOfWorkProvider(Logger); + // var unitOfWork = provider.GetUnitOfWork(); + // using (var repository = CreateRepository(unitOfWork)) + // { + // // Act + // var query = Query.Builder.Where(x => x.ServerIdentity.ToUpper() == "COMPUTER3"); + // var result = repository.GetByQuery(query); - [Test] - public void Can_Perform_Count_On_Repository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - using (var repository = CreateRepositor(unitOfWork)) - { - // Act - var query = Query.Builder.Where(x => x.ServerAddress.StartsWith("http://")); - int count = repository.Count(query); + // // Assert + // Assert.AreEqual(1, result.Count()); + // } + //} - // Assert - Assert.That(count, Is.EqualTo(2)); - } - } + //[Test] + //public void Can_Perform_Count_On_Repository() + //{ + // // Arrange + // var provider = new PetaPocoUnitOfWorkProvider(Logger); + // var unitOfWork = provider.GetUnitOfWork(); + // using (var repository = CreateRepository(unitOfWork)) + // { + // // Act + // var query = Query.Builder.Where(x => x.ServerAddress.StartsWith("http://")); + // int count = repository.Count(query); + + // // Assert + // Assert.That(count, Is.EqualTo(2)); + // } + //} [Test] public void Can_Perform_Add_On_Repository() @@ -159,7 +165,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - using (var repository = CreateRepositor(unitOfWork)) + using (var repository = CreateRepository(unitOfWork)) { // Act var server = new ServerRegistration("http://shazwazza.com", "COMPUTER4", DateTime.Now); @@ -178,7 +184,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - using (var repository = CreateRepositor(unitOfWork)) + using (var repository = CreateRepository(unitOfWork)) { // Act var server = repository.Get(2); @@ -203,7 +209,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - using (var repository = CreateRepositor(unitOfWork)) + using (var repository = CreateRepository(unitOfWork)) { // Act var server = repository.Get(3); @@ -224,7 +230,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - using (var repository = CreateRepositor(unitOfWork)) + using (var repository = CreateRepository(unitOfWork)) { // Act var exists = repository.Exists(3); @@ -246,7 +252,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var provider = new PetaPocoUnitOfWorkProvider(Logger); using (var unitOfWork = provider.GetUnitOfWork()) - using (var repository = new ServerRegistrationRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax)) + using (var repository = CreateRepository(unitOfWork)) { repository.AddOrUpdate(new ServerRegistration("http://localhost", "COMPUTER1", DateTime.Now) { IsActive = true }); repository.AddOrUpdate(new ServerRegistration("http://www.mydomain.com", "COMPUTER2", DateTime.Now)); From 5d7cbd34772d5314f8056964be1993d14e79b1e3 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 1 Sep 2015 16:11:26 +0200 Subject: [PATCH 14/20] U4-7038 - fix bugs + tests --- .../Services/EntityXmlSerializer.cs | 1 - .../PublishedMediaCacheTests.cs | 41 ++++++++++++++----- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index d0c89c32a0..772009e89a 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -100,7 +100,6 @@ namespace Umbraco.Core.Services xml.Add(new XAttribute("loginName", member.Username)); xml.Add(new XAttribute("email", member.Email)); - xml.Add(new XAttribute("key", member.Key)); return xml; } diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index d93c4d9e3c..32bbd34a77 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; +using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; @@ -106,6 +107,14 @@ namespace Umbraco.Tests.Cache.PublishedCache DoAssert(dicDoc); } + [Test] + public void DictionaryDocument_Key() + { + var key = Guid.NewGuid(); + var dicDoc = GetDictionaryDocument(keyVal: key); + DoAssert(dicDoc, keyVal: key); + } + [Test] public void DictionaryDocument_Get_Children() { @@ -122,10 +131,12 @@ namespace Umbraco.Tests.Cache.PublishedCache Assert.AreEqual(444555, dicDoc.Children.ElementAt(1).Id); } - [Test] - public void Convert_From_Search_Result() + [TestCase(true)] + [TestCase(false)] + public void Convert_From_Search_Result(bool withKey) { var ctx = GetUmbracoContext("/test", 1234); + var key = Guid.NewGuid(); var result = new SearchResult() { @@ -138,6 +149,7 @@ namespace Umbraco.Tests.Cache.PublishedCache result.Fields.Add("__Path", "-1,1234"); result.Fields.Add("__nodeName", "Test"); result.Fields.Add("id", "1234"); + if (withKey) result.Fields.Add("key", key.ToString()); result.Fields.Add("nodeName", "Test"); result.Fields.Add("nodeTypeAlias", Constants.Conventions.MediaTypes.Image); result.Fields.Add("parentID", "-1"); @@ -148,21 +160,24 @@ namespace Umbraco.Tests.Cache.PublishedCache var store = new PublishedMediaCache(ctx.Application); var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result)); - DoAssert(doc, 1234, 0, 0, "", "Image", 0, "Shannon", "", 0, 0, "-1,1234", default(DateTime), DateTime.Parse("2012-07-16T10:34:09"), 2); + DoAssert(doc, 1234, withKey ? key : default(Guid), 0, 0, "", "Image", 0, "Shannon", "", 0, 0, "-1,1234", default(DateTime), DateTime.Parse("2012-07-16T10:34:09"), 2); Assert.AreEqual(null, doc.Parent); } - [Test] - public void Convert_From_XPath_Navigator() + [TestCase(true)] + [TestCase(false)] + public void Convert_From_XPath_Navigator(bool withKey) { var ctx = GetUmbracoContext("/test", 1234); + var key = Guid.NewGuid(); var xmlDoc = GetMediaXml(); + if (withKey) ((XmlElement)xmlDoc.DocumentElement.FirstChild).SetAttribute("key", key.ToString()); var navigator = xmlDoc.SelectSingleNode("/root/Image").CreateNavigator(); var cache = new PublishedMediaCache(ctx.Application); var doc = cache.CreateFromCacheValues(cache.ConvertFromXPathNavigator(navigator, true)); - DoAssert(doc, 2000, 0, 2, "image1", "Image", 2044, "Shannon", "Shannon2", 22, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); + DoAssert(doc, 2000, withKey ? key : default(Guid), 0, 2, "image1", "Image", 2044, "Shannon", "Shannon2", 22, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); Assert.AreEqual(null, doc.Parent); Assert.AreEqual(2, doc.Children.Count()); Assert.AreEqual(2001, doc.Children.ElementAt(0).Id); @@ -197,6 +212,7 @@ namespace Umbraco.Tests.Cache.PublishedCache private Dictionary GetDictionary( int id, + Guid key, int parentId, string idKey, string templateKey, @@ -207,6 +223,7 @@ namespace Umbraco.Tests.Cache.PublishedCache return new Dictionary() { {idKey, id.ToString()}, + {"key", key.ToString()}, {templateKey, "333"}, {"sortOrder", "44"}, {nodeNameKey, "Testing"}, @@ -232,6 +249,7 @@ namespace Umbraco.Tests.Cache.PublishedCache string nodeTypeAliasKey = "nodeTypeAlias", string pathKey = "path", int idVal = 1234, + Guid keyVal = default(Guid), int parentIdVal = 321, IEnumerable children = null) { @@ -239,10 +257,10 @@ namespace Umbraco.Tests.Cache.PublishedCache children = new List(); var dicDoc = new PublishedMediaCache.DictionaryPublishedContent( //the dictionary - GetDictionary(idVal, parentIdVal, idKey, templateKey, nodeNameKey, nodeTypeAliasKey, pathKey), + GetDictionary(idVal, keyVal, parentIdVal, idKey, templateKey, nodeNameKey, nodeTypeAliasKey, pathKey), //callback to get the parent d => new PublishedMediaCache.DictionaryPublishedContent( - GetDictionary(parentIdVal, -1, idKey, templateKey, nodeNameKey, nodeTypeAliasKey, pathKey), + GetDictionary(parentIdVal, default(Guid), -1, idKey, templateKey, nodeNameKey, nodeTypeAliasKey, pathKey), //there is no parent a => null, //we're not going to test this so ignore @@ -261,6 +279,7 @@ namespace Umbraco.Tests.Cache.PublishedCache private void DoAssert( PublishedMediaCache.DictionaryPublishedContent dicDoc, int idVal = 1234, + Guid keyVal = default(Guid), int templateIdVal = 333, int sortOrderVal = 44, string urlNameVal = "testing", @@ -281,7 +300,7 @@ namespace Umbraco.Tests.Cache.PublishedCache if (!updateDateVal.HasValue) updateDateVal = DateTime.Parse("2012-01-03"); - DoAssert((IPublishedContent)dicDoc, idVal, templateIdVal, sortOrderVal, urlNameVal, nodeTypeAliasVal, nodeTypeIdVal, writerNameVal, + DoAssert((IPublishedContent)dicDoc, idVal, keyVal, templateIdVal, sortOrderVal, urlNameVal, nodeTypeAliasVal, nodeTypeIdVal, writerNameVal, creatorNameVal, writerIdVal, creatorIdVal, pathVal, createDateVal, updateDateVal, levelVal); //now validate the parentId that has been parsed, this doesn't exist on the IPublishedContent @@ -291,7 +310,8 @@ namespace Umbraco.Tests.Cache.PublishedCache private void DoAssert( IPublishedContent doc, int idVal = 1234, - int templateIdVal = 333, + Guid keyVal = default(Guid), + int templateIdVal = 333, int sortOrderVal = 44, string urlNameVal = "testing", string nodeTypeAliasVal = "myType", @@ -311,6 +331,7 @@ namespace Umbraco.Tests.Cache.PublishedCache updateDateVal = DateTime.Parse("2012-01-03"); Assert.AreEqual(idVal, doc.Id); + Assert.AreEqual(keyVal, doc.GetKey()); Assert.AreEqual(templateIdVal, doc.TemplateId); Assert.AreEqual(sortOrderVal, doc.SortOrder); Assert.AreEqual(urlNameVal, doc.UrlName); From fc8c92e9225c2dff3c9e66c7888a0c8a8032dd46 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 2 Sep 2015 14:44:02 +0200 Subject: [PATCH 15/20] U4-7056 - sync BatchedDatabaseServerMessenger on all requests --- src/Umbraco.Web/BatchedDatabaseServerMessenger.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index c20f330e13..714b79514d 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -45,22 +45,16 @@ namespace Umbraco.Web private void UmbracoModule_RouteAttempt(object sender, RoutableAttemptEventArgs e) { + // as long as umbraco is ready & configured, sync switch (e.Outcome) { case EnsureRoutableOutcome.IsRoutable: - Sync(); - break; case EnsureRoutableOutcome.NotDocumentRequest: - //so it's not a document request, we'll check if it's a back office request - if (e.HttpContext.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) - { - //it's a back office request, we should sync! - Sync(); - } + case EnsureRoutableOutcome.NoContent: + Sync(); break; //case EnsureRoutableOutcome.NotReady: //case EnsureRoutableOutcome.NotConfigured: - //case EnsureRoutableOutcome.NoContent: //default: // break; } From 42e5b4e86720a862ea72a78e78050cf7d3c78bcb Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 2 Sep 2015 15:19:37 +0200 Subject: [PATCH 16/20] U4-7055 - encodeURIComponent queries in Examine dashboard --- .../src/views/dashboard/developer/examinemgmt.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js index e439d0e37f..4a19ea9926 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js @@ -43,7 +43,7 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo umbRequestHelper.resourcePromise( $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearchResults", { searcherName: searcher.name, - query: searcher.searchText, + query: encodeURIComponent(searcher.searchText), queryType: searcher.searchType })), 'Failed to search') From 37e6e61eff260c0b415056e271492dddc6469505 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 2 Sep 2015 16:41:00 +0200 Subject: [PATCH 17/20] Manual-merge pull request #750 dealing with U4-7060 --- src/Umbraco.Core/Services/ContentService.cs | 32 ++++++++++--- src/Umbraco.Core/Services/IContentService.cs | 14 +++++- src/Umbraco.Tests/Services/BaseServiceTest.cs | 3 +- .../Services/ContentServiceTests.cs | 46 +++++++++++++++++++ 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index c9df0d685a..3cc59708e9 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1416,7 +1416,7 @@ namespace Umbraco.Core.Services /// /// Copies an object by creating a new Content object of the same type and copies all data from the current - /// to the new copy which is returned. + /// to the new copy which is returned. Recursively copies all children. /// /// The to copy /// Id of the Content's new Parent @@ -1424,6 +1424,21 @@ namespace Umbraco.Core.Services /// Optional Id of the User copying the Content /// The newly created object public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0) + { + return Copy(content, parentId, relateToOriginal, true, userId); + } + + /// + /// Copies an object by creating a new Content object of the same type and copies all data from the current + /// to the new copy which is returned. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// A value indicating whether to recursively copy children. + /// Optional Id of the User copying the Content + /// The newly created object + public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0) { //TODO: This all needs to be managed correctly so that the logic is submitted in one // transaction, the CRUD needs to be moved to the repo @@ -1466,13 +1481,16 @@ namespace Umbraco.Core.Services } } - //Look for children and copy those as well - var children = GetChildren(content.Id); - foreach (var child in children) + if (recursive) { - //TODO: This shouldn't recurse back to this method, it should be done in a private method - // that doesn't have a nested lock and so we can perform the entire operation in one commit. - Copy(child, copy.Id, relateToOriginal, userId); + //Look for children and copy those as well + var children = GetChildren(content.Id); + foreach (var child in children) + { + //TODO: This shouldn't recurse back to this method, it should be done in a private method + // that doesn't have a nested lock and so we can perform the entire operation in one commit. + Copy(child, copy.Id, relateToOriginal, true, userId); + } } Copied.RaiseEvent(new CopyEventArgs(content, copy, false, parentId, relateToOriginal), this); diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 8b308e5f21..c686cf4891 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -481,7 +481,7 @@ namespace Umbraco.Core.Services /// /// Copies an object by creating a new Content object of the same type and copies all data from the current - /// to the new copy which is returned. + /// to the new copy, which is returned. Recursively copies all children. /// /// The to copy /// Id of the Content's new Parent @@ -490,6 +490,18 @@ namespace Umbraco.Core.Services /// The newly created object IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0); + /// + /// Copies an object by creating a new Content object of the same type and copies all data from the current + /// to the new copy which is returned. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// A value indicating whether to recursively copy children. + /// Optional Id of the User copying the Content + /// The newly created object + IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0); + /// /// Checks if the passed in can be published based on the anscestors publish state. /// diff --git a/src/Umbraco.Tests/Services/BaseServiceTest.cs b/src/Umbraco.Tests/Services/BaseServiceTest.cs index b711f7c3ac..ebd0be3b55 100644 --- a/src/Umbraco.Tests/Services/BaseServiceTest.cs +++ b/src/Umbraco.Tests/Services/BaseServiceTest.cs @@ -51,7 +51,6 @@ namespace Umbraco.Tests.Services Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); trashed.Trashed = true; ServiceContext.ContentService.Save(trashed, 0); - - } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 176db015f8..841789a980 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1220,6 +1220,52 @@ namespace Umbraco.Tests.Services //Assert.AreNotEqual(content.Name, copy.Name); } + [Test] + public void Can_Copy_Recursive() + { + // Arrange + var contentService = ServiceContext.ContentService; + var temp = contentService.GetById(NodeDto.NodeIdSeed + 1); + Assert.AreEqual("Home", temp.Name); + Assert.AreEqual(2, temp.Children().Count()); + + // Act + var copy = contentService.Copy(temp, temp.ParentId, false, true, 0); + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); + + // Assert + Assert.That(copy, Is.Not.Null); + Assert.That(copy.Id, Is.Not.EqualTo(content.Id)); + Assert.AreNotSame(content, copy); + Assert.AreEqual(2, copy.Children().Count()); + + var child = contentService.GetById(NodeDto.NodeIdSeed + 2); + var childCopy = copy.Children().First(); + Assert.AreEqual(childCopy.Name, child.Name); + Assert.AreNotEqual(childCopy.Id, child.Id); + Assert.AreNotEqual(childCopy.Key, child.Key); + } + + [Test] + public void Can_Copy_NonRecursive() + { + // Arrange + var contentService = ServiceContext.ContentService; + var temp = contentService.GetById(NodeDto.NodeIdSeed + 1); + Assert.AreEqual("Home", temp.Name); + Assert.AreEqual(2, temp.Children().Count()); + + // Act + var copy = contentService.Copy(temp, temp.ParentId, false, false, 0); + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); + + // Assert + Assert.That(copy, Is.Not.Null); + Assert.That(copy.Id, Is.Not.EqualTo(content.Id)); + Assert.AreNotSame(content, copy); + Assert.AreEqual(0, copy.Children().Count()); + } + [Test] public void Can_Copy_Content_With_Tags() { From b03d7884bbee61fd5a5b162d7d19dc558a49f88b Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 3 Sep 2015 15:11:49 +0200 Subject: [PATCH 18/20] U4-7042 - bugfix the physical filesystem --- src/Umbraco.Core/IO/IOHelper.cs | 25 ++++++++--------- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 20 +++++++++++-- .../Repositories/ScriptRepository.cs | 16 +++++++++-- .../Repositories/StylesheetRepository.cs | 20 +++++++++---- .../IO/PhysicalFileSystemTests.cs | 28 +++++++++++++++++++ 5 files changed, 84 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 2d87f634f4..8378081394 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -152,12 +152,7 @@ namespace Umbraco.Core.IO /// A value indicating whether the filepath is valid. internal static bool VerifyEditPath(string filePath, string validDir) { - if (filePath.StartsWith(MapPath(SystemDirectories.Root)) == false) - filePath = MapPath(filePath); - if (validDir.StartsWith(MapPath(SystemDirectories.Root)) == false) - validDir = MapPath(validDir); - - return filePath.StartsWith(validDir); + return VerifyEditPath(filePath, new[] { validDir }); } /// @@ -182,12 +177,17 @@ namespace Umbraco.Core.IO /// A value indicating whether the filepath is valid. internal static bool VerifyEditPath(string filePath, IEnumerable validDirs) { + var mappedRoot = MapPath(SystemDirectories.Root); + if (filePath.StartsWith(mappedRoot) == false) + filePath = MapPath(filePath); + + // don't trust what we get, it may contain relative segments + filePath = Path.GetFullPath(filePath); + foreach (var dir in validDirs) { var validDir = dir; - if (filePath.StartsWith(MapPath(SystemDirectories.Root)) == false) - filePath = MapPath(filePath); - if (validDir.StartsWith(MapPath(SystemDirectories.Root)) == false) + if (validDir.StartsWith(mappedRoot) == false) validDir = MapPath(validDir); if (filePath.StartsWith(validDir)) @@ -219,11 +219,8 @@ namespace Umbraco.Core.IO /// A value indicating whether the filepath is valid. internal static bool VerifyFileExtension(string filePath, List validFileExtensions) { - if (filePath.StartsWith(MapPath(SystemDirectories.Root)) == false) - filePath = MapPath(filePath); - var f = new FileInfo(filePath); - - return validFileExtensions.Contains(f.Extension.Substring(1)); + var ext = Path.GetExtension(filePath); + return ext != null && validFileExtensions.Contains(ext.TrimStart('.')); } /// diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 13df315960..9bef38b68f 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -177,9 +177,23 @@ namespace Umbraco.Core.IO path = GetRelativePath(path); } - return !path.StartsWith(RootPath) - ? Path.Combine(RootPath, path) - : path; + // if already a full path, return + if (path.StartsWith(RootPath)) + return path; + + // else combine and sanitize, ie GetFullPath will take care of any relative + // segments in path, eg '../../foo.tmp' - it may throw a SecurityException + // if the combined path reaches illegal parts of the filesystem + var fpath = Path.Combine(RootPath, path); + fpath = Path.GetFullPath(fpath); + + // at that point, path is within legal parts of the filesystem, ie we have + // permissions to reach that path, but it may nevertheless be outside of + // our root path, due to relative segments, so better check + if (fpath.StartsWith(RootPath)) + return fpath; + + throw new FileSecurityException("File '" + path + "' is outside this filesystem's root."); } public string GetUrl(string path) diff --git a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs index b48e096e3f..f8fc459aec 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs @@ -105,12 +105,22 @@ namespace Umbraco.Core.Persistence.Repositories dirs += "," + SystemDirectories.MvcViews;*/ //Validate file - var validFile = IOHelper.VerifyEditPath(script.VirtualPath, dirs.Split(',')); + string fullPath; + try + { + // may throw for security reasons + fullPath = FileSystem.GetFullPath(script.Path); + } + catch + { + return false; + } + var isValidPath = IOHelper.VerifyEditPath(fullPath, dirs.Split(',')); //Validate extension - var validExtension = IOHelper.VerifyFileExtension(script.VirtualPath, exts); + var isValidExtension = IOHelper.VerifyFileExtension(script.Path, exts); - return validFile && validExtension; + return isValidPath && isValidExtension; } #endregion diff --git a/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs index f4d7dcda29..425f7c8253 100644 --- a/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs @@ -99,19 +99,29 @@ namespace Umbraco.Core.Persistence.Repositories return FileSystem.GetFiles(rootPath ?? string.Empty, "*.css").Select(Get); } + private static readonly List ValidExtensions = new List { "css" }; + public bool ValidateStylesheet(Stylesheet stylesheet) { var dirs = SystemDirectories.Css; //Validate file - var validFile = IOHelper.VerifyEditPath(stylesheet.VirtualPath, dirs.Split(',')); + string fullPath; + try + { + // may throw for security reasons + fullPath = FileSystem.GetFullPath(stylesheet.Path); + } + catch + { + return false; + } + var isValidPath = IOHelper.VerifyEditPath(fullPath, dirs.Split(',')); //Validate extension - var validExtension = IOHelper.VerifyFileExtension(stylesheet.VirtualPath, new List { "css" }); + var isValidExtension = IOHelper.VerifyFileExtension(stylesheet.Path, ValidExtensions); - var fileValid = validFile && validExtension; - - return fileValid; + return isValidPath && isValidExtension; } #endregion diff --git a/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs b/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs index 418cb3dda2..4ee178a954 100644 --- a/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs @@ -27,6 +27,8 @@ namespace Umbraco.Tests.IO public void TearDown() { var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests"); + if (Directory.Exists(path) == false) return; + var files = Directory.GetFiles(path); foreach (var f in files) { @@ -39,5 +41,31 @@ namespace Umbraco.Tests.IO { return "/Media/" + path; } + + [Test] + public void GetFullPathTest() + { + // outside of tests, one initializes the PhysicalFileSystem with eg ~/Dir + // and then, rootPath = /path/to/Dir and rootUrl = /Dir/ + // here we initialize the PhysicalFileSystem with + // rootPath = /path/to/FileSysTests + // rootUrl = /Media/ + + var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests"); + + // ensure that GetFullPath + // - does return the proper full path + // - does properly normalize separators + // - does throw on invalid paths + + var path = _fileSystem.GetFullPath("foo.tmp"); + Assert.AreEqual(Path.Combine(basePath, @"foo.tmp"), path); + + path = _fileSystem.GetFullPath("foo/bar.tmp"); + Assert.AreEqual(Path.Combine(basePath, @"foo\bar.tmp"), path); + + // that path is invalid as it would be outside the root directory + Assert.Throws(() => _fileSystem.GetFullPath("../../foo.tmp")); + } } } From d323b3c5c9ec5ba4943951113a181162513b4f65 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 3 Sep 2015 15:12:37 +0200 Subject: [PATCH 19/20] U4-7042 - use IFileService when editing scripts --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 + .../umbraco/config/lang/en_us.xml | 4 + .../umbraco/settings/scripts/editScript.aspx | 10 +- .../umbraco_client/Editors/EditScript.js | 92 ++++++++++--------- .../WebServices/SaveFileController.cs | 39 +++++++- .../settings/scripts/editScript.aspx.cs | 63 +++++-------- .../webservices/codeEditorSave.asmx.cs | 1 + 7 files changed, 123 insertions(+), 90 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 1ebcf1b90d..54818e4477 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -841,6 +841,10 @@ To manage your website, simply open the Umbraco back office and start adding con Partial view saved without any errors! Partial view not saved An error occurred saving the file. + Script view saved + Script view saved without any errors! + Script view not saved + An error occurred saving the file. Uses CSS syntax ex: h1, .redHeader, .blueTex 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 f8a362d4f1..3548526c83 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -842,6 +842,10 @@ To manage your website, simply open the Umbraco back office and start adding con Partial view saved without any errors! Partial view not saved An error occurred saving the file. + Script view saved + Script view saved without any errors! + Script view not saved + An error occurred saving the file. Uses CSS syntax ex: h1, .redHeader, .blueTex diff --git a/src/Umbraco.Web.UI/umbraco/settings/scripts/editScript.aspx b/src/Umbraco.Web.UI/umbraco/settings/scripts/editScript.aspx index e68922bd98..be0d68d3ba 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/scripts/editScript.aspx +++ b/src/Umbraco.Web.UI/umbraco/settings/scripts/editScript.aspx @@ -1,6 +1,7 @@ <%@ Page Language="C#" MasterPageFile="../../masterpages/umbracoPage.Master" AutoEventWireup="true" CodeBehind="editScript.aspx.cs" Inherits="umbraco.cms.presentation.settings.scripts.editScript" ValidateRequest="False" %> +<%@ Import Namespace="Umbraco.Core" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> @@ -21,13 +22,10 @@ nameTxtBox: $('#<%= NameTxt.ClientID %>'), originalFileName: '<%= NameTxt.Text %>', saveButton: $("#<%= ((Control)SaveButton).ClientID %>"), + restServiceLocation: "<%= Url.GetSaveFileServicePath() %>", editorSourceElement: $('#<%= editorSource.ClientID %>'), - text: { - fileErrorHeader: '<%= HttpUtility.JavaScriptStringEncode(umbraco.ui.Text("speechBubbles", "fileErrorHeader")) %>', - fileSavedHeader: '<%= HttpUtility.JavaScriptStringEncode(umbraco.ui.Text("speechBubbles", "fileSavedHeader")) %>', - fileSavedText: '', - fileErrorText: '<%= HttpUtility.JavaScriptStringEncode(umbraco.ui.Text("speechBubbles", "fileErrorText")) %>', - } + treeSyncPath: "<%= ScriptTreeSyncPath %>", + lttPathElement: $('#<%= lttPath.ClientID %>') }); editor.init(); diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditScript.js b/src/Umbraco.Web.UI/umbraco_client/Editors/EditScript.js index 7c66dc9a1d..a286eed582 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditScript.js +++ b/src/Umbraco.Web.UI/umbraco_client/Editors/EditScript.js @@ -30,63 +30,69 @@ doSubmit: function () { var self = this; - var fileName = this._opts.nameTxtBox.val(); - var codeVal = this._opts.editorSourceElement.val(); + var filename = this._opts.nameTxtBox.val(); + var codeval = this._opts.editorSourceElement.val(); //if CodeMirror is not defined, then the code editor is disabled. if (typeof (CodeMirror) != "undefined") { - codeVal = UmbEditor.GetCode(); + codeval = UmbEditor.GetCode(); } - umbraco.presentation.webservices.codeEditorSave.SaveScript( - fileName, self._opts.originalFileName, codeVal, - function (t) { self.submitSucces(t); }, - function (t) { self.submitFailure(t); }); + this.save( + filename, + self._opts.originalFileName, + codeval); }, - submitSucces: function(t) { + save: function (filename, oldName, contents) { + var self = this; - if (t != 'true') { - top.UmbSpeechBubble.ShowMessage('error', unescape(this._opts.text.fileErrorHeader), unescape(this._opts.text.fileErrorText)); + $.post(self._opts.restServiceLocation + "SaveScript", + JSON.stringify({ + filename: filename, + oldName: oldName, + contents: contents + }), + function (e) { + if (e.success) { + self.submitSuccess(e); + } else { + self.submitFailure(e.message, e.header); + } + }); + }, + + submitSuccess: function(args) { + var msg = args.message; + var header = args.header; + var path = this._opts.treeSyncPath; + var pathChanged = false; + if (args.path) { + if (path != args.path) { + pathChanged = true; + } + path = args.path; + } + if (args.contents) { + UmbEditor.SetCode(args.contents); } - var newFilePath = this._opts.nameTxtBox.val(); - - //if the filename changes, we need to redirect since the file name is used in the url - if (this._opts.originalFileName != newFilePath) { - var newLocation = window.location.pathname + "?" + "&file=" + newFilePath; - - UmbClientMgr.contentFrame(newLocation); - - //we need to do this after we navigate otherwise the navigation will wait unti lthe message timeout is done! - top.UmbSpeechBubble.ShowMessage('save', unescape(this._opts.text.fileSavedHeader), unescape(this._opts.text.fileSavedText)); + top.UmbSpeechBubble.ShowMessage("save", header, msg); + UmbClientMgr.mainTree().setActiveTreeType("scripts"); + if (pathChanged) { + UmbClientMgr.mainTree().moveNode(this._opts.originalFileName, path); + this._opts.treeSyncPath = args.path; + this._opts.lttPathElement.prop("href", args.url).html(args.url); } else { - - top.UmbSpeechBubble.ShowMessage('save', unescape(this._opts.text.fileSavedHeader), unescape(this._opts.text.fileSavedText)); - UmbClientMgr.mainTree().setActiveTreeType('scripts'); - - //we need to create a list of ids for each folder/file. Each folder/file's id is it's full path so we need to build each one. - var paths = []; - var parts = this._opts.originalFileName.split('/'); - for (var i = 0;i < parts.length;i++) { - if (paths.length > 0) { - paths.push(paths[i - 1] + "/" + parts[i]); - } - else { - paths.push(parts[i]); - } - } - - //we need to pass in the newId parameter so it knows which node to resync after retreival from the server - UmbClientMgr.mainTree().syncTree("-1,init," + paths.join(','), true, null, newFilePath); - //set the original file path to the new one - this._opts.originalFileName = newFilePath; + UmbClientMgr.mainTree().syncTree(path, true); } - + + this._opts.lttPathElement.prop("href", args.url).html(args.url); + this._opts.originalFileName = args.name; }, - submitFailure: function(t) { - top.UmbSpeechBubble.ShowMessage('error', unescape(this._opts.text.fileErrorHeader), unescape(this._opts.text.fileErrorText)); + submitFailure: function(err, header) { + top.UmbSpeechBubble.ShowMessage('error', header, err); } }); })(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 651b372988..3abfd31f52 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -12,8 +12,9 @@ using Umbraco.Web.Mvc; using umbraco; using umbraco.cms.businesslogic.macro; using System.Collections.Generic; +using umbraco.cms.helpers; using Umbraco.Core; - +using Umbraco.Core.Configuration; using Template = umbraco.cms.businesslogic.template.Template; namespace Umbraco.Web.WebServices @@ -216,6 +217,42 @@ namespace Umbraco.Web.WebServices } } + [HttpPost] + public JsonResult SaveScript(string filename, string oldName, string contents) + { + filename = filename.TrimStart(System.IO.Path.DirectorySeparatorChar); + + var svce = (FileService) Services.FileService; + var script = svce.GetScriptByName(oldName); + if (script == null) + script = new Script(filename); + else + script.Path = filename; + script.Content = contents; + + try + { + if (svce.ValidateScript(script) == false) + return Failed(ui.Text("speechBubbles", "scriptErrorText"), ui.Text("speechBubbles", "scriptErrorHeader"), + new FileSecurityException("File '" + filename + "' is not a valid script file.")); + + svce.SaveScript(script); + } + catch (Exception e) + { + return Failed(ui.Text("speechBubbles", "scriptErrorText"), ui.Text("speechBubbles", "scriptErrorHeader"), e); + } + + return Success(ui.Text("speechBubbles", "scriptSavedText"), ui.Text("speechBubbles", "scriptSavedHeader"), + new + { + path = DeepLink.GetTreePathFromFilePath(script.Path), + name = script.Path, + url = script.VirtualPath, + contents = script.Content + }); + } + /// /// Returns a successful message /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs index ee54297f06..dc26d20bb6 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs @@ -41,58 +41,42 @@ namespace umbraco.cms.presentation.settings.scripts protected MenuButton SaveButton; - private string file; + private string filename; + + protected string ScriptTreeSyncPath { get; private set; } + protected int ScriptId { get; private set; } protected override void OnLoad(EventArgs e) { base.OnLoad(e); - NameTxt.Text = file; + NameTxt.Text = filename; - string path = ""; - if (file.StartsWith("~/")) - path = IOHelper.ResolveUrl(file); - else - path = IOHelper.ResolveUrl(SystemDirectories.Scripts + "/" + file); + // get the script, ensure it exists (not null) and validate (because + // the file service ensures that it loads scripts from the proper location + // but does not seem to validate extensions?) - in case of an error, + // throw - that's what we did anyways. + // also scrapping the code that added .cshtml and .vbhtml extensions, and + // ~/Views directory - we're not using editScript.aspx for views anymore. - lttPath.Text = "" + path + ""; + var svce = ApplicationContext.Current.Services.FileService; + var script = svce.GetScriptByName(filename); + if (script == null) // not found + throw new FileNotFoundException("Could not find file '" + filename + "'."); - var exts = UmbracoConfig.For.UmbracoSettings().Content.ScriptFileTypes.ToList(); - if (UmbracoConfig.For.UmbracoSettings().Templates.DefaultRenderingEngine == RenderingEngine.Mvc) - { - exts.Add("cshtml"); - exts.Add("vbhtml"); - } - - var dirs = SystemDirectories.Scripts; - if (UmbracoConfig.For.UmbracoSettings().Templates.DefaultRenderingEngine == RenderingEngine.Mvc) - dirs += "," + SystemDirectories.MvcViews; - - // validate file - IOHelper.ValidateEditPath(IOHelper.MapPath(path), dirs.Split(',')); - - // validate extension - IOHelper.ValidateFileExtension(IOHelper.MapPath(path), exts); - - - StreamReader SR; - string S; - SR = File.OpenText(IOHelper.MapPath(path)); - S = SR.ReadToEnd(); - SR.Close(); - - editorSource.Text = S; + lttPath.Text = "" + script.VirtualPath + ""; + editorSource.Text = script.Content; + ScriptTreeSyncPath = DeepLink.GetTreePathFromFilePath(filename); Panel1.Text = ui.Text("editscript", base.getUser()); pp_name.Text = ui.Text("name", base.getUser()); pp_path.Text = ui.Text("path", base.getUser()); - if (!IsPostBack) + if (IsPostBack == false) { - string sPath = DeepLink.GetTreePathFromFilePath(file); ClientTools .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) - .SyncTree(sPath, false); + .SyncTree(ScriptTreeSyncPath, false); } } @@ -100,12 +84,12 @@ namespace umbraco.cms.presentation.settings.scripts { base.OnInit(e); - file = Request.QueryString["file"].TrimStart('/'); + filename = Request.QueryString["file"].TrimStart('/'); //need to change the editor type if it is XML - if (file.EndsWith("xml")) + if (filename.EndsWith("xml")) editorSource.CodeBase = uicontrols.CodeArea.EditorType.XML; - else if (file.EndsWith("master")) + else if (filename.EndsWith("master")) editorSource.CodeBase = uicontrols.CodeArea.EditorType.HTML; @@ -153,7 +137,6 @@ namespace umbraco.cms.presentation.settings.scripts } } - protected override void OnPreRender(EventArgs e) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs index 65fbf2537b..23d30181a9 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs @@ -355,6 +355,7 @@ namespace umbraco.presentation.webservices // return "false"; //} + [Obsolete("This method has been superceded by the REST service /Umbraco/RestServices/SaveFile/SaveScript which is powered by the SaveFileController.")] [WebMethod] public string SaveScript(string filename, string oldName, string contents) { From 6b195587c12f4e10863000a6472256e5780d87b3 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 3 Sep 2015 16:24:43 +0200 Subject: [PATCH 20/20] U4-7042 - fix root path for tests --- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 10 ++++++++++ src/Umbraco.Tests/App.config | 12 ++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 9bef38b68f..8fcda3d10e 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -33,6 +33,16 @@ namespace Umbraco.Core.IO if (rootPath.StartsWith("~/")) throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'"); + // rootPath should be... rooted, as in, it's a root path! + // but the test suite App.config cannot really "root" anything so we'll have to do it here + + //var localRoot = AppDomain.CurrentDomain.BaseDirectory; + var localRoot = IOHelper.GetRootDirectorySafe(); + if (Path.IsPathRooted(rootPath) == false) + { + rootPath = Path.Combine(localRoot, rootPath); + } + RootPath = rootPath; _rootUrl = rootUrl; } diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 9599390dfb..aa8ed6b0a7 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -12,41 +12,41 @@ - + - + - + - + - + - +