From 4697f2ecad9dff86fa866037e71cf1660dbbb32a Mon Sep 17 00:00:00 2001 From: Hai Le Date: Sat, 1 Aug 2015 15:54:00 +1000 Subject: [PATCH 01/85] Bug fix: Issue with stripping modifiers with no trailing characters after placeholder When using a modifier with no trailing characters after the placeholder eg. setting a color style with #{0}. The end selection is set to 0 resulting in the value also being stripped --- .../views/propertyeditors/grid/dialogs/config.controller.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.controller.js index 731912c95123..d60535acd15c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.controller.js @@ -22,7 +22,10 @@ angular.module("umbraco") } else { return val.slice(paddArray[0].length, 0); } - }else{ + } else { + if (paddArray[1].length === 0) { + return val.slice(paddArray[0].length); + } return val.slice(paddArray[0].length, -paddArray[1].length); } } From 3595dd33391a715b281ed20dc3ef914ddba1c2e2 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Wed, 19 Aug 2015 16:54:03 +0100 Subject: [PATCH 02/85] 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 75846fb40473..0cbf09f78298 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -124,5 +124,32 @@ public void GetCropUrl_PreferFocalPointCenter() 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 cceac8ab3112..b870335c91a6 100644 --- a/src/Umbraco.Web/ImageCropperBaseExtensions.cs +++ b/src/Umbraco.Web/ImageCropperBaseExtensions.cs @@ -81,6 +81,7 @@ internal static string GetCropBaseUrl(this ImageCropDataSet cropDataSet, string { 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 @@ internal static string GetCropBaseUrl(this ImageCropDataSet cropDataSet, string 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 de0f8a225eff..4bfdf6e12b01 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -153,6 +153,12 @@ public static string GetCropUrl( /// /// 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 @@ public static string GetCropUrl( /// /// 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 @@ public static string GetCropUrl( /// /// /// Use a dimension as a ratio - /// + /// /// /// If the image should be upscaled to requested dimensions - /// + /// /// /// The . /// @@ -203,8 +203,7 @@ public static string GetCropUrl( string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true - ) + bool upScale = true) { if (string.IsNullOrEmpty(imageUrl) == false) { @@ -229,11 +228,25 @@ public static string GetCropUrl( 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 @@ public static string GetCropUrl( 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 03/85] 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 0cbf09f78298..4a398082cebd 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -21,6 +21,16 @@ public void GetCropUrl_CropAliasTest() 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 @@ public void GetCropUrl_PreferFocalPointCenter() 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 @@ public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth() 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 @@ public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() 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 04/85] 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 4bfdf6e12b01..e40fcfb29778 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -207,7 +207,7 @@ public static string GetCropUrl( { 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 @@ public static string GetCropUrl( { 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 @@ public static string GetCropUrl( 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 @@ public static string GetCropUrl( } 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 @@ public static string GetCropUrl( } 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 05/85] 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 e40fcfb29778..9d097b3a3e2e 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -110,9 +110,8 @@ public static string GetCropUrl( 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 06/85] 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 4a398082cebd..2877e3bb9044 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -182,5 +182,29 @@ public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() 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 9d097b3a3e2e..a76b39f18760 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -234,14 +234,14 @@ public static string GetCropUrl( } // 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 07/85] 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 edb7f768d1bb..628112903d0a 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 70f9951de5d4..7f7b817b8a01 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 9428c647045f..24aa722cc3d1 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 229a7992f5b3..bfe5d1ba2edb 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 829942dcf910e1214b35246ac115d2e555a48e6b Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 31 Aug 2015 13:17:50 +0200 Subject: [PATCH 08/85] Bugfix MediaService --- src/Umbraco.Core/Services/MediaService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 9e5c2066504c..990b0a84d557 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -1021,7 +1021,7 @@ Attempt IMediaServiceOperations.MoveToRecycleBin(IMedia media, /// Id of the User deleting the Media public void Delete(IMedia media, int userId = 0) { - + ((IMediaServiceOperations)this).Delete(media, userId); } From 05e17e1c7f68c3a6a828a3b52aa5fb82249d5b60 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 31 Aug 2015 18:04:13 +0200 Subject: [PATCH 09/85] Fixes: U4-6973 Given custom permissions user can create, but cannot save --- src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 7684636605f3..0c7dbffd49f9 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -221,8 +221,16 @@ protected override IEnumerable ResolveCore(IContent source) } var svc = _userService.Value; - var permissions = svc.GetPermissions(UmbracoContext.Current.Security.CurrentUser, source.Id) - .FirstOrDefault(); + var permissions = svc.GetPermissions( + //TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is + // with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null + // refrence exception :( + UmbracoContext.Current.Security.CurrentUser, + // Here we need to do a special check since this could be new content, in which case we need to get the permissions + // from the parent, not the existing one otherwise permissions would be coming from the root since Id is 0. + source.HasIdentity ? source.Id : source.ParentId) + .FirstOrDefault(); + if (permissions == null) { return Enumerable.Empty(); From b883c7cb1146fcddb715924696267c92c2a0686a Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 31 Aug 2015 18:51:36 +0200 Subject: [PATCH 10/85] Fixes: U4-6947 Don't ship unnecessary js files --- src/Umbraco.Web.UI.Client/gruntFile.js | 9 +- src/Umbraco.Web.UI.Client/src/index.html | 2 +- .../src/{loader.js => loader.dev.js} | 100 +++++++++--------- 3 files changed, 57 insertions(+), 54 deletions(-) rename src/Umbraco.Web.UI.Client/src/{loader.js => loader.dev.js} (96%) diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index 9f77d700cb21..8699f4a6f1cc 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -18,10 +18,10 @@ module.exports = function (grunt) { grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']); //triggered from grunt dev or grunt - grunt.registerTask('build', ['clean', 'concat', 'recess:min', 'recess:installer', 'recess:canvasdesigner', 'bower-install-simple', 'bower', 'copy']); + grunt.registerTask('build', ['clean:pre', 'concat', 'recess:min', 'recess:installer', 'recess:canvasdesigner', 'bower-install-simple', 'bower', 'copy', 'clean:post']); //build-dev doesn't min - we are trying to speed this up and we don't want minified stuff when we are in dev mode - grunt.registerTask('build-dev', ['clean', 'concat', 'recess:build', 'recess:installer', 'bower-install-simple', 'bower', 'copy']); + grunt.registerTask('build-dev', ['clean:pre', 'concat', 'recess:build', 'recess:installer', 'bower-install-simple', 'bower', 'copy']); //utillity tasks grunt.registerTask('docs', ['ngdocs']); @@ -108,7 +108,10 @@ module.exports = function (grunt) { prod: ['<%= distdir %>/js/*.js'] }, - clean: ['<%= distdir %>/*'], + clean: { + pre: ['<%= distdir %>/*'], + post: ['<%= distdir %>/js/*.dev.js'] + }, copy: { assets: { diff --git a/src/Umbraco.Web.UI.Client/src/index.html b/src/Umbraco.Web.UI.Client/src/index.html index a16987ce2cf5..362510e04456 100644 --- a/src/Umbraco.Web.UI.Client/src/index.html +++ b/src/Umbraco.Web.UI.Client/src/index.html @@ -23,6 +23,6 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/loader.js b/src/Umbraco.Web.UI.Client/src/loader.dev.js similarity index 96% rename from src/Umbraco.Web.UI.Client/src/loader.js rename to src/Umbraco.Web.UI.Client/src/loader.dev.js index dda23dce0a73..2be659323858 100644 --- a/src/Umbraco.Web.UI.Client/src/loader.js +++ b/src/Umbraco.Web.UI.Client/src/loader.dev.js @@ -1,51 +1,51 @@ -LazyLoad.js( - [ - 'lib/jquery/jquery.min.js', - 'lib/angular/1.1.5/angular.min.js', - 'lib/underscore/underscore-min.js', - - 'lib/jquery-ui/jquery-ui.min.js', - - 'lib/angular/1.1.5/angular-cookies.min.js', - 'lib/angular/1.1.5/angular-mobile.js', - 'lib/angular/1.1.5/angular-sanitize.min.js', - - 'lib/angular/angular-ui-sortable.js', - - 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', - - 'lib/blueimp-load-image/load-image.all.min.js', - 'lib/jquery-file-upload/jquery.fileupload.js', - 'lib/jquery-file-upload/jquery.fileupload-process.js', - 'lib/jquery-file-upload/jquery.fileupload-image.js', - 'lib/jquery-file-upload/jquery.fileupload-angular.js', - - 'lib/bootstrap/js/bootstrap.2.3.2.min.js', - 'lib/bootstrap-tabdrop/bootstrap-tabdrop.min.js', - 'lib/umbraco/Extensions.js', - - 'lib/umbraco/NamespaceManager.js', - 'lib/umbraco/LegacyUmbClientMgr.js', - 'lib/umbraco/LegacySpeechBubble.js', - - 'js/umbraco.servervariables.js', - 'js/app.dev.js', - 'js/umbraco.httpbackend.js', - 'js/umbraco.testing.js', - - 'js/umbraco.directives.js', - 'js/umbraco.filters.js', - 'js/umbraco.resources.js', - 'js/umbraco.services.js', - 'js/umbraco.security.js', - 'js/umbraco.controllers.js', - 'js/routes.js', - 'js/init.js' - ], - - function () { - jQuery(document).ready(function () { - angular.bootstrap(document, ['umbraco']); - }); - } +LazyLoad.js( + [ + 'lib/jquery/jquery.min.js', + 'lib/angular/1.1.5/angular.min.js', + 'lib/underscore/underscore-min.js', + + 'lib/jquery-ui/jquery-ui.min.js', + + 'lib/angular/1.1.5/angular-cookies.min.js', + 'lib/angular/1.1.5/angular-mobile.js', + 'lib/angular/1.1.5/angular-sanitize.min.js', + + 'lib/angular/angular-ui-sortable.js', + + 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', + + 'lib/blueimp-load-image/load-image.all.min.js', + 'lib/jquery-file-upload/jquery.fileupload.js', + 'lib/jquery-file-upload/jquery.fileupload-process.js', + 'lib/jquery-file-upload/jquery.fileupload-image.js', + 'lib/jquery-file-upload/jquery.fileupload-angular.js', + + 'lib/bootstrap/js/bootstrap.2.3.2.min.js', + 'lib/bootstrap-tabdrop/bootstrap-tabdrop.min.js', + 'lib/umbraco/Extensions.js', + + 'lib/umbraco/NamespaceManager.js', + 'lib/umbraco/LegacyUmbClientMgr.js', + 'lib/umbraco/LegacySpeechBubble.js', + + 'js/umbraco.servervariables.js', + 'js/app.dev.js', + 'js/umbraco.httpbackend.js', + 'js/umbraco.testing.js', + + 'js/umbraco.directives.js', + 'js/umbraco.filters.js', + 'js/umbraco.resources.js', + 'js/umbraco.services.js', + 'js/umbraco.security.js', + 'js/umbraco.controllers.js', + 'js/routes.js', + 'js/init.js' + ], + + function () { + jQuery(document).ready(function () { + angular.bootstrap(document, ['umbraco']); + }); + } ); \ No newline at end of file From 37986f0ed5b74aca75d191efc2a5347ceaebd855 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 1 Sep 2015 10:28:35 +0200 Subject: [PATCH 11/85] 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 0e794c29d888..69a85957d934 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 09d04219f22e..f5dfc6459c2f 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 12/85] U4-7046 - fix DatabaseServerRegistrar issues --- .../ServerRegistrationRepository.cs | 76 +++++++++-------- .../Persistence/RepositoryFactory.cs | 2 +- .../Services/ServerRegistrationService.cs | 85 +++++++++++++------ 3 files changed, 101 insertions(+), 62 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs index fc16009e6c26..46e1c139a50d 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(); - - 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)); + // 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. + + 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 @@ protected override void PersistNewItem(IServerRegistration entity) entity.Id = id; entity.ResetDirtyProperties(); + ReloadCache(); } protected override void PersistUpdatedItem(IServerRegistration entity) @@ -113,13 +100,34 @@ protected override void PersistUpdatedItem(IServerRegistration entity) 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 bb213eb50f6f..011c1228616e 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -230,7 +230,7 @@ public virtual IServerRegistrationRepository CreateServerRegistrationRepository( { 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 43329ebe5fb1..5a4a48b7aa73 100644 --- a/src/Umbraco.Core/Services/ServerRegistrationService.cs +++ b/src/Umbraco.Core/Services/ServerRegistrationService.cs @@ -6,7 +6,6 @@ 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 @@ public ServerRegistrationService(IDatabaseUnitOfWorkProvider uowProvider, Reposi _lrepo = new LockingRepository(UowProvider, x => RepositoryFactory.CreateServerRegistrationRepository(x), LockingRepositoryIds, LockingRepositoryIds); - } /// @@ -51,10 +49,11 @@ public void TouchServer(string serverAddress, string serverIdentity, TimeSpan st { _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 @@ public void TouchServer(string serverAddress, string serverIdentity, TimeSpan st server.IsMaster = true; xr.Repository.AddOrUpdate(server); - xr.UnitOfWork.Commit(); - xr.Repository.DeactiveStaleServers(staleTimeout); - - // default role is single server - _currentServerRole = ServerRole.Single; - - // 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; + xr.UnitOfWork.Commit(); // triggers a cache reload + xr.Repository.DeactiveStaleServers(staleTimeout); // triggers a cache reload + + // reload - cheap, cached + regs = xr.Repository.GetAll().ToArray(); + + // 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 @@ public void TouchServer(string serverAddress, string serverIdentity, TimeSpan st /// 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 @@ public void DeactiveStaleServers(TimeSpan staleTimeout) /// 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 13/85] 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 8e58535c745f..fcc8e8c2edb4 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 14/85] 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 000000000000..b0e71221b296 --- /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 8c0eeef86fae..fef066e0b13a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs @@ -62,9 +62,20 @@ internal static IPublishedContentExtended Extend(IPublishedContent content, IEnu // 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 000000000000..492fd79796f8 --- /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 000000000000..4761a5261708 --- /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 000000000000..35d7dd6f1f48 --- /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 949b4e61c756..d0c89c32a09e 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -440,6 +440,7 @@ private XElement Serialize(IDataTypeService dataTypeService, IContentBase conten 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 f3e6cac1f105..b838db5441e9 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 aa96f5a26ad2..497fd4471fa0 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 000000000000..52bd8b2f5988 --- /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 5c0050c2a1db..35b3d6ee626d 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 @@ public override int Id 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 5450d4063fdf..4bc2f2388ae5 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 @@ internal XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, bool lazyInitia private IPublishedContent _parent; private int _id; + private Guid _key; private int _template; private string _name; private string _docTypeAlias; @@ -150,6 +151,16 @@ public override int Id } } + public override Guid Key + { + get + { + if (_initialized == false) + Initialize(); + return _key; + } + } + public override int TemplateId { get @@ -348,6 +359,8 @@ private void Initialize() 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 8f7dbc31dfed..d6cf3b3141db 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 b8ed24d5bb04..1ad99b431554 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 90091cc80e26..661e62005651 100644 --- a/src/umbraco.cms/businesslogic/CMSNode.cs +++ b/src/umbraco.cms/businesslogic/CMSNode.cs @@ -1181,6 +1181,7 @@ private void XmlPopulate(XmlDocument xd, XmlNode x, bool Deep) { // 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 f81ecce4993d..36eafb91a2de 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -408,6 +408,7 @@ public virtual void XmlPopulate(XmlDocument xd, ref XmlNode x, bool Deep) // 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 1df6dd7c404b..0c5f2c3025ad 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -1288,6 +1288,7 @@ public override void XmlPopulate(XmlDocument xd, ref XmlNode x, bool Deep) // 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 15/85] 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 2e437ccc976d..61dd50e6106e 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -273,6 +273,10 @@ internal CacheValues ConvertFromSearchResult(SearchResult searchResult) 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 @@ internal CacheValues ConvertFromXPathNavigator(XPathNavigator xpath, bool forceN 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 @@ private IEnumerable GetChildrenMedia(int parentId, XPathNavig /// 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 @@ internal class DictionaryPublishedContent : PublishedContentBase // 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 @@ public DictionaryPublishedContent( 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 @@ public override int Id get { return _id; } } + public override Guid Key { get { return _key; } } + public override int TemplateId { get @@ -803,6 +813,7 @@ public override IPublishedProperty GetProperty(string alias, bool recurse) 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 de76ab8e794a..613304c4ff12 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -145,6 +145,7 @@ internal static readonly List IndexFieldPolicies = 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 16/85] 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 1dfec6062cce..0c7d51334e36 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 e8d7ea6d9964..39258f6b1a8a 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 84ee708179bd..efab0db7da27 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 17/85] U4-6992,U4-7046 - more fixes & tests --- .../ServerRegistrationRepository.cs | 17 +++- .../Persistence/RepositoryFactory.cs | 2 +- .../ServerRegistrationRepositoryTest.cs | 96 ++++++++++--------- 3 files changed, 65 insertions(+), 50 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs index 46e1c139a50d..20d3c38042ee 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 011c1228616e..f9367d843390 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -230,7 +230,7 @@ public virtual IServerRegistrationRepository CreateServerRegistrationRepository( { 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 b94339970f32..bcb49068fad2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs @@ -4,6 +4,7 @@ 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 @@ public void Cannot_Add_Duplicate_Server_Identities() 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 @@ public void Cannot_Update_To_Duplicate_Server_Identities() 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 @@ public void Can_Instantiate_Repository() 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 @@ public void Can_Perform_Get_On_Repository() // 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 @@ public void Can_Perform_GetAll_On_Repository() // 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 @@ public void Can_Perform_GetAll_On_Repository() } - [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); - - // Assert - Assert.AreEqual(1, result.Count()); - } - } - - [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.That(count, Is.EqualTo(2)); - } - } + // queries are not supported due to in-memory caching + + //[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); + + // // Assert + // Assert.AreEqual(1, result.Count()); + // } + //} + + //[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 @@ public void Can_Perform_Add_On_Repository() // 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 @@ public void Can_Perform_Update_On_Repository() // 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 @@ public void Can_Perform_Delete_On_Repository() // 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 @@ public void Can_Perform_Exists_On_Repository() // 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 @@ public void CreateTestData() { 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 18/85] 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 d0c89c32a09e..772009e89ab2 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -100,7 +100,6 @@ public XElement Serialize(IDataTypeService dataTypeService, IMember member) 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 d93c4d9e3c98..32bbd34a77bc 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.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 @@ public void DictionaryDocument_Path_Keys(string key) 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 @@ public void DictionaryDocument_Get_Children() 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 @@ public void Convert_From_Search_Result() 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 @@ public void Convert_From_Search_Result() 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 @@ private XmlDocument GetMediaXml() private Dictionary GetDictionary( int id, + Guid key, int parentId, string idKey, string templateKey, @@ -207,6 +223,7 @@ private Dictionary GetDictionary( return new Dictionary() { {idKey, id.ToString()}, + {"key", key.ToString()}, {templateKey, "333"}, {"sortOrder", "44"}, {nodeNameKey, "Testing"}, @@ -232,6 +249,7 @@ private PublishedMediaCache.DictionaryPublishedContent GetDictionaryDocument( string nodeTypeAliasKey = "nodeTypeAlias", string pathKey = "path", int idVal = 1234, + Guid keyVal = default(Guid), int parentIdVal = 321, IEnumerable children = null) { @@ -239,10 +257,10 @@ private PublishedMediaCache.DictionaryPublishedContent GetDictionaryDocument( 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 @@ private PublishedMediaCache.DictionaryPublishedContent GetDictionaryDocument( 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 @@ private void DoAssert( 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 @@ private void DoAssert( 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 @@ private void DoAssert( 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 19/85] 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 c20f330e1371..714b79514d55 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -45,22 +45,16 @@ internal void Startup() 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 20/85] 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 e439d0e37f93..4a19ea992659 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 21/85] 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 c9df0d685ac5..3cc59708e9d1 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1416,7 +1416,7 @@ public void EmptyRecycleBin() /// /// 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 @@ public void EmptyRecycleBin() /// 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 @@ public IContent Copy(IContent content, int parentId, bool relateToOriginal, int } } - //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 8b308e5f21fe..c686cf4891a0 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -481,7 +481,7 @@ IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, /// /// 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 @@ IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, /// 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 b711f7c3ac9e..ebd0be3b5584 100644 --- a/src/Umbraco.Tests/Services/BaseServiceTest.cs +++ b/src/Umbraco.Tests/Services/BaseServiceTest.cs @@ -51,7 +51,6 @@ public virtual void CreateTestData() 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 176db015f8fe..841789a980ae 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1220,6 +1220,52 @@ public void Can_Copy_Content() //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 22/85] 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 2d87f634f401..837808139432 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -152,12 +152,7 @@ internal static string ReturnPath(string settingsKey, string standardPath) /// 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 @@ internal static bool ValidateEditPath(string filePath, string validDir) /// 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 @@ internal static bool ValidateEditPath(string filePath, IEnumerable valid /// 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 13df315960b2..9bef38b68f31 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -177,9 +177,23 @@ public string GetFullPath(string path) 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 b48e096e3f5d..f8fc459aec1a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs @@ -105,12 +105,22 @@ public bool ValidateScript(Script script) 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 f4d7dcda29be..425f7c82537e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs @@ -99,19 +99,29 @@ public IEnumerable GetStylesheetsAtPath(string rootPath = null) 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 fileValid = validFile && validExtension; + var isValidExtension = IOHelper.VerifyFileExtension(stylesheet.Path, ValidExtensions); - return fileValid; + return isValidPath && isValidExtension; } #endregion diff --git a/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs b/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs index 418cb3dda2f1..4ee178a95494 100644 --- a/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs @@ -27,6 +27,8 @@ public void Setup() 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 @@ protected override string ConstructUrl(string path) { 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 23/85] 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 1ebcf1b90d25..54818e4477aa 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 f8a362d4f163..3548526c8302 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 e68922bd9836..be0d68d3baf1 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 7c66dc9a1d15..a286eed5821e 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) { - - if (t != 'true') { - top.UmbSpeechBubble.ShowMessage('error', unescape(this._opts.text.fileErrorHeader), unescape(this._opts.text.fileErrorText)); - } + save: function (filename, oldName, contents) { + var self = this; - 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; + $.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); + } + }); + }, - UmbClientMgr.contentFrame(newLocation); + 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); + } - //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 651b3729880d..3abfd31f527d 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -12,8 +12,9 @@ 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 @@ public JsonResult SaveTemplate(string templateName, string templateAlias, string } } + [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 ee54297f06cf..dc26d20bb686 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 @@ public editScript() 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; - - string path = ""; - if (file.StartsWith("~/")) - path = IOHelper.ResolveUrl(file); - else - path = IOHelper.ResolveUrl(SystemDirectories.Scripts + "/" + file); - - - lttPath.Text = "" + path + ""; - - 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(',')); + NameTxt.Text = filename; - // validate extension - IOHelper.ValidateFileExtension(IOHelper.MapPath(path), exts); + // 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. - StreamReader SR; - string S; - SR = File.OpenText(IOHelper.MapPath(path)); - S = SR.ReadToEnd(); - SR.Close(); + var svce = ApplicationContext.Current.Services.FileService; + var script = svce.GetScriptByName(filename); + if (script == null) // not found + throw new FileNotFoundException("Could not find file '" + filename + "'."); - 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 @@ override protected void OnInit(EventArgs e) { 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 @@ override protected void OnInit(EventArgs e) } } - 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 65fbf2537b51..23d30181a90d 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 @@ public string SaveDLRScript(string fileName, string oldName, string fileContents // 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 24/85] 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 9bef38b68f31..8fcda3d10e3a 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -33,6 +33,16 @@ public PhysicalFileSystem(string rootPath, string rootUrl) 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 9599390dfbc9..aa8ed6b0a753 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -12,41 +12,41 @@ - + - + - + - + - + - + From 6c3b63cf55a78b283930fd71a96ef142705004bb Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 3 Sep 2015 16:32:25 +0200 Subject: [PATCH 25/85] fixes issue with legacy dialogs not closing the nav --- src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js index 98a99870198b..75f0850ee271 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js @@ -384,6 +384,11 @@ Umbraco.Sys.registerNamespace("Umbraco.Application"); //just call the native dialog close() method to remove the dialog lastModal.close(); + + //if it's the last one close them all + if (this._modal.length == 0) { + getRootScope().$emit("app.closeDialogs", undefined); + } } else { //instead of calling just the dialog service we funnel it through the global From a542cf56d46d3f77177564426a2e6b47fd43aea5 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 4 Sep 2015 10:42:56 +0200 Subject: [PATCH 26/85] U4-7063 - Umbraco.TypedContent(ints) should not return nulls --- .../PublishedContentMoreTests.cs | 14 +- .../PublishedContentTestElements.cs | 63 +++++- src/Umbraco.Web/PublishedContentQuery.cs | 2 +- src/Umbraco.Web/UmbracoHelper.cs | 180 ++++++++++++++++-- 4 files changed, 232 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 7c74d093bac9..ff16c5306c00 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -139,7 +139,6 @@ public void Distinct() } [Test] - [Ignore("Fails as long as PublishedContentModel is internal.")] // fixme public void OfType1() { var content = UmbracoContext.Current.ContentCache.GetAtRoot() @@ -155,7 +154,6 @@ public void OfType1() } [Test] - [Ignore("Fails as long as PublishedContentModel is internal.")] // fixme public void OfType2() { var content = UmbracoContext.Current.ContentCache.GetAtRoot() @@ -169,7 +167,6 @@ public void OfType2() } [Test] - [Ignore("Fails as long as PublishedContentModel is internal.")] // fixme public void OfType() { var content = UmbracoContext.Current.ContentCache.GetAtRoot() @@ -196,7 +193,6 @@ public void Position() } [Test] - [Ignore("Fails as long as PublishedContentModel is internal.")] // fixme public void Issue() { var content = UmbracoContext.Current.ContentCache.GetAtRoot() @@ -218,6 +214,16 @@ public void Issue() Assert.AreEqual(1234, content3.Prop1); } + [Test] + public void PublishedContentQueryTypedContentList() + { + var query = new PublishedContentQuery(UmbracoContext.Current.ContentCache, UmbracoContext.Current.MediaCache); + var result = query.TypedContent(new[] { 1, 2, 4 }).ToArray(); + Assert.AreEqual(2, result.Length); + Assert.AreEqual(1, result[0].Id); + Assert.AreEqual(2, result[1].Id); + } + static SolidPublishedCaches CreatePublishedContent() { var caches = new SolidPublishedCaches(); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs index 93c4cb0b8e1c..4abfc6e18d8d 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs @@ -14,6 +14,7 @@ namespace Umbraco.Tests.PublishedContent class SolidPublishedCaches : IPublishedCaches { public readonly SolidPublishedContentCache ContentCache = new SolidPublishedContentCache(); + public readonly SolidPublishedMediaCache MediaCache = new SolidPublishedMediaCache(); public ContextualPublishedContentCache CreateContextualContentCache(UmbracoContext context) { @@ -22,7 +23,7 @@ public ContextualPublishedContentCache CreateContextualContentCache(UmbracoConte public ContextualPublishedMediaCache CreateContextualMediaCache(UmbracoContext context) { - return null; + return new ContextualPublishedMediaCache(MediaCache, context); } } @@ -106,6 +107,66 @@ public IPublishedProperty CreateDetachedProperty(PublishedPropertyType propertyT } } + class SolidPublishedMediaCache : IPublishedMediaCache + { + private readonly Dictionary _media = new Dictionary(); + + public void Add(SolidPublishedContent content) + { + _media[content.Id] = content.CreateModel(); + } + + public void Clear() + { + _media.Clear(); + } + + public IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int contentId) + { + return _media.ContainsKey(contentId) ? _media[contentId] : null; + } + + public IEnumerable GetAtRoot(UmbracoContext umbracoContext, bool preview) + { + return _media.Values.Where(x => x.Parent == null); + } + + public IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, string xpath, Core.Xml.XPathVariable[] vars) + { + throw new NotImplementedException(); + } + + public IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, System.Xml.XPath.XPathExpression xpath, Core.Xml.XPathVariable[] vars) + { + throw new NotImplementedException(); + } + + public IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, Core.Xml.XPathVariable[] vars) + { + throw new NotImplementedException(); + } + + public IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, System.Xml.XPath.XPathExpression xpath, Core.Xml.XPathVariable[] vars) + { + throw new NotImplementedException(); + } + + public System.Xml.XPath.XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext, bool preview) + { + throw new NotImplementedException(); + } + + public bool XPathNavigatorIsNavigable + { + get { throw new NotImplementedException(); } + } + + public bool HasContent(UmbracoContext umbracoContext, bool preview) + { + return _media.Count > 0; + } + } + class SolidPublishedContent : IPublishedContent { #region Constructor diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index afeddd1bf172..16f42b81978a 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -211,7 +211,7 @@ private IPublishedContent TypedDocumentByXPath(string xpath, XPathVariable[] var private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids) { - return ids.Select(eachId => TypedDocumentById(eachId, cache)); + return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); } private IEnumerable TypedDocumentsByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index e407edda4ffa..35797a4432e4 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -584,27 +584,57 @@ public IEnumerable TypedContent(params object[] ids) return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); } + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable TypedContent(params int[] ids) { return ContentQuery.TypedContent(ids); } - public IEnumerable TypedContent(params string[] ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable TypedContent(params string[] ids) { return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedContent(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable TypedContent(IEnumerable ids) { return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedContent(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable TypedContent(IEnumerable ids) { return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedContent(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable TypedContent(IEnumerable ids) { return ContentQuery.TypedContent(ids); } @@ -651,32 +681,68 @@ public dynamic ContentSingleAtXPath(XPathExpression xpath, params XPathVariable[ return ContentQuery.ContentSingleAtXPath(xpath, vars); } + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. public dynamic Content(params object[] ids) { return ContentQuery.Content(ConvertIdsObjectToInts(ids)); } - public dynamic Content(params int[] ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public dynamic Content(params int[] ids) { return ContentQuery.Content(ids); } - public dynamic Content(params string[] ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public dynamic Content(params string[] ids) { return ContentQuery.Content(ConvertIdsObjectToInts(ids)); } - public dynamic Content(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public dynamic Content(IEnumerable ids) { return ContentQuery.Content(ConvertIdsObjectToInts(ids)); } - public dynamic Content(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public dynamic Content(IEnumerable ids) { return ContentQuery.Content(ids); } - public dynamic Content(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public dynamic Content(IEnumerable ids) { return ContentQuery.Content(ConvertIdsObjectToInts(ids)); } @@ -758,32 +824,68 @@ public IPublishedContent TypedMedia(string id) return ConvertIdObjectToInt(id, out intId) ? ContentQuery.TypedMedia(intId) : null; } - public IEnumerable TypedMedia(params object[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(params object[] ids) { return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedMedia(params int[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(params int[] ids) { return ContentQuery.TypedMedia(ids); } - public IEnumerable TypedMedia(params string[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(params string[] ids) { return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedMedia(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(IEnumerable ids) { return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedMedia(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(IEnumerable ids) { return ContentQuery.TypedMedia(ids); } - public IEnumerable TypedMedia(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(IEnumerable ids) { return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); } @@ -810,32 +912,68 @@ public dynamic Media(string id) return ConvertIdObjectToInt(id, out intId) ? ContentQuery.Media(intId) : DynamicNull.Null; } - public dynamic Media(params object[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(params object[] ids) { return ContentQuery.Media(ConvertIdsObjectToInts(ids)); } - public dynamic Media(params int[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(params int[] ids) { return ContentQuery.Media(ids); } - public dynamic Media(params string[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(params string[] ids) { return ContentQuery.Media(ConvertIdsObjectToInts(ids)); } - public dynamic Media(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(IEnumerable ids) { return ContentQuery.Media(ConvertIdsObjectToInts(ids)); } - public dynamic Media(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(IEnumerable ids) { return ContentQuery.Media(ids); } - public dynamic Media(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(IEnumerable ids) { return ContentQuery.Media(ConvertIdsObjectToInts(ids)); } From 6d6b2f72b6b262e935098b9c4f4e29d0b9dc80bf Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 7 Sep 2015 20:15:10 +0200 Subject: [PATCH 27/85] fixes open redirect issue for schema less Urls --- src/Umbraco.Web.UI/umbraco/endPreview.aspx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/endPreview.aspx b/src/Umbraco.Web.UI/umbraco/endPreview.aspx index a56af97ff5e3..05fc2ddd251e 100644 --- a/src/Umbraco.Web.UI/umbraco/endPreview.aspx +++ b/src/Umbraco.Web.UI/umbraco/endPreview.aspx @@ -5,9 +5,7 @@ protected override void OnLoad(EventArgs e) { base.OnLoad(e); - global::umbraco.presentation.preview.PreviewContent.ClearPreviewCookie(); - if (!Uri.IsWellFormedUriString(Request.QueryString["redir"], UriKind.Relative)) { Response.Redirect("/", true); @@ -17,7 +15,10 @@ { Response.Redirect("/", true); } - + if (Request.QueryString["redir"].StartsWith("//")) + { + Response.Redirect("/", true); + } Response.Redirect(url.ToString(), true); } From 3dc40590367498ba5fa519c32ccdf858f1a68cd6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 7 Sep 2015 20:28:56 +0200 Subject: [PATCH 28/85] fixes: U4-6941 Can't edit masterpages templates in 7.3.0 beta3 --- src/umbraco.controls/CodeArea.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/umbraco.controls/CodeArea.cs b/src/umbraco.controls/CodeArea.cs index 698aea2213a2..3481341ade04 100644 --- a/src/umbraco.controls/CodeArea.cs +++ b/src/umbraco.controls/CodeArea.cs @@ -80,10 +80,11 @@ protected override void OnInit(EventArgs e) ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/mode/" + CodeBase.ToString().ToLower() + "/" + CodeBase.ToString().ToLower() + ".js", "UmbracoRoot", ClientDependencyType.Javascript); if (CodeBase == EditorType.HtmlMixed) { + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/xml/xml.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/javascript/javascript.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/css/css.js", "UmbracoRoot", ClientDependencyType.Javascript); ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/htmlmixed/htmlmixed.js", "UmbracoRoot", ClientDependencyType.Javascript); - //ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/xml/xml.js", "UmbracoRoot", ClientDependencyType.Javascript); - //ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/javascript/javascript.js", "UmbracoRoot", ClientDependencyType.Javascript); - //ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/css/css.js", "UmbracoRoot", ClientDependencyType.Javascript); + } ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/search/search.js", "UmbracoRoot", ClientDependencyType.Javascript); From b3664d2391b85d28037bf2045beab061f639913c Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 7 Sep 2015 23:45:55 +0200 Subject: [PATCH 29/85] Fixes: U4-7059 SQL problem upgrading from 7.2.8 to 7.3.0 RC --- .../Expressions/InsertDataExpression.cs | 67 +++++++++++++------ .../Syntax/Insert/IInsertDataSyntax.cs | 1 + .../Syntax/Insert/InsertDataBuilder.cs | 6 ++ ...reignKeysForLanguageAndDictionaryTables.cs | 51 +++++++++++++- 4 files changed, 103 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs index 568087733b55..7cbfe8b773b7 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; @@ -9,55 +10,79 @@ public class InsertDataExpression : MigrationExpressionBase { private readonly List _rows = new List(); + [Obsolete("Use the other constructors specifying an ISqlSyntaxProvider instead")] public InsertDataExpression() { } + [Obsolete("Use the other constructors specifying an ISqlSyntaxProvider instead")] public InsertDataExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders) : base(current, databaseProviders) { } + public InsertDataExpression(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax) + { + } + + public InsertDataExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) : base(current, databaseProviders, sqlSyntax) + { + } + public string SchemaName { get; set; } public string TableName { get; set; } + public bool EnabledIdentityInsert { get; set; } - public List Rows { get { return _rows; } } - - public override string ToString() { - //TODO: This works for single insertion entries, not sure if it is valid for bulk insert operations!!! - if (IsExpressionSupported() == false) return string.Empty; var insertItems = new List(); - - foreach (var item in Rows) + var sb = new StringBuilder(); + + if (EnabledIdentityInsert && SqlSyntax.SupportsIdentityInsert()) { - var cols = ""; - var vals = ""; - foreach (var keyVal in item) + sb.AppendLine(string.Format("SET IDENTITY_INSERT {0} ON;", SqlSyntax.GetQuotedTableName(TableName))); + } + + try + { + foreach (var item in Rows) { - cols += keyVal.Key + ","; - vals += GetQuotedValue(keyVal.Value) + ","; - } - cols = cols.TrimEnd(','); - vals = vals.TrimEnd(','); + var cols = ""; + var vals = ""; + foreach (var keyVal in item) + { + cols += keyVal.Key + ","; + vals += GetQuotedValue(keyVal.Value) + ","; + } + cols = cols.TrimEnd(','); + vals = vals.TrimEnd(','); + + + var sql = string.Format(SqlSyntax.InsertData, + SqlSyntax.GetQuotedTableName(TableName), + cols, vals); + insertItems.Add(sql); + } - var sql = string.Format(SqlSyntaxContext.SqlSyntaxProvider.InsertData, - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(TableName), - cols, vals); - - insertItems.Add(sql); + sb.AppendLine(string.Join(",", insertItems)); + } + finally + { + if (EnabledIdentityInsert && SqlSyntax.SupportsIdentityInsert()) + { + sb.AppendLine(string.Format(";SET IDENTITY_INSERT {0} OFF;", SqlSyntax.GetQuotedTableName(TableName))); + } } - return string.Join(",", insertItems); + return sb.ToString(); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs index fb0585f0e6fe..5791f0cb8386 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs @@ -2,6 +2,7 @@ { public interface IInsertDataSyntax : IFluentSyntax { + IInsertDataSyntax EnableIdentityInsert(); IInsertDataSyntax Row(object dataAsAnonymousType); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs index 9b419f8daf3a..4f8bf2dfcc90 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs @@ -14,6 +14,12 @@ public InsertDataBuilder(InsertDataExpression expression) _expression = expression; } + public IInsertDataSyntax EnableIdentityInsert() + { + _expression.EnabledIdentityInsert = true; + return this; + } + public IInsertDataSyntax Row(object dataAsAnonymousType) { _expression.Rows.Add(GetData(dataAsAnonymousType)); diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs index 81b93a888381..eea56031d4d1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs @@ -3,6 +3,8 @@ using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero @@ -30,6 +32,53 @@ public override void Up() Delete.FromTable("cmsLanguageText").Row(new { pk = pk }); } + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + + if (columns.Any(x => x.ColumnName.InvariantEquals("id") + && x.TableName.InvariantEquals("umbracoLanguage") + && x.DataType.InvariantEquals("smallint"))) + { + //Ensure that the umbracoLanguage PK is INT and not SmallInt (which it might be in older db versions) + // In order to 'change' this to an INT, we have to run a full migration script which is super annoying + Create.Table("umbracoLanguage_TEMP") + .WithColumn("id").AsInt32().NotNullable().Identity() + .WithColumn("languageISOCode").AsString(10).Nullable() + .WithColumn("languageCultureName").AsString(50).Nullable(); + + var currentData = this.Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); + foreach (var languageDto in currentData) + { + Insert.IntoTable("umbracoLanguage_TEMP") + .EnableIdentityInsert() + .Row(new {id = languageDto.Id, languageISOCode = languageDto.IsoCode, languageCultureName = languageDto.CultureName}); + } + + //ok, all data has been copied over, drop the old table, rename the temp table and re-add constraints. + Delete.Table("umbracoLanguage"); + Rename.Table("umbracoLanguage_TEMP").To("umbracoLanguage"); + + //add the pk + Create.PrimaryKey("PK_language").OnTable("umbracoLanguage").Column("id"); + } + + var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) + .Select(x => new DbIndexDefinition + { + TableName = x.Item1, + IndexName = x.Item2, + ColumnName = x.Item3, + IsUnique = x.Item4 + }).ToArray(); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_id")) == false) + { + Create.Index("IX_cmsDictionary_id").OnTable("cmsDictionary") + .OnColumn("id").Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique(); + } + //now we need to create a foreign key Create.ForeignKey("FK_cmsLanguageText_umbracoLanguage_id").FromTable("cmsLanguageText").ForeignColumn("languageId") .ToTable("umbracoLanguage").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); @@ -52,7 +101,7 @@ public override void Up() public override void Down() { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } } \ No newline at end of file From e577648efdcf0d61ac9e994926dce12f4aa84ae4 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 7 Sep 2015 12:38:46 +0200 Subject: [PATCH 30/85] U4-7048 - refresh IFile instances from disk --- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 152 ++++++++++++------ src/Umbraco.Core/Models/File.cs | 58 +++++-- src/Umbraco.Core/Models/PartialView.cs | 17 +- src/Umbraco.Core/Models/PartialViewType.cs | 9 ++ src/Umbraco.Core/Models/Script.cs | 17 +- src/Umbraco.Core/Models/Stylesheet.cs | 16 +- src/Umbraco.Core/Models/Template.cs | 34 ++-- .../Persistence/Factories/TemplateFactory.cs | 4 +- .../DeepCloneRuntimeCacheProvider.cs | 9 +- .../Repositories/FileRepository.cs | 9 ++ .../PartialViewMacroRepository.cs | 2 +- .../Repositories/PartialViewRepository.cs | 31 ++-- .../Repositories/ScriptRepository.cs | 37 ++--- .../Repositories/StylesheetRepository.cs | 37 +++-- .../Repositories/TemplateRepository.cs | 75 +++++---- src/Umbraco.Core/Services/FileService.cs | 69 +++----- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Tests/Models/TemplateTests.cs | 13 +- 18 files changed, 346 insertions(+), 244 deletions(-) create mode 100644 src/Umbraco.Core/Models/PartialViewType.cs diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 8fcda3d10e3a..79b453e9cbce 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.IO; +using System.IO; using System.Linq; using Umbraco.Core.Logging; @@ -9,7 +8,7 @@ namespace Umbraco.Core.IO { public class PhysicalFileSystem : IFileSystem { - internal string RootPath { get; private set; } + private readonly string _rootPath; private readonly string _rootUrl; public PhysicalFileSystem(string virtualRoot) @@ -18,8 +17,12 @@ public PhysicalFileSystem(string virtualRoot) if (virtualRoot.StartsWith("~/") == false) throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'"); - RootPath = IOHelper.MapPath(virtualRoot); + _rootPath = IOHelper.MapPath(virtualRoot); + _rootPath = EnsureDirectorySeparatorChar(_rootPath); + _rootPath = _rootPath.TrimEnd(Path.DirectorySeparatorChar); + _rootUrl = IOHelper.ResolveUrl(virtualRoot); + _rootUrl = _rootUrl.TrimEnd('/'); } public PhysicalFileSystem(string rootPath, string rootUrl) @@ -43,18 +46,18 @@ public PhysicalFileSystem(string rootPath, string rootUrl) rootPath = Path.Combine(localRoot, rootPath); } - RootPath = rootPath; - _rootUrl = rootUrl; + _rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar); + _rootUrl = rootUrl.TrimEnd('/'); } public IEnumerable GetDirectories(string path) { - path = EnsureTrailingSeparator(GetFullPath(path)); + var fullPath = GetFullPath(path); try { - if (Directory.Exists(path)) - return Directory.EnumerateDirectories(path).Select(GetRelativePath); + if (Directory.Exists(fullPath)) + return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath); } catch (UnauthorizedAccessException ex) { @@ -75,12 +78,13 @@ public void DeleteDirectory(string path) public void DeleteDirectory(string path, bool recursive) { - if (DirectoryExists(path) == false) + var fullPath = GetFullPath(path); + if (Directory.Exists(fullPath) == false) return; try { - Directory.Delete(GetFullPath(path), recursive); + Directory.Delete(fullPath, recursive); } catch (DirectoryNotFoundException ex) { @@ -90,7 +94,8 @@ public void DeleteDirectory(string path, bool recursive) public bool DirectoryExists(string path) { - return Directory.Exists(GetFullPath(path)); + var fullPath = GetFullPath(path); + return Directory.Exists(fullPath); } public void AddFile(string path, Stream stream) @@ -100,17 +105,17 @@ public void AddFile(string path, Stream stream) public void AddFile(string path, Stream stream, bool overrideIfExists) { - var fsRelativePath = GetRelativePath(path); - - var exists = FileExists(fsRelativePath); - if (exists && overrideIfExists == false) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); + var fullPath = GetFullPath(path); + var exists = File.Exists(fullPath); + if (exists && overrideIfExists == false) + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - EnsureDirectory(Path.GetDirectoryName(fsRelativePath)); + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); // ensure it exists if (stream.CanSeek) stream.Seek(0, 0); - using (var destination = (Stream)File.Create(GetFullPath(fsRelativePath))) + using (var destination = (Stream)File.Create(fullPath)) stream.CopyTo(destination); } @@ -121,9 +126,7 @@ public IEnumerable GetFiles(string path) public IEnumerable GetFiles(string path, string filter) { - var fsRelativePath = GetRelativePath(path); - - var fullPath = EnsureTrailingSeparator(GetFullPath(fsRelativePath)); + var fullPath = GetFullPath(path); try { @@ -150,12 +153,13 @@ public Stream OpenFile(string path) public void DeleteFile(string path) { - if (!FileExists(path)) + var fullPath = GetFullPath(path); + if (File.Exists(fullPath) == false) return; try { - File.Delete(GetFullPath(path)); + File.Delete(fullPath); } catch (FileNotFoundException ex) { @@ -165,53 +169,101 @@ public void DeleteFile(string path) public bool FileExists(string path) { - return File.Exists(GetFullPath(path)); + var fullpath = GetFullPath(path); + return File.Exists(fullpath); } + /// + /// Gets the relative path. + /// + /// The full path or url. + /// The path, relative to this filesystem's root. + /// + /// The relative path is relative to this filesystem's root, not starting with any + /// directory separator. All separators are converted to Path.DirectorySeparatorChar. + /// public string GetRelativePath(string fullPathOrUrl) { - var relativePath = fullPathOrUrl - .TrimStart(_rootUrl) - .Replace('/', Path.DirectorySeparatorChar) - .TrimStart(RootPath) - .TrimStart(Path.DirectorySeparatorChar); - - return relativePath; + // test url + var path = EnsureUrlSeparatorChar(fullPathOrUrl); + if (PathStartsWith(path, _rootUrl, '/')) + return path.Substring(_rootUrl.Length) + .Replace('/', Path.DirectorySeparatorChar) + .TrimStart(Path.DirectorySeparatorChar); + + // test path + path = EnsureDirectorySeparatorChar(fullPathOrUrl); + if (PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) + return path.Substring(_rootPath.Length) + .TrimStart(Path.DirectorySeparatorChar); + + return fullPathOrUrl; + + // previous code kept for reference + + //var relativePath = fullPathOrUrl + // .TrimStart(_rootUrl) + // .Replace('/', Path.DirectorySeparatorChar) + // .TrimStart(RootPath) + // .TrimStart(Path.DirectorySeparatorChar); + //return relativePath; } + /// + /// Gets the full path. + /// + /// The full or relative path. + /// The full path. + /// + /// On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this + /// filesystem's root) path. All separators are converted to Path.DirectorySeparatorChar. + /// public string GetFullPath(string path) { - //if the path starts with a '/' then it's most likely not a FS relative path which is required so convert it - if (path.StartsWith("/")) - { + // normalize + var opath = path; + path = EnsureDirectorySeparatorChar(path); + + // not sure what we are doing here - so if input starts with a (back) slash, + // we assume it's not a FS relative path and we try to convert it... but it + // really makes little sense? + if (path.StartsWith(Path.DirectorySeparatorChar.ToString())) path = GetRelativePath(path); - } // if already a full path, return - if (path.StartsWith(RootPath)) + if (PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) 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); + 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)) + if (PathStartsWith(fpath, _rootPath, Path.DirectorySeparatorChar)) return fpath; - throw new FileSecurityException("File '" + path + "' is outside this filesystem's root."); + throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root."); + } + + private static bool PathStartsWith(string path, string root, char separator) + { + // either it is identical to root, + // or it is root + separator + anything + + if (path.StartsWith(root, StringComparison.OrdinalIgnoreCase) == false) return false; + if (path.Length == root.Length) return true; + if (path.Length < root.Length) return false; + return path[root.Length] == separator; } public string GetUrl(string path) { - return _rootUrl.TrimEnd("/") + "/" + path - .TrimStart(Path.DirectorySeparatorChar) - .Replace(Path.DirectorySeparatorChar, '/') - .TrimEnd("/"); + path = EnsureUrlSeparatorChar(path).Trim('/'); + return _rootUrl + "/" + path; } public DateTimeOffset GetLastModified(string path) @@ -238,9 +290,19 @@ protected virtual void EnsureDirectory(string path) protected string EnsureTrailingSeparator(string path) { - if (!path.EndsWith(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)) - path = path + Path.DirectorySeparatorChar; + return path.EnsureEndsWith(Path.DirectorySeparatorChar); + } + protected string EnsureDirectorySeparatorChar(string path) + { + path = path.Replace('/', Path.DirectorySeparatorChar); + path = path.Replace('\\', Path.DirectorySeparatorChar); + return path; + } + + protected string EnsureUrlSeparatorChar(string path) + { + path = path.Replace('\\', '/'); return path; } diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index ae4aac6a1c39..ce6121f46f0a 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -2,6 +2,8 @@ using System.IO; using System.Reflection; using System.Runtime.Serialization; +using System.Text; +using Umbraco.Core.IO; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models @@ -15,12 +17,20 @@ public abstract class File : Entity, IFile { private string _path; private string _originalPath; - private string _content = string.Empty; //initialize to empty string, not null - protected File(string path) + // initialize to string.Empty so that it is possible to save a new file, + // should use the lazyContent ctor to set it to null when loading existing. + // cannot simply use HasIdentity as some classes (eg Script) override it + // in a weird way. + private string _content; + internal Func GetFileContent { get; set; } + + protected File(string path, Func getFileContent = null) { _path = path; _originalPath = _path; + GetFileContent = getFileContent; + _content = getFileContent != null ? null : string.Empty; } private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); @@ -96,15 +106,26 @@ public void ResetOriginalPath() /// /// Gets or sets the Content of a File /// + /// Marked as DoNotClone, because it should be lazy-reloaded from disk. [DataMember] + [DoNotClone] public virtual string Content { - get { return _content; } + get + { + if (_content != null) + return _content; + + // else, must lazy-load, and ensure it's not null + if (GetFileContent != null) + _content = GetFileContent(this); + return _content ?? (_content = string.Empty); + } set { SetPropertyValueAndDetectChanges(o => { - _content = value; + _content = value ?? string.Empty; // cannot set to null return _content; }, _content, ContentSelector); } @@ -121,17 +142,32 @@ public virtual bool IsValid() return true; } + // this exists so that class that manage name and alias differently, eg Template, + // can implement their own cloning - (though really, not sure it's even needed) + protected virtual void DeepCloneNameAndAlias(File clone) + { + // set fields that have a lazy value, by forcing evaluation of the lazy + clone._name = Name; + clone._alias = Alias; + } + public override object DeepClone() { - var clone = (File)base.DeepClone(); - //turn off change tracking + var clone = (File) base.DeepClone(); + + // clear fields that were memberwise-cloned and that we don't want to clone + clone._content = null; + + // turn off change tracking clone.DisableChangeTracking(); - //need to manually assign since they are readonly properties - clone._alias = Alias; - clone._name = Name; - //this shouldn't really be needed since we're not tracking + + // ... + DeepCloneNameAndAlias(clone); + + // this shouldn't really be needed since we're not tracking clone.ResetDirtyProperties(false); - //re-enable tracking + + // re-enable tracking clone.EnableChangeTracking(); return clone; diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index d483de4176f9..75914820f0c3 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; -using System.Text.RegularExpressions; -using Umbraco.Core.IO; +using Umbraco.Core.Services; namespace Umbraco.Core.Models { @@ -15,12 +12,14 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class PartialView : File, IPartialView { - public PartialView(string path) - : base(path) - { - base.Path = path; - } + : this(path, null) + { } + + internal PartialView(string path, Func getFileContent) + : base(path, getFileContent) + { } + internal PartialViewType ViewType { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PartialViewType.cs b/src/Umbraco.Core/Models/PartialViewType.cs new file mode 100644 index 000000000000..2b45448271d9 --- /dev/null +++ b/src/Umbraco.Core/Models/PartialViewType.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Models +{ + internal enum PartialViewType : byte + { + Unknown = 0, // default + PartialView = 1, + PartialViewMacro = 2 + } +} diff --git a/src/Umbraco.Core/Models/Script.cs b/src/Umbraco.Core/Models/Script.cs index 71f06844479c..325885d9bac9 100644 --- a/src/Umbraco.Core/Models/Script.cs +++ b/src/Umbraco.Core/Models/Script.cs @@ -1,7 +1,5 @@ using System; -using System.Linq; using System.Runtime.Serialization; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -15,16 +13,17 @@ namespace Umbraco.Core.Models public class Script : File { public Script(string path) - : base(path) - { - - } + : this(path, (Func) null) + { } + + internal Script(string path, Func getFileContent) + : base(path, getFileContent) + { } [Obsolete("This is no longer used and will be removed from the codebase in future versions")] public Script(string path, IContentSection contentConfig) - : this(path) - { - } + : base(path) + { } /// /// Indicates whether the current entity has an identity, which in this case is a path/name. diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index ca203d835dd3..060246df542e 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Data; using System.Linq; using System.Runtime.Serialization; -using System.Text; using Umbraco.Core.IO; using Umbraco.Core.Strings.Css; @@ -16,12 +16,16 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Stylesheet : File { - public Stylesheet(string path) - : base(path.EnsureEndsWith(".css")) - { + public Stylesheet(string path) + : this(path, null) + { } + + internal Stylesheet(string path, Func getFileContent) + : base(path.EnsureEndsWith(".css"), getFileContent) + { InitializeProperties(); } - + private Lazy> _properties; private void InitializeProperties() @@ -81,7 +85,7 @@ private void InitializeProperties() /// /// /// - void Property_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + void Property_PropertyChanged(object sender, PropertyChangedEventArgs e) { var prop = (StylesheetProperty) sender; diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 3a8cbf879488..4aca88f28666 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Drawing; +using System.IO; using System.Reflection; using System.Runtime.Serialization; +using System.Text; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -29,12 +32,16 @@ public class Template : File, ITemplate private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public Template(string name, string alias) - : base(string.Empty) + : this(name, alias, (Func) null) + { } + + internal Template(string name, string alias, Func getFileContent) + : base(string.Empty, getFileContent) { _name = name; _alias = alias.ToCleanString(CleanStringType.UnderscoreAlias); _masterTemplateId = new Lazy(() => -1); - } + } [Obsolete("This constructor should not be used, file path is determined by alias, setting the path here will have no affect")] public Template(string path, string name, string alias) @@ -123,7 +130,6 @@ internal override void AddingEntity() Key = Guid.NewGuid(); } - public void SetMasterTemplate(ITemplate masterTemplate) { if (masterTemplate == null) @@ -139,27 +145,9 @@ public void SetMasterTemplate(ITemplate masterTemplate) } - public override object DeepClone() + protected override void DeepCloneNameAndAlias(File clone) { - - //We cannot call in to the base classes to clone because the base File class treats Alias, Name.. differently so we need to manually do the clone - - //Memberwise clone on Entity will work since it doesn't have any deep elements - // for any sub class this will work for standard properties as well that aren't complex object's themselves. - var clone = (Template)MemberwiseClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //Automatically deep clone ref properties that are IDeepCloneable - DeepCloneHelper.DeepCloneRefProperties(this, clone); - - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; + // do nothing - prevents File from doing its stuff } - - } } diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index d1742dc030c8..60cde916b60f 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -33,9 +33,9 @@ public TemplateFactory(int primaryKey, Guid nodeObjectTypeId) #region Implementation of IEntityFactory - public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions) + public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions, Func getFileContent) { - var template = new Template(dto.NodeDto.Text, dto.Alias) + var template = new Template(dto.NodeDto.Text, dto.Alias, getFileContent) { CreateDate = dto.NodeDto.CreateDate, Id = dto.NodeId, diff --git a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs index a87ec1e764bd..0b5b42660dab 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs @@ -79,7 +79,7 @@ public object GetCacheItem(string cacheKey) public object GetCacheItem(string cacheKey, Func getCacheItem) { - return InnerProvider.GetCacheItem(cacheKey, () => + var cached = InnerProvider.GetCacheItem(cacheKey, () => { var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache @@ -87,18 +87,21 @@ public object GetCacheItem(string cacheKey, Func getCacheItem) return CheckCloneableAndTracksChanges(value); }); + return CheckCloneableAndTracksChanges(cached); } public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - return InnerProvider.GetCacheItem(cacheKey, () => + var cached = InnerProvider.GetCacheItem(cacheKey, () => { var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache if (value == null) return null; // do not store null values (backward compat) return CheckCloneableAndTracksChanges(value); - }, timeout, isSliding, priority, removedCallback, dependentFiles); + }, timeout, isSliding, priority, removedCallback, dependentFiles); + + return CheckCloneableAndTracksChanges(cached); } public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) diff --git a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs index 391a777e5eb7..04ac5b6f62e7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs @@ -219,6 +219,15 @@ protected IEnumerable FindAllFiles(string path, string filter) return list; } + protected string GetFileContent(string filename) + { + using (var stream = FileSystem.OpenFile(filename)) + using (var reader = new StreamReader(stream, Encoding.UTF8, true)) + { + return reader.ReadToEnd(); + } + } + /// /// Dispose any disposable properties /// diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs index f4d6906cd1f8..e055c3dd93ad 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs @@ -7,7 +7,6 @@ namespace Umbraco.Core.Persistence.Repositories { internal class PartialViewMacroRepository : PartialViewRepository { - public PartialViewMacroRepository(IUnitOfWork work) : this(work, new PhysicalFileSystem(SystemDirectories.MvcViews + "/MacroPartials/")) { @@ -18,5 +17,6 @@ public PartialViewMacroRepository(IUnitOfWork work, IFileSystem fileSystem) { } + protected override PartialViewType ViewType { get { return PartialViewType.PartialViewMacro; } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs index 2fd024070168..254d2cbc0c37 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs @@ -20,6 +20,8 @@ public PartialViewRepository(IUnitOfWork work, IFileSystem fileSystem) : base(wo { } + protected virtual PartialViewType ViewType { get { return PartialViewType.PartialView; } } + public override IPartialView Get(string id) { if (FileSystem.FileExists(id) == false) @@ -27,29 +29,19 @@ public override IPartialView Get(string id) return null; } - string content; - using (var stream = FileSystem.OpenFile(id)) - { - var bytes = new byte[stream.Length]; - stream.Position = 0; - stream.Read(bytes, 0, (int)stream.Length); - content = Encoding.UTF8.GetString(bytes); - } - var path = FileSystem.GetRelativePath(id); var created = FileSystem.GetCreated(path).UtcDateTime; var updated = FileSystem.GetLastModified(path).UtcDateTime; - - var script = new PartialView(path) + var script = new PartialView(path, file => GetFileContent(file.Path)) { //id can be the hash Id = path.GetHashCode(), - Content = content, Key = path.EncodeAsGuid(), CreateDate = created, UpdateDate = updated, - VirtualPath = FileSystem.GetUrl(id) + VirtualPath = FileSystem.GetUrl(id), + ViewType = ViewType }; //on initial construction we don't want to have dirty properties tracked @@ -59,6 +51,19 @@ public override IPartialView Get(string id) return script; } + public override void AddOrUpdate(IPartialView entity) + { + var partialView = entity as PartialView; + if (partialView != null) + partialView.ViewType = ViewType; + + base.AddOrUpdate(entity); + + // ensure that from now on, content is lazy-loaded + if (partialView != null && partialView.GetFileContent == null) + partialView.GetFileContent = file => GetFileContent(file.Path); + } + public override IEnumerable GetAll(params string[] ids) { //ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries diff --git a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs index f8fc459aec1a..e288efdc856a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -28,33 +26,27 @@ public ScriptRepository(IUnitOfWork work, IFileSystem fileSystem, IContentSectio public override Script Get(string id) { - if(FileSystem.FileExists(id) == false) - { - return null; - } + // get the relative path within the filesystem + // (though... id should be relative already) + var path = FileSystem.GetRelativePath(id); - string content; - using (var stream = FileSystem.OpenFile(id)) - { - var bytes = new byte[stream.Length]; - stream.Position = 0; - stream.Read(bytes, 0, (int)stream.Length); - content = Encoding.UTF8.GetString(bytes); - } + if (FileSystem.FileExists(path) == false) + return null; - var path = FileSystem.GetRelativePath(id); + // content will be lazy-loaded when required var created = FileSystem.GetCreated(path).UtcDateTime; var updated = FileSystem.GetLastModified(path).UtcDateTime; + //var content = GetFileContent(path); - var script = new Script(path) + var script = new Script(path, file => GetFileContent(file.Path)) { //id can be the hash Id = path.GetHashCode(), - Content = content, Key = path.EncodeAsGuid(), + //Content = content, CreateDate = created, UpdateDate = updated, - VirtualPath = FileSystem.GetUrl(id) + VirtualPath = FileSystem.GetUrl(path) }; //on initial construction we don't want to have dirty properties tracked @@ -64,6 +56,15 @@ public override Script Get(string id) return script; } + public override void AddOrUpdate(Script entity) + { + base.AddOrUpdate(entity); + + // ensure that from now on, content is lazy-loaded + if (entity.GetFileContent == null) + entity.GetFileContent = file => GetFileContent(file.Path); + } + public override IEnumerable + \ No newline at end of file From 91a1955454f20048d6406ae7ddfd07c5fec808ac Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 17 Sep 2015 12:20:11 +0200 Subject: [PATCH 72/85] Make sure to redirect to the current version of HTML Agility Pack --- src/Umbraco.Web.UI/web.Template.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index b3d69ff778c1..221aecfbd09a 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -250,7 +250,7 @@ - + From 7d8247047b32e8ea81a45c1371782fa098a07d40 Mon Sep 17 00:00:00 2001 From: Jeavon Date: Mon, 21 Sep 2015 14:23:36 +0100 Subject: [PATCH 73/85] Fix for U4-6784 Multiple Media Picker - Failed to retrieve entity data for ids --- src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 55095e1f8e84..ac7e410a8f3e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -307,7 +307,7 @@ private Sql GetFullSqlForMedia(Sql entitySql, Action filter = null) .Append(new Sql(") tmpTbl LEFT JOIN (")) .Append(joinSql) .Append(new Sql(") as property ON id = property.contentNodeId")) - .OrderBy("sortOrder"); + .OrderBy("sortOrder, id"); return wrappedSql; } From 3612780d150f9b0b5f61f3ed5e02cb7d6fe8e89e Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 22 Sep 2015 11:18:06 +0200 Subject: [PATCH 74/85] git-ignore loader.dev.js --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ebee9f6ae966..04e39e37c9aa 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/umbraco.* src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/routes.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.dev.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.dev.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.js From 25cfe0193ce44dc71ab4a3a1bbb088adacfbbb9d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Sep 2015 12:19:14 +0200 Subject: [PATCH 75/85] adds detailed shutdown info to logs by default --- src/Umbraco.Core/UmbracoApplicationBase.cs | 44 +++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs index 5b9c8903c619..6aadbd269abb 100644 --- a/src/Umbraco.Core/UmbracoApplicationBase.cs +++ b/src/Umbraco.Core/UmbracoApplicationBase.cs @@ -1,5 +1,7 @@ using System; +using System.Diagnostics; using System.Linq; +using System.Reflection; using System.Web; using System.Web.Hosting; using log4net; @@ -180,7 +182,47 @@ protected void Application_End(object sender, EventArgs e) { if (SystemUtilities.GetCurrentTrustLevel() == AspNetHostingPermissionLevel.Unrestricted) { - Logger.Info("Application shutdown. Reason: " + HostingEnvironment.ShutdownReason); + //Try to log the detailed shutdown message (typical asp.net hack: http://weblogs.asp.net/scottgu/433194) + try + { + var runtime = (HttpRuntime)typeof(HttpRuntime).InvokeMember("_theRuntime", + BindingFlags.NonPublic + | BindingFlags.Static + | BindingFlags.GetField, + null, + null, + null); + if (runtime == null) + return; + + var shutDownMessage = (string)runtime.GetType().InvokeMember("_shutDownMessage", + BindingFlags.NonPublic + | BindingFlags.Instance + | BindingFlags.GetField, + null, + runtime, + null); + + var shutDownStack = (string)runtime.GetType().InvokeMember("_shutDownStack", + BindingFlags.NonPublic + | BindingFlags.Instance + | BindingFlags.GetField, + null, + runtime, + null); + + var shutdownMsg = string.Format("{0}\r\n\r\n_shutDownMessage={1}\r\n\r\n_shutDownStack={2}", + HostingEnvironment.ShutdownReason, + shutDownMessage, + shutDownStack); + + Logger.Info("Application shutdown. Details: " + shutdownMsg); + } + catch (Exception) + { + //if for some reason that fails, then log the normal output + Logger.Info("Application shutdown. Reason: " + HostingEnvironment.ShutdownReason); + } } OnApplicationEnd(sender, e); From 03f68cefb7ecc2cc1534ebb063d0a1a35e0525c2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Sep 2015 14:39:55 +0200 Subject: [PATCH 76/85] performs a fallback check for HttpContext in the batched server messengers in some cases where cache refreshers are executed on async threads... though there's not much we can do about when they are fired on background threads. --- src/Umbraco.Web/BatchedDatabaseServerMessenger.cs | 12 ++++++++++-- src/Umbraco.Web/BatchedWebServiceServerMessenger.cs | 13 +++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index 714b79514d55..00dc4e8063cc 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -98,11 +98,19 @@ public void FlushBatch() protected ICollection GetBatch(bool ensureHttpContext) { - var httpContext = UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext; + //try get the http context from the UmbracoContext, we do this because in the case we are launching an async + // thread and we know that the cache refreshers will execute, we will ensure the UmbracoContext and therefore we + // can get the http context from it + var httpContext = (UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext) + //if this is null, it could be that an async thread is calling this method that we weren't aware of and the UmbracoContext + // wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be + // the case if the asp.net synchronization context has kicked in + ?? (HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current)); + if (httpContext == null) { if (ensureHttpContext) - throw new NotSupportedException("Cannot execute without a valid/current UmbracoContext with an HttpContext assigned."); + throw new NotSupportedException("Cannot execute without a valid/current HttpContext assigned."); return null; } diff --git a/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs b/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs index 5fc3b48eee4c..63536ca9f7e4 100644 --- a/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs +++ b/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Web; using Umbraco.Core.Sync; namespace Umbraco.Web @@ -38,11 +39,19 @@ public BatchedWebServiceServerMessenger(Func> getLoginAndP protected override ICollection GetBatch(bool ensureHttpContext) { - var httpContext = UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext; + //try get the http context from the UmbracoContext, we do this because in the case we are launching an async + // thread and we know that the cache refreshers will execute, we will ensure the UmbracoContext and therefore we + // can get the http context from it + var httpContext = (UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext) + //if this is null, it could be that an async thread is calling this method that we weren't aware of and the UmbracoContext + // wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be + // the case if the asp.net synchronization context has kicked in + ?? (HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current)); + if (httpContext == null) { if (ensureHttpContext) - throw new NotSupportedException("Cannot execute without a valid/current UmbracoContext with an HttpContext assigned."); + throw new NotSupportedException("Cannot execute without a valid/current HttpContext assigned."); return null; } From e9afa78444f5372a03097cdf48bf524d029bcbc2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 22 Sep 2015 16:13:21 +0200 Subject: [PATCH 77/85] U4-7124 - add initial migration as part of BaseDataCreation --- .../Persistence/DatabaseSchemaHelper.cs | 3 --- .../Migrations/Initial/BaseDataCreation.cs | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs index 55c1fe505f66..dc3b2f3e8b8a 100644 --- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs @@ -80,9 +80,6 @@ internal void CreateDatabaseSchemaDo(IMigrationEntryService migrationEntryServic var creation = new DatabaseSchemaCreation(_db, _logger, _syntaxProvider); creation.InitializeDatabaseSchema(); - //Now ensure to cretae the tag in the db for the current migration version - migrationEntryService.CreateEntry(GlobalSettings.UmbracoMigrationName, UmbracoVersion.GetSemanticVersion()); - _logger.Info("Finalized database schema creation"); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index c36e0850c113..fbe7675836df 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -86,11 +87,17 @@ public void InitializeBaseData(string tableName) { CreateUmbracoRelationTypeData(); } + if (tableName.Equals("cmsTaskType")) { CreateCmsTaskTypeData(); } + if (tableName.Equals("umbracoMigration")) + { + CreateUmbracoMigrationData(); + } + _logger.Info(string.Format("Done creating data in table {0}", tableName)); } @@ -274,5 +281,18 @@ private void CreateCmsTaskTypeData() { _database.Insert("cmsTaskType", "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); } + + private void CreateUmbracoMigrationData() + { + var dto = new MigrationDto + { + Id = 1, + Name = GlobalSettings.UmbracoMigrationName, + Version = UmbracoVersion.GetSemanticVersion().ToString(), + CreateDate = DateTime.Now + }; + + _database.Insert("umbracoMigration", "pk", false, dto); + } } } \ No newline at end of file From 3124ea4e676aa4b70716c913f4a9085756c436e4 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 22 Sep 2015 15:38:19 +0200 Subject: [PATCH 78/85] U4-7129 - BatchedDatabaseServerMessenger degraded non-batching mode when no context --- .../BatchedDatabaseServerMessenger.cs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index 00dc4e8063cc..730efd380b53 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -85,7 +85,11 @@ public void FlushBatch() var instructions = batch.SelectMany(x => x.Instructions).ToArray(); batch.Clear(); if (instructions.Length == 0) return; + WriteInstructions(instructions); + } + private void WriteInstructions(RefreshInstruction[] instructions) + { var dto = new CacheInstructionDto { UtcStamp = DateTime.UtcNow, @@ -96,29 +100,25 @@ public void FlushBatch() ApplicationContext.DatabaseContext.Database.Insert(dto); } - protected ICollection GetBatch(bool ensureHttpContext) + protected ICollection GetBatch(bool create) { - //try get the http context from the UmbracoContext, we do this because in the case we are launching an async + // try get the http context from the UmbracoContext, we do this because in the case we are launching an async // thread and we know that the cache refreshers will execute, we will ensure the UmbracoContext and therefore we // can get the http context from it var httpContext = (UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext) - //if this is null, it could be that an async thread is calling this method that we weren't aware of and the UmbracoContext + // if this is null, it could be that an async thread is calling this method that we weren't aware of and the UmbracoContext // wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be // the case if the asp.net synchronization context has kicked in ?? (HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current)); - if (httpContext == null) - { - if (ensureHttpContext) - throw new NotSupportedException("Cannot execute without a valid/current HttpContext assigned."); - return null; - } + // if no context was found, return null - we cannot not batch + if (httpContext == null) return null; var key = typeof (BatchedDatabaseServerMessenger).Name; // no thread-safety here because it'll run in only 1 thread (request) at a time var batch = (ICollection)httpContext.Items[key]; - if (batch == null && ensureHttpContext) + if (batch == null && create) httpContext.Items[key] = batch = new List(); return batch; } @@ -132,11 +132,13 @@ protected void BatchMessage( string json = null) { var batch = GetBatch(true); - if (batch == null) - throw new Exception("Failed to get a batch."); + var instructions = RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json); - batch.Add(new RefreshInstructionEnvelope(servers, refresher, - RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json))); + // batch if we can, else write to DB immediately + if (batch == null) + WriteInstructions(instructions.ToArray()); + else + batch.Add(new RefreshInstructionEnvelope(servers, refresher, instructions)); } } } \ No newline at end of file From 5b688d42ba4b468d14a580b4f035467bcee623fb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 23 Sep 2015 15:56:05 +1000 Subject: [PATCH 79/85] Add custom repo package resotore Adds the ability for contributors to pull the Umbraco specific Nuget packages from the Myget repository using package restore. https://docs.nuget.org/consume/nuget-config-file --- src/NuGet.Config | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/NuGet.Config diff --git a/src/NuGet.Config b/src/NuGet.Config new file mode 100644 index 000000000000..44cee4cc3ca3 --- /dev/null +++ b/src/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From 6b639e940c077d6bda6b242c3d82fb414c768a49 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 23 Sep 2015 16:05:07 +1000 Subject: [PATCH 80/85] Update ImageProcessor dependency for core An update to U4-7053. Reduced memory usage plus web optimised images. --- build/NuSpecs/UmbracoCms.Core.nuspec | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 8 ++------ src/Umbraco.Web.UI/packages.config | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 0c7d51334e36..cba4de7cbe9e 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 39258f6b1a8a..9f34fca8378b 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -135,12 +135,8 @@ False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - ..\packages\ImageProcessor.2.2.8.0\lib\net45\ImageProcessor.dll - True - - - ..\packages\ImageProcessor.Web.4.3.6.0\lib\net45\ImageProcessor.Web.dll + + ..\packages\ImageProcessor.2.3.0.0\lib\net45\ImageProcessor.dll True diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index efab0db7da27..289aed138d17 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -5,8 +5,8 @@ - - + + From d05395075dd9e5b953d1323d4d73db2daea49de2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 23 Sep 2015 08:34:55 +0200 Subject: [PATCH 81/85] Indentation --- src/NuGet.Config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NuGet.Config b/src/NuGet.Config index 44cee4cc3ca3..f8a7d2049977 100644 --- a/src/NuGet.Config +++ b/src/NuGet.Config @@ -2,6 +2,6 @@ - + \ No newline at end of file From 2599fdb1d288dc706fac2b2a4641ffd4ca822a18 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 23 Sep 2015 18:06:49 +1000 Subject: [PATCH 82/85] Fix missing project reference --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 9f34fca8378b..bb30ea09b92a 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -139,6 +139,10 @@ ..\packages\ImageProcessor.2.3.0.0\lib\net45\ImageProcessor.dll True + + ..\packages\ImageProcessor.Web.4.4.0.0\lib\net45\ImageProcessor.Web.dll + True + False ..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll From 14c4e9dda7a4b0033df5410432b5a058c22d4ca9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Sep 2015 11:29:29 +0200 Subject: [PATCH 83/85] Fixes upgrade issue for when the same alias is used between content types and media types. --- .../AddUniqueIdPropertyTypeColumn.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs index f2f9261f52e8..58278eff84fe 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs @@ -6,6 +6,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { + [Migration("7.3.0", 13, GlobalSettings.UmbracoMigrationName)] public class AddUniqueIdPropertyTypeColumn : MigrationBase { @@ -35,13 +36,17 @@ public override void Up() // the already existing data, see: http://issues.umbraco.org/issue/U4-6942 foreach (var data in Context.Database.Query(@" -SELECT cmsPropertyType.id ptId, cmsPropertyType.Alias ptAlias, cmsContentType.alias ctAlias +SELECT cmsPropertyType.id ptId, cmsPropertyType.Alias ptAlias, cmsContentType.alias ctAlias, umbracoNode.nodeObjectType nObjType FROM cmsPropertyType INNER JOIN cmsContentType -ON cmsPropertyType.contentTypeId = cmsContentType.nodeId")) +ON cmsPropertyType.contentTypeId = cmsContentType.nodeId +INNER JOIN umbracoNode +ON cmsContentType.nodeId = umbracoNode.id")) { - //create a guid from the concatenation of the property type alias + the doc type alias - string concatAlias = data.ptAlias + data.ctAlias; + //create a guid from the concatenation of the: + // property type alias + the doc type alias + the content type node object type + // - the latter is required because there can be a content type and media type with the same alias!! + string concatAlias = data.ptAlias + data.ctAlias + data.nObjType; var ptGuid = concatAlias.ToGuid(); //set the Unique Id to the one we've generated From b384f1b0c3265329064be5aeac9e5627cdfa5118 Mon Sep 17 00:00:00 2001 From: Per Ploug Date: Thu, 24 Sep 2015 11:39:43 +0200 Subject: [PATCH 84/85] adds a private identifier validation method Fixes issue with Umbraco Forms not being able to save --- .../services/contenteditinghelper.service.js | 71 ++++++++++++------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 4576f3344b46..ae4ca3cde8dd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -2,13 +2,32 @@ /** * @ngdoc service * @name umbraco.services.contentEditingHelper -* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by +* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by * all editors to share logic and reduce the amount of replicated code among editors. **/ function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, serverValidationManager, dialogService, formHelper, appState, keyboardService) { + function isValidIdentifier(id){ + //empty id <= 0 + if(angular.isNumber(id) && id > 0){ + return true; + } + + //empty guid + if(id === "00000000-0000-0000-0000-000000000000"){ + return false; + } + + //empty string / alias + if(id === ""){ + return false; + } + + return true; + } + return { - + /** Used by the content editor and mini content editor to perform saving operations */ contentEditorPerformSave: function (args) { if (!angular.isObject(args)) { @@ -30,7 +49,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var self = this; var deferred = $q.defer(); - + if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage })) { args.scope.busy = true; @@ -71,9 +90,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica return deferred.promise; }, - + /** Returns the action button definitions based on what permissions the user has. - The content.allowedActions parameter contains a list of chars, each represents a button by permission so + The content.allowedActions parameter contains a list of chars, each represents a button by permission so here we'll build the buttons according to the chars of the user. */ configureContentEditorButtons: function (args) { @@ -136,7 +155,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica handler: args.methods.unPublish }; default: - return null; + return null; } } @@ -160,8 +179,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //Now we need to make the drop down button list, this is also slightly tricky because: //We cannot have any buttons if there's no default button above. - //We cannot have the unpublish button (Z) when there's no publish permission. - //We cannot have the unpublish button (Z) when the item is not published. + //We cannot have the unpublish button (Z) when there's no publish permission. + //We cannot have the unpublish button (Z) when the item is not published. if (buttons.defaultButton) { //get the last index of the button order @@ -174,7 +193,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } - //if we are not creating, then we should add unpublish too, + //if we are not creating, then we should add unpublish too, // so long as it's already published and if the user has access to publish if (!args.create) { if (args.content.publishDate && _.contains(args.content.allowedActions, "U")) { @@ -235,13 +254,13 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } } } - + actions.push(defaultAction); //Now we need to make the drop down button list, this is also slightly tricky because: //We cannot have any buttons if there's no default button above. - //We cannot have the unpublish button (Z) when there's no publish permission. - //We cannot have the unpublish button (Z) when the item is not published. + //We cannot have the unpublish button (Z) when there's no publish permission. + //We cannot have the unpublish button (Z) when the item is not published. if (defaultAction) { //get the last index of the button order var lastIndex = _.indexOf(actionOrder, defaultAction); @@ -253,7 +272,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } } - //if we are not creating, then we should add unpublish too, + //if we are not creating, then we should add unpublish too, // so long as it's already published and if the user has access to publish if (!creating) { if (content.publishDate && _.contains(content.allowedActions,"U")) { @@ -329,7 +348,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var allOrigProps = this.getAllProps(origContent); var allNewProps = this.getAllProps(savedContent); - function getNewProp(alias) { + function getNewProp(alias) { return _.find(allNewProps, function (item) { return item.alias === alias; }); @@ -343,12 +362,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica }; //check for changed built-in properties of the content for (var o in origContent) { - + //ignore the ones listed in the array if (shouldIgnore(o)) { continue; } - + if (!_.isEqual(origContent[o], savedContent[o])) { origContent[o] = savedContent[o]; } @@ -362,8 +381,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //they have changed so set the origContent prop to the new one var origVal = allOrigProps[p].value; allOrigProps[p].value = newProp.value; - - //instead of having a property editor $watch their expression to check if it has + + //instead of having a property editor $watch their expression to check if it has // been updated, instead we'll check for the existence of a special method on their model // and just call it. if (angular.isFunction(allOrigProps[p].onValueChanged)) { @@ -388,7 +407,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * A function to handle what happens when we have validation issues from the server side */ handleSaveError: function (args) { - + if (!args.err) { throw "args.err cannot be null"; } @@ -402,7 +421,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica if (args.err.status === 400) { //now we need to look through all the validation errors if (args.err.data && (args.err.data.ModelState)) { - + //wire up the server validation errs formHelper.handleServerValidation(args.err.data.ModelState); @@ -414,7 +433,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { args.rebindCallback(); } - + serverValidationManager.executeAndClearAllSubscriptions(); } @@ -453,7 +472,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { - + //we are not redirecting because this is not new content, it is existing content. In this case // we need to detect what properties have changed and re-bind them with the server data. //call the callback @@ -471,14 +490,14 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * * @description * Changes the location to be editing the newly created content after create was successful. - * We need to decide if we need to redirect to edito mode or if we will remain in create mode. + * We need to decide if we need to redirect to edito mode or if we will remain in create mode. * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID */ redirectToCreatedContent: function (id, modelState) { //only continue if we are currently in create mode and if there is no 'Name' modelstate errors // since we need at least a name to create content. - if ($routeParams.create && (id > 0 && (!modelState || !modelState["Name"]))) { + if ($routeParams.create && (isValidIdentifier(id) && (!modelState || !modelState["Name"]))) { //need to change the location to not be in 'create' mode. Currently the route will be something like: // /belle/#/content/edit/1234?doctype=newsArticle&create=true @@ -487,7 +506,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //clear the query strings $location.search(""); - + //change to new path $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this @@ -498,4 +517,4 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } }; } -angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper); \ No newline at end of file +angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper); From 407cd0ca60617966393825cf367ce3d8943709b3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Sep 2015 11:29:09 +0200 Subject: [PATCH 85/85] Streamlines TypeFinder with better lock and an error check on app_code which could otherwise bring down the app --- src/Umbraco.Core/TypeFinder.cs | 227 ++++++++++++--------------------- 1 file changed, 81 insertions(+), 146 deletions(-) diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs index 632322789399..0b4e85e237b1 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/TypeFinder.cs @@ -26,11 +26,8 @@ namespace Umbraco.Core /// public static class TypeFinder { - private static readonly HashSet LocalFilteredAssemblyCache = new HashSet(); - private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim(); - private static HashSet _allAssemblies = null; - private static HashSet _binFolderAssemblies = null; - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + private static volatile HashSet _localFilteredAssemblyCache = null; + private static readonly object LocalFilteredAssemblyCacheLocker = new object(); /// /// lazily load a reference to all assemblies and only local assemblies. @@ -46,162 +43,97 @@ public static class TypeFinder /// internal static HashSet GetAllAssemblies() { - using (var lck = new UpgradeableReadLock(Locker)) - { - if (_allAssemblies == null) - { + return AllAssemblies.Value; + } - lck.UpgradeToWriteLock(); + //Lazy access to the all assemblies list + private static readonly Lazy> AllAssemblies = new Lazy>(() => + { + HashSet assemblies = null; + try + { + var isHosted = HttpContext.Current != null; - HashSet assemblies = null; - try + try + { + if (isHosted) { - var isHosted = HttpContext.Current != null; + assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + } + } + catch (InvalidOperationException e) + { + if (!(e.InnerException is SecurityException)) + throw; + } + if (assemblies == null) + { + //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have + // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. + var binFolder = IOHelper.GetRootDirectoryBinFolder(); + var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; + //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + assemblies = new HashSet(); + foreach (var a in binAssemblyFiles) + { try { - if (isHosted) - { - assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); - } - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; + var assName = AssemblyName.GetAssemblyName(a); + var ass = Assembly.Load(assName); + assemblies.Add(ass); } - - - if (assemblies == null) + catch (Exception e) { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = IOHelper.GetRootDirectoryBinFolder(); - var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new HashSet(); - foreach (var a in binAssemblyFiles) + if (e is SecurityException || e is BadImageFormatException) { - try - { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); - } - catch (Exception e) - { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } - } + //swallow these exceptions } - } - - //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. - if (!assemblies.Any()) - { - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + else { - assemblies.Add(a); + throw; } } - - //here we are trying to get the App_Code assembly - var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); - //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))) - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } - - //now set the _allAssemblies - _allAssemblies = new HashSet(assemblies); - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; + } - _binFolderAssemblies = _allAssemblies; + //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. + if (!assemblies.Any()) + { + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + assemblies.Add(a); } } - return _allAssemblies; - } - } - - /// - /// Returns only assemblies found in the bin folder that have been loaded into the app domain. - /// - /// - /// - /// This will be used if we implement App_Plugins from Umbraco v5 but currently it is not used. - /// - internal static HashSet GetBinAssemblies() - { - - if (_binFolderAssemblies == null) - { - using (new WriteLock(Locker)) + //here we are trying to get the App_Code assembly + var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported + var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); + //check if the folder exists and if there are any files in it with the supported file extensions + if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))) { - var assemblies = GetAssembliesWithKnownExclusions().ToArray(); - var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - var domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName); - var safeDomainAssemblies = new HashSet(); - var binFolderAssemblies = new HashSet(); - - foreach (var a in assemblies) + try { - try - { - //do a test to see if its queryable in med trust - var assemblyFile = a.GetAssemblyFile(); - safeDomainAssemblies.Add(a); - } - catch (SecurityException) - { - //we will just ignore this because this will fail - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to - //an assembly that is ok. - } + var appCodeAssembly = Assembly.Load("App_Code"); + if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already + assemblies.Add(appCodeAssembly); } - - foreach (var assemblyName in domainAssemblyNames) + catch (FileNotFoundException ex) { - try - { - var foundAssembly = - safeDomainAssemblies.FirstOrDefault(a => a.GetAssemblyFile() == assemblyName.GetAssemblyFile()); - if (foundAssembly != null) - { - binFolderAssemblies.Add(foundAssembly); - } - } - catch (SecurityException) - { - //we will just ignore this because if we are trying to do a call to: - // AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName))) - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to - //an assembly that is ok. - } + //this will occur if it cannot load the assembly + LogHelper.Error(typeof(TypeFinder), "Could not load assembly App_Code", ex); } - - _binFolderAssemblies = new HashSet(binFolderAssemblies); } } - return _binFolderAssemblies; - } + catch (InvalidOperationException e) + { + if (!(e.InnerException is SecurityException)) + throw; + } + + return assemblies; + }); /// /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan @@ -213,20 +145,23 @@ internal static HashSet GetBinAssemblies() internal static HashSet GetAssembliesWithKnownExclusions( IEnumerable excludeFromResults = null) { - using (var lck = new UpgradeableReadLock(LocalFilteredAssemblyCacheLocker)) + if (_localFilteredAssemblyCache == null) { - if (LocalFilteredAssemblyCache.Any()) return LocalFilteredAssemblyCache; - - lck.UpgradeToWriteLock(); - - var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); - foreach (var a in assemblies) + lock (LocalFilteredAssemblyCacheLocker) { - LocalFilteredAssemblyCache.Add(a); + //double check + if (_localFilteredAssemblyCache == null) + { + _localFilteredAssemblyCache = new HashSet(); + var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); + foreach (var a in assemblies) + { + _localFilteredAssemblyCache.Add(a); + } + } } - - return LocalFilteredAssemblyCache; } + return _localFilteredAssemblyCache; } ///