From ae110c1cae5bc652548385be473438b437008349 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Tue, 23 Aug 2016 08:45:30 +0100 Subject: [PATCH 01/46] Honour ConfigSource when setting ConnectionString Lets umbraco honour the configSource attribute on a connectionstrings section, meaning you can install the connectionstring into a file other than web.config. --- src/Umbraco.Core/DatabaseContext.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 05aba6b97d..8d257a937b 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -341,6 +341,22 @@ namespace Umbraco.Core var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); var connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single(); + // honour configSource, if its set, change the xml file we are saving the configuration + // to the one set in the configSource attribute + if (connectionstrings.Attribute("configSource") != null) + { + var source = connectionstrings.Attribute("configSource").Value; + var configFile = IOHelper.MapPath(string.Format("{0}/{1}", SystemDirectories.Root, source)); + LogHelper.Info("storing ConnectionString in {0}", () => configFile); + if (System.IO.File.Exists(configFile)) + { + xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); + fileName = configFile; + } + + connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single(); + } + // Update connectionString if it exists, or else create a new appSetting for the given key and value var setting = connectionstrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == GlobalSettings.UmbracoConnectionName); if (setting == null) From b2004057a50382e4ab45e3dcc2e851256d4fcf4d Mon Sep 17 00:00:00 2001 From: Claus Date: Fri, 9 Feb 2018 11:55:34 +0100 Subject: [PATCH 02/46] U4-10957 If RequiredSection is not defined in a tour - backoffice will throw errors --- src/Umbraco.Web/Editors/TourController.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs index da16659cfe..98a01f2c6e 100644 --- a/src/Umbraco.Web/Editors/TourController.cs +++ b/src/Umbraco.Web/Editors/TourController.cs @@ -70,14 +70,20 @@ namespace Umbraco.Web.Editors //Checking to see if the user has access to the required tour sections, else we remove the tour foreach (var backOfficeTourFile in result) { - foreach (var tour in backOfficeTourFile.Tours) + if (backOfficeTourFile.Tours != null) { - foreach (var toursRequiredSection in tour.RequiredSections) + foreach (var tour in backOfficeTourFile.Tours) { - if (allowedSections.Contains(toursRequiredSection) == false) + if (tour.RequiredSections != null) { - toursToBeRemoved.Add(backOfficeTourFile); - break; + foreach (var toursRequiredSection in tour.RequiredSections) + { + if (allowedSections.Contains(toursRequiredSection) == false) + { + toursToBeRemoved.Add(backOfficeTourFile); + break; + } + } } } } From 7019c9433be9d9fc3fe662e3a6cc967df36c5485 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 23 Feb 2018 16:40:29 +1000 Subject: [PATCH 03/46] U4-10991 TourController Object reference not set to an instance of an object - when loading an invalid tour --- src/Umbraco.Web/Models/BackOfficeTour.cs | 5 +++++ src/Umbraco.Web/Models/BackOfficeTourFile.cs | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Models/BackOfficeTour.cs b/src/Umbraco.Web/Models/BackOfficeTour.cs index 78a4cd1897..268d5667f4 100644 --- a/src/Umbraco.Web/Models/BackOfficeTour.cs +++ b/src/Umbraco.Web/Models/BackOfficeTour.cs @@ -9,6 +9,11 @@ namespace Umbraco.Web.Models [DataContract(Name = "tour", Namespace = "")] public class BackOfficeTour { + public BackOfficeTour() + { + RequiredSections = new List(); + } + [DataMember(Name = "name")] public string Name { get; set; } [DataMember(Name = "alias")] diff --git a/src/Umbraco.Web/Models/BackOfficeTourFile.cs b/src/Umbraco.Web/Models/BackOfficeTourFile.cs index 7291a89ff4..6840171f48 100644 --- a/src/Umbraco.Web/Models/BackOfficeTourFile.cs +++ b/src/Umbraco.Web/Models/BackOfficeTourFile.cs @@ -9,6 +9,11 @@ namespace Umbraco.Web.Models [DataContract(Name = "tourFile", Namespace = "")] public class BackOfficeTourFile { + public BackOfficeTourFile() + { + Tours = new List(); + } + /// /// The file name for the tour /// @@ -27,4 +32,4 @@ namespace Umbraco.Web.Models [DataMember(Name = "tours")] public IEnumerable Tours { get; set; } } -} \ No newline at end of file +} From 9c8c9d8a398a4d0fc0810bb9474bb3b709d76ed7 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 27 Feb 2018 15:47:47 +0100 Subject: [PATCH 04/46] UAASSCRUM-1405 Clean up preview for Headless --- .../components/content/edit.controller.js | 8 +++--- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 12 +++++++++ src/Umbraco.Web/Editors/PreviewController.cs | 27 +++++++++++++++++++ src/Umbraco.Web/Features/DisabledFeatures.cs | 5 ++++ src/Umbraco.Web/Mvc/BackOfficeArea.cs | 14 +++++----- 5 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 src/Umbraco.Web/Editors/PreviewController.cs diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 1ecb2d7403..8441963e87 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -242,10 +242,10 @@ // Chromes popup blocker will kick in if a window is opened // without the initial scoped request. This trick will fix that. // - var previewWindow = $window.open('preview/?init=true&id=' + content.id, 'umbpreview'); + var previewWindow = $window.open('previews/?init=true&id=' + content.id, 'umbpreview'); // Build the correct path so both /#/ and #/ work. - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/previews/?id=' + content.id; //The user cannot save if they don't have access to do that, in which case we just want to preview //and that's it otherwise they'll get an unauthorized access message @@ -255,11 +255,9 @@ else { $scope.save().then(function (data) { previewWindow.location.href = redirect; - }); + }); } - } - }; $scope.restore = function (content) { diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 8ca1759cf1..7d370140b5 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -837,6 +837,18 @@ + + + + + + + + + + + + Web.Template.config diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs new file mode 100644 index 0000000000..971b504d61 --- /dev/null +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -0,0 +1,27 @@ +using System; +using System.Web.Mvc; +using Umbraco.Web.Features; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web.Editors +{ + [UmbracoAuthorize] + [DisableBrowserCache] + public class PreviewController : Controller + { + private const string ViewsPath = "~/Umbraco/Views/Preview/"; + + public ActionResult Index() + { + ViewData["DisableDevicePreview"] = FeaturesResolver.Current.Features.Disabled.DevicePreview; + return View(ViewsPath + "Index.cshtml"); + } + + [AllowAnonymous] + public ActionResult Editors(string editor) + { + if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException("editor"); + return View(ViewsPath + editor.Replace(".html", string.Empty) + ".cshtml"); + } + } +} diff --git a/src/Umbraco.Web/Features/DisabledFeatures.cs b/src/Umbraco.Web/Features/DisabledFeatures.cs index b9d8d0a2d9..c061faefce 100644 --- a/src/Umbraco.Web/Features/DisabledFeatures.cs +++ b/src/Umbraco.Web/Features/DisabledFeatures.cs @@ -20,5 +20,10 @@ namespace Umbraco.Web.Features /// Gets the disabled controllers. /// public TypeList Controllers { get; private set; } + + /// + /// Disables the device preview feature of previewing. + /// + public bool DevicePreview { get; set; } } } diff --git a/src/Umbraco.Web/Mvc/BackOfficeArea.cs b/src/Umbraco.Web/Mvc/BackOfficeArea.cs index 68afc3fc39..b594b0272b 100644 --- a/src/Umbraco.Web/Mvc/BackOfficeArea.cs +++ b/src/Umbraco.Web/Mvc/BackOfficeArea.cs @@ -1,10 +1,6 @@ -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; +using System.Web.Mvc; using Umbraco.Core.Configuration; using Umbraco.Web.Editors; -using Umbraco.Web.Install; -using Umbraco.Web.Install.Controllers; namespace Umbraco.Web.Mvc { @@ -24,6 +20,12 @@ namespace Umbraco.Web.Mvc /// public override void RegisterArea(AreaRegistrationContext context) { + context.MapRoute( + "Umbraco_preview", + GlobalSettings.UmbracoMvcArea + "/previews/{action}/{editor}", + new {controller = "Preview", action = "Index", editor = UrlParameter.Optional}, + new[] { "Umbraco.Web.Editors" }); + context.MapRoute( "Umbraco_back_office", GlobalSettings.UmbracoMvcArea + "/{action}/{id}", @@ -51,4 +53,4 @@ namespace Umbraco.Web.Mvc get { return GlobalSettings.UmbracoMvcArea; } } } -} \ No newline at end of file +} From 0ee3ebd180c42baf54aa824954f893d947d0131b Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 28 Feb 2018 08:58:16 +0100 Subject: [PATCH 05/46] adding the preview controller to project. removing old static html files from src (they're now cshtml files in Views). copy task should not copy static canvasdesigner files anymore. --- src/Umbraco.Web.UI.Client/gulpfile.js | 4 +- .../canvasdesigner/editors/background.html | 57 ------------------- .../src/canvasdesigner/editors/border.html | 20 ------- .../src/canvasdesigner/editors/color.html | 3 - .../editors/googlefontpicker.html | 34 ----------- .../src/canvasdesigner/editors/gridRow.html | 8 --- .../src/canvasdesigner/editors/layout.html | 10 ---- .../src/canvasdesigner/editors/margin.html | 14 ----- .../src/canvasdesigner/editors/padding.html | 14 ----- .../src/canvasdesigner/editors/radius.html | 21 ------- .../src/canvasdesigner/editors/shadow.html | 8 --- .../src/canvasdesigner/editors/slider.html | 8 --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 - src/Umbraco.Web/Umbraco.Web.csproj | 1 + 14 files changed, 2 insertions(+), 201 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/background.html delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/border.html delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/color.html delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.html delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/gridRow.html delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/layout.html delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/margin.html delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/padding.html delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/radius.html delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/shadow.html delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/slider.html diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 841c0476e2..bb3ff65cf0 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -73,7 +73,6 @@ var sources = { js: { preview: { files: ["src/canvasdesigner/**/*.js"], out: "umbraco.canvasdesigner.js" }, installer: { files: ["src/installer/**/*.js"], out: "umbraco.installer.js" }, - controllers: { files: ["src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" }, directives: { files: ["src/common/directives/**/*.js"], out: "umbraco.directives.js" }, filters: { files: ["src/common/filters/**/*.js"], out: "umbraco.filters.js" }, @@ -85,8 +84,7 @@ var sources = { //selectors for copying all views into the build //processed in the views task views:{ - umbraco: {files: ["src/views/**/*html"], folder: ""}, - preview: { files: ["src/canvasdesigner/**/*.html"], folder: "../preview"}, + umbraco: {files: ["src/views/**/*.html"], folder: ""}, installer: {files: ["src/installer/steps/*.html"], folder: "install"} }, diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/background.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/background.html deleted file mode 100644 index 84add72919..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/background.html +++ /dev/null @@ -1,57 +0,0 @@ - -
- -
-
-
- -
-
- - -
-
- -
- - diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/border.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/border.html deleted file mode 100644 index 651cd67446..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/border.html +++ /dev/null @@ -1,20 +0,0 @@ - -
- -
-
    -
  • -
-
- -
-
- - -
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/color.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/color.html deleted file mode 100644 index 94a412f128..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/color.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.html deleted file mode 100644 index 6ff7aa2a72..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.html +++ /dev/null @@ -1,34 +0,0 @@ - -
- -
-
- Aa - {{ item.values.fontFamily }} - -
-
- -
- - diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/gridRow.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/gridRow.html deleted file mode 100644 index bcfc79ef63..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/gridRow.html +++ /dev/null @@ -1,8 +0,0 @@ - -
- -
- -
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/layout.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/layout.html deleted file mode 100644 index 5a0dfed448..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/layout.html +++ /dev/null @@ -1,10 +0,0 @@ - -
- -
- Box - Wide - Full -
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/margin.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/margin.html deleted file mode 100644 index 145c38f2f3..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/margin.html +++ /dev/null @@ -1,14 +0,0 @@ - -
- -
-
    -
  • -
-
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/padding.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/padding.html deleted file mode 100644 index e645ce0a67..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/padding.html +++ /dev/null @@ -1,14 +0,0 @@ - -
- -
-
    -
  • -
-
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/radius.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/radius.html deleted file mode 100644 index 328698b44b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/radius.html +++ /dev/null @@ -1,21 +0,0 @@ - -
- -
-
    - -
  • - - - - -
  • - -
-
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/shadow.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/shadow.html deleted file mode 100644 index f9f88401c4..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/shadow.html +++ /dev/null @@ -1,8 +0,0 @@ - -
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/slider.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/slider.html deleted file mode 100644 index f33d314841..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/slider.html +++ /dev/null @@ -1,8 +0,0 @@ - -
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 7d370140b5..48ad0bb58f 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1006,7 +1006,6 @@ - diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c6d528bb3d..a505fb477a 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -316,6 +316,7 @@ + From 13c675d718a9f8487243a70e9e497ea9f7da9377 Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 28 Feb 2018 12:04:39 +0100 Subject: [PATCH 06/46] updating route to use the old url. removing leftover index.html. adding migration for renaming preview folder on disk. --- .gitignore | 1 + src/Umbraco.Core/IO/SystemDirectories.cs | 1 - .../RenamePreviewFolder.cs | 41 +++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../src/canvasdesigner/index.html | 165 ------------------ .../components/content/edit.controller.js | 4 +- src/Umbraco.Web/Mvc/BackOfficeArea.cs | 2 +- 7 files changed, 46 insertions(+), 169 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTenZero/RenamePreviewFolder.cs delete mode 100644 src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html diff --git a/.gitignore b/.gitignore index 6a4c95895d..1226db8c71 100644 --- a/.gitignore +++ b/.gitignore @@ -113,6 +113,7 @@ build/ApiDocs/* build/ApiDocs/Output/* src/Umbraco.Web.UI.Client/bower_components/* /src/Umbraco.Web.UI/Umbraco/preview +/src/Umbraco.Web.UI/Umbraco/preview.old #Ignore Rule for output of generated documentation files from Grunt docserve src/Umbraco.Web.UI.Client/docs/api diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index 9c815504c7..2a4a0709f3 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -203,7 +203,6 @@ namespace Umbraco.Core.IO { get { - //by default the packages folder should exist in the data folder return IOHelper.ReturnPath("umbracoPreviewPath", Data + IOHelper.DirSepChar + "preview"); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTenZero/RenamePreviewFolder.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTenZero/RenamePreviewFolder.cs new file mode 100644 index 0000000000..248492a3ea --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTenZero/RenamePreviewFolder.cs @@ -0,0 +1,41 @@ +using System.IO; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; +using File = System.IO.File; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTenZero +{ + /// + /// Renames the preview folder containing static html files to ensure it does not interfere with the MVC route + /// that is now supposed to render these views dynamically. We don't want to delete as people may have made + /// customizations to these files that would need to be migrated to the new .cshtml view files. + /// + [Migration("7.10.0", 1, Constants.System.UmbracoMigrationName)] + public class RenamePreviewFolder : MigrationBase + { + public RenamePreviewFolder(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + var previewFolderPath = IOHelper.MapPath(SystemDirectories.Umbraco + "/preview"); + if (Directory.Exists(previewFolderPath)) + { + var newPath = previewFolderPath.Replace("preview", "preview.old"); + Directory.Move(previewFolderPath, newPath); + var readmeText = + $"Static html files used for preview and canvas editing functionality no longer live in this directory.\r\n" + + $"Instead they have been recreated as MVC views and can now be found in '~/Umbraco/Views/Preview'.\r\n" + + $"See issue: http://issues.umbraco.org/issue/UAASSCRUM-1405"; + File.WriteAllText(Path.Combine(newPath, "readme.txt"), readmeText); + } + } + + public override void Down() + { + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index bc58456e48..96670a390d 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -603,6 +603,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html deleted file mode 100644 index e5778a9d9b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - Umbraco Canvas Designer - - - - - - - -
- -
- -
- -
- -
- -
- -
- - - - - -
- -
- -
-

Palette Style

-
- - - - - -
- -
- -
-
-

Select

-
-
-
    -
  • - {{configItem.name}} -
  • -
-
-
- -
-
-

{{configItem.name}}

-
-
-
-

- {{category}} - - -

-
-
-
{{item.name}}
-
-
-
-
-
-
- - - -
- -
- -
- -
-

Styles saved and published

-
- - - - - - - diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 8441963e87..4452e35835 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -242,10 +242,10 @@ // Chromes popup blocker will kick in if a window is opened // without the initial scoped request. This trick will fix that. // - var previewWindow = $window.open('previews/?init=true&id=' + content.id, 'umbpreview'); + var previewWindow = $window.open('preview/?init=true&id=' + content.id, 'umbpreview'); // Build the correct path so both /#/ and #/ work. - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/previews/?id=' + content.id; + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; //The user cannot save if they don't have access to do that, in which case we just want to preview //and that's it otherwise they'll get an unauthorized access message diff --git a/src/Umbraco.Web/Mvc/BackOfficeArea.cs b/src/Umbraco.Web/Mvc/BackOfficeArea.cs index b594b0272b..091ba5f283 100644 --- a/src/Umbraco.Web/Mvc/BackOfficeArea.cs +++ b/src/Umbraco.Web/Mvc/BackOfficeArea.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.Mvc { context.MapRoute( "Umbraco_preview", - GlobalSettings.UmbracoMvcArea + "/previews/{action}/{editor}", + GlobalSettings.UmbracoMvcArea + "/preview/{action}/{editor}", new {controller = "Preview", action = "Index", editor = UrlParameter.Optional}, new[] { "Umbraco.Web.Editors" }); From 52684b6189841ede827c4442ced7fbfe1611e51a Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 28 Feb 2018 13:41:49 +0100 Subject: [PATCH 07/46] allowing to extend html used in preview view. --- src/Umbraco.Web/Editors/PreviewController.cs | 4 ++++ src/Umbraco.Web/Features/EnabledFeatures.cs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index 971b504d61..bd53fd55b6 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -14,6 +14,10 @@ namespace Umbraco.Web.Editors public ActionResult Index() { ViewData["DisableDevicePreview"] = FeaturesResolver.Current.Features.Disabled.DevicePreview; + if (string.IsNullOrWhiteSpace(FeaturesResolver.Current.Features.Enabled.ExtendPreviewHtml) == false) + { + ViewData["ExtendPreviewHtml"] = FeaturesResolver.Current.Features.Enabled.ExtendPreviewHtml; + } return View(ViewsPath + "Index.cshtml"); } diff --git a/src/Umbraco.Web/Features/EnabledFeatures.cs b/src/Umbraco.Web/Features/EnabledFeatures.cs index 7f0d047992..cef30a1e1a 100644 --- a/src/Umbraco.Web/Features/EnabledFeatures.cs +++ b/src/Umbraco.Web/Features/EnabledFeatures.cs @@ -10,5 +10,10 @@ namespace Umbraco.Web.Features /// This is to allow JSON preview of content with no template set. /// public bool RenderNoTemplate { get; set; } + + /// + /// This allows us to inject html into the preview function extending the view with custom data. + /// + public string ExtendPreviewHtml { get; set; } } } From 23638702c8cfe4e27d947240a51d4013c9073ace Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 1 Mar 2018 12:57:43 +0100 Subject: [PATCH 08/46] adding missing views... --- .../Umbraco/Views/Preview/Background.cshtml | 57 +++++++ .../Umbraco/Views/Preview/Border.cshtml | 20 +++ .../Umbraco/Views/Preview/Color.cshtml | 4 + .../Views/Preview/Googlefontpicker.cshtml | 34 ++++ .../Umbraco/Views/Preview/GridRow.cshtml | 8 + .../Umbraco/Views/Preview/Index.cshtml | 148 ++++++++++++++++++ .../Umbraco/Views/Preview/Layout.cshtml | 10 ++ .../Umbraco/Views/Preview/Margin.cshtml | 14 ++ .../Umbraco/Views/Preview/Padding.cshtml | 14 ++ .../Umbraco/Views/Preview/Radius.cshtml | 21 +++ .../Umbraco/Views/Preview/Shadow.cshtml | 8 + .../Umbraco/Views/Preview/Slider.cshtml | 8 + 12 files changed, 346 insertions(+) create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Background.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Border.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Color.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Googlefontpicker.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/GridRow.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Layout.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Margin.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Padding.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Radius.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Shadow.cshtml create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Slider.cshtml diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Background.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Background.cshtml new file mode 100644 index 0000000000..6e3793d840 --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Background.cshtml @@ -0,0 +1,57 @@ +@inherits System.Web.Mvc.WebViewPage +
+ +
+
+
+ +
+
+ + +
+
+ +
+ + diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Border.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Border.cshtml new file mode 100644 index 0000000000..40d46a0f4d --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Border.cshtml @@ -0,0 +1,20 @@ +@inherits System.Web.Mvc.WebViewPage +
+ +
+
    +
  • +
+
+ +
+
+ + +
+ +
+
+
+ +
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Color.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Color.cshtml new file mode 100644 index 0000000000..02213f2cd2 --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Color.cshtml @@ -0,0 +1,4 @@ +@inherits System.Web.Mvc.WebViewPage +
+
+
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Googlefontpicker.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Googlefontpicker.cshtml new file mode 100644 index 0000000000..e1e61e379e --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Googlefontpicker.cshtml @@ -0,0 +1,34 @@ +@inherits System.Web.Mvc.WebViewPage +
+ +
+
+ Aa + {{ item.values.fontFamily }} + +
+
+ +
+ + diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/GridRow.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/GridRow.cshtml new file mode 100644 index 0000000000..52f24cb25e --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/GridRow.cshtml @@ -0,0 +1,8 @@ +@inherits System.Web.Mvc.WebViewPage +
+ +
+ +
+ +
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml new file mode 100644 index 0000000000..1ccac9379c --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -0,0 +1,148 @@ +@inherits System.Web.Mvc.WebViewPage +@{ + var disableDevicePreview = ViewData["DisableDevicePreview"].ToString().ToLowerInvariant(); + var extendPreviewHtml = ViewData["ExtendPreviewHtml"] != null ? ViewData["ExtendPreviewHtml"].ToString() : null; +} + + + + Umbraco Canvas Designer + + + + + + @if (extendPreviewHtml != null) + { + @Html.Raw(extendPreviewHtml) + } +
+
+ +
+
+
+
+ +
+ + +
+ +
+
+
+

Select

+
+
+
    +
  • + {{configItem.name}} +
  • +
+
+
+
+
+

{{configItem.name}}

+
+
+
+

+ {{category}} + + +

+
+
+
{{item.name}}
+
+
+
+
+
+
+ +
+
+
+
+

Styles saved and published

+
+ + + + diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Layout.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Layout.cshtml new file mode 100644 index 0000000000..3df51ae23f --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Layout.cshtml @@ -0,0 +1,10 @@ +@inherits System.Web.Mvc.WebViewPage +
+ +
+ Box + Wide + Full +
+ +
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Margin.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Margin.cshtml new file mode 100644 index 0000000000..f14e592420 --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Margin.cshtml @@ -0,0 +1,14 @@ +@inherits System.Web.Mvc.WebViewPage +
+ +
+
    +
  • +
+
+ +
+
+
+ +
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Padding.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Padding.cshtml new file mode 100644 index 0000000000..3f2c440945 --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Padding.cshtml @@ -0,0 +1,14 @@ +@inherits System.Web.Mvc.WebViewPage +
+ +
+
    +
  • +
+
+ +
+
+
+ +
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Radius.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Radius.cshtml new file mode 100644 index 0000000000..1e8a96b712 --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Radius.cshtml @@ -0,0 +1,21 @@ +@inherits System.Web.Mvc.WebViewPage +
+ +
+
    + +
  • + + + + +
  • + +
+
+ +
+
+
+ +
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Shadow.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Shadow.cshtml new file mode 100644 index 0000000000..6b9d4763e8 --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Shadow.cshtml @@ -0,0 +1,8 @@ +@inherits System.Web.Mvc.WebViewPage +
+ +
+
+
+ +
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Slider.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Slider.cshtml new file mode 100644 index 0000000000..414159d714 --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Slider.cshtml @@ -0,0 +1,8 @@ +@inherits System.Web.Mvc.WebViewPage +
+ +
+
+
+ +
From 3061dc4e6fa9d1a8423afbefa5b3415a8998c44f Mon Sep 17 00:00:00 2001 From: Niels Hartvig Date: Tue, 6 Mar 2018 15:37:52 +0100 Subject: [PATCH 09/46] Adds support for *not* closing an overlay on enter --- .../directives/components/overlays/umboverlay.directive.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js index d728865015..fbf3c7eef8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js @@ -496,6 +496,7 @@ Opens an overlay to show a custom YSOD.
var activeElementType = document.activeElement.tagName; var clickableElements = ["A", "BUTTON"]; var submitOnEnter = document.activeElement.hasAttribute("overlay-submit-on-enter"); + var submitOnEnterValue = submitOnEnter ? document.activeElement.getAttribute("overlay-submit-on-enter") : ""; if(clickableElements.indexOf(activeElementType) === 0) { document.activeElement.click(); @@ -503,7 +504,9 @@ Opens an overlay to show a custom YSOD.
} else if(activeElementType === "TEXTAREA" && !submitOnEnter) { - } else { + } else if (submitOnEnter && submitOnEnterValue === "false") { + // don't do anything + }else { scope.$apply(function () { scope.submitForm(scope.model); }); From cd6380689549ee8ea89683ef14696fa34509d771 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 6 Mar 2018 16:11:27 +0100 Subject: [PATCH 10/46] U4-10756 - id/guid cache --- src/Umbraco.Core/Services/IdkMap.cs | 164 +++++++++++++++--- src/Umbraco.Web/Cache/MediaCacheRefresher.cs | 5 +- .../Cache/UnpublishedPageCacheRefresher.cs | 4 +- src/Umbraco.Web/PublishedContentQuery.cs | 26 +-- 4 files changed, 155 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Core/Services/IdkMap.cs b/src/Umbraco.Core/Services/IdkMap.cs index 91367a6316..42bbc8a146 100644 --- a/src/Umbraco.Core/Services/IdkMap.cs +++ b/src/Umbraco.Core/Services/IdkMap.cs @@ -22,19 +22,111 @@ namespace Umbraco.Core.Services // note - no need for uow, scope would be enough, but a pain to wire // note - for pure read-only we might want to *not* enforce a transaction? - public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) + // notes + // + // - this class assumes that the id/guid map is unique; that is, if an id and a guid map + // to each other, then the id will never map to another guid, and the guid will never map + // to another id + // + // - LeeK's solution in 7.7 was to look for the id/guid in the content cache "on demand" via + // XPath, which is probably fast enough but cannot deal with media ids + // see https://github.com/umbraco/Umbraco-CMS/pull/2398 + // + // - Andy's solution in a package was to prefetch all by sql; it cannot prefecth reserved ids + // as we don't know the corresponding object type, but that's not a big issue + // see https://github.com/AndyButland/UmbracoUdiToIdCache + // + // - the original IdkMap implementation that was used by services, did a database lookup on + // each cache miss, which is fine enough for services, but would be really slow at content + // cache level - so this implementation prefetches all document and media ids + // + // - and this implementation works for document and media ids + // + // - cache is cleared by MediaCacheRefresher, UnpublishedPageCacheRefresher, and other + // refreshers - because id/guid map is unique, we only clear to avoid leaking memory, 'cos + // we don't risk caching obsolete values - and only when actually deleting + + private void PopulateLocked() + { + // don't if not empty + if (_key2Id.Count > 0) return; + + using (var uow = _uowProvider.GetUnitOfWork()) + { + // populate content and media items + var types = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media }; + var values = uow.Database.Fetch("SELECT id, uniqueId, nodeObjectType FROM umbracoNode WHERE nodeObjectType IN @types", new { types }); + foreach (var value in values) + { + UmbracoObjectTypes umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(value.NodeObjectType); + _id2Key.Add(value.Id, new TypedId(value.UniqueId, umbracoObjectType)); + _key2Id.Add(value.UniqueId, new TypedId(value.Id, umbracoObjectType)); + } + } + } + + private Attempt PopulateAndGetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) { - TypedId id; try { - _locker.EnterReadLock(); - if (_key2Id.TryGetValue(key, out id) && id.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(id.Id); + _locker.EnterWriteLock(); + + PopulateLocked(); + + return _key2Id.TryGetValue(key, out var id) && id.UmbracoObjectType == umbracoObjectType + ? Attempt.Succeed(id.Id) + : Attempt.Fail(); + } finally { if (_locker.IsReadLockHeld) _locker.ExitReadLock(); } + } + + private Attempt PopulateAndGetKeyForId(int id, UmbracoObjectTypes umbracoObjectType) + { + try + { + _locker.EnterWriteLock(); + + PopulateLocked(); + + return _id2Key.TryGetValue(id, out var key) && key.UmbracoObjectType == umbracoObjectType + ? Attempt.Succeed(key.Id) + : Attempt.Fail(); + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + } + + public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) + { + bool empty; + + try + { + _locker.EnterReadLock(); + if (_key2Id.TryGetValue(key, out var id) && id.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(id.Id); + empty = _key2Id.Count == 0; + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + + // if cache is empty and looking for a document or a media, + // populate the cache at once and return what we found + if (empty && (umbracoObjectType == UmbracoObjectTypes.Document || umbracoObjectType == UmbracoObjectTypes.Media)) + return PopulateAndGetIdForKey(key, umbracoObjectType); + + // optimize for read speed: reading database outside a lock means that we could read + // multiple times, but we don't lock the cache while accessing the database = better int? val; using (var uow = _uowProvider.GetUnitOfWork()) @@ -83,13 +175,23 @@ namespace Umbraco.Core.Services return GetIdForKey(guidUdi.Guid, umbracoType); } + public Attempt GetUdiForId(int id, UmbracoObjectTypes umbracoObjectType) + { + var keyAttempt = GetKeyForId(id, umbracoObjectType); + return keyAttempt + ? Attempt.Succeed(new GuidUdi(Constants.UdiEntityType.FromUmbracoObjectType(umbracoObjectType), keyAttempt.Result)) + : Attempt.Fail(); + } + public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType) { - TypedId key; + bool empty; + try { _locker.EnterReadLock(); - if (_id2Key.TryGetValue(id, out key) && key.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(key.Id); + if (_id2Key.TryGetValue(id, out var key) && key.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(key.Id); + empty = _id2Key.Count == 0; } finally { @@ -97,6 +199,14 @@ namespace Umbraco.Core.Services _locker.ExitReadLock(); } + // if cache is empty and looking for a document or a media, + // populate the cache at once and return what we found + if (empty && (umbracoObjectType == UmbracoObjectTypes.Document || umbracoObjectType == UmbracoObjectTypes.Media)) + return PopulateAndGetKeyForId(id, umbracoObjectType); + + // optimize for read speed: reading database outside a lock means that we could read + // multiple times, but we don't lock the cache while accessing the database = better + Guid? val; using (var uow = _uowProvider.GetUnitOfWork()) { @@ -142,6 +252,8 @@ namespace Umbraco.Core.Services return guid; } + // invoked on UnpublishedPageCacheRefresher.RefreshAll + // anything else will use the id-specific overloads public void ClearCache() { try @@ -162,8 +274,7 @@ namespace Umbraco.Core.Services try { _locker.EnterWriteLock(); - TypedId key; - if (_id2Key.TryGetValue(id, out key) == false) return; + if (_id2Key.TryGetValue(id, out var key) == false) return; _id2Key.Remove(id); _key2Id.Remove(key.Id); } @@ -179,8 +290,7 @@ namespace Umbraco.Core.Services try { _locker.EnterWriteLock(); - TypedId id; - if (_key2Id.TryGetValue(key, out id) == false) return; + if (_key2Id.TryGetValue(key, out var id) == false) return; _id2Key.Remove(id.Id); _key2Id.Remove(key); } @@ -191,26 +301,28 @@ namespace Umbraco.Core.Services } } + // ReSharper disable ClassNeverInstantiated.Local + // ReSharper disable UnusedAutoPropertyAccessor.Local + private class TypedIdDto + { + public int Id { get; set; } + public Guid UniqueId { get; set; } + public Guid NodeObjectType { get; set; } + } + // ReSharper restore ClassNeverInstantiated.Local + // ReSharper restore UnusedAutoPropertyAccessor.Local + private struct TypedId { - private readonly T _id; - private readonly UmbracoObjectTypes _umbracoObjectType; - - public T Id - { - get { return _id; } - } - - public UmbracoObjectTypes UmbracoObjectType - { - get { return _umbracoObjectType; } - } - public TypedId(T id, UmbracoObjectTypes umbracoObjectType) { - _umbracoObjectType = umbracoObjectType; - _id = id; + UmbracoObjectType = umbracoObjectType; + Id = id; } + + public UmbracoObjectTypes UmbracoObjectType { get; } + + public T Id { get; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs index 3613da426d..783ca95841 100644 --- a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs @@ -155,7 +155,8 @@ namespace Umbraco.Web.Cache foreach (var payload in payloads) { - ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id); + if (payload.Operation == OperationType.Deleted) + ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id); var mediaCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); @@ -190,4 +191,4 @@ namespace Umbraco.Web.Cache } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs index 2941357f19..6404d60b40 100644 --- a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs @@ -84,7 +84,6 @@ namespace Umbraco.Web.Cache public override void Refresh(int id) { - ApplicationContext.Current.Services.IdkMap.ClearCache(id); ClearRepositoryCacheItemById(id); ClearAllIsolatedCacheByEntityType(); content.Instance.UpdateSortOrder(id); @@ -104,7 +103,6 @@ namespace Umbraco.Web.Cache public override void Refresh(IContent instance) { - ApplicationContext.Current.Services.IdkMap.ClearCache(instance.Id); ClearRepositoryCacheItemById(instance.Id); ClearAllIsolatedCacheByEntityType(); content.Instance.UpdateSortOrder(instance); @@ -150,4 +148,4 @@ namespace Umbraco.Web.Cache } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 8cbbfd3fda..d231cd04dd 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -13,6 +13,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Dynamics; using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Web.Models; using Umbraco.Web.PublishedCache; @@ -55,6 +56,9 @@ namespace Umbraco.Web _dynamicContentQuery = dynamicContentQuery; } + // TODO use this to implement media-by-GUID but is a breaking change? + private IdkMap IdkMap => ApplicationContext.Current.Services.IdkMap; // fixme inject - v8 + #region Content public IPublishedContent TypedContent(int id) @@ -67,7 +71,7 @@ namespace Umbraco.Web public IPublishedContent TypedContent(Guid id) { return _typedContentQuery == null - ? TypedDocumentById(id, _contentCache) + ? TypedDocumentById(id, _contentCache, UmbracoObjectTypes.Document) : _typedContentQuery.TypedContent(id); } @@ -88,7 +92,7 @@ namespace Umbraco.Web public IEnumerable TypedContent(IEnumerable ids) { return _typedContentQuery == null - ? TypedDocumentsByIds(_contentCache, ids) + ? TypedDocumentsByIds(_contentCache, ids, UmbracoObjectTypes.Document) : _typedContentQuery.TypedContent(ids); } @@ -232,13 +236,10 @@ namespace Umbraco.Web return doc; } - private IPublishedContent TypedDocumentById(Guid id, ContextualPublishedCache cache) + private IPublishedContent TypedDocumentById(Guid key, ContextualPublishedCache cache, UmbracoObjectTypes umbracoObjectType) { - // todo: in v8, implement in a more efficient way - var legacyXml = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; - var xpath = legacyXml ? "//node [@key=$guid]" : "//* [@key=$guid]"; - var doc = cache.GetSingleByXPath(xpath, new XPathVariable("guid", id.ToString())); - return doc; + var idAttempt = IdkMap.GetIdForKey(key, umbracoObjectType); + return idAttempt ? TypedDocumentById(idAttempt.Result, cache) : null; } private IPublishedContent TypedDocumentByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) @@ -259,10 +260,9 @@ namespace Umbraco.Web return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); } - private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids) + private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids, UmbracoObjectTypes umbracoObjectType) { - // todo: in v8, implement in a more efficient way - return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); + return ids.Select(eachId => TypedDocumentById(eachId, cache, umbracoObjectType)).WhereNotNull(); } private IEnumerable TypedDocumentsByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) @@ -292,7 +292,7 @@ namespace Umbraco.Web private dynamic DocumentById(Guid id, ContextualPublishedCache cache, object ifNotFound) { - var doc = TypedDocumentById(id, cache); + var doc = TypedDocumentById(id, cache, UmbracoObjectTypes.Document); return doc == null ? ifNotFound : new DynamicPublishedContent(doc).AsDynamic(); @@ -548,4 +548,4 @@ namespace Umbraco.Web #endregion } -} \ No newline at end of file +} From ca74100e12d36ca8750b72643b613dff674d39e3 Mon Sep 17 00:00:00 2001 From: Niels Hartvig Date: Tue, 6 Mar 2018 16:11:44 +0100 Subject: [PATCH 11/46] TLC for multiple pre values editor --- src/Umbraco.Web.UI.Client/src/less/belle.less | 5 +- .../components/prevalues/multivalues.less | 53 +++++++++++++++++++ .../prevalueeditors/multivalues.controller.js | 8 +++ .../views/prevalueeditors/multivalues.html | 22 +++++--- 4 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index d2a80d93aa..fb31889fd0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -168,6 +168,9 @@ //used for property editors @import "property-editors.less"; +//used for prevalue editors +@import "components/prevalues/multivalues.less"; + @import "typeahead.less"; @import "hacks.less"; @@ -175,4 +178,4 @@ @import "healthcheck.less"; // cleanup properties.less when it is done -@import "properties.less"; \ No newline at end of file +@import "properties.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less new file mode 100644 index 0000000000..a307e5c585 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less @@ -0,0 +1,53 @@ +.umb-prevalues-multivalues { + width: 400px; +} + +.umb-prevalues-multivalues__left { + display: flex; + flex: 1 1 auto; +} + +.umb-prevalues-multivalues__right { + display: flex; + flex: 0 0 auto; + align-items: center; +} + +.umb-prevalues-multivalues__add { + display: flex; +} + +.umb-prevalues-multivalues__add input { + width: 320px; +} + +.umb-prevalues-multivalues__add input { + display: flex; +} + +.umb-prevalues-multivalues__add button { + margin: 0 6px 0 0; + float: right +} + +.umb-prevalues-multivalues__listitem { + display: flex; + padding: 6px; + margin: 10px 0px !important; + background: #F3F3F5; + cursor: move; +} + +.umb-prevalues-multivalues__listitem i { + display: flex; + align-items: center; + margin-right: 5px +} + +.umb-prevalues-multivalues__listitem a { + cursor: pointer; +} + +.umb-prevalues-multivalues__listitem input { + width: 295px; +} diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.controller.js index a55a40d5ec..a8f5093b9e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.controller.js @@ -5,6 +5,7 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiValuesControl $scope.newItem = ""; $scope.hasError = false; + $scope.focusOnNew = false; if (!angular.isArray($scope.model.value)) { @@ -43,6 +44,7 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiValuesControl $scope.model.value.push({ value: $scope.newItem }); $scope.newItem = ""; $scope.hasError = false; + $scope.focusOnNew = true; return; } } @@ -73,6 +75,12 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiValuesControl } }; + $scope.createNew = function (event) { + if (event.keyCode == 13) { + $scope.add(event); + } + } + function getElementIndexByPrevalueText(value) { for (var i = 0; i < $scope.model.value.length; i++) { if ($scope.model.value[i].value === value) { diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.html index 561872cb2f..5c7d3b52c2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.html @@ -1,13 +1,21 @@ -
-
- - +
+
+
+ +
+
+ +
-
+
- - +
+ +
+
+ Remove +
From bedce100d20a98174bdd3ed16a43ac8f0b775b13 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Mar 2018 12:55:34 +1100 Subject: [PATCH 12/46] Fixes merge and the preview views path --- src/Umbraco.Web/Editors/PreviewController.cs | 9 +++++---- src/Umbraco.Web/Features/UmbracoFeatures.cs | 7 ++++++- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index bd53fd55b6..df72bf9dae 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -1,5 +1,7 @@ using System; using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Web.Features; using Umbraco.Web.Mvc; @@ -9,8 +11,7 @@ namespace Umbraco.Web.Editors [DisableBrowserCache] public class PreviewController : Controller { - private const string ViewsPath = "~/Umbraco/Views/Preview/"; - + public ActionResult Index() { ViewData["DisableDevicePreview"] = FeaturesResolver.Current.Features.Disabled.DevicePreview; @@ -18,14 +19,14 @@ namespace Umbraco.Web.Editors { ViewData["ExtendPreviewHtml"] = FeaturesResolver.Current.Features.Enabled.ExtendPreviewHtml; } - return View(ViewsPath + "Index.cshtml"); + return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + "Index.cshtml"); } [AllowAnonymous] public ActionResult Editors(string editor) { if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException("editor"); - return View(ViewsPath + editor.Replace(".html", string.Empty) + ".cshtml"); + return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + editor.Replace(".html", string.Empty) + ".cshtml"); } } } diff --git a/src/Umbraco.Web/Features/UmbracoFeatures.cs b/src/Umbraco.Web/Features/UmbracoFeatures.cs index abe9a76d06..95ebedfd01 100644 --- a/src/Umbraco.Web/Features/UmbracoFeatures.cs +++ b/src/Umbraco.Web/Features/UmbracoFeatures.cs @@ -27,7 +27,12 @@ namespace Umbraco.Web.Features /// Gets the disabled features. /// public DisabledFeatures Disabled { get; set; } - + + /// + /// Gets the enabled features. + /// + public EnabledFeatures Enabled { get; set; } + /// /// Determines whether a controller is enabled. /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 04f74fd62e..a505fb477a 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -332,6 +332,7 @@ + From f42b122e7385de0436f3a69c40636638acc42990 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Mar 2018 13:35:51 +1100 Subject: [PATCH 13/46] adding missing files --- src/Umbraco.Web/Features/EnabledFeatures.cs | 10 ++++++++++ src/Umbraco.Web/Features/UmbracoFeatures.cs | 9 +-------- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 3 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 src/Umbraco.Web/Features/EnabledFeatures.cs diff --git a/src/Umbraco.Web/Features/EnabledFeatures.cs b/src/Umbraco.Web/Features/EnabledFeatures.cs new file mode 100644 index 0000000000..92752e1c6a --- /dev/null +++ b/src/Umbraco.Web/Features/EnabledFeatures.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Web.Features +{ + /// + /// Represents enabled features. + /// + internal class EnabledFeatures + { + + } +} diff --git a/src/Umbraco.Web/Features/UmbracoFeatures.cs b/src/Umbraco.Web/Features/UmbracoFeatures.cs index abe9a76d06..4814f59b14 100644 --- a/src/Umbraco.Web/Features/UmbracoFeatures.cs +++ b/src/Umbraco.Web/Features/UmbracoFeatures.cs @@ -15,14 +15,7 @@ namespace Umbraco.Web.Features { Disabled = new DisabledFeatures(); } - - // note - // currently, the only thing a FeatureSet does is list disabled controllers, - // but eventually we could enable and disable more parts of Umbraco. and then - // we would need some logic to figure out what's enabled/disabled - hence it's - // better to use IsEnabled, where the logic would go, rather than directly - // accessing the Disabled collection. - + /// /// Gets the disabled features. /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index aa21001093..c6d528bb3d 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -331,6 +331,7 @@ + From 3e2fa6482009e6fa87d34b074779c71d5be786bc Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Mar 2018 13:56:49 +1100 Subject: [PATCH 14/46] Changes the Preview Index.cshtml to use a real model instead of viewdata, adds the correct razor web.config for these preview views, changes the enabled feature to be able to inject a razor view instead of strings --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 + .../Umbraco/Views/Preview/Index.cshtml | 13 +++++-------- src/Umbraco.Web/Editors/PreviewController.cs | 17 ++++++++++++++--- src/Umbraco.Web/Features/DisabledFeatures.cs | 2 +- src/Umbraco.Web/Features/EnabledFeatures.cs | 7 +++++-- src/Umbraco.Web/Features/UmbracoFeatures.cs | 1 + .../Models/ContentEditing/AuditLog.cs | 3 ++- .../Models/ContentEditing/BackOfficePreview.cs | 11 +++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 9 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 61ac4c8e29..1ca41f656f 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -849,6 +849,7 @@ + Web.Template.config diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 1ccac9379c..556d4ff0c1 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -1,8 +1,5 @@ -@inherits System.Web.Mvc.WebViewPage -@{ - var disableDevicePreview = ViewData["DisableDevicePreview"].ToString().ToLowerInvariant(); - var extendPreviewHtml = ViewData["ExtendPreviewHtml"] != null ? ViewData["ExtendPreviewHtml"].ToString() : null; -} +@inherits System.Web.Mvc.WebViewPage + @@ -12,15 +9,15 @@ - @if (extendPreviewHtml != null) + @if (string.IsNullOrWhiteSpace(Model.PreviewExtendedView) == false) { - @Html.Raw(extendPreviewHtml) + @Html.Partial(Model.PreviewExtendedView) }
-
+
/// Disables the device preview feature of previewing. /// - public bool DevicePreview { get; set; } + public bool DisableDevicePreview { get; set; } /// /// If true, all references to templates will be removed in the back office and routing diff --git a/src/Umbraco.Web/Features/EnabledFeatures.cs b/src/Umbraco.Web/Features/EnabledFeatures.cs index a9ebae60cd..4e6b5fa085 100644 --- a/src/Umbraco.Web/Features/EnabledFeatures.cs +++ b/src/Umbraco.Web/Features/EnabledFeatures.cs @@ -7,8 +7,11 @@ namespace Umbraco.Web.Features { /// - /// This allows us to inject html into the preview function extending the view with custom data. + /// This allows us to inject a razor view into the Umbraco preview view to extend it /// - public string ExtendPreviewHtml { get; set; } + /// + /// This is set to a virtual path of a razor view file + /// + public string PreviewExtendedView { get; set; } } } diff --git a/src/Umbraco.Web/Features/UmbracoFeatures.cs b/src/Umbraco.Web/Features/UmbracoFeatures.cs index 03b70c874d..fb2684583e 100644 --- a/src/Umbraco.Web/Features/UmbracoFeatures.cs +++ b/src/Umbraco.Web/Features/UmbracoFeatures.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.Features public UmbracoFeatures() { Disabled = new DisabledFeatures(); + Enabled = new EnabledFeatures(); } /// diff --git a/src/Umbraco.Web/Models/ContentEditing/AuditLog.cs b/src/Umbraco.Web/Models/ContentEditing/AuditLog.cs index 48c489135a..b7a5a4afe4 100644 --- a/src/Umbraco.Web/Models/ContentEditing/AuditLog.cs +++ b/src/Umbraco.Web/Models/ContentEditing/AuditLog.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { + [DataContract(Name = "auditLog", Namespace = "")] public class AuditLog { @@ -31,4 +32,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "comment")] public string Comment { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs b/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs new file mode 100644 index 0000000000..aa2cee57b9 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// The model representing Previewing of a content item from the back office + /// + public class BackOfficePreview + { + public string PreviewExtendedView { get; set; } + public bool DisableDevicePreview { get; set; } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a505fb477a..650c840e30 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -334,6 +334,7 @@ + From 830f460b7e42382d9bc7492c05b4153c1ba37f33 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Mar 2018 13:58:48 +1100 Subject: [PATCH 15/46] bumps version --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 279cc645e2..9da63d813f 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.9.2")] -[assembly: AssemblyInformationalVersion("7.9.2")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.10.0")] +[assembly: AssemblyInformationalVersion("7.10.0-beta004")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index e3ecd4c5b0..9268cbbd91 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.9.2"); + private static readonly Version Version = new Version("7.10.0"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 1ca41f656f..fe8fb807d4 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1035,9 +1035,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" True True - 7920 + 7100 / - http://localhost:7920 + http://localhost:7100 False False From 0feb1b6cc01fc67a06bac0af70bc1856bfd2d13e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Mar 2018 14:02:35 +1100 Subject: [PATCH 16/46] fixes build since we don't have preview files --- build/Modules/Umbraco.Build/Umbraco.Build.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Modules/Umbraco.Build/Umbraco.Build.psm1 b/build/Modules/Umbraco.Build/Umbraco.Build.psm1 index 61cc0abe3f..6b9cdb28da 100644 --- a/build/Modules/Umbraco.Build/Umbraco.Build.psm1 +++ b/build/Modules/Umbraco.Build/Umbraco.Build.psm1 @@ -376,7 +376,7 @@ function Prepare-Packages Copy-Files "$src\Umbraco.Web.UI\umbraco\js" "*" "$tmp\WebApp\umbraco\js" Copy-Files "$src\Umbraco.Web.UI\umbraco\lib" "*" "$tmp\WebApp\umbraco\lib" Copy-Files "$src\Umbraco.Web.UI\umbraco\views" "*" "$tmp\WebApp\umbraco\views" - Copy-Files "$src\Umbraco.Web.UI\umbraco\preview" "*" "$tmp\WebApp\umbraco\preview" + } # From 4e531976e94a0416d5926f4158d75373249cc6c3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Mar 2018 14:24:11 +1100 Subject: [PATCH 17/46] missing file --- .../Umbraco/Views/Preview/web.config | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/web.config diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/web.config b/src/Umbraco.Web.UI/Umbraco/Views/Preview/web.config new file mode 100644 index 0000000000..4cedc95dff --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/web.config @@ -0,0 +1,43 @@ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 00fdace570b23824a1b97b205bcb1266cd9b6bfa Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Mar 2018 14:38:21 +1100 Subject: [PATCH 18/46] fixes preview view web.config and makes sure we pass the preview model to the view --- src/Umbraco.Web.UI/Umbraco/Views/Preview/web.config | 2 -- src/Umbraco.Web/Editors/PreviewController.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/web.config b/src/Umbraco.Web.UI/Umbraco/Views/Preview/web.config index 4cedc95dff..5ab8bbc7d6 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/web.config +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/web.config @@ -15,9 +15,7 @@ - - diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index 203fbd29e0..0f4f4e978a 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.Editors } } - return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + "Index.cshtml"); + return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + "Index.cshtml", model); } [AllowAnonymous] From 05c38db54c0fc798536fb258b6795966f44f714b Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Mar 2018 15:24:30 +1100 Subject: [PATCH 19/46] updates naming of the extending preview property and fixes the angular rendering on ng-init --- src/SolutionInfo.cs | 2 +- .../Umbraco/Views/Preview/Index.cshtml | 16 ++++++++++------ src/Umbraco.Web/Editors/PreviewController.cs | 8 ++++---- .../Models/ContentEditing/BackOfficePreview.cs | 3 ++- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 9da63d813f..083b2d3e90 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.10.0")] -[assembly: AssemblyInformationalVersion("7.10.0-beta004")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.10.0-beta005")] \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 556d4ff0c1..71e9ff36c9 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -1,5 +1,7 @@ @inherits System.Web.Mvc.WebViewPage - +@{ + var disableDevicePreview = Model.DisableDevicePreview.ToString().ToLowerInvariant(); +} @@ -9,15 +11,17 @@ - @if (string.IsNullOrWhiteSpace(Model.PreviewExtendedView) == false) - { - @Html.Partial(Model.PreviewExtendedView) - }
+ + @if (string.IsNullOrWhiteSpace(Model.PreviewExtendedHeaderView) == false) + { + @Html.Partial(Model.PreviewExtendedHeaderView) + } +
-
+
public class BackOfficePreview { - public string PreviewExtendedView { get; set; } + public string PreviewExtendedHeaderView { get; set; } + //TODO: We could potentially have a 'footer' view public bool DisableDevicePreview { get; set; } } } From 6bc68005f440b6ab4a70d72695f638229b1b163c Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 15 Mar 2018 09:46:39 +0100 Subject: [PATCH 20/46] U4-10756 - id/guid cache, new version --- src/Umbraco.Core/Services/IdkMap.cs | 117 +++++++++++++----- src/Umbraco.Core/Umbraco.Core.csproj | 3 + src/Umbraco.Core/packages.config | 1 + .../Umbraco.Tests.Benchmarks.csproj | 4 +- src/Umbraco.Tests.Benchmarks/app.config | 4 + src/Umbraco.Tests.Benchmarks/packages.config | 2 +- .../ContextualPublishedCache.cs | 12 +- .../ContextualPublishedContentCache.cs | 9 ++ .../ContextualPublishedMediaCache.cs | 9 ++ .../PublishedContentCache.cs | 82 +++++++++++- .../XmlPublishedCache/PublishedMediaCache.cs | 7 ++ src/Umbraco.Web/PublishedContentQuery.cs | 18 ++- src/Umbraco.Web/Umbraco.Web.csproj | 3 + src/Umbraco.Web/packages.config | 1 + 14 files changed, 227 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Core/Services/IdkMap.cs b/src/Umbraco.Core/Services/IdkMap.cs index 42bbc8a146..ab858a75aa 100644 --- a/src/Umbraco.Core/Services/IdkMap.cs +++ b/src/Umbraco.Core/Services/IdkMap.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using Umbraco.Core.Models; @@ -29,23 +30,64 @@ namespace Umbraco.Core.Services // to another id // // - LeeK's solution in 7.7 was to look for the id/guid in the content cache "on demand" via - // XPath, which is probably fast enough but cannot deal with media ids + // XPath, which is probably fast enough but cannot deal with media ids + it maintains a + // separate, duplicate cache // see https://github.com/umbraco/Umbraco-CMS/pull/2398 // // - Andy's solution in a package was to prefetch all by sql; it cannot prefecth reserved ids - // as we don't know the corresponding object type, but that's not a big issue + // as we don't know the corresponding object type, but that's not a big issue - but then we + // have a full database query on startup // see https://github.com/AndyButland/UmbracoUdiToIdCache // // - the original IdkMap implementation that was used by services, did a database lookup on // each cache miss, which is fine enough for services, but would be really slow at content - // cache level - so this implementation prefetches all document and media ids - // - // - and this implementation works for document and media ids + // cache level // // - cache is cleared by MediaCacheRefresher, UnpublishedPageCacheRefresher, and other // refreshers - because id/guid map is unique, we only clear to avoid leaking memory, 'cos // we don't risk caching obsolete values - and only when actually deleting + // + // so... + // + // - there's a single caching point, and it's idkMap + // - there are no "helper methods" - the published content cache itself knows about Guids + // - when the published content cache is instanciated, it populates the idkMap with what it knows + // and it registers a way for the idkMap to look for id/keys in the published content cache + // - we do NOT prefetch anything from database + // - when a request comes in: + // the published content cache uses the idkMap to map id/key + // if the idkMap already knows about the map, it returns the value + // else it tries the published cache via XPath + // else it hits the database + + private readonly ConcurrentDictionary id2key, Func key2id)> _dictionary + = new ConcurrentDictionary id2key, Func key2id)>(); + + internal void SetMapper(UmbracoObjectTypes umbracoObjectType, Func id2key, Func key2id) + { + _dictionary[umbracoObjectType] = (id2key, key2id); + } + + internal void Set(IEnumerable<(int id, Guid key)> pairs, UmbracoObjectTypes umbracoObjectType) + { + try + { + _locker.EnterWriteLock(); + foreach (var pair in pairs) + { + _id2Key.Add(pair.id, new TypedId(pair.key, umbracoObjectType)); + _key2Id.Add(pair.key, new TypedId(pair.id, umbracoObjectType)); + } + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } + } + +#if POPULATE_FROM_DATABASE private void PopulateLocked() { // don't if not empty @@ -58,7 +100,7 @@ namespace Umbraco.Core.Services var values = uow.Database.Fetch("SELECT id, uniqueId, nodeObjectType FROM umbracoNode WHERE nodeObjectType IN @types", new { types }); foreach (var value in values) { - UmbracoObjectTypes umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(value.NodeObjectType); + var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(value.NodeObjectType); _id2Key.Add(value.Id, new TypedId(value.UniqueId, umbracoObjectType)); _key2Id.Add(value.UniqueId, new TypedId(value.Id, umbracoObjectType)); } @@ -103,6 +145,7 @@ namespace Umbraco.Core.Services _locker.ExitReadLock(); } } +#endif public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) { @@ -120,28 +163,37 @@ namespace Umbraco.Core.Services _locker.ExitReadLock(); } +#if POPULATE_FROM_DATABASE // if cache is empty and looking for a document or a media, // populate the cache at once and return what we found if (empty && (umbracoObjectType == UmbracoObjectTypes.Document || umbracoObjectType == UmbracoObjectTypes.Media)) return PopulateAndGetIdForKey(key, umbracoObjectType); +#endif // optimize for read speed: reading database outside a lock means that we could read // multiple times, but we don't lock the cache while accessing the database = better - int? val; - using (var uow = _uowProvider.GetUnitOfWork()) + int? val = null; + + if (_dictionary.TryGetValue(umbracoObjectType, out var mappers)) + if ((val = mappers.key2id(key)) == default(int)) val = null; + + if (val == null) { - //if it's unknown don't include the nodeObjectType in the query - if (umbracoObjectType == UmbracoObjectTypes.Unknown) + using (var uow = _uowProvider.GetUnitOfWork()) { - val = uow.Database.ExecuteScalar("SELECT id FROM umbracoNode WHERE uniqueId=@id", new { id = key}); + //if it's unknown don't include the nodeObjectType in the query + if (umbracoObjectType == UmbracoObjectTypes.Unknown) + { + val = uow.Database.ExecuteScalar("SELECT id FROM umbracoNode WHERE uniqueId=@id", new { id = key}); + } + else + { + val = uow.Database.ExecuteScalar("SELECT id FROM umbracoNode WHERE uniqueId=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)", + new { id = key, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid }); + } + uow.Commit(); } - else - { - val = uow.Database.ExecuteScalar("SELECT id FROM umbracoNode WHERE uniqueId=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)", - new { id = key, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid }); - } - uow.Commit(); } if (val == null) return Attempt.Fail(); @@ -199,28 +251,37 @@ namespace Umbraco.Core.Services _locker.ExitReadLock(); } +#if POPULATE_FROM_DATABASE // if cache is empty and looking for a document or a media, // populate the cache at once and return what we found if (empty && (umbracoObjectType == UmbracoObjectTypes.Document || umbracoObjectType == UmbracoObjectTypes.Media)) return PopulateAndGetKeyForId(id, umbracoObjectType); +#endif // optimize for read speed: reading database outside a lock means that we could read // multiple times, but we don't lock the cache while accessing the database = better - Guid? val; - using (var uow = _uowProvider.GetUnitOfWork()) + Guid? val = null; + + if (_dictionary.TryGetValue(umbracoObjectType, out var mappers)) + if ((val = mappers.id2key(id)) == default(Guid)) val = null; + + if (val == null) { - //if it's unknown don't include the nodeObjectType in the query - if (umbracoObjectType == UmbracoObjectTypes.Unknown) + using (var uow = _uowProvider.GetUnitOfWork()) { - val = uow.Database.ExecuteScalar("SELECT uniqueId FROM umbracoNode WHERE id=@id", new { id }); + //if it's unknown don't include the nodeObjectType in the query + if (umbracoObjectType == UmbracoObjectTypes.Unknown) + { + val = uow.Database.ExecuteScalar("SELECT uniqueId FROM umbracoNode WHERE id=@id", new { id }); + } + else + { + val = uow.Database.ExecuteScalar("SELECT uniqueId FROM umbracoNode WHERE id=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)", + new { id, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid }); + } + uow.Commit(); } - else - { - val = uow.Database.ExecuteScalar("SELECT uniqueId FROM umbracoNode WHERE id=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)", - new { id, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid }); - } - uow.Commit(); } if (val == null) return Attempt.Fail(); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index bc58456e48..588da8bacd 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -115,6 +115,9 @@ + + ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll + diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index 617efac224..eaeddb1627 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -18,4 +18,5 @@ + \ No newline at end of file diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 94fa42efcd..3dce759b34 100644 --- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -139,8 +139,8 @@ ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll - - ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + + ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll diff --git a/src/Umbraco.Tests.Benchmarks/app.config b/src/Umbraco.Tests.Benchmarks/app.config index 165e8a7e82..09e26c159d 100644 --- a/src/Umbraco.Tests.Benchmarks/app.config +++ b/src/Umbraco.Tests.Benchmarks/app.config @@ -62,6 +62,10 @@ + + + + \ No newline at end of file diff --git a/src/Umbraco.Tests.Benchmarks/packages.config b/src/Umbraco.Tests.Benchmarks/packages.config index 82ed3b4bf2..e7344a5b29 100644 --- a/src/Umbraco.Tests.Benchmarks/packages.config +++ b/src/Umbraco.Tests.Benchmarks/packages.config @@ -51,7 +51,7 @@ - + diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs index 8decd0100a..74f462e434 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs @@ -47,8 +47,7 @@ namespace Umbraco.Web.PublishedCache /// Considers published or unpublished content depending on context. public IPublishedContent GetById(Guid contentId) { - var intId = UmbracoContext.Application.Services.EntityService.GetIdForKey(contentId, UmbracoObjectTypes.Document); - return GetById(intId.Success ? intId.Result : -1); + return GetById(UmbracoContext.InPreviewMode, contentId); } /// @@ -59,6 +58,15 @@ namespace Umbraco.Web.PublishedCache /// The content, or null. public abstract IPublishedContent GetById(bool preview, int contentId); + // same with Guid + // cannot make this public nor abstract without breaking backward compatibility + protected virtual IPublishedContent GetById(bool preview, Guid contentKey) + { + // original implementation - override in concrete classes + var intId = UmbracoContext.Application.Services.EntityService.GetIdForKey(contentKey, UmbracoObjectTypes.Document); + return GetById(intId.Success ? intId.Result : -1); + } + /// /// Gets content at root. /// diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs index ebe2743f4b..5e687604f5 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Umbraco.Core.Models; +using Umbraco.Web.PublishedCache.XmlPublishedCache; namespace Umbraco.Web.PublishedCache { @@ -20,6 +21,14 @@ namespace Umbraco.Web.PublishedCache : base(umbracoContext, cache) { } + protected override IPublishedContent GetById(bool preview, Guid contentKey) + { + if (InnerCache is PublishedContentCache cc) + return cc.GetById(UmbracoContext, preview, contentKey); + + return base.GetById(preview, contentKey); + } + /// /// Gets content identified by a route. /// diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs index 494a0cd419..b8cfb8d4ae 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Umbraco.Core.Models; +using Umbraco.Web.PublishedCache.XmlPublishedCache; namespace Umbraco.Web.PublishedCache { @@ -19,5 +20,13 @@ namespace Umbraco.Web.PublishedCache internal ContextualPublishedMediaCache(IPublishedMediaCache cache, UmbracoContext umbracoContext) : base(umbracoContext, cache) { } + + protected override IPublishedContent GetById(bool preview, Guid contentKey) + { + if (InnerCache is PublishedMediaCache cc) + return cc.GetById(UmbracoContext, preview, contentKey); + + return base.GetById(preview, contentKey); + } } } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index ae612afe09..d69b7104f6 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -346,11 +346,91 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache #region Getters + private readonly object _idkMapLocker = new object(); + private IdkMap _idkMap; + + // populate the idkmap by indexing the content cache + // assuming that the content cache cannot be corrupted + // PROBLEM: still, the idk map on-demand will do a DB lookup, is it bad? + // OR should we register PROVIDERS for the map FFS what a mess + private void EnsureIdkMap(UmbracoContext umbracoContext) + { + lock (_idkMapLocker) + { + if (_idkMap != null) return; + + _idkMap = ApplicationContext.Current.Services.IdkMap; // fixme inject + + // give the map a fast mapper + _idkMap.SetMapper(UmbracoObjectTypes.Document, GetKeyForId, GetIdForKey); + + // populate the map with what we know, so far + var xml = GetXml(umbracoContext, false); + var nav = xml.CreateNavigator(); + var iter = nav.SelectDescendants(XPathNodeType.Element, true); + _idkMap.Set(Enumerate(iter), UmbracoObjectTypes.Document); + } + } + + private IEnumerable<(int id, Guid key)> Enumerate(XPathNodeIterator iter) + { + while (iter.MoveNext()) + { + string idString = null; + string keyString = null; + + if (iter.Current.MoveToFirstAttribute()) + { + do + { + switch (iter.Current.Name) + { + case "id": + idString = iter.Current.Value; + break; + case "key": + keyString = iter.Current.Value; + break; + } + } while ((idString == null || keyString == null) && iter.Current.MoveToNextAttribute()); + + iter.Current.MoveToParent(); + } + + if (idString == null || keyString == null) continue; + + var id = int.Parse(idString); + var key = Guid.Parse(keyString); + yield return (id, key); + } + } + + private Guid GetKeyForId(int id) + { + var xml = GetXml(UmbracoContext.Current, false); + var elt = xml.GetElementById(id.ToString(CultureInfo.InvariantCulture)); + return elt == null ? default (Guid) : Guid.Parse(elt.GetAttribute("key")); + } + + private int GetIdForKey(Guid key) + { + var xml = GetXml(UmbracoContext.Current, false); + var elt = xml.SelectSingleNode("//* [@key=$guid]", new XPathVariable("guid", key.ToString())) as XmlElement; + return elt == null ? default (int) : int.Parse(elt.GetAttribute("id")); + } + public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int nodeId) { return ConvertToDocument(GetXml(umbracoContext, preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)), preview); } + public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, Guid nodeKey) + { + EnsureIdkMap(umbracoContext); + var mapAttempt = _idkMap.GetIdForKey(nodeKey, UmbracoObjectTypes.Document); + return mapAttempt ? GetById(umbracoContext, preview, mapAttempt.Result) : null; + } + public virtual IEnumerable GetAtRoot(UmbracoContext umbracoContext, bool preview) { return ConvertToDocuments(GetXml(umbracoContext, preview).SelectNodes(XPathStrings.RootDocuments), preview); @@ -490,4 +570,4 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index fb40905a03..978ca6b454 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -72,6 +72,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return GetUmbracoMedia(nodeId); } + public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, Guid nodeKey) + { + // fixme optimize with Lucene and stuff? + var mapAttempt = ApplicationContext.Current.Services.IdkMap.GetIdForKey(nodeKey, UmbracoObjectTypes.Media); + return mapAttempt ? GetById(umbracoContext, preview, mapAttempt.Result) : null; + } + public virtual IEnumerable GetAtRoot(UmbracoContext umbracoContext, bool preview) { var searchProvider = GetSearchProviderSafe(); diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index d231cd04dd..032a7ff26b 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -56,9 +56,6 @@ namespace Umbraco.Web _dynamicContentQuery = dynamicContentQuery; } - // TODO use this to implement media-by-GUID but is a breaking change? - private IdkMap IdkMap => ApplicationContext.Current.Services.IdkMap; // fixme inject - v8 - #region Content public IPublishedContent TypedContent(int id) @@ -71,7 +68,7 @@ namespace Umbraco.Web public IPublishedContent TypedContent(Guid id) { return _typedContentQuery == null - ? TypedDocumentById(id, _contentCache, UmbracoObjectTypes.Document) + ? TypedDocumentById(id, _contentCache) : _typedContentQuery.TypedContent(id); } @@ -92,7 +89,7 @@ namespace Umbraco.Web public IEnumerable TypedContent(IEnumerable ids) { return _typedContentQuery == null - ? TypedDocumentsByIds(_contentCache, ids, UmbracoObjectTypes.Document) + ? TypedDocumentsByIds(_contentCache, ids) : _typedContentQuery.TypedContent(ids); } @@ -236,10 +233,9 @@ namespace Umbraco.Web return doc; } - private IPublishedContent TypedDocumentById(Guid key, ContextualPublishedCache cache, UmbracoObjectTypes umbracoObjectType) + private IPublishedContent TypedDocumentById(Guid key, ContextualPublishedCache cache) { - var idAttempt = IdkMap.GetIdForKey(key, umbracoObjectType); - return idAttempt ? TypedDocumentById(idAttempt.Result, cache) : null; + return cache.GetById(key); } private IPublishedContent TypedDocumentByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) @@ -260,9 +256,9 @@ namespace Umbraco.Web return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); } - private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids, UmbracoObjectTypes umbracoObjectType) + private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids) { - return ids.Select(eachId => TypedDocumentById(eachId, cache, umbracoObjectType)).WhereNotNull(); + return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); } private IEnumerable TypedDocumentsByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) @@ -292,7 +288,7 @@ namespace Umbraco.Web private dynamic DocumentById(Guid id, ContextualPublishedCache cache, object ifNotFound) { - var doc = TypedDocumentById(id, cache, UmbracoObjectTypes.Document); + var doc = TypedDocumentById(id, cache); return doc == null ? ifNotFound : new DynamicPublishedContent(doc).AsDynamic(); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c6d528bb3d..8957ea6f87 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -202,6 +202,9 @@ ..\packages\System.Threading.Tasks.Dataflow.4.7.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + + ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll + 3.5 diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 10e9915015..0acf6ef32e 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -28,4 +28,5 @@ + \ No newline at end of file From af0a03a3e084547391fabe7f8ff10905b45798ec Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 15 Mar 2018 09:51:12 +0100 Subject: [PATCH 21/46] U4-10756 - cleanup --- .../PublishedCache/XmlPublishedCache/PublishedContentCache.cs | 2 -- .../PublishedCache/XmlPublishedCache/PublishedMediaCache.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index d69b7104f6..c073963754 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -351,8 +351,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // populate the idkmap by indexing the content cache // assuming that the content cache cannot be corrupted - // PROBLEM: still, the idk map on-demand will do a DB lookup, is it bad? - // OR should we register PROVIDERS for the map FFS what a mess private void EnsureIdkMap(UmbracoContext umbracoContext) { lock (_idkMapLocker) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 978ca6b454..3bc0ecb7cd 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -74,7 +74,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, Guid nodeKey) { - // fixme optimize with Lucene and stuff? + // TODO optimize with Examine? var mapAttempt = ApplicationContext.Current.Services.IdkMap.GetIdForKey(nodeKey, UmbracoObjectTypes.Media); return mapAttempt ? GetById(umbracoContext, preview, mapAttempt.Result) : null; } From eac9785c457773b530e39247a3a10e73c917a6b7 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 15 Mar 2018 10:02:20 +0100 Subject: [PATCH 22/46] Apply PR #2507 --- .../IO/FileSystemProviderManager.cs | 5 + src/Umbraco.Web.UI.Client/bower.json | 7 +- .../javascriptlibraryhelper.service.js | 40 ++ .../src/common/services/user.service.js | 531 ++++++++++-------- src/Umbraco.Web.UI.Client/src/init.js | 33 +- .../fileupload/fileupload.controller.js | 38 +- .../test/config/karma.conf.js | 2 +- .../Editors/BackOfficeAssetsController .cs | 28 + .../Editors/BackOfficeServerVariables.cs | 4 + src/Umbraco.Web/UI/JavaScript/JsInitialize.js | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 11 files changed, 407 insertions(+), 284 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/services/javascriptlibraryhelper.service.js create mode 100644 src/Umbraco.Web/Editors/BackOfficeAssetsController .cs diff --git a/src/Umbraco.Core/IO/FileSystemProviderManager.cs b/src/Umbraco.Core/IO/FileSystemProviderManager.cs index e50322d95c..5958fb54bd 100644 --- a/src/Umbraco.Core/IO/FileSystemProviderManager.cs +++ b/src/Umbraco.Core/IO/FileSystemProviderManager.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; +using System.IO; using System.Linq; using System.Reflection; using Umbraco.Core.Configuration; @@ -26,6 +27,7 @@ namespace Umbraco.Core.IO private ShadowWrapper _xsltFileSystem; private ShadowWrapper _masterPagesFileSystem; private ShadowWrapper _mvcViewsFileSystem; + private ShadowWrapper _javaScriptLibraryFileSystem; #region Singleton & Constructor @@ -113,6 +115,7 @@ namespace Umbraco.Core.IO var xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt); var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); + var javaScriptLibraryFileSystem = new PhysicalFileSystem(Path.Combine(SystemDirectories.Umbraco, "lib")); _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", ScopeProvider); _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", ScopeProvider); @@ -123,6 +126,7 @@ namespace Umbraco.Core.IO _xsltFileSystem = new ShadowWrapper(xsltFileSystem, "xslt", ScopeProvider); _masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", ScopeProvider); _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", ScopeProvider); + _javaScriptLibraryFileSystem = new ShadowWrapper(javaScriptLibraryFileSystem, "Lib", ScopeProvider); // filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again MediaFileSystem = GetFileSystemProvider(); @@ -143,6 +147,7 @@ namespace Umbraco.Core.IO public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } } public IFileSystem2 MasterPagesFileSystem { get { return _mvcViewsFileSystem; } } public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem; } } + internal IFileSystem2 JavaScriptLibraryFileSystem { get { return _javaScriptLibraryFileSystem; } } public MediaFileSystem MediaFileSystem { get; private set; } #endregion diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 4f2a106236..80d7d9366b 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -42,7 +42,12 @@ "codemirror" ], "sources": { - "moment": "bower_components/moment/min/moment-with-locales.js", + "moment": [ + "bower_components/moment/min/moment.min.js", + "bower_components/moment/min/moment-with-locales.js", + "bower_components/moment/min/moment-with-locales.min.js", + "bower_components/moment/locale/*.js" + ], "underscore": [ "bower_components/underscore/underscore-min.js", "bower_components/underscore/underscore-min.map" diff --git a/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibraryhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibraryhelper.service.js new file mode 100644 index 0000000000..f536bf4dc2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibraryhelper.service.js @@ -0,0 +1,40 @@ +(function () { + 'use strict'; + + function javascriptLibraryHelperService($q, $http, umbRequestHelper) { + + var existingLocales = []; + + function getSupportedLocalesForMoment() { + var deferred = $q.defer(); + + if (existingLocales.length === 0) { + umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "backOfficeAssetsApiBaseUrl", + "GetSupportedMomentLocales")), + 'Failed to get cultures').then(function(locales) { + existingLocales = locales; + deferred.resolve(existingLocales); + }); + } else { + deferred.resolve(existingLocales); + } + + return deferred.promise; + } + + var service = { + getSupportedLocalesForMoment: getSupportedLocalesForMoment + }; + + return service; + + } + + angular.module('umbraco.services').factory('javascriptLibraryHelperService', javascriptLibraryHelperService); + + +})(); + diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index f20c3df44f..58f5add356 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,285 +1,324 @@ angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) { + .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http, javascriptLibraryHelperService) { - var currentUser = null; - var lastUserId = null; - var loginDialog = null; + var currentUser = null; + var lastUserId = null; + var loginDialog = null; - //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server - // this is used so that we know when to go and get the user's remaining seconds directly. - var lastServerTimeoutSet = null; + //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server + // this is used so that we know when to go and get the user's remaining seconds directly. + var lastServerTimeoutSet = null; - function openLoginDialog(isTimedOut) { - if (!loginDialog) { - loginDialog = dialogService.open({ + function openLoginDialog(isTimedOut) { + if (!loginDialog) { + loginDialog = dialogService.open({ - //very special flag which means that global events cannot close this dialog - manualClose: true, + //very special flag which means that global events cannot close this dialog + manualClose: true, - template: 'views/common/dialogs/login.html', - modalClass: "login-overlay", - animation: "slide", - show: true, - callback: onLoginDialogClose, - dialogData: { - isTimedOut: isTimedOut - } - }); - } - } - - function onLoginDialogClose(success) { - loginDialog = null; - - if (success) { - securityRetryQueue.retryAll(currentUser.name); - } - else { - securityRetryQueue.cancelAll(); - $location.path('/'); - } - } - - /** - This methods will set the current user when it is resolved and - will then start the counter to count in-memory how many seconds they have - remaining on the auth session - */ - function setCurrentUser(usr) { - if (!usr.remainingAuthSeconds) { - throw "The user object is invalid, the remainingAuthSeconds is required."; - } - currentUser = usr; - lastServerTimeoutSet = new Date(); - //start the timer - countdownUserTimeout(); - } - - /** - Method to count down the current user's timeout seconds, - this will continually count down their current remaining seconds every 5 seconds until - there are no more seconds remaining. - */ - function countdownUserTimeout() { - - $timeout(function () { - - if (currentUser) { - //countdown by 5 seconds since that is how long our timer is for. - currentUser.remainingAuthSeconds -= 5; - - //if there are more than 30 remaining seconds, recurse! - if (currentUser.remainingAuthSeconds > 30) { - - //we need to check when the last time the timeout was set from the server, if - // it has been more than 30 seconds then we'll manually go and retrieve it from the - // server - this helps to keep our local countdown in check with the true timeout. - if (lastServerTimeoutSet != null) { - var now = new Date(); - var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; - - if (seconds > 30) { - - //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we - // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. - lastServerTimeoutSet = null; - - //now go get it from the server - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - authResource.getRemainingTimeoutSeconds().then(function (result) { - setUserTimeoutInternal(result); - }); + template: 'views/common/dialogs/login.html', + modalClass: "login-overlay", + animation: "slide", + show: true, + callback: onLoginDialogClose, + dialogData: { + isTimedOut: isTimedOut + } }); - } } + } - //recurse the countdown! - countdownUserTimeout(); - } - else { + function onLoginDialogClose(success) { + loginDialog = null; - //we are either timed out or very close to timing out so we need to show the login dialog. - if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - try { - //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we - // don't actually care about this result. - authResource.getRemainingTimeoutSeconds(); - } - finally { - userAuthExpired(); - } - }); + if (success) { + securityRetryQueue.retryAll(currentUser.name); } else { - //we've got less than 30 seconds remaining so let's check the server - - if (lastServerTimeoutSet != null) { - //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we - // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. - lastServerTimeoutSet = null; - - //now go get it from the server - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - authResource.getRemainingTimeoutSeconds().then(function (result) { - setUserTimeoutInternal(result); - }); - }); - } - - //recurse the countdown! - countdownUserTimeout(); - + securityRetryQueue.cancelAll(); + $location.path('/'); } - } } - }, 5000, //every 5 seconds - false); //false = do NOT execute a digest for every iteration - } - /** Called to update the current user's timeout */ - function setUserTimeoutInternal(newTimeout) { - - - var asNumber = parseFloat(newTimeout); - if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { - currentUser.remainingAuthSeconds = newTimeout; - lastServerTimeoutSet = new Date(); - } - } - - /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */ - function userAuthExpired(isLogout) { - //store the last user id and clear the user - if (currentUser && currentUser.id !== undefined) { - lastUserId = currentUser.id; - } - - if (currentUser) { - currentUser.remainingAuthSeconds = 0; - } - - lastServerTimeoutSet = null; - currentUser = null; - - //broadcast a global event that the user is no longer logged in - eventsService.emit("app.notAuthenticated"); - - openLoginDialog(isLogout === undefined ? true : !isLogout); - } - - // Register a handler for when an item is added to the retry queue - securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) { - if (securityRetryQueue.hasMore()) { - userAuthExpired(); - } - }); - - return { - - /** Internal method to display the login dialog */ - _showLoginDialog: function () { - openLoginDialog(); - }, - /** Returns a promise, sends a request to the server to check if the current cookie is authorized */ - isAuthenticated: function () { - //if we've got a current user then just return true - if (currentUser) { - var deferred = $q.defer(); - deferred.resolve(true); - return deferred.promise; + /** + This methods will set the current user when it is resolved and + will then start the counter to count in-memory how many seconds they have + remaining on the auth session + */ + function setCurrentUser(usr) { + if (!usr.remainingAuthSeconds) { + throw "The user object is invalid, the remainingAuthSeconds is required."; + } + currentUser = usr; + lastServerTimeoutSet = new Date(); + //start the timer + countdownUserTimeout(); } - return authResource.isAuthenticated(); - }, - /** Returns a promise, sends a request to the server to validate the credentials */ - authenticate: function (login, password) { + /** + Method to count down the current user's timeout seconds, + this will continually count down their current remaining seconds every 5 seconds until + there are no more seconds remaining. + */ + function countdownUserTimeout() { - return authResource.performLogin(login, password) - .then(this.setAuthenticationSuccessful); - }, - setAuthenticationSuccessful: function (data) { + $timeout(function () { - //when it's successful, return the user data - setCurrentUser(data); + if (currentUser) { + //countdown by 5 seconds since that is how long our timer is for. + currentUser.remainingAuthSeconds -= 5; - var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" }; + //if there are more than 30 remaining seconds, recurse! + if (currentUser.remainingAuthSeconds > 30) { - //broadcast a global event - eventsService.emit("app.authenticated", result); - return result; - }, + //we need to check when the last time the timeout was set from the server, if + // it has been more than 30 seconds then we'll manually go and retrieve it from the + // server - this helps to keep our local countdown in check with the true timeout. + if (lastServerTimeoutSet != null) { + var now = new Date(); + var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; - /** Logs the user out - */ - logout: function () { + if (seconds > 30) { - return authResource.performLogout() - .then(function (data) { - userAuthExpired(); - //done! - return null; - }); - }, + //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. + lastServerTimeoutSet = null; - /** Refreshes the current user data with the data stored for the user on the server and returns it */ - refreshCurrentUser: function() { - var deferred = $q.defer(); + //now go get it from the server + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + }); + }); + } + } - authResource.getCurrentUser() - .then(function (data) { + //recurse the countdown! + countdownUserTimeout(); + } + else { - var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; - - setCurrentUser(data); + //we are either timed out or very close to timing out so we need to show the login dialog. + if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + try { + //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we + // don't actually care about this result. + authResource.getRemainingTimeoutSeconds(); + } + finally { + userAuthExpired(); + } + }); + } + else { + //we've got less than 30 seconds remaining so let's check the server - deferred.resolve(currentUser); - }, function () { - //it failed, so they are not logged in - deferred.reject(); - }); + if (lastServerTimeoutSet != null) { + //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. + lastServerTimeoutSet = null; - return deferred.promise; - }, + //now go get it from the server + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + }); + }); + } - /** Returns the current user object in a promise */ - getCurrentUser: function (args) { - var deferred = $q.defer(); + //recurse the countdown! + countdownUserTimeout(); - if (!currentUser) { - authResource.getCurrentUser() - .then(function (data) { + } + } + } + }, 5000, //every 5 seconds + false); //false = do NOT execute a digest for every iteration + } - var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; + /** Called to update the current user's timeout */ + function setUserTimeoutInternal(newTimeout) { - if (args && args.broadcastEvent) { - //broadcast a global event, will inform listening controllers to load in the user specific data + + var asNumber = parseFloat(newTimeout); + if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { + currentUser.remainingAuthSeconds = newTimeout; + lastServerTimeoutSet = new Date(); + } + } + + /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */ + function userAuthExpired(isLogout) { + //store the last user id and clear the user + if (currentUser && currentUser.id !== undefined) { + lastUserId = currentUser.id; + } + + if (currentUser) { + currentUser.remainingAuthSeconds = 0; + } + + lastServerTimeoutSet = null; + currentUser = null; + + //broadcast a global event that the user is no longer logged in + eventsService.emit("app.notAuthenticated"); + + openLoginDialog(isLogout === undefined ? true : !isLogout); + } + + // Register a handler for when an item is added to the retry queue + securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) { + if (securityRetryQueue.hasMore()) { + userAuthExpired(); + } + }); + + return { + + /** Internal method to display the login dialog */ + _showLoginDialog: function () { + openLoginDialog(); + }, + /** Returns a promise, sends a request to the server to check if the current cookie is authorized */ + isAuthenticated: function () { + //if we've got a current user then just return true + if (currentUser) { + var deferred = $q.defer(); + deferred.resolve(true); + return deferred.promise; + } + return authResource.isAuthenticated(); + }, + + /** Returns a promise, sends a request to the server to validate the credentials */ + authenticate: function (login, password) { + + return authResource.performLogin(login, password) + .then(this.setAuthenticationSuccessful); + }, + setAuthenticationSuccessful: function (data) { + + //when it's successful, return the user data + setCurrentUser(data); + + var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" }; + + //broadcast a global event eventsService.emit("app.authenticated", result); - } + return result; + }, - setCurrentUser(data); + /** Logs the user out + */ + logout: function () { - deferred.resolve(currentUser); - }, function () { - //it failed, so they are not logged in - deferred.reject(); - }); + return authResource.performLogout() + .then(function (data) { + userAuthExpired(); + //done! + return null; + }); + }, - } - else { - deferred.resolve(currentUser); - } + /** Refreshes the current user data with the data stored for the user on the server and returns it */ + refreshCurrentUser: function () { + var deferred = $q.defer(); - return deferred.promise; - }, + authResource.getCurrentUser() + .then(function (data) { - /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ - setUserTimeout: function (newTimeout) { - setUserTimeoutInternal(newTimeout); - } - }; + var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; - }); + setCurrentUser(data); + + deferred.resolve(currentUser); + }, function () { + //it failed, so they are not logged in + deferred.reject(); + }); + + return deferred.promise; + }, + + /** Returns the current user object in a promise */ + getCurrentUser: function (args) { + var deferred = $q.defer(); + + if (!currentUser) { + authResource.getCurrentUser() + .then(function (data) { + + var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; + + if (args && args.broadcastEvent) { + //broadcast a global event, will inform listening controllers to load in the user specific data + eventsService.emit("app.authenticated", result); + } + + setCurrentUser(data); + + deferred.resolve(currentUser); + }, function () { + //it failed, so they are not logged in + deferred.reject(); + }); + + } + else { + deferred.resolve(currentUser); + } + + return deferred.promise; + }, + + /** Loads the Moment.js Locale for the current user. */ + loadMomentLocaleForCurrentUser: function () { + var deferred = $q.defer(); + + + function loadLocales(currentUser, supportedLocales) { + var locale = currentUser.locale.toLowerCase(); + if (locale !== 'en-us') { + var localeUrls = []; + if (supportedLocales.indexOf(locale + '.js') > -1) { + localeUrls.push('lib/moment/' + locale + '.js'); + } + if (locale.indexOf('-') > -1) { + var majorLocale = locale.split('-')[0] + '.js'; + if (supportedLocales.indexOf(majorLocale) > -1) { + localeUrls.push('lib/moment/' + majorLocale); + } + } + assetsService.load(localeUrls).then(function () { + deferred.resolve(localeUrls); + }); + } else { + deferred.resolve(['']); + } + } + + var promises = { + currentUser: this.getCurrentUser(), + supportedLocales: javascriptLibraryHelperService.getSupportedLocalesForMoment() + } + + $q.all(promises).then((values) => { + loadLocales(values.currentUser, values.supportedLocales); + }); + + return deferred.promise; + + }, + + /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ + setUserTimeout: function (newTimeout) { + setUserTimeoutInternal(newTimeout); + } + }; + + }); diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index e86fa25c42..32462826fe 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -18,23 +18,26 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi eventsService.on("app.authenticated", function(evt, data) { assetsService._loadInitAssets().then(function() { - - //Register all of the tours on the server - tourService.registerAllTours().then(function () { - appReady(data); - - // Auto start intro tour - tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) { - // start intro tour if it hasn't been completed or disabled - if (introTour && introTour.disabled !== true && introTour.completed !== true) { - tourService.startTour(introTour); - } + + // Loads the user's locale settings for Moment. + userService.loadMomentLocaleForCurrentUser().then(function() { + + //Register all of the tours on the server + tourService.registerAllTours().then(function () { + appReady(data); + + // Auto start intro tour + tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) { + // start intro tour if it hasn't been completed or disabled + if (introTour && introTour.disabled !== true && introTour.completed !== true) { + tourService.startTour(introTour); + } + }); + + }, function(){ + appReady(data); }); - - }, function(){ - appReady(data); }); - }); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index d8a7710bbb..2a66ee457b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -157,26 +157,24 @@ angular.module("umbraco") .controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController) .run(function(mediaHelper, umbRequestHelper, assetsService){ if (mediaHelper && mediaHelper.registerFileResolver) { - assetsService.load(["lib/moment/moment-with-locales.js"]).then( - function () { - //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource - // they contain different data structures so if we need to query against it we need to be aware of this. - mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){ - if (thumbnail) { - if (mediaHelper.detectIfImageByExtension(property.value)) { - //get default big thumbnail from image processor - var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first"; - return thumbnailUrl; - } - else { - return null; - } - } - else { - return property.value; - } - }); + + //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource + // they contain different data structures so if we need to query against it we need to be aware of this. + mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){ + if (thumbnail) { + if (mediaHelper.detectIfImageByExtension(property.value)) { + //get default big thumbnail from image processor + var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first"; + return thumbnailUrl; + } + else { + return null; + } } - ); + else { + return property.value; + } + }); + } }); diff --git a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js index 711430d5ed..1b10ab0ce4 100644 --- a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js +++ b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js @@ -99,4 +99,4 @@ module.exports = function (config) { 'karma-phantomjs-launcher' ] }); -}; \ No newline at end of file +}; diff --git a/src/Umbraco.Web/Editors/BackOfficeAssetsController .cs b/src/Umbraco.Web/Editors/BackOfficeAssetsController .cs new file mode 100644 index 0000000000..32b7b1b432 --- /dev/null +++ b/src/Umbraco.Web/Editors/BackOfficeAssetsController .cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Http; +using Umbraco.Core.IO; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web.Editors +{ + [PluginController("UmbracoApi")] + public class BackOfficeAssetsController : UmbracoAuthorizedJsonController + { + + [HttpGet] + public IEnumerable GetSupportedMomentLocales() + { + var momentLocaleFolder = "moment"; + var fileSystem = FileSystemProviderManager.Current.JavaScriptLibraryFileSystem; + var cultures = fileSystem.GetFiles(momentLocaleFolder, "*.js").ToList(); + for (var i = 0; i < cultures.Count(); i++) + { + cultures[i] = cultures[i] + .Substring(cultures[i].IndexOf(momentLocaleFolder) + momentLocaleFolder.Length + 1); + } + return cultures; + } + + } +} diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index dac78e9979..3d0996d886 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -270,6 +270,10 @@ namespace Umbraco.Web.Editors { "helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetContextHelpForPage("","","")) + }, + { + "backOfficeAssetsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetSupportedMomentLocales()) } } }, diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js index 961fb8e54e..39185cff3f 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js @@ -3,7 +3,7 @@ 'lib/angular/1.1.5/angular.min.js', 'lib/underscore/underscore-min.js', - 'lib/moment/moment-with-locales.js', + 'lib/moment/moment.min.js', 'lib/jquery-ui/jquery-ui.min.js', 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 650c840e30..458c61bbe7 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -317,6 +317,7 @@ + From 5161fd0cef595ccd8508705b4ce764d981d1f2c6 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 15 Mar 2018 10:07:04 +0100 Subject: [PATCH 23/46] Remove trailing space from filename and clean up code a little --- ...etsController .cs => BackOfficeAssetsController.cs} | 10 +++++----- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) rename src/Umbraco.Web/Editors/{BackOfficeAssetsController .cs => BackOfficeAssetsController.cs} (75%) diff --git a/src/Umbraco.Web/Editors/BackOfficeAssetsController .cs b/src/Umbraco.Web/Editors/BackOfficeAssetsController.cs similarity index 75% rename from src/Umbraco.Web/Editors/BackOfficeAssetsController .cs rename to src/Umbraco.Web/Editors/BackOfficeAssetsController.cs index 32b7b1b432..5a5f6fc5e2 100644 --- a/src/Umbraco.Web/Editors/BackOfficeAssetsController .cs +++ b/src/Umbraco.Web/Editors/BackOfficeAssetsController.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Web.Http; using Umbraco.Core.IO; @@ -13,16 +14,15 @@ namespace Umbraco.Web.Editors [HttpGet] public IEnumerable GetSupportedMomentLocales() { - var momentLocaleFolder = "moment"; + const string momentLocaleFolder = "moment"; var fileSystem = FileSystemProviderManager.Current.JavaScriptLibraryFileSystem; var cultures = fileSystem.GetFiles(momentLocaleFolder, "*.js").ToList(); - for (var i = 0; i < cultures.Count(); i++) + for (var i = 0; i < cultures.Count; i++) { cultures[i] = cultures[i] - .Substring(cultures[i].IndexOf(momentLocaleFolder) + momentLocaleFolder.Length + 1); + .Substring(cultures[i].IndexOf(momentLocaleFolder, StringComparison.Ordinal) + momentLocaleFolder.Length + 1); } return cultures; } - } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 458c61bbe7..5287b8754e 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -317,7 +317,7 @@ - + From a465ed415d41904f33037f14173f4690dd8a3c24 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 15 Mar 2018 10:08:00 +0100 Subject: [PATCH 24/46] Rename to javascriptLibraryService --- ...helper.service.js => javascriptlibrary.service.js} | 11 ++++------- .../src/common/services/user.service.js | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) rename src/Umbraco.Web.UI.Client/src/common/services/{javascriptlibraryhelper.service.js => javascriptlibrary.service.js} (75%) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibraryhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibrary.service.js similarity index 75% rename from src/Umbraco.Web.UI.Client/src/common/services/javascriptlibraryhelper.service.js rename to src/Umbraco.Web.UI.Client/src/common/services/javascriptlibrary.service.js index f536bf4dc2..4829dff506 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibraryhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibrary.service.js @@ -1,7 +1,7 @@ (function () { - 'use strict'; + "use strict"; - function javascriptLibraryHelperService($q, $http, umbRequestHelper) { + function javascriptLibraryService($q, $http, umbRequestHelper) { var existingLocales = []; @@ -14,7 +14,7 @@ umbRequestHelper.getApiUrl( "backOfficeAssetsApiBaseUrl", "GetSupportedMomentLocales")), - 'Failed to get cultures').then(function(locales) { + "Failed to get cultures").then(function(locales) { existingLocales = locales; deferred.resolve(existingLocales); }); @@ -30,11 +30,8 @@ }; return service; - } - angular.module('umbraco.services').factory('javascriptLibraryHelperService', javascriptLibraryHelperService); - + angular.module("umbraco.services").factory("javascriptLibraryService", javascriptLibraryService); })(); - diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 58f5add356..a70238eb5a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,5 +1,5 @@ angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http, javascriptLibraryHelperService) { + .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http, javascriptLibraryService) { var currentUser = null; var lastUserId = null; @@ -304,7 +304,7 @@ angular.module('umbraco.services') var promises = { currentUser: this.getCurrentUser(), - supportedLocales: javascriptLibraryHelperService.getSupportedLocalesForMoment() + supportedLocales: javascriptLibraryService.getSupportedLocalesForMoment() } $q.all(promises).then((values) => { From f66e239584b641c30169d29bb2d904a17225e824 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 15 Mar 2018 10:32:25 +0100 Subject: [PATCH 25/46] PhantomJs doesn't understand fancy ES6 notation --- src/Umbraco.Web.UI.Client/src/common/services/user.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index a70238eb5a..eda46fbb71 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -307,7 +307,7 @@ angular.module('umbraco.services') supportedLocales: javascriptLibraryService.getSupportedLocalesForMoment() } - $q.all(promises).then((values) => { + $q.all(promises).then(function (values) { loadLocales(values.currentUser, values.supportedLocales); }); From 69f7623fb7ca6658de915e0948bf89d33e3d5c35 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 16 Mar 2018 14:40:26 +1100 Subject: [PATCH 26/46] Doh! we had test code checked in .. oops! --- src/Umbraco.Web/Editors/BackOfficeServerVariables.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 3d0996d886..6903d9db23 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -341,7 +341,7 @@ namespace Umbraco.Web.Editors { "disabledFeatures", new Dictionary { - { "disableTemplates", true/*FeaturesResolver.Current.Features.Disabled.DisableTemplates*/} + { "disableTemplates", FeaturesResolver.Current.Features.Disabled.DisableTemplates} } } From e3e425695f724cbbf1b25e2dfe97d90d8cdcd9f2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 16 Mar 2018 16:34:58 +0100 Subject: [PATCH 27/46] U4-8724 tinyMceConfig.config customConfig bool not correct deserialized --- .../common/directives/components/grid/grid.rte.directive.js | 6 ++++++ .../src/views/propertyeditors/rte/rte.controller.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index 90daac73db..e18137085b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -145,6 +145,12 @@ angular.module("umbraco.directives") } } } + if (val === "true") { + tinyMceConfig.customConfig[i] = true; + } + if (val === "false") { + tinyMceConfig.customConfig[i] = false; + } } angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index 0a6bededae..f886c60bc1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -185,6 +185,12 @@ angular.module("umbraco") //cannot parse, we'll just leave it } } + if (val === "true") { + tinyMceConfig.customConfig[i] = true; + } + if (val === "false") { + tinyMceConfig.customConfig[i] = false; + } } } From dcf7f35078adb1aa2185d513711919d56d52856c Mon Sep 17 00:00:00 2001 From: David Wandar Date: Tue, 20 Mar 2018 10:00:53 +0100 Subject: [PATCH 28/46] Fixed typo. Xloud -> Cloud Xloud sounds awsome though :) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index afedc5f5a7..8aee632cad 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ As an Open Source platform, Umbraco is more than just a CMS. We are transparent [Umbraco Cloud](https://umbraco.com) is the easiest and fastest way to use Umbraco yet with full support for all your custom .NET code and intergrations. You're up and running in less than a minute and your life will be made easier with automated upgrades and a built-in deployment engine. We offer a free 14 day trial, no credit card needed. -If you want to DIY you can [download Umbraco](https://our.umbraco.org/download) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Xloud, but you'll need to find a place to host yourself and handling deployments and upgrades is all down to you. +If you want to DIY you can [download Umbraco](https://our.umbraco.org/download) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host yourself and handling deployments and upgrades is all down to you. ## Community From edf70388487a4dec7985148c5163986a73cf314e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 20 Mar 2018 15:00:09 +0100 Subject: [PATCH 29/46] U4-11020 Deleting a member group that is part of a Public Access feature, from the system, does not also remove the corresponding rows from the umbracoAccessRule table. --- .../umbraco/create/MemberGroupTasks.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs index 42f8419723..85f6906e36 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs @@ -1,5 +1,6 @@ using System; using System.Data; +using System.Linq; using System.Web.Security; using Umbraco.Web.UI; using umbraco.BusinessLogic; @@ -7,6 +8,8 @@ using umbraco.DataLayer; using umbraco.BasePages; using Umbraco.Core.IO; using umbraco.cms.businesslogic.member; +using Umbraco.Core; +using Umbraco.Web; namespace umbraco { @@ -21,13 +24,32 @@ namespace umbraco public override bool PerformDelete() { + var roleDeleted = false; + // only built-in roles can be deleted if (Member.IsUsingUmbracoRoles()) { MemberGroup.GetByName(Alias).delete(); - return true; + roleDeleted = true; } - return false; + + // Need to delete the member group from any content item that has it assigned in public access settings + var publicAccessService = UmbracoContext.Current.Application.Services.PublicAccessService; + var allPublicAccessRules = publicAccessService.GetAll(); + + // Find only rules which have the current role name (alias) assigned to them + var rulesWithDeletedRoles = allPublicAccessRules.Where(x => x.Rules.Any(r => r.RuleValue == Alias)); + + var contentService = UmbracoContext.Current.Application.Services.ContentService; + foreach (var publicAccessEntry in rulesWithDeletedRoles) + { + var contentItem = contentService.GetById(publicAccessEntry.ProtectedNodeId); + var rulesToDelete = publicAccessEntry.Rules.ToList(); + foreach (var rule in rulesToDelete) + publicAccessService.RemoveRule(contentItem, rule.RuleType, rule.RuleValue); + } + + return roleDeleted; } private string _returnUrl = ""; From ff31cd9f7259bd94cbc944cb9d5745dae5930536 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 21 Mar 2018 11:31:30 +1100 Subject: [PATCH 30/46] Changes `Set` to `Populate`, changes `Fetch` to `Query` and makes GetById public --- src/Umbraco.Core/Services/IdkMap.cs | 4 ++-- src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs | 2 +- .../PublishedCache/ContextualPublishedContentCache.cs | 2 +- .../PublishedCache/ContextualPublishedMediaCache.cs | 2 +- .../PublishedCache/XmlPublishedCache/PublishedContentCache.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Services/IdkMap.cs b/src/Umbraco.Core/Services/IdkMap.cs index ab858a75aa..7cf69f2e64 100644 --- a/src/Umbraco.Core/Services/IdkMap.cs +++ b/src/Umbraco.Core/Services/IdkMap.cs @@ -69,7 +69,7 @@ namespace Umbraco.Core.Services _dictionary[umbracoObjectType] = (id2key, key2id); } - internal void Set(IEnumerable<(int id, Guid key)> pairs, UmbracoObjectTypes umbracoObjectType) + internal void Populate(IEnumerable<(int id, Guid key)> pairs, UmbracoObjectTypes umbracoObjectType) { try { @@ -97,7 +97,7 @@ namespace Umbraco.Core.Services { // populate content and media items var types = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media }; - var values = uow.Database.Fetch("SELECT id, uniqueId, nodeObjectType FROM umbracoNode WHERE nodeObjectType IN @types", new { types }); + var values = uow.Database.Query("SELECT id, uniqueId, nodeObjectType FROM umbracoNode WHERE nodeObjectType IN @types", new { types }); foreach (var value in values) { var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(value.NodeObjectType); diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs index 74f462e434..429e2a2f9e 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.PublishedCache // same with Guid // cannot make this public nor abstract without breaking backward compatibility - protected virtual IPublishedContent GetById(bool preview, Guid contentKey) + public virtual IPublishedContent GetById(bool preview, Guid contentKey) { // original implementation - override in concrete classes var intId = UmbracoContext.Application.Services.EntityService.GetIdForKey(contentKey, UmbracoObjectTypes.Document); diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs index 5e687604f5..c927644817 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.PublishedCache : base(umbracoContext, cache) { } - protected override IPublishedContent GetById(bool preview, Guid contentKey) + public override IPublishedContent GetById(bool preview, Guid contentKey) { if (InnerCache is PublishedContentCache cc) return cc.GetById(UmbracoContext, preview, contentKey); diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs index b8cfb8d4ae..0b4ccc18c4 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.PublishedCache : base(umbracoContext, cache) { } - protected override IPublishedContent GetById(bool preview, Guid contentKey) + public override IPublishedContent GetById(bool preview, Guid contentKey) { if (InnerCache is PublishedMediaCache cc) return cc.GetById(UmbracoContext, preview, contentKey); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index c073963754..59772e8237 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -366,7 +366,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var xml = GetXml(umbracoContext, false); var nav = xml.CreateNavigator(); var iter = nav.SelectDescendants(XPathNodeType.Element, true); - _idkMap.Set(Enumerate(iter), UmbracoObjectTypes.Document); + _idkMap.Populate(Enumerate(iter), UmbracoObjectTypes.Document); } } From 1a590664e7a5dafd7627cd5d35243a865c518305 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 21 Mar 2018 12:32:27 +1100 Subject: [PATCH 31/46] Revert "Merge pull request #2502 from umbraco/temp-U4-11032" This reverts commit 2aa0dfb2c5a682d74eca8858f26bcfe563515a3f, reversing changes made to 2c6e7e46c219253040b38b246648d006cae10d54. --- src/Umbraco.Core/Constants-PropertyEditors.cs | 7 +- .../src/views/prevalueeditors/boolean.html | 2 +- .../dropdownFlexible.controller.js | 81 ------------------ .../dropdownFlexible/dropdownFlexible.html | 19 ----- .../DropDownMultiplePropertyEditor.cs | 4 +- .../DropDownMultipleWithKeysPropertyEditor.cs | 4 +- .../PropertyEditors/DropDownPropertyEditor.cs | 4 +- .../DropDownWithKeysPropertyEditor.cs | 4 +- .../DropdownFlexiblePropertyEditor.cs | 84 ------------------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 10 files changed, 10 insertions(+), 200 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js delete mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html delete mode 100644 src/Umbraco.Web/PropertyEditors/DropdownFlexiblePropertyEditor.cs diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 43779ea44a..1f3986eeaf 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -122,11 +122,6 @@ namespace Umbraco.Core /// Alias for the Dropdown list, publishing keys datatype. /// public const string DropdownlistPublishingKeysAlias = "Umbraco.DropdownlistPublishingKeys"; - - /// - /// Alias for the "new" Dropdown list, that replaces the old four deprecated ones and works as other list based property editors - /// - public const string DropDownListFlexibleAlias = "Umbraco.DropDown.Flexible"; /// /// Guid for the Folder browser datatype. @@ -457,4 +452,4 @@ namespace Umbraco.Core } } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html index 3e6be09650..8af17a18ea 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html @@ -1 +1 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js deleted file mode 100644 index c7a472d80e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js +++ /dev/null @@ -1,81 +0,0 @@ -angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleController", - function($scope) { - - //setup the default config - var config = { - items: [], - multiple: false - }; - - //map the user config - angular.extend(config, $scope.model.config); - - //map back to the model - $scope.model.config = config; - - function convertArrayToDictionaryArray(model){ - //now we need to format the items in the dictionary because we always want to have an array - var newItems = []; - for (var i = 0; i < model.length; i++) { - newItems.push({ id: model[i], sortOrder: 0, value: model[i] }); - } - - return newItems; - } - - - function convertObjectToDictionaryArray(model){ - //now we need to format the items in the dictionary because we always want to have an array - var newItems = []; - var vals = _.values($scope.model.config.items); - var keys = _.keys($scope.model.config.items); - - for (var i = 0; i < vals.length; i++) { - var label = vals[i].value ? vals[i].value : vals[i]; - newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: label }); - } - - return newItems; - } - - $scope.updateSingleDropdownValue = function() { - $scope.model.value = [$scope.model.singleDropdownValue]; - } - - if (angular.isArray($scope.model.config.items)) { - //PP: I dont think this will happen, but we have tests that expect it to happen.. - //if array is simple values, convert to array of objects - if(!angular.isObject($scope.model.config.items[0])){ - $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items); - } - } - else if (angular.isObject($scope.model.config.items)) { - $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items); - } - else { - throw "The items property must be either an array or a dictionary"; - } - - - //sort the values - $scope.model.config.items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); - - //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set - // to "" gets selected by default - if ($scope.model.value === null || $scope.model.value === undefined) { - if ($scope.model.config.multiple) { - $scope.model.value = []; - } - else { - $scope.model.value = ""; - } - } - - // if we run in single mode we'll store the value in a local variable - // so we can pass an array as the model as our PropertyValueEditor expects that - $scope.model.singleDropdownValue = ""; - if ($scope.model.config.multiple === "0") { - $scope.model.singleDropdownValue = Array.isArray($scope.model.value) ? $scope.model.value[0] : $scope.model.value; - } - - }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html deleted file mode 100644 index a68a614ad1..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html +++ /dev/null @@ -1,19 +0,0 @@ -
- - - - - -
diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs index 5cfd745d62..a4dfd6c2ad 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.PropertyEditors [ParameterEditor("propertyTypePickerMultiple", "Name", "textbox")] [ParameterEditor("contentTypeMultiple", "Name", "textbox")] [ParameterEditor("tabPickerMultiple", "Name", "textbox")] - [PropertyEditor(Constants.PropertyEditors.DropDownListMultipleAlias, "Dropdown list multiple", "dropdown", Group = "lists", Icon="icon-bulleted-list", IsDeprecated = true)] + [PropertyEditor(Constants.PropertyEditors.DropDownListMultipleAlias, "Dropdown list multiple", "dropdown", Group = "lists", Icon="icon-bulleted-list")] public class DropDownMultiplePropertyEditor : DropDownMultipleWithKeysPropertyEditor { protected override PropertyValueEditor CreateValueEditor() @@ -28,4 +28,4 @@ namespace Umbraco.Web.PropertyEditors -} +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs index ac28379f0d..05ba3e2644 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.PropertyEditors /// Due to maintaining backwards compatibility this data type stores the value as a string which is a comma separated value of the /// ids of the individual items so we have logic in here to deal with that. /// - [PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list", IsDeprecated = true)] + [PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list")] public class DropDownMultipleWithKeysPropertyEditor : DropDownPropertyEditor { protected override PropertyValueEditor CreateValueEditor() @@ -62,4 +62,4 @@ namespace Umbraco.Web.PropertyEditors } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs index 4c9b0ae99a..115b99dea4 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the string value is published /// in cache and not the int ID. /// - [PropertyEditor(Constants.PropertyEditors.DropDownListAlias, "Dropdown list", "dropdown", ValueType = PropertyEditorValueTypes.String, Group = "lists", Icon = "icon-indent", IsDeprecated = true)] + [PropertyEditor(Constants.PropertyEditors.DropDownListAlias, "Dropdown list", "dropdown", ValueType = PropertyEditorValueTypes.String, Group = "lists", Icon = "icon-indent")] public class DropDownPropertyEditor : DropDownWithKeysPropertyEditor { /// @@ -29,4 +29,4 @@ namespace Umbraco.Web.PropertyEditors } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs index 4e8efd93a5..a33115003c 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the INT ID value is published /// in cache and not the string value. /// - [PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = PropertyEditorValueTypes.Integer, Group = "lists", Icon = "icon-indent", IsDeprecated = true)] + [PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = PropertyEditorValueTypes.Integer, Group = "lists", Icon = "icon-indent")] public class DropDownWithKeysPropertyEditor : PropertyEditor { @@ -24,4 +24,4 @@ namespace Umbraco.Web.PropertyEditors return new ValueListPreValueEditor(); } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DropdownFlexiblePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropdownFlexiblePropertyEditor.cs deleted file mode 100644 index 236aa273c3..0000000000 --- a/src/Umbraco.Web/PropertyEditors/DropdownFlexiblePropertyEditor.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - [PropertyEditor(Constants.PropertyEditors.DropDownListFlexibleAlias, "Dropdown", "dropdownFlexible", Group = "lists", Icon = "icon-indent")] - public class DropdownFlexiblePropertyEditor : PropertyEditor - { - private static readonly string _multipleKey = "multiple"; - - /// - /// Return a custom pre-value editor - /// - /// - /// - /// We are just going to re-use the ValueListPreValueEditor - /// - protected override PreValueEditor CreatePreValueEditor() - { - return new DropdownFlexiblePreValueEditor(); - } - - /// - /// We need to override the value editor so that we can ensure the string value is published in cache and not the integer ID value. - /// - /// - protected override PropertyValueEditor CreateValueEditor() - { - return new PublishValuesMultipleValueEditor(false, base.CreateValueEditor()); - } - - internal class DropdownFlexiblePreValueEditor : ValueListPreValueEditor - { - public DropdownFlexiblePreValueEditor() - { - Fields.Insert(0, new PreValueField - { - Key = "multiple", - Name = "Enable multiple choice", - Description = "When checked, the dropdown will be a select multiple / combo box style dropdown", - View = "boolean" - }); - } - - public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) - { - - var result = base.ConvertEditorToDb(editorValue, currentValue); - - // get multiple config - var multipleValue = editorValue[_multipleKey] != null ? editorValue[_multipleKey].ToString() : "0"; - result.Add(_multipleKey, new PreValue(-1, multipleValue)); - - return result; - } - - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - // weird way, but as the value stored is 0 or 1 need to do it this way - string multipleMode = "0"; - if (persistedPreVals != null && persistedPreVals.PreValuesAsDictionary[_multipleKey] != null) - { - multipleMode = persistedPreVals.PreValuesAsDictionary[_multipleKey].Value; - - // remove from the collection sent to the base multiple values collection - persistedPreVals.PreValuesAsDictionary.Remove(_multipleKey); - } - - var returnVal = base.ConvertDbToEditor(defaultPreVals, persistedPreVals); - - returnVal[_multipleKey] = multipleMode; - return returnVal; - } - - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d11475c0fb..c6d528bb3d 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -337,7 +337,6 @@ - From 2ad79956d98bf4f79b50ce1608049641d8a777f2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 21 Mar 2018 12:37:26 +1100 Subject: [PATCH 32/46] Re-apply Drop Down Flexible "Merge pull request #2502 from umbraco/temp-U4-11032"" --- src/Umbraco.Core/Constants-PropertyEditors.cs | 7 +- .../src/views/prevalueeditors/boolean.html | 2 +- .../dropdownFlexible.controller.js | 81 ++++++++++++++++++ .../dropdownFlexible/dropdownFlexible.html | 19 +++++ .../DropDownMultiplePropertyEditor.cs | 4 +- .../DropDownMultipleWithKeysPropertyEditor.cs | 4 +- .../PropertyEditors/DropDownPropertyEditor.cs | 4 +- .../DropDownWithKeysPropertyEditor.cs | 4 +- .../DropdownFlexiblePropertyEditor.cs | 84 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 10 files changed, 200 insertions(+), 10 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html create mode 100644 src/Umbraco.Web/PropertyEditors/DropdownFlexiblePropertyEditor.cs diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 1f3986eeaf..43779ea44a 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -122,6 +122,11 @@ namespace Umbraco.Core /// Alias for the Dropdown list, publishing keys datatype. /// public const string DropdownlistPublishingKeysAlias = "Umbraco.DropdownlistPublishingKeys"; + + /// + /// Alias for the "new" Dropdown list, that replaces the old four deprecated ones and works as other list based property editors + /// + public const string DropDownListFlexibleAlias = "Umbraco.DropDown.Flexible"; /// /// Guid for the Folder browser datatype. @@ -452,4 +457,4 @@ namespace Umbraco.Core } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html index 8af17a18ea..3e6be09650 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js new file mode 100644 index 0000000000..c7a472d80e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js @@ -0,0 +1,81 @@ +angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleController", + function($scope) { + + //setup the default config + var config = { + items: [], + multiple: false + }; + + //map the user config + angular.extend(config, $scope.model.config); + + //map back to the model + $scope.model.config = config; + + function convertArrayToDictionaryArray(model){ + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + for (var i = 0; i < model.length; i++) { + newItems.push({ id: model[i], sortOrder: 0, value: model[i] }); + } + + return newItems; + } + + + function convertObjectToDictionaryArray(model){ + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + var vals = _.values($scope.model.config.items); + var keys = _.keys($scope.model.config.items); + + for (var i = 0; i < vals.length; i++) { + var label = vals[i].value ? vals[i].value : vals[i]; + newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: label }); + } + + return newItems; + } + + $scope.updateSingleDropdownValue = function() { + $scope.model.value = [$scope.model.singleDropdownValue]; + } + + if (angular.isArray($scope.model.config.items)) { + //PP: I dont think this will happen, but we have tests that expect it to happen.. + //if array is simple values, convert to array of objects + if(!angular.isObject($scope.model.config.items[0])){ + $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items); + } + } + else if (angular.isObject($scope.model.config.items)) { + $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items); + } + else { + throw "The items property must be either an array or a dictionary"; + } + + + //sort the values + $scope.model.config.items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); + + //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set + // to "" gets selected by default + if ($scope.model.value === null || $scope.model.value === undefined) { + if ($scope.model.config.multiple) { + $scope.model.value = []; + } + else { + $scope.model.value = ""; + } + } + + // if we run in single mode we'll store the value in a local variable + // so we can pass an array as the model as our PropertyValueEditor expects that + $scope.model.singleDropdownValue = ""; + if ($scope.model.config.multiple === "0") { + $scope.model.singleDropdownValue = Array.isArray($scope.model.value) ? $scope.model.value[0] : $scope.model.value; + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html new file mode 100644 index 0000000000..a68a614ad1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html @@ -0,0 +1,19 @@ +
+ + + + + +
diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs index a4dfd6c2ad..5cfd745d62 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.PropertyEditors [ParameterEditor("propertyTypePickerMultiple", "Name", "textbox")] [ParameterEditor("contentTypeMultiple", "Name", "textbox")] [ParameterEditor("tabPickerMultiple", "Name", "textbox")] - [PropertyEditor(Constants.PropertyEditors.DropDownListMultipleAlias, "Dropdown list multiple", "dropdown", Group = "lists", Icon="icon-bulleted-list")] + [PropertyEditor(Constants.PropertyEditors.DropDownListMultipleAlias, "Dropdown list multiple", "dropdown", Group = "lists", Icon="icon-bulleted-list", IsDeprecated = true)] public class DropDownMultiplePropertyEditor : DropDownMultipleWithKeysPropertyEditor { protected override PropertyValueEditor CreateValueEditor() @@ -28,4 +28,4 @@ namespace Umbraco.Web.PropertyEditors -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs index 05ba3e2644..ac28379f0d 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.PropertyEditors /// Due to maintaining backwards compatibility this data type stores the value as a string which is a comma separated value of the /// ids of the individual items so we have logic in here to deal with that. /// - [PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list")] + [PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list", IsDeprecated = true)] public class DropDownMultipleWithKeysPropertyEditor : DropDownPropertyEditor { protected override PropertyValueEditor CreateValueEditor() @@ -62,4 +62,4 @@ namespace Umbraco.Web.PropertyEditors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs index 115b99dea4..4c9b0ae99a 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the string value is published /// in cache and not the int ID. /// - [PropertyEditor(Constants.PropertyEditors.DropDownListAlias, "Dropdown list", "dropdown", ValueType = PropertyEditorValueTypes.String, Group = "lists", Icon = "icon-indent")] + [PropertyEditor(Constants.PropertyEditors.DropDownListAlias, "Dropdown list", "dropdown", ValueType = PropertyEditorValueTypes.String, Group = "lists", Icon = "icon-indent", IsDeprecated = true)] public class DropDownPropertyEditor : DropDownWithKeysPropertyEditor { /// @@ -29,4 +29,4 @@ namespace Umbraco.Web.PropertyEditors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs index a33115003c..4e8efd93a5 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the INT ID value is published /// in cache and not the string value. /// - [PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = PropertyEditorValueTypes.Integer, Group = "lists", Icon = "icon-indent")] + [PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = PropertyEditorValueTypes.Integer, Group = "lists", Icon = "icon-indent", IsDeprecated = true)] public class DropDownWithKeysPropertyEditor : PropertyEditor { @@ -24,4 +24,4 @@ namespace Umbraco.Web.PropertyEditors return new ValueListPreValueEditor(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DropdownFlexiblePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropdownFlexiblePropertyEditor.cs new file mode 100644 index 0000000000..236aa273c3 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DropdownFlexiblePropertyEditor.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + [PropertyEditor(Constants.PropertyEditors.DropDownListFlexibleAlias, "Dropdown", "dropdownFlexible", Group = "lists", Icon = "icon-indent")] + public class DropdownFlexiblePropertyEditor : PropertyEditor + { + private static readonly string _multipleKey = "multiple"; + + /// + /// Return a custom pre-value editor + /// + /// + /// + /// We are just going to re-use the ValueListPreValueEditor + /// + protected override PreValueEditor CreatePreValueEditor() + { + return new DropdownFlexiblePreValueEditor(); + } + + /// + /// We need to override the value editor so that we can ensure the string value is published in cache and not the integer ID value. + /// + /// + protected override PropertyValueEditor CreateValueEditor() + { + return new PublishValuesMultipleValueEditor(false, base.CreateValueEditor()); + } + + internal class DropdownFlexiblePreValueEditor : ValueListPreValueEditor + { + public DropdownFlexiblePreValueEditor() + { + Fields.Insert(0, new PreValueField + { + Key = "multiple", + Name = "Enable multiple choice", + Description = "When checked, the dropdown will be a select multiple / combo box style dropdown", + View = "boolean" + }); + } + + public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) + { + + var result = base.ConvertEditorToDb(editorValue, currentValue); + + // get multiple config + var multipleValue = editorValue[_multipleKey] != null ? editorValue[_multipleKey].ToString() : "0"; + result.Add(_multipleKey, new PreValue(-1, multipleValue)); + + return result; + } + + public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) + { + // weird way, but as the value stored is 0 or 1 need to do it this way + string multipleMode = "0"; + if (persistedPreVals != null && persistedPreVals.PreValuesAsDictionary[_multipleKey] != null) + { + multipleMode = persistedPreVals.PreValuesAsDictionary[_multipleKey].Value; + + // remove from the collection sent to the base multiple values collection + persistedPreVals.PreValuesAsDictionary.Remove(_multipleKey); + } + + var returnVal = base.ConvertDbToEditor(defaultPreVals, persistedPreVals); + + returnVal[_multipleKey] = multipleMode; + return returnVal; + } + + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a876dee934..104d4932c2 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -343,6 +343,7 @@ + From 995f3f38a33ba4dd0ab68cdc5152d0c2bb634a8f Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 21 Mar 2018 12:52:17 +1100 Subject: [PATCH 33/46] Fixes upgrade issue with copying the preview.old directory and updates the link to the the correct public one --- .../RenamePreviewFolder.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTenZero/RenamePreviewFolder.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTenZero/RenamePreviewFolder.cs index 248492a3ea..5545038ba0 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTenZero/RenamePreviewFolder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTenZero/RenamePreviewFolder.cs @@ -25,12 +25,15 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTenZero if (Directory.Exists(previewFolderPath)) { var newPath = previewFolderPath.Replace("preview", "preview.old"); - Directory.Move(previewFolderPath, newPath); - var readmeText = - $"Static html files used for preview and canvas editing functionality no longer live in this directory.\r\n" + - $"Instead they have been recreated as MVC views and can now be found in '~/Umbraco/Views/Preview'.\r\n" + - $"See issue: http://issues.umbraco.org/issue/UAASSCRUM-1405"; - File.WriteAllText(Path.Combine(newPath, "readme.txt"), readmeText); + if (Directory.Exists(newPath) == false) + { + Directory.Move(previewFolderPath, newPath); + var readmeText = + $"Static html files used for preview and canvas editing functionality no longer live in this directory.\r\n" + + $"Instead they have been recreated as MVC views and can now be found in '~/Umbraco/Views/Preview'.\r\n" + + $"See issue: http://issues.umbraco.org/issue/U4-11090"; + File.WriteAllText(Path.Combine(newPath, "readme.txt"), readmeText); + } } } From ebea5e8170b4caa038a3008bc99eb9ce4b7bd530 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 21 Mar 2018 13:53:54 +1100 Subject: [PATCH 34/46] fixes boolean pre-value editor Html Id --- .../src/views/prevalueeditors/boolean.controller.js | 4 ++++ .../src/views/prevalueeditors/boolean.html | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.controller.js diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.controller.js new file mode 100644 index 0000000000..a7bf0ac280 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.controller.js @@ -0,0 +1,4 @@ +angular.module("umbraco").controller("Umbraco.PrevalueEditors.BooleanController", + function ($scope) { + $scope.htmlId = "bool-" + String.CreateGuid(); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html index 3e6be09650..880647a0df 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/boolean.html @@ -1 +1,4 @@ - + From b361cfebb7152302bebc0c5d5acecd4fe1a16ea2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 21 Mar 2018 13:55:01 +1100 Subject: [PATCH 35/46] updates npm build dependencies --- src/Umbraco.Web.UI.Client/package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index b1ee7be7bf..a25176927c 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -27,9 +27,9 @@ "gulp": "^3.9.1", "gulp-concat": "^2.6.0", "gulp-connect": "5.0.0", - "gulp-less": "^3.1.0", + "gulp-less": "^3.5.0", "gulp-ngdocs": "^0.3.0", - "gulp-open": "^2.0.0", + "gulp-open": "^2.1.0", "gulp-postcss": "^6.2.0", "gulp-rename": "^1.2.2", "gulp-sort": "^2.0.0", @@ -38,11 +38,11 @@ "gulp-wrap-js": "^0.4.1", "jasmine-core": "2.5.2", "karma": "^1.7.0", - "karma-jasmine": "^1.1.0", + "karma-jasmine": "^1.1.1", "karma-phantomjs-launcher": "^1.0.4", - "less": "^2.6.1", - "lodash": "^4.16.3", + "less": "^2.7.3", + "lodash": "^4.17.5", "merge-stream": "^1.0.1", - "run-sequence": "^2.1.0" + "run-sequence": "^2.2.1" } } From 0d2df4de811fbbd5760e0ef70adf0abcf8da326c Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 21 Mar 2018 10:22:08 +0100 Subject: [PATCH 36/46] Some cleanup --- .../umbraco/create/MemberGroupTasks.cs | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs index 85f6906e36..14fd55ee98 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs @@ -1,24 +1,17 @@ -using System; -using System.Data; using System.Linq; using System.Web.Security; -using Umbraco.Web.UI; using umbraco.BusinessLogic; -using umbraco.DataLayer; -using umbraco.BasePages; -using Umbraco.Core.IO; using umbraco.cms.businesslogic.member; -using Umbraco.Core; -using Umbraco.Web; +using Umbraco.Web.UI; -namespace umbraco +namespace Umbraco.Web.umbraco.presentation.umbraco.create { public class MemberGroupTasks : LegacyDialogTask { public override bool PerformSave() { Roles.CreateRole(Alias); - _returnUrl = string.Format("members/EditMemberGroup.aspx?id={0}", System.Web.HttpContext.Current.Server.UrlEncode(Alias)); + _returnUrl = $"members/EditMemberGroup.aspx?id={System.Web.HttpContext.Current.Server.UrlEncode(Alias)}"; return true; } @@ -29,7 +22,7 @@ namespace umbraco // only built-in roles can be deleted if (Member.IsUsingUmbracoRoles()) { - MemberGroup.GetByName(Alias).delete(); + Roles.DeleteRole(Alias); roleDeleted = true; } @@ -54,14 +47,8 @@ namespace umbraco private string _returnUrl = ""; - public override string ReturnUrl - { - get { return _returnUrl; } - } + public override string ReturnUrl => _returnUrl; - public override string AssignedApp - { - get { return DefaultApps.member.ToString(); } - } + public override string AssignedApp => DefaultApps.member.ToString(); } } From 97d7e3db8424a650386486dce08764da2fe45cba Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 21 Mar 2018 10:46:42 +0100 Subject: [PATCH 37/46] Forgot add the new namespace --- src/Umbraco.Tests/UI/LegacyDialogTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/UI/LegacyDialogTests.cs b/src/Umbraco.Tests/UI/LegacyDialogTests.cs index e7749cf80b..304f8256b8 100644 --- a/src/Umbraco.Tests/UI/LegacyDialogTests.cs +++ b/src/Umbraco.Tests/UI/LegacyDialogTests.cs @@ -1,13 +1,11 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using NUnit.Framework; using Umbraco.Core; using Umbraco.Web.UI; using umbraco; using umbraco.BusinessLogic; using umbraco.interfaces; +using Umbraco.Web.umbraco.presentation.umbraco.create; namespace Umbraco.Tests.UI { From 3c4f052a278442eb18a7e75000b6bd4976725fb9 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 21 Mar 2018 13:45:44 +0100 Subject: [PATCH 38/46] Moved some code around based on feedback --- .../umbraco.presentation/umbraco/create/MemberGroupTasks.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs index 14fd55ee98..905aa42ca3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs @@ -22,8 +22,7 @@ namespace Umbraco.Web.umbraco.presentation.umbraco.create // only built-in roles can be deleted if (Member.IsUsingUmbracoRoles()) { - Roles.DeleteRole(Alias); - roleDeleted = true; + roleDeleted = Roles.DeleteRole(Alias); } // Need to delete the member group from any content item that has it assigned in public access settings From 014ac98d0beb79e08435e3dcf3a1323d54b8804d Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Wed, 21 Mar 2018 18:08:24 +0000 Subject: [PATCH 39/46] U4-11052 - Enabled IntegerValueConverter to handle `Int64` source values (#2500) * Enabled IntegerValueConverter to handle `Int64` source values --- .../ValueConverters/IntegerValueConverter.cs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs index 34b1ebffb1..20ab244de5 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models.PublishedContent; +using System; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.PropertyEditors.ValueConverters { @@ -14,19 +15,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) { - if (source == null) return 0; - - // in XML an integer is a string - var sourceString = source as string; - if (sourceString != null) - { - int i; - return (int.TryParse(sourceString, out i)) ? i : 0; - } - - // in the database an integer is an integer - // default value is zero - return (source is int) ? source : 0; + return source.TryConvertTo().Result; } } } From b4a0dadee94bc1e5991b784d57e5e6dd6159bde3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 21 Mar 2018 19:52:56 +0100 Subject: [PATCH 40/46] Re-apply the nice C# syntax James provided --- src/Umbraco.Core/ObjectExtensions.cs | 84 ++++++++++------------------ 1 file changed, 28 insertions(+), 56 deletions(-) diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 185a75fac9..479e425c99 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -140,8 +140,7 @@ namespace Umbraco.Core if (underlying != null) { // Special case for empty strings for bools/dates which should return null if an empty string. - var inputString = input as string; - if (inputString != null) + if (input is string inputString) { //TODO: Why the check against only bool/date when a string is null/empty? In what scenario can we convert to another type when the string is null or empty other than just being null? if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool))) @@ -168,8 +167,7 @@ namespace Umbraco.Core { // target is not a generic type - var inputString = input as string; - if (inputString != null) + if (input is string inputString) { // Try convert from string, returns an Attempt if the string could be // processed (either succeeded or failed), else null if we need to try @@ -218,8 +216,7 @@ namespace Umbraco.Core } // Re-check convertables since we altered the input through recursion - var convertible2 = input as IConvertible; - if (convertible2 != null) + if (input is IConvertible convertible2) { return Attempt.Succeed(Convert.ChangeType(convertible2, target)); } @@ -277,8 +274,7 @@ namespace Umbraco.Core { if (target == typeof(int)) { - int value; - if (int.TryParse(input, out value)) + if (int.TryParse(input, out var value)) { return Attempt.Succeed(value); } @@ -286,30 +282,26 @@ namespace Umbraco.Core // Because decimal 100.01m will happily convert to integer 100, it // makes sense that string "100.01" *also* converts to integer 100. var input2 = NormalizeNumberDecimalSeparator(input); - decimal value2; - return Attempt.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt32(value2)); + return Attempt.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2)); } if (target == typeof(long)) { - long value; - if (long.TryParse(input, out value)) + if (long.TryParse(input, out var value)) { return Attempt.Succeed(value); } // Same as int var input2 = NormalizeNumberDecimalSeparator(input); - decimal value2; - return Attempt.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt64(value2)); + return Attempt.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2)); } // TODO: Should we do the decimal trick for short, byte, unsigned? if (target == typeof(bool)) { - bool value; - if (bool.TryParse(input, out value)) + if (bool.TryParse(input, out var value)) { return Attempt.Succeed(value); } @@ -322,53 +314,42 @@ namespace Umbraco.Core switch (Type.GetTypeCode(target)) { case TypeCode.Int16: - short value; - return Attempt.SucceedIf(short.TryParse(input, out value), value); + return Attempt.SucceedIf(short.TryParse(input, out var value), value); case TypeCode.Double: var input2 = NormalizeNumberDecimalSeparator(input); - double valueD; - return Attempt.SucceedIf(double.TryParse(input2, out valueD), valueD); + return Attempt.SucceedIf(double.TryParse(input2, out var valueD), valueD); case TypeCode.Single: var input3 = NormalizeNumberDecimalSeparator(input); - float valueF; - return Attempt.SucceedIf(float.TryParse(input3, out valueF), valueF); + return Attempt.SucceedIf(float.TryParse(input3, out var valueF), valueF); case TypeCode.Char: - char valueC; - return Attempt.SucceedIf(char.TryParse(input, out valueC), valueC); + return Attempt.SucceedIf(char.TryParse(input, out var valueC), valueC); case TypeCode.Byte: - byte valueB; - return Attempt.SucceedIf(byte.TryParse(input, out valueB), valueB); + return Attempt.SucceedIf(byte.TryParse(input, out var valueB), valueB); case TypeCode.SByte: - sbyte valueSb; - return Attempt.SucceedIf(sbyte.TryParse(input, out valueSb), valueSb); + return Attempt.SucceedIf(sbyte.TryParse(input, out var valueSb), valueSb); case TypeCode.UInt32: - uint valueU; - return Attempt.SucceedIf(uint.TryParse(input, out valueU), valueU); + return Attempt.SucceedIf(uint.TryParse(input, out var valueU), valueU); case TypeCode.UInt16: - ushort valueUs; - return Attempt.SucceedIf(ushort.TryParse(input, out valueUs), valueUs); + return Attempt.SucceedIf(ushort.TryParse(input, out var valueUs), valueUs); case TypeCode.UInt64: - ulong valueUl; - return Attempt.SucceedIf(ulong.TryParse(input, out valueUl), valueUl); + return Attempt.SucceedIf(ulong.TryParse(input, out var valueUl), valueUl); } } else if (target == typeof(Guid)) { - Guid value; - return Attempt.SucceedIf(Guid.TryParse(input, out value), value); + return Attempt.SucceedIf(Guid.TryParse(input, out var value), value); } else if (target == typeof(DateTime)) { - DateTime value; - if (DateTime.TryParse(input, out value)) + if (DateTime.TryParse(input, out var value)) { switch (value.Kind) { @@ -388,24 +369,20 @@ namespace Umbraco.Core } else if (target == typeof(DateTimeOffset)) { - DateTimeOffset value; - return Attempt.SucceedIf(DateTimeOffset.TryParse(input, out value), value); + return Attempt.SucceedIf(DateTimeOffset.TryParse(input, out var value), value); } else if (target == typeof(TimeSpan)) { - TimeSpan value; - return Attempt.SucceedIf(TimeSpan.TryParse(input, out value), value); + return Attempt.SucceedIf(TimeSpan.TryParse(input, out var value), value); } else if (target == typeof(decimal)) { var input2 = NormalizeNumberDecimalSeparator(input); - decimal value; - return Attempt.SucceedIf(decimal.TryParse(input2, out value), value); + return Attempt.SucceedIf(decimal.TryParse(input2, out var value), value); } else if (input != null && target == typeof(Version)) { - Version value; - return Attempt.SucceedIf(Version.TryParse(input, out value), value); + return Attempt.SucceedIf(Version.TryParse(input, out var value), value); } // E_NOTIMPL IPAddress, BigInteger @@ -690,8 +667,7 @@ namespace Umbraco.Core { var key = new CompositeTypeTypeKey(source, target); - TypeConverter typeConverter; - if (InputTypeConverterCache.TryGetValue(key, out typeConverter)) + if (InputTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter)) { return typeConverter; } @@ -711,8 +687,7 @@ namespace Umbraco.Core { var key = new CompositeTypeTypeKey(source, target); - TypeConverter typeConverter; - if (DestinationTypeConverterCache.TryGetValue(key, out typeConverter)) + if (DestinationTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter)) { return typeConverter; } @@ -730,8 +705,7 @@ namespace Umbraco.Core [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Type GetCachedGenericNullableType(Type type) { - Type underlyingType; - if (NullableGenericCache.TryGetValue(type, out underlyingType)) + if (NullableGenericCache.TryGetValue(type, out Type underlyingType)) { return underlyingType; } @@ -750,8 +724,7 @@ namespace Umbraco.Core private static bool GetCachedCanAssign(object input, Type source, Type target) { var key = new CompositeTypeTypeKey(source, target); - bool canConvert; - if (AssignableTypeCache.TryGetValue(key, out canConvert)) + if (AssignableTypeCache.TryGetValue(key, out bool canConvert)) { return canConvert; } @@ -770,8 +743,7 @@ namespace Umbraco.Core [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool GetCachedCanConvertToBoolean(Type type) { - bool result; - if (BoolConvertCache.TryGetValue(type, out result)) + if (BoolConvertCache.TryGetValue(type, out bool result)) { return result; } From c4e5ab54d236bcb8e0f1c6029edce19278bd6b14 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 22 Mar 2018 19:55:55 +1100 Subject: [PATCH 41/46] redirects preview to login when not authorized --- src/Umbraco.Web/Editors/PreviewController.cs | 4 +- .../Mvc/UmbracoAuthorizeAttribute.cs | 53 +++++++++++++++---- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index 488272153a..08de0f83ba 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -8,11 +8,10 @@ using Umbraco.Web.Mvc; namespace Umbraco.Web.Editors { - [UmbracoAuthorize] [DisableBrowserCache] public class PreviewController : Controller { - + [UmbracoAuthorize(redirectToUmbracoLogin: true)] public ActionResult Index() { var model = new BackOfficePreview @@ -33,7 +32,6 @@ namespace Umbraco.Web.Editors return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + "Index.cshtml", model); } - [AllowAnonymous] public ActionResult Editors(string editor) { if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException("editor"); diff --git a/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs index c5ebf24626..fdcade45e2 100644 --- a/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs @@ -4,6 +4,7 @@ using System.Web.Mvc; using Umbraco.Core; using Umbraco.Web.Security; using umbraco.BasePages; +using Umbraco.Core.Configuration; namespace Umbraco.Web.Mvc { @@ -14,6 +15,7 @@ namespace Umbraco.Web.Mvc { private readonly ApplicationContext _applicationContext; private readonly UmbracoContext _umbracoContext; + private readonly string _redirectUrl; private ApplicationContext GetApplicationContext() { @@ -36,16 +38,40 @@ namespace Umbraco.Web.Mvc _applicationContext = _umbracoContext.Application; } + /// + /// Default constructor + /// public UmbracoAuthorizeAttribute() { } - /// - /// Ensures that the user must be in the Administrator or the Install role - /// - /// - /// - protected override bool AuthorizeCore(HttpContextBase httpContext) + /// + /// Constructor specifying to redirect to the specified location if not authorized + /// + /// + public UmbracoAuthorizeAttribute(string redirectUrl) + { + _redirectUrl = redirectUrl ?? throw new ArgumentNullException(nameof(redirectUrl)); + } + + /// + /// Constructor specifying to redirect to the umbraco login page if not authorized + /// + /// + public UmbracoAuthorizeAttribute(bool redirectToUmbracoLogin) + { + if (redirectToUmbracoLogin) + { + _redirectUrl = GlobalSettings.Path.EnsureStartsWith("~"); + } + } + + /// + /// Ensures that the user must be in the Administrator or the Install role + /// + /// + /// + protected override bool AuthorizeCore(HttpContextBase httpContext) { if (httpContext == null) throw new ArgumentNullException("httpContext"); @@ -73,11 +99,20 @@ namespace Umbraco.Web.Mvc /// protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { - filterContext.Result = (ActionResult)new HttpUnauthorizedResult("You must login to view this resource."); - + if (_redirectUrl.IsNullOrWhiteSpace()) + { + filterContext.Result = (ActionResult)new HttpUnauthorizedResult("You must login to view this resource."); + + + } + else + { + filterContext.Result = new RedirectResult(_redirectUrl); + } + //DON'T do a FormsAuth redirect... argh!! thankfully we're running .Net 4.5 :) filterContext.RequestContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true; } } -} \ No newline at end of file +} From dd8e43d80a69563be6028c0fe731af9ccd02fde9 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 22 Mar 2018 20:35:41 +1100 Subject: [PATCH 42/46] Fixes U4-10472 Cannot upgrade to 7.7+ when using MySql (#2493) * Fixes U4-10472 Cannot upgrade to 7.7+ when using MySql * Fixes dropping the FK for the AddUserGroupTables migration --- .../AddUserGroupTables.cs | 24 +++++++---- src/Umbraco.Core/Services/UserService.cs | 42 ++++++++----------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs index 5e67dcfedd..ff778fdabc 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero @@ -343,15 +344,24 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe if (tables.InvariantContains("umbracoUserType") && tables.InvariantContains("umbracoUser")) { - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_umbracoUser_umbracoUserType_id"))) + if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) { - Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser"); + //In MySql, this will drop the FK according to it's special naming rules + Delete.ForeignKey().FromTable("umbracoUser").ForeignColumn("userType").ToTable("umbracoUserType").PrimaryColumn("id"); } - //This is the super old constraint name of the FK for user type so check this one too - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_user_userType"))) + else { - Delete.ForeignKey("FK_user_userType").OnTable("umbracoUser"); - } + //Delete the FK if it exists before dropping the column + if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_umbracoUser_umbracoUserType_id"))) + { + Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser"); + } + //This is the super old constraint name of the FK for user type so check this one too + if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_user_userType"))) + { + Delete.ForeignKey("FK_user_userType").OnTable("umbracoUser"); + } + } Delete.Column("userType").FromTable("umbracoUser"); Delete.Table("umbracoUserType"); @@ -361,4 +371,4 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe public override void Down() { } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 4117d582c2..9b52af6827 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -207,20 +207,17 @@ namespace Umbraco.Core.Services { return repository.GetByUsername(username, includeSecurityData: true); } - catch (Exception ex) + catch (DbException ex) { - if (ex is SqlException || ex is SqlCeException) + //we need to handle this one specific case which is when we are upgrading to 7.7 since the user group + //tables don't exist yet. This is the 'easiest' way to deal with this without having to create special + //version checks in the BackOfficeSignInManager and calling into other special overloads that we'd need + //like "GetUserById(int id, bool includeSecurityData)" which may cause confusion because the result of + //that method would not be cached. + if (ApplicationContext.Current.IsUpgrading) { - //we need to handle this one specific case which is when we are upgrading to 7.7 since the user group - //tables don't exist yet. This is the 'easiest' way to deal with this without having to create special - //version checks in the BackOfficeSignInManager and calling into other special overloads that we'd need - //like "GetUserById(int id, bool includeSecurityData)" which may cause confusion because the result of - //that method would not be cached. - if (ApplicationContext.Current.IsUpgrading) - { - //NOTE: this will not be cached - return repository.GetByUsername(username, includeSecurityData: false); - } + //NOTE: this will not be cached + return repository.GetByUsername(username, includeSecurityData: false); } throw; } @@ -789,20 +786,17 @@ namespace Umbraco.Core.Services var result = repository.Get(id); return result; } - catch (Exception ex) + catch (DbException ex) { - if (ex is SqlException || ex is SqlCeException) + //we need to handle this one specific case which is when we are upgrading to 7.7 since the user group + //tables don't exist yet. This is the 'easiest' way to deal with this without having to create special + //version checks in the BackOfficeSignInManager and calling into other special overloads that we'd need + //like "GetUserById(int id, bool includeSecurityData)" which may cause confusion because the result of + //that method would not be cached. + if (ApplicationContext.Current.IsUpgrading) { - //we need to handle this one specific case which is when we are upgrading to 7.7 since the user group - //tables don't exist yet. This is the 'easiest' way to deal with this without having to create special - //version checks in the BackOfficeSignInManager and calling into other special overloads that we'd need - //like "GetUserById(int id, bool includeSecurityData)" which may cause confusion because the result of - //that method would not be cached. - if (ApplicationContext.Current.IsUpgrading) - { - //NOTE: this will not be cached - return repository.Get(id, includeSecurityData: false); - } + //NOTE: this will not be cached + return repository.Get(id, includeSecurityData: false); } throw; } From 4bb75d73ddd0fbfc749ce3d526f078e13c52d252 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 22 Mar 2018 10:50:12 +0100 Subject: [PATCH 43/46] Bump version to 7.9.3 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 279cc645e2..dd901ab1d1 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.9.2")] -[assembly: AssemblyInformationalVersion("7.9.2")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.9.3")] +[assembly: AssemblyInformationalVersion("7.9.3")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index e3ecd4c5b0..4a81e95c26 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.9.2"); + private static readonly Version Version = new Version("7.9.3"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 43a2c08bcd..9a0122ad83 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1023,9 +1023,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" True True - 7920 + 7930 / - http://localhost:7920 + http://localhost:7930 False False From 19e5715a846e2168ae6208843f57659571492b31 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 22 Mar 2018 14:55:02 +0100 Subject: [PATCH 44/46] Whoops, that was not supposed to say beta --- src/SolutionInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 083b2d3e90..c21446bf8d 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.10.0")] -[assembly: AssemblyInformationalVersion("7.10.0-beta005")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.10.0")] \ No newline at end of file From 8c0ec489c2bfef4748bf098009987a17045c9af1 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 23 Mar 2018 09:54:11 +0100 Subject: [PATCH 45/46] Bump version to 7.11 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index c21446bf8d..a3a3080dd3 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.10.0")] -[assembly: AssemblyInformationalVersion("7.10.0")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.11.0")] +[assembly: AssemblyInformationalVersion("7.11.0")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 9268cbbd91..7eca275901 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.10.0"); + private static readonly Version Version = new Version("7.11.0"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index fe8fb807d4..1577442ede 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1035,9 +1035,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" True True - 7100 + 7110 / - http://localhost:7100 + http://localhost:7110 False False From 716020602dc821b6c29c0ad8d7e7591736b92520 Mon Sep 17 00:00:00 2001 From: Claus Date: Fri, 23 Mar 2018 11:55:23 +0100 Subject: [PATCH 46/46] adding using statement to preview file to prevent missing htmlhelper error. --- src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 71e9ff36c9..621d542edc 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -1,4 +1,5 @@ -@inherits System.Web.Mvc.WebViewPage +@using System.Web.Mvc.Html +@inherits System.Web.Mvc.WebViewPage @{ var disableDevicePreview = Model.DisableDevicePreview.ToString().ToLowerInvariant(); }