diff --git a/.github/ISSUE_TEMPLATE/1_Bug.md b/.github/ISSUE_TEMPLATE/1_Bug.md index a1e33e3854..619452f700 100644 --- a/.github/ISSUE_TEMPLATE/1_Bug.md +++ b/.github/ISSUE_TEMPLATE/1_Bug.md @@ -12,7 +12,7 @@ thoroughly. Then, proceed by filling out the rest of the details in the issue template below. The more details you can give us, the easier it will be for us to determine the cause of a problem. -See: https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/.github/CONTRIBUTING.md +See: https://github.com/umbraco/Umbraco-CMS/blob/v8/dev/.github/CONTRIBUTING.md --> diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index f3f8f47b57..fce15eb487 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -14,6 +14,7 @@ Contains the core assemblies needed to run Umbraco Cms en-US umbraco + @@ -44,7 +45,7 @@ - + @@ -52,9 +53,9 @@ - + - + diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 3c8fed78f5..4aa354eba2 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -14,6 +14,7 @@ Contains the core assemblies needed to run Umbraco Cms en-US umbraco + @@ -43,7 +44,7 @@ - + @@ -53,12 +54,12 @@ - - - + + + - - + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 5cdacca419..b7bfaaff5b 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -14,6 +14,7 @@ Installs Umbraco Cms in your Visual Studio ASP.NET project en-US umbraco + @@ -25,7 +26,7 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - + diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt index 4d660301a8..828bb8612f 100644 --- a/build/NuSpecs/tools/Views.Web.config.install.xdt +++ b/build/NuSpecs/tools/Views.Web.config.install.xdt @@ -8,7 +8,7 @@ - + @@ -18,13 +18,13 @@ - + diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 1411cb5c97..0be28f9467 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -18,9 +18,20 @@ if ($project) { # Copy umbraco and umbraco_files from package to project folder $umbracoFolder = Join-Path $projectPath "Umbraco" - New-Item -ItemType Directory -Force -Path $umbracoFolder + New-Item -ItemType Directory -Force -Path $umbracoFolder $umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco" - robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log + + Write-Host "copying files to $umbracoFolder ..." + # see https://support.microsoft.com/en-us/help/954404/return-codes-that-are-used-by-the-robocopy-utility-in-windows-server-2 + robocopy $umbracoFolderSource $umbracoFolder /is /it /e + if (($lastexitcode -eq 1) -or ($lastexitcode -eq 3) -or ($lastexitcode -eq 5) -or ($lastexitcode -eq 7)) + { + write-host "Copy succeeded!" + } + else + { + write-host "Copy failed with exit code:" $lastexitcode + } $copyWebconfig = $true $destinationWebConfig = Join-Path $projectPath "Web.config" @@ -40,7 +51,11 @@ if ($project) { } } } - Catch { } + Catch + { + Write-Host "An error occurred:" + Write-Host $_ + } } if($copyWebconfig -eq $true) @@ -74,7 +89,9 @@ if ($project) { } Catch { - # Not a big problem if this fails, let it go + # Not a big problem if this fails, let it go + # Write-Host "An error occurred:" + # Write-Host $_ } } diff --git a/build/build.ps1 b/build/build.ps1 index 55b686c98e..e4994d2c4a 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -15,7 +15,7 @@ [Parameter(Mandatory=$false)] [Alias("doc")] [switch] $docfx = $false, - + # keep the build directories, don't clear them [Parameter(Mandatory=$false)] [Alias("c")] @@ -392,13 +392,13 @@ &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Core.nuspec" ` -Properties BuildTmp="$($this.BuildTemp)" ` -Version "$($this.Version.Semver.ToString())" ` - -Symbols -SymbolPackageFormat snupkg -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log" + -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log" if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Core." } &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Web.nuspec" ` -Properties BuildTmp="$($this.BuildTemp)" ` -Version "$($this.Version.Semver.ToString())" ` - -Symbols -SymbolPackageFormat snupkg -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log" + -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log" if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Web." } &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" ` @@ -429,37 +429,37 @@ Write-Host "Prepare Azure Gallery" $this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput) }) - + $ubuild.DefineMethod("PrepareCSharpDocs", { Write-Host "Prepare C# Documentation" - + $src = "$($this.SolutionRoot)\src" $tmp = $this.BuildTemp $out = $this.BuildOutput $DocFxJson = Join-Path -Path $src "\ApiDocs\docfx.json" $DocFxSiteOutput = Join-Path -Path $tmp "\_site\*.*" - + #restore nuget packages $this.RestoreNuGet() # run DocFx $DocFx = $this.BuildEnv.DocFx - + & $DocFx metadata $DocFxJson & $DocFx build $DocFxJson # zip it & $this.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput }) - + $ubuild.DefineMethod("PrepareAngularDocs", { Write-Host "Prepare Angular Documentation" - + $src = "$($this.SolutionRoot)\src" $out = $this.BuildOutput - + $this.CompileBelle() "Moving to Umbraco.Web.UI.Client folder" diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 9ed398d52f..a4e859988e 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.1.0")] -[assembly: AssemblyInformationalVersion("8.1.0")] +[assembly: AssemblyFileVersion("8.2.0")] +[assembly: AssemblyInformationalVersion("8.2.0")] diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs index 158b132f26..6dd866705e 100644 --- a/src/Umbraco.Core/AsyncLock.cs +++ b/src/Umbraco.Core/AsyncLock.cs @@ -67,31 +67,34 @@ namespace Umbraco.Core : new NamedSemaphoreReleaser(_semaphore2); } - public Task LockAsync() - { - var wait = _semaphore != null - ? _semaphore.WaitAsync() - : _semaphore2.WaitOneAsync(); + //NOTE: We don't use the "Async" part of this lock at all + //TODO: Remove this and rename this class something like SystemWideLock, then we can re-instate this logic if we ever need an Async lock again - return wait.IsCompleted - ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named - : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()), - this, CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - } + //public Task LockAsync() + //{ + // var wait = _semaphore != null + // ? _semaphore.WaitAsync() + // : _semaphore2.WaitOneAsync(); - public Task LockAsync(int millisecondsTimeout) - { - var wait = _semaphore != null - ? _semaphore.WaitAsync(millisecondsTimeout) - : _semaphore2.WaitOneAsync(millisecondsTimeout); + // return wait.IsCompleted + // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named + // : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()), + // this, CancellationToken.None, + // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + //} - return wait.IsCompleted - ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named - : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()), - this, CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - } + //public Task LockAsync(int millisecondsTimeout) + //{ + // var wait = _semaphore != null + // ? _semaphore.WaitAsync(millisecondsTimeout) + // : _semaphore2.WaitOneAsync(millisecondsTimeout); + + // return wait.IsCompleted + // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named + // : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()), + // this, CancellationToken.None, + // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + //} public IDisposable Lock() { diff --git a/src/Umbraco.Core/Compose/AuditEventsComponent.cs b/src/Umbraco.Core/Compose/AuditEventsComponent.cs index 15fdfeacff..453fd6314a 100644 --- a/src/Umbraco.Core/Compose/AuditEventsComponent.cs +++ b/src/Umbraco.Core/Compose/AuditEventsComponent.cs @@ -44,21 +44,22 @@ namespace Umbraco.Core.Compose public void Terminate() { } + internal static IUser UnknownUser => new User { Id = Constants.Security.UnknownUserId, Name = Constants.Security.UnknownUserName, Email = "" }; + private IUser CurrentPerformingUser { get { var identity = Thread.CurrentPrincipal?.GetUmbracoIdentity(); - return identity == null - ? new User { Id = 0, Name = "SYSTEM", Email = "" } - : _userService.GetUserById(Convert.ToInt32(identity.Id)); + var user = identity == null ? null : _userService.GetUserById(Convert.ToInt32(identity.Id)); + return user ?? UnknownUser; } } private IUser GetPerformingUser(int userId) { var found = userId >= 0 ? _userService.GetUserById(userId) : null; - return found ?? new User {Id = 0, Name = "SYSTEM", Email = ""}; + return found ?? UnknownUser; } private string PerformingIp diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index af9277fce9..fe7a561eca 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -42,8 +42,8 @@ namespace Umbraco.Core.Composing private string _currentAssembliesHash; private IEnumerable _assemblies; private bool _reportedChange; - private static string _localTempPath; - private static string _fileBasePath; + private readonly string _localTempPath; + private string _fileBasePath; /// /// Initializes a new instance of the class. diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index 828a577c34..5dd33c2a60 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Dictionary; using Umbraco.Core.IO; using Umbraco.Core.Logging.Viewer; +using Umbraco.Core.Manifest; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PackageActions; using Umbraco.Core.Persistence.Mappers; @@ -66,9 +67,16 @@ namespace Umbraco.Core /// Gets the validators collection builder. /// /// The composition. - internal static ManifestValueValidatorCollectionBuilder Validators(this Composition composition) + internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this Composition composition) => composition.WithCollectionBuilder(); + /// + /// Gets the manifest filter collection builder. + /// + /// The composition. + public static ManifestFilterCollectionBuilder ManifestFilters(this Composition composition) + => composition.WithCollectionBuilder(); + /// /// Gets the components collection builder. /// diff --git a/src/Umbraco.Core/ConfigsExtensions.cs b/src/Umbraco.Core/ConfigsExtensions.cs index 6fdf7ea3b9..d1672c6c7f 100644 --- a/src/Umbraco.Core/ConfigsExtensions.cs +++ b/src/Umbraco.Core/ConfigsExtensions.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; namespace Umbraco.Core { @@ -41,7 +42,12 @@ namespace Umbraco.Core configs.Add(() => new CoreDebug()); // GridConfig depends on runtime caches, manifest parsers... and cannot be available during composition - configs.Add(factory => new GridConfig(factory.GetInstance(), factory.GetInstance(), configDir, factory.GetInstance().Debug)); + configs.Add(factory => new GridConfig( + factory.GetInstance(), + factory.GetInstance(), + configDir, + factory.GetInstance(), + factory.GetInstance().Debug)); } } } diff --git a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs index b2dae09fc9..9aead74886 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs @@ -1,14 +1,15 @@ using System.IO; using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; namespace Umbraco.Core.Configuration.Grid { class GridConfig : IGridConfig { - public GridConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, bool isDebug) + public GridConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, ManifestParser manifestParser, bool isDebug) { - EditorsConfig = new GridEditorsConfig(logger, appCaches, configFolder, isDebug); + EditorsConfig = new GridEditorsConfig(logger, appCaches, configFolder, manifestParser, isDebug); } public IGridEditorsConfig EditorsConfig { get; } diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index 0e7ef62c58..d434da8c70 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using Newtonsoft.Json.Linq; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; @@ -15,13 +13,15 @@ namespace Umbraco.Core.Configuration.Grid private readonly ILogger _logger; private readonly AppCaches _appCaches; private readonly DirectoryInfo _configFolder; + private readonly ManifestParser _manifestParser; private readonly bool _isDebug; - public GridEditorsConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, bool isDebug) + public GridEditorsConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, ManifestParser manifestParser, bool isDebug) { _logger = logger; _appCaches = appCaches; _configFolder = configFolder; + _manifestParser = manifestParser; _isDebug = isDebug; } @@ -31,9 +31,6 @@ namespace Umbraco.Core.Configuration.Grid { List GetResult() { - // TODO: should use the common one somehow! + ignoring _appPlugins here! - var parser = new ManifestParser(_appCaches, Current.ManifestValidators, _logger); - var editors = new List(); var gridConfig = Path.Combine(_configFolder.FullName, "grid.editors.config.js"); if (File.Exists(gridConfig)) @@ -42,7 +39,7 @@ namespace Umbraco.Core.Configuration.Grid try { - editors.AddRange(parser.ParseGridEditors(sourceString)); + editors.AddRange(_manifestParser.ParseGridEditors(sourceString)); } catch (Exception ex) { @@ -51,7 +48,7 @@ namespace Umbraco.Core.Configuration.Grid } // add manifest editors, skip duplicates - foreach (var gridEditor in parser.Manifest.GridEditors) + foreach (var gridEditor in _manifestParser.Manifest.GridEditors) { if (editors.Contains(gridEditor) == false) editors.Add(gridEditor); } diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index f2b31be28f..673da8f9a3 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -1,27 +1,375 @@ -namespace Umbraco.Core +using System; + +namespace Umbraco.Core { public static partial class Constants { public static class DataTypes { - public const int LabelString = -92; + //NOTE: unfortunately due to backwards compat we can't move/rename these, with the addition of the GUID + //constants, it would make more sense to have these suffixed with "ID" or in a Subclass called "INT", for + //now all we can do is make a subclass called Guids to put the GUID IDs. + + public const int LabelString = System.DefaultLabelDataTypeId; public const int LabelInt = -91; public const int LabelBigint = -93; public const int LabelDateTime = -94; public const int LabelTime = -98; public const int LabelDecimal = -99; + public const int Textarea = -89; public const int Textbox = -88; + public const int RichtextEditor = -87; public const int Boolean = -49; public const int DateTime = -36; public const int DropDownSingle = -39; public const int DropDownMultiple = -42; + public const int Upload = -90; public const int DefaultContentListView = -95; public const int DefaultMediaListView = -96; public const int DefaultMembersListView = -97; + public const int ImageCropper = 1043; public const int Tags = 1041; + + public static class ReservedPreValueKeys + { + public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; + } + + /// + /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management. + /// + public static class Guids + { + + /// + /// Guid for Content Picker as string + /// + public const string ContentPicker = "FD1E0DA5-5606-4862-B679-5D0CF3A52A59"; + + /// + /// Guid for Content Picker + /// + public static readonly Guid ContentPickerGuid = new Guid(ContentPicker); + + + /// + /// Guid for Member Picker as string + /// + public const string MemberPicker = "1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"; + + /// + /// Guid for Member Picker + /// + public static readonly Guid MemberPickerGuid = new Guid(MemberPicker); + + + /// + /// Guid for Media Picker as string + /// + public const string MediaPicker = "135D60E0-64D9-49ED-AB08-893C9BA44AE5"; + + /// + /// Guid for Media Picker + /// + public static readonly Guid MediaPickerGuid = new Guid(MediaPicker); + + + /// + /// Guid for Multiple Media Picker as string + /// + public const string MultipleMediaPicker = "9DBBCBBB-2327-434A-B355-AF1B84E5010A"; + + /// + /// Guid for Multiple Media Picker + /// + public static readonly Guid MultipleMediaPickerGuid = new Guid(MultipleMediaPicker); + + + /// + /// Guid for Related Links as string + /// + public const string RelatedLinks = "B4E3535A-1753-47E2-8568-602CF8CFEE6F"; + + /// + /// Guid for Related Links + /// + public static readonly Guid RelatedLinksGuid = new Guid(RelatedLinks); + + + /// + /// Guid for Member as string + /// + public const string Member = "d59be02f-1df9-4228-aa1e-01917d806cda"; + + /// + /// Guid for Member + /// + public static readonly Guid MemberGuid = new Guid(Member); + + + /// + /// Guid for Image Cropper as string + /// + public const string ImageCropper = "1df9f033-e6d4-451f-b8d2-e0cbc50a836f"; + + /// + /// Guid for Image Cropper + /// + public static readonly Guid ImageCropperGuid = new Guid(ImageCropper); + + + /// + /// Guid for Tags as string + /// + public const string Tags = "b6b73142-b9c1-4bf8-a16d-e1c23320b549"; + + /// + /// Guid for Tags + /// + public static readonly Guid TagsGuid = new Guid(Tags); + + + /// + /// Guid for List View - Content as string + /// + public const string ListViewContent = "C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"; + + /// + /// Guid for List View - Content + /// + public static readonly Guid ListViewContentGuid = new Guid(ListViewContent); + + + /// + /// Guid for List View - Media as string + /// + public const string ListViewMedia = "3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"; + + /// + /// Guid for List View - Media + /// + public static readonly Guid ListViewMediaGuid = new Guid(ListViewMedia); + + + /// + /// Guid for List View - Members as string + /// + public const string ListViewMembers = "AA2C52A0-CE87-4E65-A47C-7DF09358585D"; + + /// + /// Guid for List View - Members + /// + public static readonly Guid ListViewMembersGuid = new Guid(ListViewMembers); + + + /// + /// Guid for Date Picker with time as string + /// + public const string DatePickerWithTime = "e4d66c0f-b935-4200-81f0-025f7256b89a"; + + /// + /// Guid for Date Picker with time + /// + public static readonly Guid DatePickerWithTimeGuid = new Guid(DatePickerWithTime); + + + /// + /// Guid for Approved Color as string + /// + public const string ApprovedColor = "0225af17-b302-49cb-9176-b9f35cab9c17"; + + /// + /// Guid for Approved Color + /// + public static readonly Guid ApprovedColorGuid = new Guid(ApprovedColor); + + + /// + /// Guid for Dropdown multiple as string + /// + public const string DropdownMultiple = "f38f0ac7-1d27-439c-9f3f-089cd8825a53"; + + /// + /// Guid for Dropdown multiple + /// + public static readonly Guid DropdownMultipleGuid = new Guid(DropdownMultiple); + + + /// + /// Guid for Radiobox as string + /// + public const string Radiobox = "bb5f57c9-ce2b-4bb9-b697-4caca783a805"; + + /// + /// Guid for Radiobox + /// + public static readonly Guid RadioboxGuid = new Guid(Radiobox); + + + /// + /// Guid for Date Picker as string + /// + public const string DatePicker = "5046194e-4237-453c-a547-15db3a07c4e1"; + + /// + /// Guid for Date Picker + /// + public static readonly Guid DatePickerGuid = new Guid(DatePicker); + + + /// + /// Guid for Dropdown as string + /// + public const string Dropdown = "0b6a45e7-44ba-430d-9da5-4e46060b9e03"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid DropdownGuid = new Guid(Dropdown); + + + /// + /// Guid for Checkbox list as string + /// + public const string CheckboxList = "fbaf13a8-4036-41f2-93a3-974f678c312a"; + + /// + /// Guid for Checkbox list + /// + public static readonly Guid CheckboxListGuid = new Guid(CheckboxList); + + + /// + /// Guid for Checkbox as string + /// + public const string Checkbox = "92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"; + + /// + /// Guid for Checkbox + /// + public static readonly Guid CheckboxGuid = new Guid(Checkbox); + + + /// + /// Guid for Numeric as string + /// + public const string Numeric = "2e6d3631-066e-44b8-aec4-96f09099b2b5"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid NumericGuid = new Guid(Numeric); + + + /// + /// Guid for Richtext editor as string + /// + public const string RichtextEditor = "ca90c950-0aff-4e72-b976-a30b1ac57dad"; + + /// + /// Guid for Richtext editor + /// + public static readonly Guid RichtextEditorGuid = new Guid(RichtextEditor); + + + /// + /// Guid for Textstring as string + /// + public const string Textstring = "0cc0eba1-9960-42c9-bf9b-60e150b429ae"; + + /// + /// Guid for Textstring + /// + public static readonly Guid TextstringGuid = new Guid(Textstring); + + + /// + /// Guid for Textarea as string + /// + public const string Textarea = "c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid TextareaGuid = new Guid(Textarea); + + + /// + /// Guid for Upload as string + /// + public const string Upload = "84c6b441-31df-4ffe-b67e-67d5bc3ae65a"; + + /// + /// Guid for Upload + /// + public static readonly Guid UploadGuid = new Guid(Upload); + + + /// + /// Guid for Label as string + /// + public const string LabelString = "f0bc4bfb-b499-40d6-ba86-058885a5178c"; + + /// + /// Guid for Label string + /// + public static readonly Guid LabelStringGuid = new Guid(LabelString); + + /// + /// Guid for Label as int + /// + public const string LabelInt = "8e7f995c-bd81-4627-9932-c40e568ec788"; + + /// + /// Guid for Label int + /// + public static readonly Guid LabelIntGuid = new Guid(LabelInt); + + /// + /// Guid for Label as big int + /// + public const string LabelBigInt = "930861bf-e262-4ead-a704-f99453565708"; + + /// + /// Guid for Label big int + /// + public static readonly Guid LabelBigIntGuid = new Guid(LabelBigInt); + + /// + /// Guid for Label as date time + /// + public const string LabelDateTime = "0e9794eb-f9b5-4f20-a788-93acd233a7e4"; + + /// + /// Guid for Label date time + /// + public static readonly Guid LabelDateTimeGuid = new Guid(LabelDateTime); + + /// + /// Guid for Label as time + /// + public const string LabelTime = "a97cec69-9b71-4c30-8b12-ec398860d7e8"; + + /// + /// Guid for Label time + /// + public static readonly Guid LabelTimeGuid = new Guid(LabelTime); + + /// + /// Guid for Label as decimal + /// + public const string LabelDecimal = "8f1ef1e1-9de4-40d3-a072-6673f631ca64"; + + /// + /// Guid for Label decimal + /// + public static readonly Guid LabelDecimalGuid = new Guid(LabelDecimal); + + + } } } } diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs index d3e8b4ad3b..05213ed1c4 100644 --- a/src/Umbraco.Core/Constants-Icons.cs +++ b/src/Umbraco.Core/Constants-Icons.cs @@ -5,39 +5,89 @@ public static class Icons { /// - /// System contenttype icon + /// System default icon /// - public const string ContentType = "icon-arrangement"; + public const string DefaultIcon = Content; /// - /// System datatype icon + /// System content icon + /// + public const string Content = "icon-document"; + + /// + /// System content type icon + /// + public const string ContentType = "icon-item-arrangement"; + + /// + /// System data type icon /// public const string DataType = "icon-autofill"; /// - /// System property editor icon + /// System list view icon /// - public const string PropertyEditor = "icon-autofill"; + public const string ListView = "icon-thumbnail-list"; /// /// System macro icon /// public const string Macro = "icon-settings-alt"; + /// + /// System media file icon + /// + public const string MediaFile = "icon-document"; + + /// + /// System media folder icon + /// + public const string MediaFolder = "icon-folder"; + + /// + /// System media image icon + /// + public const string MediaImage = "icon-picture"; + + /// + /// System media type icon + /// + public const string MediaType = "icon-thumbnails"; + /// /// System member icon /// public const string Member = "icon-user"; /// - /// System member icon + /// System member group icon + /// + public const string MemberGroup = "icon-users-alt"; + + /// + /// System member type icon /// public const string MemberType = "icon-users"; + /// + /// System property editor icon + /// + public const string PropertyEditor = "icon-autofill"; + /// /// System member icon /// public const string Template = "icon-layout"; + + /// + /// System user icon + /// + public const string User = "icon-user"; + + /// + /// System user group icon + /// + public const string UserGroup = "icon-users"; } } } diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index f9b298e586..cc461a5775 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -14,6 +14,23 @@ namespace Umbraco.Core /// public const string InternalGenericPropertiesPrefix = "_umb_"; + public static class Legacy + { + public static class Aliases + { + public const string Textbox = "Umbraco.Textbox"; + public const string Date = "Umbraco.Date"; + public const string ContentPicker2 = "Umbraco.ContentPicker2"; + public const string MediaPicker2 = "Umbraco.MediaPicker2"; + public const string MemberPicker2 = "Umbraco.MemberPicker2"; + public const string MultiNodeTreePicker2 = "Umbraco.MultiNodeTreePicker2"; + public const string TextboxMultiple = "Umbraco.TextboxMultiple"; + public const string RelatedLinks2 = "Umbraco.RelatedLinks2"; + public const string RelatedLinks = "Umbraco.RelatedLinks"; + + } + } + /// /// Defines Umbraco built-in property editor aliases. /// @@ -191,6 +208,24 @@ namespace Umbraco.Core /// Must be a valid value. public const string DataValueType = "umbracoDataValueType"; } + + /// + /// Defines Umbraco's built-in property editor groups. + /// + public static class Groups + { + public const string Common = "Common"; + + public const string Lists = "Lists"; + + public const string Media = "Media"; + + public const string People = "People"; + + public const string Pickers = "Pickers"; + + public const string RichContent = "Rich Content"; + } } } } diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 80f18eb2e0..27d6716000 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -15,13 +15,18 @@ namespace Umbraco.Core public const int SuperUserId = -1; /// - /// The id for the 'unknown' user + /// The id for the 'unknown' user. /// /// /// This is a user row that exists in the DB only for referential integrity but the user is never returned from any of the services /// public const int UnknownUserId = 0; + /// + /// The name of the 'unknown' user. + /// + public const string UnknownUserName = "SYTEM"; + public const string AdminGroupAlias = "admin"; public const string SensitiveDataGroupAlias = "sensitiveData"; public const string TranslatorGroupAlias = "translator"; diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index d25997b5f0..9fdc5f0b90 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -66,44 +66,44 @@ namespace Umbraco.Core /// /// Determines whether the content type is invariant. /// - public static bool VariesByNothing(this PublishedContentType contentType) => contentType.Variations.VariesByNothing(); + public static bool VariesByNothing(this IPublishedContentType contentType) => contentType.Variations.VariesByNothing(); /// /// Determines whether the content type varies by culture. /// /// And then it could also vary by segment. - public static bool VariesByCulture(this PublishedContentType contentType) => contentType.Variations.VariesByCulture(); + public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture(); /// /// Determines whether the content type varies by segment. /// /// And then it could also vary by culture. - public static bool VariesBySegment(this PublishedContentType contentType) => contentType.Variations.VariesBySegment(); + public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment(); /// /// Determines whether the content type varies by culture and segment. /// - public static bool VariesByCultureAndSegment(this PublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); /// /// Determines whether the property type is invariant. /// - public static bool VariesByNothing(this PublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing(); + public static bool VariesByNothing(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing(); /// /// Determines whether the property type varies by culture. /// - public static bool VariesByCulture(this PublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); + public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); /// /// Determines whether the property type varies by segment. /// - public static bool VariesBySegment(this PublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); + public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); /// /// Determines whether the property type varies by culture and segment. /// - public static bool VariesByCultureAndSegment(this PublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); + public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); /// /// Determines whether a variation is invariant. diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index 3719bb0750..59f5937874 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -10,6 +10,8 @@ namespace Umbraco.Core /// public static class EnumerableExtensions { + internal static bool IsCollectionEmpty(this IReadOnlyCollection list) => list == null || list.Count == 0; + internal static bool HasDuplicates(this IEnumerable items, bool includeNull) { var hs = new HashSet(); @@ -112,7 +114,6 @@ namespace Umbraco.Core } } - /// /// Returns true if all items in the other collection exist in this collection /// diff --git a/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs b/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs index 4aaf6d25f2..b39a3f38df 100644 --- a/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs +++ b/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs @@ -26,26 +26,26 @@ namespace Umbraco.Core.Logging.Viewer /// A count of number of errors /// By counting Warnings with Exceptions, Errors & Fatal messages /// - int GetNumberOfErrors(DateTimeOffset startDate, DateTimeOffset endDate); + int GetNumberOfErrors(LogTimePeriod logTimePeriod); /// /// Returns a number of the different log level entries /// - LogLevelCounts GetLogLevelCounts(DateTimeOffset startDate, DateTimeOffset endDate); + LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod); /// /// Returns a list of all unique message templates and their counts /// - IEnumerable GetMessageTemplates(DateTimeOffset startDate, DateTimeOffset endDate); + IEnumerable GetMessageTemplates(LogTimePeriod logTimePeriod); bool CanHandleLargeLogs { get; } - bool CheckCanOpenLogs(DateTimeOffset startDate, DateTimeOffset endDate); + bool CheckCanOpenLogs(LogTimePeriod logTimePeriod); /// /// Returns the collection of logs /// - PagedResult GetLogs(DateTimeOffset startDate, DateTimeOffset endDate, + PagedResult GetLogs(LogTimePeriod logTimePeriod, int pageNumber = 1, int pageSize = 100, Direction orderDirection = Direction.Descending, diff --git a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs index 9e6d911489..bbe2f3704d 100644 --- a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs +++ b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Logging.Viewer public override bool CanHandleLargeLogs => false; - public override bool CheckCanOpenLogs(DateTimeOffset startDate, DateTimeOffset endDate) + public override bool CheckCanOpenLogs(LogTimePeriod logTimePeriod) { //Log Directory var logDirectory = _logsPath; @@ -36,7 +36,7 @@ namespace Umbraco.Core.Logging.Viewer //foreach full day in the range - see if we can find one or more filenames that end with //yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing - for (var day = startDate.Date; day.Date <= endDate.Date; day = day.AddDays(1)) + for (var day = logTimePeriod.StartTime.Date; day.Date <= logTimePeriod.EndTime.Date; day = day.AddDays(1)) { //Filename ending to search for (As could be multiple) var filesToFind = GetSearchPattern(day); @@ -57,7 +57,7 @@ namespace Umbraco.Core.Logging.Viewer return $"*{day:yyyyMMdd}*.json"; } - protected override IReadOnlyList GetLogs(DateTimeOffset startDate, DateTimeOffset endDate, ILogFilter filter, int skip, int take) + protected override IReadOnlyList GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take) { var logs = new List(); @@ -68,7 +68,7 @@ namespace Umbraco.Core.Logging.Viewer //foreach full day in the range - see if we can find one or more filenames that end with //yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing - for (var day = startDate.Date; day.Date <= endDate.Date; day = day.AddDays(1)) + for (var day = logTimePeriod.StartTime.Date; day.Date <= logTimePeriod.EndTime.Date; day = day.AddDays(1)) { //Filename ending to search for (As could be multiple) var filesToFind = GetSearchPattern(day); diff --git a/src/Umbraco.Core/Logging/Viewer/LogTimePeriod.cs b/src/Umbraco.Core/Logging/Viewer/LogTimePeriod.cs new file mode 100644 index 0000000000..0f41faef0a --- /dev/null +++ b/src/Umbraco.Core/Logging/Viewer/LogTimePeriod.cs @@ -0,0 +1,16 @@ +using System; + +namespace Umbraco.Core.Logging.Viewer +{ + public class LogTimePeriod + { + public LogTimePeriod(DateTime startTime, DateTime endTime) + { + StartTime = startTime; + EndTime = endTime; + } + + public DateTime StartTime { get; } + public DateTime EndTime { get; } + } +} diff --git a/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs b/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs index 3578a56e74..acb2f5dbf9 100644 --- a/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs +++ b/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs @@ -27,9 +27,9 @@ namespace Umbraco.Core.Logging.Viewer /// /// Get all logs from your chosen data source back as Serilog LogEvents /// - protected abstract IReadOnlyList GetLogs(DateTimeOffset startDate, DateTimeOffset endDate, ILogFilter filter, int skip, int take); + protected abstract IReadOnlyList GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take); - public abstract bool CheckCanOpenLogs(DateTimeOffset startDate, DateTimeOffset endDate); + public abstract bool CheckCanOpenLogs(LogTimePeriod logTimePeriod); public virtual IReadOnlyList GetSavedSearches() { @@ -82,24 +82,24 @@ namespace Umbraco.Core.Logging.Viewer return searches; } - public int GetNumberOfErrors(DateTimeOffset startDate, DateTimeOffset endDate) + public int GetNumberOfErrors(LogTimePeriod logTimePeriod) { var errorCounter = new ErrorCounterFilter(); - GetLogs(startDate, endDate, errorCounter, 0, int.MaxValue); + GetLogs(logTimePeriod, errorCounter, 0, int.MaxValue); return errorCounter.Count; } - public LogLevelCounts GetLogLevelCounts(DateTimeOffset startDate, DateTimeOffset endDate) + public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod) { var counter = new CountingFilter(); - GetLogs(startDate, endDate, counter, 0, int.MaxValue); + GetLogs(logTimePeriod, counter, 0, int.MaxValue); return counter.Counts; } - public IEnumerable GetMessageTemplates(DateTimeOffset startDate, DateTimeOffset endDate) + public IEnumerable GetMessageTemplates(LogTimePeriod logTimePeriod) { var messageTemplates = new MessageTemplateFilter(); - GetLogs(startDate, endDate, messageTemplates, 0, int.MaxValue); + GetLogs(logTimePeriod, messageTemplates, 0, int.MaxValue); var templates = messageTemplates.Counts. Select(x => new LogTemplate { MessageTemplate = x.Key, Count = x.Value }) @@ -108,14 +108,14 @@ namespace Umbraco.Core.Logging.Viewer return templates; } - public PagedResult GetLogs(DateTimeOffset startDate, DateTimeOffset endDate, + public PagedResult GetLogs(LogTimePeriod logTimePeriod, int pageNumber = 1, int pageSize = 100, Direction orderDirection = Direction.Descending, string filterExpression = null, string[] logLevels = null) { var expression = new ExpressionFilter(filterExpression); - var filteredLogs = GetLogs(startDate, endDate, expression, 0, int.MaxValue); + var filteredLogs = GetLogs(logTimePeriod, expression, 0, int.MaxValue); //This is user used the checkbox UI to toggle which log levels they wish to see //If an empty array or null - its implied all levels to be viewed diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index ca875c2167..d1012fb669 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Web.Hosting; @@ -113,7 +114,7 @@ namespace Umbraco.Core lock (_locko) { - _logger.Debug("Signaled {Signaled} ({SignalSource})", _signaled ? "(again)" : string.Empty, source); + _logger.Debug("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source); if (_signaled) return; if (_isMainDom == false) return; // probably not needed _signaled = true; @@ -171,6 +172,7 @@ namespace Umbraco.Core // if more than 1 instance reach that point, one will get the lock // and the other one will timeout, which is accepted + //TODO: This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset? _asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds); _isMainDom = true; @@ -181,6 +183,9 @@ namespace Umbraco.Core // which is accepted _signal.Reset(); + + //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread + _signal.WaitOneAsync() .ContinueWith(_ => OnSignal("signal")); diff --git a/src/Umbraco.Core/Manifest/IManifestFilter.cs b/src/Umbraco.Core/Manifest/IManifestFilter.cs new file mode 100644 index 0000000000..505f13d385 --- /dev/null +++ b/src/Umbraco.Core/Manifest/IManifestFilter.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Manifest +{ + /// + /// Provides filtering for package manifests. + /// + public interface IManifestFilter + { + /// + /// Filters package manifests. + /// + /// The package manifests. + /// + /// It is possible to remove, change, or add manifests. + /// + void Filter(List manifests); + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs b/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs new file mode 100644 index 0000000000..febdb7e356 --- /dev/null +++ b/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Manifest +{ + /// + /// Contains the manifest filters. + /// + public class ManifestFilterCollection : BuilderCollectionBase + { + /// + /// Initializes a new instance of the class. + /// + public ManifestFilterCollection(IEnumerable items) + : base(items) + { } + + /// + /// Filters package manifests. + /// + /// The package manifests. + public void Filter(List manifests) + { + foreach (var filter in this) + filter.Filter(manifests); + } + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs b/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs new file mode 100644 index 0000000000..4d98700f93 --- /dev/null +++ b/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs @@ -0,0 +1,12 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Manifest +{ + public class ManifestFilterCollectionBuilder : OrderedCollectionBuilderBase + { + protected override ManifestFilterCollectionBuilder This => this; + + // do NOT cache this, it's only used once + protected override Lifetime CollectionLifetime => Lifetime.Transient; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index dc40cd90a2..efd9e92b1f 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -22,24 +22,26 @@ namespace Umbraco.Core.Manifest private readonly IAppPolicyCache _cache; private readonly ILogger _logger; private readonly ManifestValueValidatorCollection _validators; + private readonly ManifestFilterCollection _filters; private string _path; /// /// Initializes a new instance of the class. /// - public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ILogger logger) - : this(appCaches, validators, "~/App_Plugins", logger) + public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, ILogger logger) + : this(appCaches, validators, filters, "~/App_Plugins", logger) { } /// /// Initializes a new instance of the class. /// - private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, string path, ILogger logger) + private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, string path, ILogger logger) { if (appCaches == null) throw new ArgumentNullException(nameof(appCaches)); _cache = appCaches.RuntimeCache; _validators = validators ?? throw new ArgumentNullException(nameof(validators)); + _filters = filters ?? throw new ArgumentNullException(nameof(filters)); if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); Path = path; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -78,6 +80,7 @@ namespace Umbraco.Core.Manifest if (string.IsNullOrWhiteSpace(text)) continue; var manifest = ParseManifest(text); + manifest.Source = path; manifests.Add(manifest); } catch (Exception e) @@ -86,6 +89,8 @@ namespace Umbraco.Core.Manifest } } + _filters.Filter(manifests); + return manifests; } diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 475ee8a7f8..e50eb69467 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -9,6 +9,16 @@ namespace Umbraco.Core.Manifest /// public class PackageManifest { + /// + /// Gets the source path of the manifest. + /// + /// + /// Gets the full absolute file path of the manifest, + /// using system directory separators. + /// + [JsonIgnore] + public string Source { get; set; } + /// /// Gets or sets the scripts listed in the manifest. /// diff --git a/src/Umbraco.Core/Mapping/UmbracoMapper.cs b/src/Umbraco.Core/Mapping/UmbracoMapper.cs index 2d495b38b5..8915ebcf74 100644 --- a/src/Umbraco.Core/Mapping/UmbracoMapper.cs +++ b/src/Umbraco.Core/Mapping/UmbracoMapper.cs @@ -191,7 +191,7 @@ namespace Umbraco.Core.Mapping private TTarget Map(object source, Type sourceType, MapperContext context) { if (source == null) - throw new ArgumentNullException(nameof(source)); + return default; var targetType = typeof(TTarget); diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs index b11ef7e2c8..6bf450a9b8 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs @@ -25,11 +25,25 @@ namespace Umbraco.Core.Migrations.Expressions.Create.KeysAndIndexes var syntax = _context.SqlContext.SqlSyntax; var tableDefinition = DefinitionFactory.GetTableDefinition(TypeOfDto, syntax); + // note: of course we are creating the keys and indexes as per the DTO, so + // changing the DTO may break old migrations - or, better, these migrations + // should capture a copy of the DTO class that will not change + ExecuteSql(syntax.FormatPrimaryKey(tableDefinition)); foreach (var sql in syntax.Format(tableDefinition.Indexes)) ExecuteSql(sql); foreach (var sql in syntax.Format(tableDefinition.ForeignKeys)) ExecuteSql(sql); + + // note: we do *not* create the DF_ default constraints + /* + foreach (var column in tableDefinition.Columns) + { + var sql = syntax.FormatDefaultConstraint(column); + if (!sql.IsNullOrWhiteSpace()) + ExecuteSql(sql); + } + */ } private void ExecuteSql(string sql) diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs index 373b375fa8..92bc11b04d 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs @@ -10,9 +10,13 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.DefaultConstraint IDeleteDefaultConstraintOnTableBuilder, IDeleteDefaultConstraintOnColumnBuilder { - public DeleteDefaultConstraintBuilder(DeleteDefaultConstraintExpression expression) + private readonly IMigrationContext _context; + + public DeleteDefaultConstraintBuilder(IMigrationContext context, DeleteDefaultConstraintExpression expression) : base(expression) - { } + { + _context = context; + } /// public IDeleteDefaultConstraintOnColumnBuilder OnTable(string tableName) @@ -25,6 +29,9 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.DefaultConstraint public IExecutableBuilder OnColumn(string columnName) { Expression.ColumnName = columnName; + Expression.HasDefaultConstraint = _context.SqlContext.SqlSyntax.TryGetDefaultConstraint(_context.Database, Expression.TableName, columnName, out var constraintName); + Expression.ConstraintName = constraintName ?? string.Empty; + return new ExecutableBuilder(Expression); } } diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs index d081305cde..9a4f437f62 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs @@ -1,4 +1,4 @@ -using NPoco; +using Umbraco.Core.Exceptions; using Umbraco.Core.Migrations.Expressions.Common; using Umbraco.Core.Migrations.Expressions.Delete.Column; using Umbraco.Core.Migrations.Expressions.Delete.Constraint; @@ -29,9 +29,19 @@ namespace Umbraco.Core.Migrations.Expressions.Delete } /// - public IExecutableBuilder KeysAndIndexes(string tableName = null) + public IExecutableBuilder KeysAndIndexes(bool local = true, bool foreign = true) { - return new DeleteKeysAndIndexesBuilder(_context) { TableName = tableName }; + var syntax = _context.SqlContext.SqlSyntax; + var tableDefinition = DefinitionFactory.GetTableDefinition(typeof(TDto), syntax); + return KeysAndIndexes(tableDefinition.Name, local, foreign); + } + + /// + public IExecutableBuilder KeysAndIndexes(string tableName, bool local = true, bool foreign = true) + { + if (tableName.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(tableName)); + return new DeleteKeysAndIndexesBuilder(_context) { TableName = tableName, DeleteLocal = local, DeleteForeign = foreign }; } /// @@ -100,7 +110,7 @@ namespace Umbraco.Core.Migrations.Expressions.Delete public IDeleteDefaultConstraintOnTableBuilder DefaultConstraint() { var expression = new DeleteDefaultConstraintExpression(_context); - return new DeleteDefaultConstraintBuilder(expression); + return new DeleteDefaultConstraintBuilder(_context, expression); } } } diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs index 8b0b20c0e2..b73d3f0d13 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs @@ -10,12 +10,17 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.Expressions public virtual string TableName { get; set; } public virtual string ColumnName { get; set; } + public virtual string ConstraintName { get; set; } + public virtual bool HasDefaultConstraint { get; set; } protected override string GetSql() { - return string.Format(SqlSyntax.DeleteDefaultConstraint, - SqlSyntax.GetQuotedTableName(TableName), - SqlSyntax.GetQuotedColumnName(ColumnName)); + return HasDefaultConstraint + ? string.Format(SqlSyntax.DeleteDefaultConstraint, + SqlSyntax.GetQuotedTableName(TableName), + SqlSyntax.GetQuotedColumnName(ColumnName), + SqlSyntax.GetQuotedName(ConstraintName)) + : string.Empty; } } } diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs index 07faf5028e..84e44d0d93 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs @@ -19,9 +19,14 @@ namespace Umbraco.Core.Migrations.Expressions.Delete IExecutableBuilder Table(string tableName); /// - /// Specifies the table to delete keys and indexes for. + /// Builds a Delete Keys and Indexes expression, and executes. /// - IExecutableBuilder KeysAndIndexes(string tableName = null); + IExecutableBuilder KeysAndIndexes(bool local = true, bool foreign = true); + + /// + /// Builds a Delete Keys and Indexes expression, and executes. + /// + IExecutableBuilder KeysAndIndexes(string tableName, bool local = true, bool foreign = true); /// /// Specifies the column to delete. diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs index 1c595d9103..9b13457b76 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs @@ -18,34 +18,38 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.KeysAndIndexes public string TableName { get; set; } + public bool DeleteLocal { get; set; } + + public bool DeleteForeign { get; set; } + /// public void Do() { - if (TableName == null) - { - // drop keys - var keys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToArray(); - foreach (var key in keys.Where(x => x.Item2.StartsWith("FK_"))) - Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do(); - foreach (var key in keys.Where(x => x.Item2.StartsWith("PK_"))) - Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do(); + _context.BuildingExpression = false; - // drop indexes - var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToArray(); - foreach (var index in indexes) - Delete.Index(index.IndexName).OnTable(index.TableName).Do(); + // drop keys + if (DeleteLocal || DeleteForeign) + { + // table, constraint + var tableKeys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList(); + if (DeleteForeign) + { + foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_"))) + Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do(); + } + if (DeleteLocal) + { + foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_"))) + Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do(); + + // note: we do *not* delete the DEFAULT constraints + } } - else - { - // drop keys - var keys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToArray(); - foreach (var key in keys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_"))) - Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do(); - foreach (var key in keys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_"))) - Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do(); - // drop indexes - var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToArray(); + // drop indexes + if (DeleteLocal) + { + var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList(); foreach (var index in indexes.Where(x => x.TableName == TableName)) Delete.Index(index.IndexName).OnTable(index.TableName).Do(); } diff --git a/src/Umbraco.Core/Migrations/IMigrationContext.cs b/src/Umbraco.Core/Migrations/IMigrationContext.cs index 2baadc79eb..c76de8dfb3 100644 --- a/src/Umbraco.Core/Migrations/IMigrationContext.cs +++ b/src/Umbraco.Core/Migrations/IMigrationContext.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Migrations bool BuildingExpression { get; set; } /// - /// Adds a post-migrations. + /// Adds a post-migration. /// void AddPostMigration() where TMigration : IMigration; diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index 5c4defab0c..d86c682bd5 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -389,7 +389,7 @@ namespace Umbraco.Core.Migrations.Install private DatabaseSchemaResult ValidateSchema(IScope scope) { - if (_databaseFactory.Configured == false) + if (_databaseFactory.Initialized == false) return new DatabaseSchemaResult(_databaseFactory.SqlContext.SqlSyntax); if (_databaseSchemaValidationResult != null) @@ -513,7 +513,7 @@ namespace Umbraco.Core.Migrations.Install private Attempt CheckReadyForInstall() { - if (_databaseFactory.Configured == false) + if (_databaseFactory.CanConnect == false) { return Attempt.Fail(new Result { @@ -539,7 +539,7 @@ namespace Umbraco.Core.Migrations.Install { Message = "The database configuration failed with the following message: " + ex.Message + - "\n Please check log file for additional information (can be found in '/App_Data/Logs/UmbracoTraceLog.txt')", + "\n Please check log file for additional information (can be found in '/App_Data/Logs/')", Success = false, Percentage = "90" }; diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 1de983636b..dd5c17713a 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -101,33 +101,33 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = Constants.ObjectTypes.SystemRoot, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = Constants.ObjectTypes.ContentRecycleBin, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = Constants.ObjectTypes.MediaRecycleBin, CreateDate = DateTime.Now }); - InsertDataTypeNodeDto(Constants.DataTypes.LabelString, 35, "f0bc4bfb-b499-40d6-ba86-058885a5178c", "Label"); - InsertDataTypeNodeDto(Constants.DataTypes.LabelInt, 36, "8e7f995c-bd81-4627-9932-c40e568ec788", "Label (integer)"); - InsertDataTypeNodeDto(Constants.DataTypes.LabelBigint, 36, "930861bf-e262-4ead-a704-f99453565708", "Label (bigint)"); - InsertDataTypeNodeDto(Constants.DataTypes.LabelDateTime, 37, "0e9794eb-f9b5-4f20-a788-93acd233a7e4", "Label (datetime)"); - InsertDataTypeNodeDto(Constants.DataTypes.LabelTime, 38, "a97cec69-9b71-4c30-8b12-ec398860d7e8", "Label (time)"); - InsertDataTypeNodeDto(Constants.DataTypes.LabelDecimal, 39, "8f1ef1e1-9de4-40d3-a072-6673f631ca64", "Label (decimal)"); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownSingle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownSingle}", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultContentListView}", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + InsertDataTypeNodeDto(Constants.DataTypes.LabelString, 35, Constants.DataTypes.Guids.LabelString, "Label (string)"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelInt, 36, Constants.DataTypes.Guids.LabelInt, "Label (integer)"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelBigint, 36, Constants.DataTypes.Guids.LabelBigInt, "Label (bigint)"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelDateTime, 37, Constants.DataTypes.Guids.LabelDateTime, "Label (datetime)"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelTime, 38, Constants.DataTypes.Guids.LabelTime, "Label (time)"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelDecimal, 39, Constants.DataTypes.Guids.LabelDecimal, "Label (decimal)"); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Upload, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Upload}", SortOrder = 34, UniqueId = Constants.DataTypes.Guids.UploadGuid, Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Textarea, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Textarea}", SortOrder = 33, UniqueId = Constants.DataTypes.Guids.TextareaGuid, Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Textbox, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Textbox}", SortOrder = 32, UniqueId = Constants.DataTypes.Guids.TextstringGuid, Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.RichtextEditor, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.RichtextEditor}", SortOrder = 4, UniqueId = Constants.DataTypes.Guids.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.NumericGuid, Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Boolean, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Boolean}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.CheckboxGuid, Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.CheckboxListGuid, Text = "Checkbox list", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownSingle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownSingle}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.DropdownGuid, Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.DatePickerGuid, Text = "Date Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.RadioboxGuid, Text = "Radiobox", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.DropdownMultipleGuid, Text = "Dropdown multiple", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.ApprovedColorGuid, Text = "Approved Color", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DateTime, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DateTime}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.DatePickerWithTimeGuid, Text = "Date Picker with time", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultContentListView}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.ListViewContentGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.ListViewMediaGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Tags, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Tags}", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.ImageCropper, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.ImageCropper}", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = Constants.ObjectTypes.MemberType, CreateDate = DateTime.Now }); //New UDI pickers with newer Ids @@ -155,10 +155,10 @@ namespace Umbraco.Core.Migrations.Install private void CreateContentTypeData() { - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user", Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = Constants.Icons.MediaFolder, Thumbnail = Constants.Icons.MediaFolder, IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = Constants.Icons.MediaImage, Thumbnail = Constants.Icons.MediaImage, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = Constants.Icons.MediaFile, Thumbnail = Constants.Icons.MediaFile, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = Constants.Icons.Member, Thumbnail = Constants.Icons.Member, Variations = (byte) ContentVariation.Nothing }); } private void CreateUserData() @@ -210,19 +210,19 @@ namespace Umbraco.Core.Migrations.Install private void CreatePropertyTypeData() { - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = Constants.DataTypes.ImageCropper, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = Constants.DataTypes.Upload, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); //membership property types - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = Constants.DataTypes.Textarea, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); @@ -266,30 +266,30 @@ namespace Umbraco.Core.Migrations.Install const string layouts = "[" + cardLayout + "," + listLayout + "]"; // TODO: Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -49, EditorAlias = Constants.PropertyEditors.Aliases.Boolean, DbType = "Integer" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Boolean, EditorAlias = Constants.PropertyEditors.Aliases.Boolean, DbType = "Integer" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -51, EditorAlias = Constants.PropertyEditors.Aliases.Integer, DbType = "Integer" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -87, EditorAlias = Constants.PropertyEditors.Aliases.TinyMce, DbType = "Ntext", Configuration = "{\"value\":\",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|\"}" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -88, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -89, EditorAlias = Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -90, EditorAlias = Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Textbox, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Textarea, EditorAlias = Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Upload, EditorAlias = Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" }); InsertDataTypeDto(Constants.DataTypes.LabelString, Constants.PropertyEditors.Aliases.Label, "Nvarchar", "{\"umbracoDataValueType\":\"STRING\"}"); InsertDataTypeDto(Constants.DataTypes.LabelInt, Constants.PropertyEditors.Aliases.Label, "Integer", "{\"umbracoDataValueType\":\"INT\"}"); InsertDataTypeDto(Constants.DataTypes.LabelBigint, Constants.PropertyEditors.Aliases.Label, "Nvarchar", "{\"umbracoDataValueType\":\"BIGINT\"}"); InsertDataTypeDto(Constants.DataTypes.LabelDateTime, Constants.PropertyEditors.Aliases.Label, "Date", "{\"umbracoDataValueType\":\"DATETIME\"}"); InsertDataTypeDto(Constants.DataTypes.LabelDecimal, Constants.PropertyEditors.Aliases.Label, "Decimal", "{\"umbracoDataValueType\":\"DECIMAL\"}"); InsertDataTypeDto(Constants.DataTypes.LabelTime, Constants.PropertyEditors.Aliases.Label, "Date", "{\"umbracoDataValueType\":\"TIME\"}"); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -36, EditorAlias = Constants.PropertyEditors.Aliases.DateTime, DbType = "Date" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DateTime, EditorAlias = Constants.PropertyEditors.Aliases.DateTime, DbType = "Date" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -37, EditorAlias = Constants.PropertyEditors.Aliases.ColorPicker, DbType = "Nvarchar" }); InsertDataTypeDto(Constants.DataTypes.DropDownSingle, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":false}"); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -40, EditorAlias = Constants.PropertyEditors.Aliases.RadioButtonList, DbType = "Nvarchar" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = "Umbraco.DateTime", DbType = "Date", Configuration = "{\"format\":\"YYYY-MM-DD\"}" }); InsertDataTypeDto(Constants.DataTypes.DropDownMultiple, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":true}"); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -43, EditorAlias = Constants.PropertyEditors.Aliases.CheckBoxList, DbType = "Nvarchar" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1041, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext", + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Tags, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext", Configuration = "{\"group\":\"default\", \"storageType\":\"Json\"}" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1043, EditorAlias = Constants.PropertyEditors.Aliases.ImageCropper, DbType = "Ntext" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.ImageCropper, EditorAlias = Constants.PropertyEditors.Aliases.ImageCropper, DbType = "Ntext" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultContentListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMediaListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", diff --git a/src/Umbraco.Core/Migrations/MigrationContext.cs b/src/Umbraco.Core/Migrations/MigrationContext.cs index a8d052036c..0bb2bc11ae 100644 --- a/src/Umbraco.Core/Migrations/MigrationContext.cs +++ b/src/Umbraco.Core/Migrations/MigrationContext.cs @@ -41,6 +41,7 @@ namespace Umbraco.Core.Migrations public void AddPostMigration() where TMigration : IMigration { + // just adding - will be de-duplicated when executing PostMigrations.Add(typeof(TMigration)); } } diff --git a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs index 8b5d9cc78c..be06a32ec7 100644 --- a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs @@ -92,6 +92,7 @@ namespace Umbraco.Core.Migrations if (_executed) throw new InvalidOperationException("This expression has already been executed."); _executed = true; + Context.BuildingExpression = false; if (sql == null) { diff --git a/src/Umbraco.Core/Migrations/MigrationPlan.cs b/src/Umbraco.Core/Migrations/MigrationPlan.cs index 4be4ae7d30..37d1a03a5a 100644 --- a/src/Umbraco.Core/Migrations/MigrationPlan.cs +++ b/src/Umbraco.Core/Migrations/MigrationPlan.cs @@ -240,7 +240,8 @@ namespace Umbraco.Core.Migrations if (finalState == null) finalState = kvp.Key; else - throw new Exception("Multiple final states have been detected."); + throw new InvalidOperationException($"Multiple final states have been detected in the plan (\"{finalState}\", \"{kvp.Key}\")." + + " Make sure the plan contains only one final state."); } // now check for loops @@ -254,7 +255,8 @@ namespace Umbraco.Core.Migrations while (nextTransition != null && !verified.Contains(nextTransition.SourceState)) { if (visited.Contains(nextTransition.SourceState)) - throw new Exception("A loop has been detected."); + throw new InvalidOperationException($"A loop has been detected in the plan around state \"{nextTransition.SourceState}\"." + + " Make sure the plan does not contain circular transition paths."); visited.Add(nextTransition.SourceState); nextTransition = _transitions[nextTransition.TargetState]; } @@ -264,6 +266,14 @@ namespace Umbraco.Core.Migrations _finalState = finalState; } + /// + /// Throws an exception when the initial state is unknown. + /// + protected virtual void ThrowOnUnknownInitialState(string state) + { + throw new InvalidOperationException($"The migration plan does not support migrating from state \"{state}\"."); + } + /// /// Executes the plan. /// @@ -287,13 +297,15 @@ namespace Umbraco.Core.Migrations logger.Info("At {OrigState}", string.IsNullOrWhiteSpace(origState) ? "origin": origState); if (!_transitions.TryGetValue(origState, out var transition)) - throw new Exception($"Unknown state \"{origState}\"."); + ThrowOnUnknownInitialState(origState); var context = new MigrationContext(scope.Database, logger); context.PostMigrations.AddRange(_postMigrationTypes); while (transition != null) { + logger.Info("Execute {MigrationType}", transition.MigrationType.Name); + var migration = migrationBuilder.Build(transition.MigrationType, context); migration.Migrate(); @@ -302,6 +314,8 @@ namespace Umbraco.Core.Migrations logger.Info("At {OrigState}", origState); + // throw a raw exception here: this should never happen as the plan has + // been validated - this is just a paranoid safety test if (!_transitions.TryGetValue(origState, out transition)) throw new Exception($"Unknown state \"{origState}\"."); } @@ -322,7 +336,8 @@ namespace Umbraco.Core.Migrations logger.Info("Done (pending scope completion)."); - // safety check + // safety check - again, this should never happen as the plan has been validated, + // and this is just a paranoid safety test if (origState != _finalState) throw new Exception($"Internal error, reached state {origState} which is not final state {_finalState}"); diff --git a/src/Umbraco.Core/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs b/src/Umbraco.Core/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs new file mode 100644 index 0000000000..29006c8811 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs @@ -0,0 +1,21 @@ +using Umbraco.Core.Migrations.Install; + +namespace Umbraco.Core.Migrations.Upgrade.Common +{ + public class CreateKeysAndIndexes : MigrationBase + { + public CreateKeysAndIndexes(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // remove those that may already have keys + Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.KeyValue).Do(); + + // re-create *all* keys and indexes + foreach (var x in DatabaseSchemaCreator.OrderedTables) + Create.KeysAndIndexes(x).Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/Common/DeleteKeysAndIndexes.cs b/src/Umbraco.Core/Migrations/Upgrade/Common/DeleteKeysAndIndexes.cs new file mode 100644 index 0000000000..9e4af17c09 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/Common/DeleteKeysAndIndexes.cs @@ -0,0 +1,75 @@ +namespace Umbraco.Core.Migrations.Upgrade.Common +{ + public class DeleteKeysAndIndexes : MigrationBase + { + public DeleteKeysAndIndexes(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // all v7.14 tables + var tables = new[] + { + "cmsContent", + "cmsContentType", + "cmsContentType2ContentType", + "cmsContentTypeAllowedContentType", + "cmsContentVersion", + "cmsContentXml", + "cmsDataType", + "cmsDataTypePreValues", + "cmsDictionary", + "cmsDocument", + "cmsDocumentType", + "cmsLanguageText", + "cmsMacro", + "cmsMacroProperty", + "cmsMedia", + "cmsMember", + "cmsMember2MemberGroup", + "cmsMemberType", + "cmsPreviewXml", + "cmsPropertyData", + "cmsPropertyType", + "cmsPropertyTypeGroup", + "cmsTagRelationship", + "cmsTags", + "cmsTask", + "cmsTaskType", + "cmsTemplate", + "umbracoAccess", + "umbracoAccessRule", + "umbracoAudit", + "umbracoCacheInstruction", + "umbracoConsent", + "umbracoDomains", + "umbracoExternalLogin", + "umbracoLanguage", + "umbracoLock", + "umbracoLog", + "umbracoMigration", + "umbracoNode", + "umbracoRedirectUrl", + "umbracoRelation", + "umbracoRelationType", + "umbracoServer", + "umbracoUser", + "umbracoUser2NodeNotify", + "umbracoUser2UserGroup", + "umbracoUserGroup", + "umbracoUserGroup2App", + "umbracoUserGroup2NodePermission", + "umbracoUserLogin", + "umbracoUserStartNode", + }; + + // delete *all* keys and indexes - because of FKs + // on known v7 tables only + foreach (var table in tables) + Delete.KeysAndIndexes(table, false, true).Do(); + foreach (var table in tables) + Delete.KeysAndIndexes(table, true, false).Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index cf06a16d31..e8fd3414ec 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -2,8 +2,7 @@ using System.Configuration; using Semver; using Umbraco.Core.Configuration; -using Umbraco.Core.Migrations.Upgrade.V_7_12_0; -using Umbraco.Core.Migrations.Upgrade.V_7_14_0; +using Umbraco.Core.Migrations.Upgrade.Common; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_1; using Umbraco.Core.Migrations.Upgrade.V_8_1_0; @@ -15,6 +14,9 @@ namespace Umbraco.Core.Migrations.Upgrade /// public class UmbracoPlan : MigrationPlan { + private const string InitPrefix = "{init-"; + private const string InitSuffix = "}"; + /// /// Initializes a new instance of the class. /// @@ -24,6 +26,27 @@ namespace Umbraco.Core.Migrations.Upgrade DefinePlan(); } + /// + /// Gets the initial state corresponding to a version. + /// + private static string GetInitState(SemVersion version) + => InitPrefix + version + InitSuffix; + + /// + /// Tries to extract a version from an initial state. + /// + private static bool TryGetInitStateVersion(string state, out string version) + { + if (state.StartsWith(InitPrefix) && state.EndsWith(InitSuffix)) + { + version = state.TrimStart(InitPrefix).TrimEnd(InitSuffix); + return true; + } + + version = null; + return false; + } + /// /// /// The default initial state in plans is string.Empty. @@ -41,23 +64,37 @@ namespace Umbraco.Core.Migrations.Upgrade if (!SemVersion.TryParse(ConfigurationManager.AppSettings[Constants.AppSettings.ConfigurationStatus], out var currentVersion)) throw new InvalidOperationException($"Could not get current version from web.config {Constants.AppSettings.ConfigurationStatus} appSetting."); - // we currently support upgrading from 7.10.0 and later - if (currentVersion < new SemVersion(7, 10)) - throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {UmbracoVersion.SemanticVersion}."); - // cannot go back in time if (currentVersion > UmbracoVersion.SemanticVersion) throw new InvalidOperationException($"Version {currentVersion} cannot be downgraded to {UmbracoVersion.SemanticVersion}."); - // upgrading from version 7 => initial state is eg "{init-7.10.0}" - // anything else is not supported - ie if 8 and above, we should have an initial state already - if (currentVersion.Major != 7) - throw new InvalidOperationException($"Version {currentVersion} is not supported by the migration plan."); + // only from 7.14.0 and above + var minVersion = new SemVersion(7, 14); + if (currentVersion < minVersion) + throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {UmbracoVersion.SemanticVersion}." + + $" Please upgrade first to at least {minVersion}."); - return "{init-" + currentVersion + "}"; + // Force versions between 7.14.*-7.15.* into into 7.14 initial state. Because there is no db-changes, + // and we don't want users to workaround my putting in version 7.14.0 them self. + if (minVersion <= currentVersion && currentVersion < new SemVersion(7, 16)) + return GetInitState(minVersion); + + // initial state is eg "{init-7.14.0}" + return GetInitState(currentVersion); } } + protected override void ThrowOnUnknownInitialState(string state) + { + if (TryGetInitStateVersion(state, out var initVersion)) + { + throw new InvalidOperationException($"Version {UmbracoVersion.SemanticVersion} does not support migrating from {initVersion}." + + $" Please verify which versions support migrating from {initVersion}."); + } + + base.ThrowOnUnknownInitialState(state); + } + // define the plan protected void DefinePlan() { @@ -70,21 +107,23 @@ namespace Umbraco.Core.Migrations.Upgrade // // If the new migration causes a merge conflict, because someone else also added another // new migration, you NEED to fix the conflict by providing one default path, and paths - // out of the conflict states (see example below). + // out of the conflict states (see examples below). // // * Porting from version 7: // Append the ported migration to the main chain, using a new guid (same as above). - // Create a new special chain from the {init-...} state to the main chain (see example - // below). + // Create a new special chain from the {init-...} state to the main chain. - // plan starts at 7.10.0 (anything before 7.10.0 is not supported) - // upgrades from 7 to 8, and then takes care of all eventual upgrades + // plan starts at 7.14.0 (anything before 7.14.0 is not supported) // - From("{init-7.10.0}"); + From(GetInitState(new SemVersion(7, 14, 0))); + + // begin migrating from v7 - remove all keys and indexes + To("{B36B9ABD-374E-465B-9C5F-26AB0D39326F}"); + To("{7C447271-CA3F-4A6A-A913-5D77015655CB}"); To("{CBFF58A2-7B50-4F75-8E98-249920DB0F37}"); - To("{3D18920C-E84D-405C-A06A-B7CEE52FE5DD}"); + To("{5CB66059-45F4-48BA-BCBD-C5035D79206B}"); To("{FB0A5429-587E-4BD0-8A67-20F0E7E62FF7}"); To("{F0C42457-6A3B-4912-A7EA-F27ED85A2092}"); To("{8640C9E4-A1C0-4C59-99BB-609B4E604981}"); @@ -95,91 +134,55 @@ namespace Umbraco.Core.Migrations.Upgrade ToWithReplace("{941B2ABA-2D06-4E04-81F5-74224F1DB037}", "{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}"); // kill AddVariationTable1 To("{A7540C58-171D-462A-91C5-7A9AA5CB8BFD}"); - Merge().To("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}") // shannon added that one - .With().To("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}") // stephan added that one + Merge() + .To("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}") + .With() + .To("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}") .As("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); To("{1350617A-4930-4D61-852F-E3AA9E692173}"); - To("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0 - - Merge() - .To("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}") // from 7.12.0 - .To("{EB34B5DC-BB87-4005-985E-D983EA496C38}") // from 7.12.0 - .To("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}") // from 7.12.0 - .To("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}") // from 7.12.0 - .With() - .To("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}") // andy added that one - .As("{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); - - ToWithReplace("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}", "{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // merge - ToWithReplace("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}", "{290C18EE-B3DE-4769-84F1-1F467F3F76DA}"); // merge + To("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}"); + To("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); + To("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}"); To("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}"); - To("{77874C77-93E5-4488-A404-A630907CEEF0}"); To("{8804D8E8-FE62-4E3A-B8A2-C047C2118C38}"); To("{23275462-446E-44C7-8C2C-3B8C1127B07D}"); To("{6B251841-3069-4AD5-8AE9-861F9523E8DA}"); To("{EE429F1B-9B26-43CA-89F8-A86017C809A3}"); To("{08919C4B-B431-449C-90EC-2B8445B5C6B1}"); To("{7EB0254C-CB8B-4C75-B15B-D48C55B449EB}"); - To("{648A2D5F-7467-48F8-B309-E99CEEE00E2A}"); // fixed version To("{C39BF2A7-1454-4047-BBFE-89E40F66ED63}"); To("{64EBCE53-E1F0-463A-B40B-E98EFCCA8AE2}"); To("{0009109C-A0B8-4F3F-8FEB-C137BBDDA268}"); - To("{8A027815-D5CD-4872-8B88-9A51AB5986A6}"); // from 7.14.0 To("{ED28B66A-E248-4D94-8CDB-9BDF574023F0}"); To("{38C809D5-6C34-426B-9BEA-EFD39162595C}"); To("{6017F044-8E70-4E10-B2A3-336949692ADD}"); - To("{98339BEF-E4B2-48A8-B9D1-D173DC842BBE}"); Merge() .To("{CDBEDEE4-9496-4903-9CF2-4104E00FF960}") .With() - .To("{940FD19A-00A8-4D5C-B8FF-939143585726}") + .To("{940FD19A-00A8-4D5C-B8FF-939143585726}") .As("{0576E786-5C30-4000-B969-302B61E90CA3}"); + To("{48AD6CCD-C7A4-4305-A8AB-38728AD23FC5}"); + + // finish migrating from v7 - recreate all keys and indexes + To("{3F9764F5-73D0-4D45-8804-1240A66E43A2}"); + To("{E0CBE54D-A84F-4A8F-9B13-900945FD7ED9}"); To("{78BAF571-90D0-4D28-8175-EF96316DA789}"); + // release-8.0.0 + + // to 8.0.1... To("{80C0A0CB-0DD5-4573-B000-C4B7C313C70D}"); + // release-8.0.1 + + // to 8.1.0... To("{B69B6E8C-A769-4044-A27E-4A4E18D1645A}"); + To("{0372A42B-DECF-498D-B4D1-6379E907EB94}"); + To("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}"); //FINAL - - - - - // and then, need to support upgrading from more recent 7.x - // - From("{init-7.10.1}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.10.2}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.10.3}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.10.4}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.10.5}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.11.0}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.11.1}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.11.2}").To("{init-7.10.0}"); // same as 7.10.0 - - // 7.12.0 has migrations, define a custom chain which copies the chain - // going from {init-7.10.0} to former final (1350617A) , and then goes straight to - // main chain, skipping the migrations - // - From("{init-7.12.0}"); - // clone start / clone stop / target - ToWithClone("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); - - From("{init-7.12.1}").To("{init-7.10.0}"); // same as 7.12.0 - From("{init-7.12.2}").To("{init-7.10.0}"); // same as 7.12.0 - From("{init-7.12.3}").To("{init-7.10.0}"); // same as 7.12.0 - From("{init-7.12.4}").To("{init-7.10.0}"); // same as 7.12.0 - From("{init-7.13.0}").To("{init-7.10.0}"); // same as 7.12.0 - From("{init-7.13.1}").To("{init-7.10.0}"); // same as 7.12.0 - - // 7.14.0 has migrations, handle it... - // clone going from 7.10 to 1350617A (the last one before we started to merge 7.12 migrations), then - // clone going from CF51B39B (after 7.12 migrations) to 0009109C (the last one before we started to merge 7.12 migrations), - // ending in 8A027815 (after 7.14 migrations) - From("{init-7.14.0}") - .ToWithClone("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{9109B8AF-6B34-46EE-9484-7434196D0C79}") - .ToWithClone("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}", "{0009109C-A0B8-4F3F-8FEB-C137BBDDA268}", "{8A027815-D5CD-4872-8B88-9A51AB5986A6}"); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_10_0/RenamePreviewFolder.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_10_0/RenamePreviewFolder.cs deleted file mode 100644 index 48e6d0085d..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_10_0/RenamePreviewFolder.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.IO; -using Umbraco.Core.IO; -using File = System.IO.File; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_10_0 -{ - /// - /// 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. - /// - public class RenamePreviewFolder : MigrationBase - { - public RenamePreviewFolder(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var previewFolderPath = IOHelper.MapPath(SystemDirectories.Umbraco + "/preview"); - if (Directory.Exists(previewFolderPath)) - { - var newPath = previewFolderPath.Replace("preview", "preview.old"); - 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); - } - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/AddRelationTypeForMediaFolderOnDelete.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/AddRelationTypeForMediaFolderOnDelete.cs deleted file mode 100644 index f34ed9fb68..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/AddRelationTypeForMediaFolderOnDelete.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 -{ - public class AddRelationTypeForMediaFolderOnDelete : MigrationBase - { - - public AddRelationTypeForMediaFolderOnDelete(IMigrationContext context) : base(context) - { - } - - public override void Migrate() - { - var relationTypeCount = Context.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoRelationType WHERE alias=@alias", - new { alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias }); - - if (relationTypeCount > 0) - return; - - var uniqueId = (Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias + "____" + Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName).ToGuid(); - Insert.IntoTable("umbracoRelationType").Row(new - { - typeUniqueId = uniqueId, - alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, - name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName, - childObjectType = Constants.ObjectTypes.MediaType, - parentObjectType = Constants.ObjectTypes.MediaType, - dual = false - }).Do(); - } - - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/IncreaseLanguageIsoCodeColumnLength.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/IncreaseLanguageIsoCodeColumnLength.cs deleted file mode 100644 index fc7a21f4fa..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/IncreaseLanguageIsoCodeColumnLength.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 -{ - public class IncreaseLanguageIsoCodeColumnLength : MigrationBase - { - public IncreaseLanguageIsoCodeColumnLength(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - // Some people seem to have a constraint in their DB instead of an index, we'd need to drop that one - // See: https://our.umbraco.com/forum/using-umbraco-and-getting-started/93282-upgrade-from-711-to-712-fails - var constraints = SqlSyntax.GetConstraintsPerTable(Context.Database).Distinct().ToArray(); - if (constraints.Any(x => x.Item2.InvariantEquals("IX_umbracoLanguage_languageISOCode"))) - { - Delete.UniqueConstraint("IX_umbracoLanguage_languageISOCode").FromTable("umbracoLanguage").Do(); - } - - //Now check for indexes of that name and drop that if it exists - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoLanguage_languageISOCode"))) - { - Delete.Index("IX_umbracoLanguage_languageISOCode").OnTable("umbracoLanguage").Do(); - } - - Alter.Table("umbracoLanguage") - .AlterColumn("languageISOCode") - .AsString(14) - .Nullable() - .Do(); - - Create.Index("IX_umbracoLanguage_languageISOCode") - .OnTable("umbracoLanguage") - .OnColumn("languageISOCode") - .Ascending() - .WithOptions() - .Unique() - .Do(); - } - - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/RenameTrueFalseField.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/RenameTrueFalseField.cs deleted file mode 100644 index 4022739ece..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/RenameTrueFalseField.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 -{ - public class RenameTrueFalseField : MigrationBase - { - public RenameTrueFalseField(IMigrationContext context) : base(context) - { - } - - public override void Migrate() - { - //rename the existing true/false field - Update.Table(NodeDto.TableName).Set(new { text = "Checkbox" }).Where(new { id = Constants.DataTypes.Boolean }).Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/SetDefaultTagsStorageType.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/SetDefaultTagsStorageType.cs deleted file mode 100644 index c8d65961f4..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/SetDefaultTagsStorageType.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 -{ - /// - /// Set the default storageType for the tags datatype to "CSV" to ensure backwards compatibility since the default is going to be JSON in new versions. - /// - public class SetDefaultTagsStorageType : MigrationBase - { - public SetDefaultTagsStorageType(IMigrationContext context) - : base(context) - { } - - // dummy editor for deserialization - private class TagConfigurationEditor : ConfigurationEditor - { } - - public override void Migrate() - { - // get all Umbraco.Tags datatypes - var dataTypeDtos = Database.Fetch(Context.SqlContext.Sql() - .Select() - .From() - .Where(x => x.EditorAlias == Constants.PropertyEditors.Aliases.Tags)); - - // get a dummy editor for deserialization - var editor = new TagConfigurationEditor(); - - foreach (var dataTypeDto in dataTypeDtos) - { - // need to check storageType on raw dictionary, as TagConfiguration would have a default value - var dictionary = JsonConvert.DeserializeObject(dataTypeDto.Configuration); - - // if missing, use TagConfiguration to properly update the configuration - // due to ... reasons ... the key can start with a lower or upper 'S' - if (!dictionary.ContainsKey("storageType") && !dictionary.ContainsKey("StorageType")) - { - var configuration = (TagConfiguration)editor.FromDatabase(dataTypeDto.Configuration); - configuration.StorageType = TagsStorageType.Csv; - dataTypeDto.Configuration = ConfigurationEditor.ToDatabase(configuration); - Database.Update(dataTypeDto); - } - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/UpdateUmbracoConsent.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/UpdateUmbracoConsent.cs deleted file mode 100644 index 7596e3d7dd..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/UpdateUmbracoConsent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 -{ public class UpdateUmbracoConsent : MigrationBase { - public UpdateUmbracoConsent(IMigrationContext context) - : base(context) - { } - - public override void Migrate() { Alter.Table(Constants.DatabaseSchema.Tables.Consent).AlterColumn("comment").AsString().Nullable().Do(); } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_14_0/UpdateMemberGroupPickerData.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_14_0/UpdateMemberGroupPickerData.cs deleted file mode 100644 index c70f42076f..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_14_0/UpdateMemberGroupPickerData.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Umbraco.Core.Migrations.Upgrade.V_7_14_0 -{ - /// - /// Migrates member group picker properties from NVarchar to NText. See https://github.com/umbraco/Umbraco-CMS/issues/3268. - /// - public class UpdateMemberGroupPickerData : MigrationBase - { - /// - /// Migrates member group picker properties from NVarchar to NText. See https://github.com/umbraco/Umbraco-CMS/issues/3268. - /// - public UpdateMemberGroupPickerData(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - Database.Execute($@"UPDATE umbracoPropertyData SET textValue = varcharValue, varcharValue = NULL - WHERE textValue IS NULL AND id IN ( - SELECT id FROM umbracoPropertyData WHERE propertyTypeId in ( - SELECT id from cmsPropertyType where dataTypeId IN ( - SELECT nodeId FROM umbracoDataType WHERE propertyEditorAlias = '{Constants.PropertyEditors.Aliases.MemberGroupPicker}' - ) - ) - )"); - - Database.Execute($"UPDATE umbracoDataType SET dbType = 'Ntext' WHERE propertyEditorAlias = '{Constants.PropertyEditors.Aliases.MemberGroupPicker}'"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/AddRedirectUrlTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/AddRedirectUrlTable.cs deleted file mode 100644 index 7040874a74..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/AddRedirectUrlTable.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_5_0 -{ - public class AddRedirectUrlTable : MigrationBase - { - public AddRedirectUrlTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var database = Database; - var umbracoRedirectUrlTableName = "umbracoRedirectUrl"; - - var tables = SqlSyntax.GetTablesInSchema(database).ToArray(); - - if (tables.InvariantContains(umbracoRedirectUrlTableName)) - { - var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); - if (columns.Any(x => x.TableName.InvariantEquals(umbracoRedirectUrlTableName) && x.ColumnName.InvariantEquals("id") && x.DataType == "uniqueidentifier")) - return; - Delete.Table(umbracoRedirectUrlTableName).Do(); - } - - Create.Table(umbracoRedirectUrlTableName) - .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_" + umbracoRedirectUrlTableName) - .WithColumn("createDateUtc").AsDateTime().NotNullable() - .WithColumn("url").AsString(2048).NotNullable() - .WithColumn("contentKey").AsGuid().NotNullable() - .WithColumn("urlHash").AsString(40).NotNullable() - .Do(); - - Create.Index("IX_" + umbracoRedirectUrlTableName).OnTable(umbracoRedirectUrlTableName) - .OnColumn("urlHash") - .Ascending() - .OnColumn("contentKey") - .Ascending() - .OnColumn("createDateUtc") - .Descending() - .WithOptions().NonClustered() - .Do(); - - Create.ForeignKey("FK_" + umbracoRedirectUrlTableName) - .FromTable(umbracoRedirectUrlTableName).ForeignColumn("contentKey") - .ToTable("umbracoNode").PrimaryColumn("uniqueID") - .Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/RemoveStylesheetDataAndTablesAgain.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/RemoveStylesheetDataAndTablesAgain.cs deleted file mode 100644 index d27ed11a21..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/RemoveStylesheetDataAndTablesAgain.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_5_0 -{ - /// - /// This is here to re-remove these tables, we dropped them in 7.3 but new installs created them again so we're going to re-drop them - /// - public class RemoveStylesheetDataAndTablesAgain : MigrationBase - { - public RemoveStylesheetDataAndTablesAgain(IMigrationContext context) - : base(context) - { - } - - public override void Migrate() - { - // these have been obsoleted, need to copy the values here - var stylesheetPropertyObjectType = new Guid("5555da4f-a123-42b2-4488-dcdfb25e4111"); - var stylesheetObjectType = new Guid("9F68DA4F-A3A8-44C2-8226-DCBD125E4840"); - - //Clear all stylesheet data if the tables exist - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("cmsStylesheetProperty")) - { - Delete.FromTable("cmsStylesheetProperty").AllRows().Do(); - Delete.FromTable("umbracoNode").Row(new { nodeObjectType = stylesheetPropertyObjectType }).Do(); - - Delete.Table("cmsStylesheetProperty").Do(); - } - if (tables.InvariantContains("cmsStylesheet")) - { - Delete.FromTable("cmsStylesheet").AllRows().Do(); - Delete.FromTable("umbracoNode").Row(new { nodeObjectType = stylesheetObjectType }).Do(); - - Delete.Table("cmsStylesheet").Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/UpdateUniqueIndexOnPropertyData.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/UpdateUniqueIndexOnPropertyData.cs deleted file mode 100644 index 8c688cfd28..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/UpdateUniqueIndexOnPropertyData.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_5_0 -{ - /// - /// See: http://issues.umbraco.org/issue/U4-8522 - /// - public class UpdateUniqueIndexOnPropertyData : MigrationBase - { - public UpdateUniqueIndexOnPropertyData(IMigrationContext context) - : base(context) - { - } - - public override void Migrate() - { - //Clear all stylesheet data if the tables exist - //tuple = tablename, indexname, columnname, unique - var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); - var found = indexes.FirstOrDefault( - x => x.Item1.InvariantEquals("cmsPropertyData") - && x.Item2.InvariantEquals("IX_cmsPropertyData_1") - //we're searching for the old index which is not unique - && x.Item4 == false); - - if (found != null) - { - Database.Execute("DELETE FROM cmsPropertyData WHERE id NOT IN (SELECT MIN(id) FROM cmsPropertyData GROUP BY nodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL)"); - - //we need to re create this index - Delete.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData").Do(); - Create.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData") - .OnColumn("nodeId").Ascending() - .OnColumn("versionId").Ascending() - .OnColumn("propertytypeid").Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_5/UpdateAllowedMediaTypesAtRoot.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_5/UpdateAllowedMediaTypesAtRoot.cs deleted file mode 100644 index 362b8e251c..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_5/UpdateAllowedMediaTypesAtRoot.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Umbraco.Core.Migrations.Upgrade.V_7_5_5 -{ - /// - /// See: http://issues.umbraco.org/issue/U4-4196 - /// - public class UpdateAllowedMediaTypesAtRoot : MigrationBase - { - public UpdateAllowedMediaTypesAtRoot(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - Database.Execute("UPDATE cmsContentType SET allowAtRoot = 1 WHERE nodeId = 1032 OR nodeId = 1033"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToCmsMemberLoginName.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToCmsMemberLoginName.cs deleted file mode 100644 index a223d76a07..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToCmsMemberLoginName.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddIndexToCmsMemberLoginName : MigrationBase - { - public AddIndexToCmsMemberLoginName(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var database = Database; - - //Now we need to check if we can actually d6 this because we won't be able to if there's data in there that is too long - //http://issues.umbraco.org/issue/U4-9758 - - var colLen = database.ExecuteScalar("select max(datalength(LoginName)) from cmsMember"); - - if (colLen < 900 == false && colLen != null) - { - return; - } - - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false) - { - //we can apply the index - Create.Index("IX_cmsMember_LoginName").OnTable("cmsMember") - .OnColumn("LoginName") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUmbracoNodePath.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUmbracoNodePath.cs deleted file mode 100644 index 191f410b47..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUmbracoNodePath.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddIndexToUmbracoNodePath : MigrationBase - { - public AddIndexToUmbracoNodePath(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodePath")) == false) - { - Create.Index("IX_umbracoNodePath").OnTable("umbracoNode") - .OnColumn("path") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUser2NodePermission.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUser2NodePermission.cs deleted file mode 100644 index 87b392b09c..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUser2NodePermission.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddIndexToUser2NodePermission : MigrationBase - { - public AddIndexToUser2NodePermission(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoUser2NodePermission_nodeId")) == false) - { - Create.Index("IX_umbracoUser2NodePermission_nodeId").OnTable("umbracoUser2NodePermission") - .OnColumn("nodeId") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexesToUmbracoRelationTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexesToUmbracoRelationTables.cs deleted file mode 100644 index 9042ae105e..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexesToUmbracoRelationTables.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddIndexesToUmbracoRelationTables : MigrationBase - { - public AddIndexesToUmbracoRelationTables(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database).ToArray(); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelation_parentChildType")) == false) - { - //This will remove any corrupt/duplicate data in the relation table before the index is applied - //Ensure this executes in a deferred block which will be done inside of the migration transaction - var database = Database; - - //We need to check if this index has corrupted data and then clear that data - var duplicates = database.Fetch("SELECT parentId,childId,relType FROM umbracoRelation GROUP BY parentId,childId,relType HAVING COUNT(*) > 1"); - if (duplicates.Count > 0) - { - //need to fix this there cannot be duplicates so we'll take the latest entries, it's really not going to matter though - foreach (var duplicate in duplicates) - { - var ids = database.Fetch("SELECT id FROM umbracoRelation WHERE parentId=@parentId AND childId=@childId AND relType=@relType ORDER BY datetime DESC", - new { parentId = duplicate.parentId, childId = duplicate.childId, relType = duplicate.relType }); - - if (ids.Count == 1) - { - //this is just a safety check, this should absolutely never happen - throw new InvalidOperationException("Duplicates were detected but could not be discovered"); - } - - //delete the others - ids = ids.Skip(0).ToList(); - - //iterate in groups of 2000 to avoid the max sql parameter limit - foreach (var idGroup in ids.InGroupsOf(2000)) - { - database.Execute("DELETE FROM umbracoRelation WHERE id IN (@ids)", new { ids = idGroup }); - } - } - } - - //unique index to prevent duplicates - and for better perf - Create.Index("IX_umbracoRelation_parentChildType").OnTable("umbracoRelation") - .OnColumn("parentId").Ascending() - .OnColumn("childId").Ascending() - .OnColumn("relType").Ascending() - .WithOptions() - .Unique() - .Do(); - } - - //need indexes on alias and name for relation type since these are queried against - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_alias")) == false) - { - Create.Index("IX_umbracoRelationType_alias").OnTable("umbracoRelationType") - .OnColumn("alias") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_name")) == false) - { - Create.Index("IX_umbracoRelationType_name").OnTable("umbracoRelationType") - .OnColumn("name") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockObjects.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockObjects.cs deleted file mode 100644 index 2cc62cd2f9..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockObjects.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddLockObjects : MigrationBase - { - public AddLockObjects(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - EnsureLockObject(Constants.Locks.Servers, "Servers"); - } - - private void EnsureLockObject(int id, string name) - { - var db = Database; - var exists = db.Exists(id); - if (exists) return; - // be safe: delete old umbracoNode lock objects if any - db.Execute("DELETE FROM umbracoNode WHERE id=@id;", new { id }); - // then create umbracoLock object - db.Execute("INSERT umbracoLock (id, name, value) VALUES (@id, @name, 1);", new { id, name }); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockTable.cs deleted file mode 100644 index 369bda0758..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockTable.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddLockTable : MigrationBase - { - public AddLockTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoLock") == false) - { - Create.Table("umbracoLock") - .WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock") - .WithColumn("value").AsInt32().NotNullable() - .WithColumn("name").AsString(64).NotNullable() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddMacroUniqueIdColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddMacroUniqueIdColumn.cs deleted file mode 100644 index 82888ab970..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddMacroUniqueIdColumn.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddMacroUniqueIdColumn : MigrationBase - { - public AddMacroUniqueIdColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("cmsMacro") && x.ColumnName.InvariantEquals("uniqueId")) == false) - { - Create.Column("uniqueId").OnTable("cmsMacro").AsGuid().Nullable().Do(); - UpdateMacroGuids(); - Alter.Table("cmsMacro").AlterColumn("uniqueId").AsGuid().NotNullable().Do(); - Create.Index("IX_cmsMacro_UniqueId").OnTable("cmsMacro").OnColumn("uniqueId") - .Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique() - .Do(); - - } - - if (columns.Any(x => x.TableName.InvariantEquals("cmsMacroProperty") && x.ColumnName.InvariantEquals("uniquePropertyId")) == false) - { - Create.Column("uniquePropertyId").OnTable("cmsMacroProperty").AsGuid().Nullable().Do(); - UpdateMacroPropertyGuids(); - Alter.Table("cmsMacroProperty").AlterColumn("uniquePropertyId").AsGuid().NotNullable().Do(); - Create.Index("IX_cmsMacroProperty_UniquePropertyId").OnTable("cmsMacroProperty").OnColumn("uniquePropertyId") - .Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique() - .Do(); - } - } - - private void UpdateMacroGuids() - { - var database = Database; - - var updates = database.Query("SELECT id, macroAlias FROM cmsMacro") - .Select(macro => Tuple.Create((int) macro.id, ("macro____" + (string) macro.macroAlias).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE cmsMacro set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - } - - private void UpdateMacroPropertyGuids() - { - var database = Database; - - var updates = database.Query(@"SELECT cmsMacroProperty.id id, macroPropertyAlias propertyAlias, cmsMacro.macroAlias macroAlias -FROM cmsMacroProperty -JOIN cmsMacro ON cmsMacroProperty.macro=cmsMacro.id") - .Select(prop => Tuple.Create((int) prop.id, ("macro____" + (string) prop.macroAlias + "____" + (string) prop.propertyAlias).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE cmsMacroProperty set uniquePropertyId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddRelationTypeUniqueIdColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddRelationTypeUniqueIdColumn.cs deleted file mode 100644 index e166a4582d..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddRelationTypeUniqueIdColumn.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddRelationTypeUniqueIdColumn : MigrationBase - { - public AddRelationTypeUniqueIdColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoRelationType") && x.ColumnName.InvariantEquals("typeUniqueId")) == false) - { - Create.Column("typeUniqueId").OnTable("umbracoRelationType").AsGuid().Nullable().Do(); - UpdateRelationTypeGuids(); - Alter.Table("umbracoRelationType").AlterColumn("typeUniqueId").AsGuid().NotNullable().Do(); - Create.Index("IX_umbracoRelationType_UniqueId").OnTable("umbracoRelationType").OnColumn("typeUniqueId") - .Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique() - .Do(); - } - } - - private void UpdateRelationTypeGuids() - { - var database = Database; - var updates = database.Query("SELECT id, alias, name FROM umbracoRelationType") - .Select(relationType => Tuple.Create((int) relationType.id, ("relationType____" + (string) relationType.alias + "____" + (string) relationType.name).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE umbracoRelationType set typeUniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/NormalizeTemplateGuids.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/NormalizeTemplateGuids.cs deleted file mode 100644 index 9e8c739104..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/NormalizeTemplateGuids.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class NormalizeTemplateGuids : MigrationBase - { - public NormalizeTemplateGuids(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var database = Database; - - // we need this migration because ppl running pre-7.6 on Cloud and Courier have templates in different - // environments having different GUIDs (Courier does not sync template GUIDs) and we need to normalize - // these GUIDs so templates with the same alias on different environments have the same GUID. - // however, if already running a prerelease version of 7.6, we do NOT want to normalize the GUIDs as quite - // probably, we are already running Deploy and the GUIDs are OK. assuming noone is running a prerelease - // of 7.6 on Courier. - // so... testing if we already have a 7.6.0 version installed. not pretty but...? - // - var version = database.FirstOrDefault("SELECT version FROM umbracoMigration WHERE name=@name ORDER BY version DESC", new { name = Constants.System.UmbracoUpgradePlanName }); - if (version != null && version.StartsWith("7.6.0")) return; - - var updates = database.Query(@"SELECT umbracoNode.id, cmsTemplate.alias FROM umbracoNode -JOIN cmsTemplate ON umbracoNode.id=cmsTemplate.nodeId -WHERE nodeObjectType = @guid", new { guid = Constants.ObjectTypes.TemplateType }) - .Select(template => Tuple.Create((int) template.id, ("template____" + (string) template.alias).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE umbracoNode set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/ReduceLoginNameColumnsSize.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/ReduceLoginNameColumnsSize.cs deleted file mode 100644 index 1af21617f3..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/ReduceLoginNameColumnsSize.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class ReduceLoginNameColumnsSize : MigrationBase - { - public ReduceLoginNameColumnsSize(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - //Now we need to check if we can actually d6 this because we won't be able to if there's data in there that is too long - //http://issues.umbraco.org/issue/U4-9758 - - var database = Database; - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database); - - var colLen = database.ExecuteScalar("select max(datalength(LoginName)) from cmsMember"); - - if (colLen < 900 == false) return; - - //if an index exists on this table we need to drop it. Normally we'd check via index name but in some odd cases (i.e. Our) - //the index name is something odd (starts with "mi_"). In any case, the index cannot exist if we want to alter the column - //so we'll drop whatever index is there and add one with the correct name after. - var loginNameIndex = dbIndexes.FirstOrDefault(x => x.TableName.InvariantEquals("cmsMember") && x.ColumnName.InvariantEquals("LoginName")); - if (loginNameIndex != null) - { - Delete.Index(loginNameIndex.IndexName).OnTable("cmsMember").Do(); - } - - //we can apply the col length change - Alter.Table("cmsMember") - .AlterColumn("LoginName") - .AsString(225) - .NotNullable() - .Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemovePropertyDataIdIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemovePropertyDataIdIndex.cs deleted file mode 100644 index 804714d1ff..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemovePropertyDataIdIndex.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - /// - /// See: http://issues.umbraco.org/issue/U4-9188 - /// - public class UpdateUniqueIndexOnPropertyData : MigrationBase - { - public UpdateUniqueIndexOnPropertyData(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - //tuple = tablename, indexname, columnname, unique - var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); - var found = indexes.FirstOrDefault( - x => x.Item1.InvariantEquals("cmsPropertyData") - && x.Item2.InvariantEquals("IX_cmsPropertyData")); - - if (found != null) - { - //drop the index - Delete.Index("IX_cmsPropertyData").OnTable("cmsPropertyData").Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemoveUmbracoDeployTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemoveUmbracoDeployTables.cs deleted file mode 100644 index 744b441b32..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemoveUmbracoDeployTables.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class RemoveUmbracoDeployTables : MigrationBase - { - public RemoveUmbracoDeployTables(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - // there are two versions of umbracoDeployDependency, - // 1. one created by 7.4 and never used, we need to remove it (has a sourceId column) - // 2. one created by Deploy itself, we need to keep it (has a sourceUdi column) - if (tables.InvariantContains("umbracoDeployDependency")) - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - if (columns.Any(x => x.TableName.InvariantEquals("umbracoDeployDependency") && x.ColumnName.InvariantEquals("sourceId"))) - Delete.Table("umbracoDeployDependency").Do(); - } - - // always remove umbracoDeployChecksum - if (tables.InvariantContains("umbracoDeployChecksum")) - Delete.Table("umbracoDeployChecksum").Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddIndexToDictionaryKeyColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddIndexToDictionaryKeyColumn.cs deleted file mode 100644 index b366c7dab9..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddIndexToDictionaryKeyColumn.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - public class AddIndexToDictionaryKeyColumn : MigrationBase - { - public AddIndexToDictionaryKeyColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var database = Database; - //Now we need to check if we can actually do this because we won't be able to if there's data in there that is too long - var colLen = database.ExecuteScalar(string.Format("select max(datalength({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))); - - if (colLen < 900 == false && colLen != null) - { - return; - } - - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_key")) == false) - { - //we can apply the index - Create.Index("IX_cmsDictionary_key").OnTable("cmsDictionary") - .OnColumn("key") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserGroupTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserGroupTables.cs deleted file mode 100644 index edd78e6c84..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserGroupTables.cs +++ /dev/null @@ -1,357 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using ColumnInfo = Umbraco.Core.Persistence.SqlSyntax.ColumnInfo; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - public class AddUserGroupTables : MigrationBase - { - private readonly string _collateSyntax; - - public AddUserGroupTables(IMigrationContext context) - : base(context) - { - //For some of the migration data inserts we require to use a special MSSQL collate expression since - //some databases may have a custom collation specified and if that is the case, when we compare strings - //in dynamic SQL it will try to compare strings in different collations and this will yield errors. - _collateSyntax = "COLLATE DATABASE_DEFAULT"; - } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToList(); - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - //In some very rare cases, there might already be user group tables that we'll need to remove first - //but of course we don't want to remove the tables we will be creating below if they already exist so - //need to do some checks first since these old rare tables have a different schema - RemoveOldTablesIfExist(tables, columns); - - if (AddNewTables(tables)) - { - MigrateUserPermissions(); - MigrateUserTypesToGroups(); - DeleteOldTables(tables, constraints); - SetDefaultIcons(); - } - else - { - //if we aren't adding the tables, make sure that the umbracoUserGroup table has the correct FKs - these - //were added after the beta release so we need to do some cleanup - //if the FK doesn't exist - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUserGroup") - && x.Item2.InvariantEquals("startContentId") - && x.Item3.InvariantEquals("FK_startContentId_umbracoNode_id")) == false) - { - //before we add any foreign key we need to make sure there's no stale data in there which would have happened in the beta - //release if a start node was assigned and then that start node was deleted. - Database.Execute(@"UPDATE umbracoUserGroup SET startContentId = NULL WHERE startContentId NOT IN (SELECT id FROM umbracoNode)"); - - Create.ForeignKey("FK_startContentId_umbracoNode_id") - .FromTable("umbracoUserGroup") - .ForeignColumn("startContentId") - .ToTable("umbracoNode") - .PrimaryColumn("id") - .OnDelete(Rule.None) - .OnUpdate(Rule.None) - .Do(); - } - - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUserGroup") - && x.Item2.InvariantEquals("startMediaId") - && x.Item3.InvariantEquals("FK_startMediaId_umbracoNode_id")) == false) - { - //before we add any foreign key we need to make sure there's no stale data in there which would have happened in the beta - //release if a start node was assigned and then that start node was deleted. - Database.Execute(@"UPDATE umbracoUserGroup SET startMediaId = NULL WHERE startMediaId NOT IN (SELECT id FROM umbracoNode)"); - - Create.ForeignKey("FK_startMediaId_umbracoNode_id") - .FromTable("umbracoUserGroup") - .ForeignColumn("startMediaId") - .ToTable("umbracoNode") - .PrimaryColumn("id") - .OnDelete(Rule.None) - .OnUpdate(Rule.None) - .Do(); - } - } - } - - /// - /// In some very rare cases, there might already be user group tables that we'll need to remove first - /// but of course we don't want to remove the tables we will be creating below if they already exist so - /// need to do some checks first since these old rare tables have a different schema - /// - /// - /// - private void RemoveOldTablesIfExist(List tables, ColumnInfo[] columns) - { - if (tables.Contains("umbracoUser2userGroup", StringComparer.InvariantCultureIgnoreCase)) - { - //this column doesn't exist in the 7.7 schema, so if it's there, then this is a super old table - var foundOldColumn = columns - .FirstOrDefault(x => - x.ColumnName.Equals("user", StringComparison.InvariantCultureIgnoreCase) - && x.TableName.Equals("umbracoUser2userGroup", StringComparison.InvariantCultureIgnoreCase)); - if (foundOldColumn != null) - { - Delete.Table("umbracoUser2userGroup").Do(); - //remove from the tables list since this will be re-checked in further logic - tables.Remove("umbracoUser2userGroup"); - } - } - - if (tables.Contains("umbracoUserGroup", StringComparer.InvariantCultureIgnoreCase)) - { - //The new schema has several columns, the super old one for this table only had 2 so if it's 2 get rid of it - var countOfCols = columns - .Count(x => x.TableName.Equals("umbracoUserGroup", StringComparison.InvariantCultureIgnoreCase)); - if (countOfCols == 2) - { - Delete.Table("umbracoUserGroup").Do(); - //remove from the tables list since this will be re-checked in further logic - tables.Remove("umbracoUserGroup"); - } - } - } - - private void SetDefaultIcons() - { - Database.Execute($"UPDATE umbracoUserGroup SET icon = \'\' WHERE userGroupAlias = \'{Constants.Security.AdminGroupAlias}\'"); - Database.Execute("UPDATE umbracoUserGroup SET icon = \'icon-edit\' WHERE userGroupAlias = \'writer\'"); - Database.Execute("UPDATE umbracoUserGroup SET icon = \'icon-tools\' WHERE userGroupAlias = \'editor\'"); - Database.Execute("UPDATE umbracoUserGroup SET icon = \'icon-globe\' WHERE userGroupAlias = \'translator\'"); - } - - private bool AddNewTables(List tables) - { - var updated = false; - if (tables.InvariantContains("umbracoUserGroup") == false) - { - Create.Table().Do(); - updated = true; - } - - if (tables.InvariantContains("umbracoUser2UserGroup") == false) - { - Create.Table().Do(); - updated = true; - } - - if (tables.InvariantContains("umbracoUserGroup2App") == false) - { - Create.Table().Do(); - updated = true; - } - - if (tables.InvariantContains("umbracoUserGroup2NodePermission") == false) - { - Create.Table().Do(); - updated = true; - } - - return updated; - } - - private void MigrateUserTypesToGroups() - { - // Create a user group for each user type - Database.Execute(@"INSERT INTO umbracoUserGroup (userGroupAlias, userGroupName, userGroupDefaultPermissions) - SELECT userTypeAlias, userTypeName, userTypeDefaultPermissions - FROM umbracoUserType"); - - // Add each user to the group created from their type - Database.Execute(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) - SELECT u.id, ug.id - FROM umbracoUser u - INNER JOIN umbracoUserType ut ON ut.id = u.userType - INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias {0} = ut.userTypeAlias {0}", _collateSyntax)); - - // Add the built-in administrator account to all apps - // this will lookup all of the apps that the admin currently has access to in order to assign the sections - // instead of use statically assigning since there could be extra sections we don't know about. - Database.Execute(@"INSERT INTO umbracoUserGroup2app (userGroupId,app) - SELECT ug.id, app - FROM umbracoUserGroup ug - INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id - INNER JOIN umbracoUser u ON u.id = u2ug.userId - INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id - WHERE u.id = 0"); - - // Add the default section access to the other built-in accounts - // writer: - Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'content' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'writer' {0}", _collateSyntax)); - // editor - Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'content' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'editor' {0}", _collateSyntax)); - Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'media' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'editor' {0}", _collateSyntax)); - // translator - Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'translation' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'translator' {0}", _collateSyntax)); - - //We need to lookup all distinct combinations of section access and create a group for each distinct collection - //and assign groups accordingly. We'll perform the lookup 'now' to then create the queued SQL migrations. - var userAppsData = Context.Database.Query(@"SELECT u.id, u2a.app FROM umbracoUser u - INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id - ORDER BY u.id, u2a.app"); - var usersWithApps = new Dictionary>(); - foreach (var userApps in userAppsData) - { - if (usersWithApps.TryGetValue(userApps.id, out List apps) == false) - { - apps = new List {userApps.app}; - usersWithApps.Add(userApps.id, apps); - } - else - { - apps.Add(userApps.app); - } - } - //At this stage we have a dictionary of users with a collection of their apps which are sorted - //and we need to determine the unique/distinct app collections for each user to create groups with. - //We can do this by creating a hash value of all of the app values and since they are already sorted we can get a distinct - //collection by this hash. - var distinctApps = usersWithApps - .Select(x => new {appCollection = x.Value, appsHash = string.Join("", x.Value).GenerateHash()}) - .DistinctBy(x => x.appsHash) - .ToArray(); - //Now we need to create user groups for each of these distinct app collections, and then assign the corresponding users to those groups - for (var i = 0; i < distinctApps.Length; i++) - { - //create the group - var alias = "MigratedSectionAccessGroup_" + (i + 1); - Insert.IntoTable("umbracoUserGroup").Row(new - { - userGroupAlias = "MigratedSectionAccessGroup_" + (i + 1), - userGroupName = "Migrated Section Access Group " + (i + 1) - }).Do(); - //now assign the apps - var distinctApp = distinctApps[i]; - foreach (var app in distinctApp.appCollection) - { - Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, '" + app + @"' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = '" + alias + "' {0}", _collateSyntax)); - } - //now assign the corresponding users to this group - foreach (var userWithApps in usersWithApps) - { - //check if this user's groups hash matches the current groups hash - var hash = string.Join("", userWithApps.Value).GenerateHash(); - if (hash == distinctApp.appsHash) - { - //it matches so assign the user to this group - Database.Execute(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) - SELECT " + userWithApps.Key + @", ug.id - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = '" + alias + "' {0}", _collateSyntax)); - } - } - } - - // Rename some groups for consistency (plural form) - Database.Execute("UPDATE umbracoUserGroup SET userGroupName = 'Writers' WHERE userGroupAlias = 'writer'"); - Database.Execute("UPDATE umbracoUserGroup SET userGroupName = 'Translators' WHERE userGroupAlias = 'translator'"); - - //Ensure all built in groups have a start node of -1 - Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'editor'"); - Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'editor'"); - Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'writer'"); - Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'writer'"); - Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'translator'"); - Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'translator'"); - Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'admin'"); - Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'admin'"); - } - - private void MigrateUserPermissions() - { - // Create user group records for all non-admin users that have specific permissions set - Database.Execute(@"INSERT INTO umbracoUserGroup(userGroupAlias, userGroupName) - SELECT 'permissionGroupFor' + userLogin, 'Migrated Permission Group for ' + userLogin - FROM umbracoUser - WHERE (id IN ( - SELECT userid - FROM umbracoUser2NodePermission - )) - AND id > 0"); - - // Associate those groups with the users - Database.Execute(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) - SELECT u.id, ug.id - FROM umbracoUser u - INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias {0} = 'permissionGroupFor' + userLogin {0}", _collateSyntax)); - - // Create node permissions on the groups - Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2NodePermission (userGroupId,nodeId,permission) - SELECT ug.id, nodeId, permission - FROM umbracoUserGroup ug - INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id - INNER JOIN umbracoUser u ON u.id = u2ug.userId - INNER JOIN umbracoUser2NodePermission u2np ON u2np.userId = u.id - WHERE ug.userGroupAlias {0} NOT IN ( - SELECT userTypeAlias {0} - FROM umbracoUserType - )", _collateSyntax)); - - // Create app permissions on the groups - Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId,app) - SELECT ug.id, app - FROM umbracoUserGroup ug - INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id - INNER JOIN umbracoUser u ON u.id = u2ug.userId - INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id - WHERE ug.userGroupAlias {0} NOT IN ( - SELECT userTypeAlias {0} - FROM umbracoUserType - )", _collateSyntax)); - } - - private void DeleteOldTables(List tables, Tuple[] constraints) - { - if (tables.InvariantContains("umbracoUser2App")) - { - Delete.Table("umbracoUser2App").Do(); - } - - if (tables.InvariantContains("umbracoUser2NodePermission")) - { - Delete.Table("umbracoUser2NodePermission").Do(); - } - - if (tables.InvariantContains("umbracoUserType") && tables.InvariantContains("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").Do(); - } - - //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").Do(); - } - - Delete.Column("userType").FromTable("umbracoUser").Do(); - Delete.Table("umbracoUserType").Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserStartNodeTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserStartNodeTable.cs deleted file mode 100644 index 54545e06d3..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserStartNodeTable.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - public class AddUserStartNodeTable : MigrationBase - { - public AddUserStartNodeTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains("umbracoUserStartNode")) return; - - Create.Table().Do(); - - MigrateUserStartNodes(); - - //now remove the old columns - - Delete.Column("startStructureID").FromTable("umbracoUser").Do(); - Delete.Column("startMediaID").FromTable("umbracoUser").Do(); - } - - private void MigrateUserStartNodes() - { - Database.Execute(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) - SELECT id, startStructureID, 1 - FROM umbracoUser - WHERE startStructureID IS NOT NULL AND startStructureID > 0 AND startStructureID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Document + "')"); - - Database.Execute(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) - SELECT id, startMediaID, 2 - FROM umbracoUser - WHERE startMediaID IS NOT NULL AND startMediaID > 0 AND startMediaID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Media + "')"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/EnsureContentTemplatePermissions.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/EnsureContentTemplatePermissions.cs deleted file mode 100644 index 1f7e2faf33..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/EnsureContentTemplatePermissions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - /// - /// Ensures the built-in user groups have the blueprint permission by default on upgrade - /// - public class EnsureContentTemplatePermissions : MigrationBase - { - public EnsureContentTemplatePermissions(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var database = Database; - var userGroups = database.Fetch( - Context.SqlContext.Sql().Select("*") - .From() - .Where(x => x.Alias == "admin" || x.Alias == "editor")); - - foreach (var userGroup in userGroups) - { - if (userGroup.DefaultPermissions.Contains('�') == false) - { - userGroup.DefaultPermissions += "�"; - Update.Table("umbracoUserGroup") - .Set(new { userGroupDefaultPermissions = userGroup.DefaultPermissions }) - .Where(new { id = userGroup.Id }) - .Do(); - } - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/ReduceDictionaryKeyColumnsSize.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/ReduceDictionaryKeyColumnsSize.cs deleted file mode 100644 index 17dd3064eb..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/ReduceDictionaryKeyColumnsSize.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - public class ReduceDictionaryKeyColumnsSize : MigrationBase - { - public ReduceDictionaryKeyColumnsSize(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - //Now we need to check if we can actually do this because we won't be able to if there's data in there that is too long - - var database = Database; - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database); - - var colLen = database.ExecuteScalar(string.Format("select max(datalength({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))); - - if (colLen < 900 == false) return; - - //if it exists we need to drop it first - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_key"))) - { - Delete.Index("IX_cmsDictionary_key").OnTable("cmsDictionary").Do(); - } - - //we can apply the col length change - Alter.Table("cmsDictionary") - .AlterColumn("key") - .AsString(450) - .NotNullable() - .Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/UpdateUserTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/UpdateUserTables.cs deleted file mode 100644 index 509b3f91dd..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/UpdateUserTables.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Linq; -using System.Web.Security; -using Newtonsoft.Json; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Security; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - public class UpdateUserTables : MigrationBase - { - public UpdateUserTables(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - //Don't execute if the column is already there - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("createDate")) == false) - Create.Column("createDate").OnTable("umbracoUser").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime).Do(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("updateDate")) == false) - Create.Column("updateDate").OnTable("umbracoUser").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime).Do(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("emailConfirmedDate")) == false) - Create.Column("emailConfirmedDate").OnTable("umbracoUser").AsDateTime().Nullable().Do(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("invitedDate")) == false) - Create.Column("invitedDate").OnTable("umbracoUser").AsDateTime().Nullable().Do(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("avatar")) == false) - Create.Column("avatar").OnTable("umbracoUser").AsString(500).Nullable().Do(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("passwordConfig")) == false) - { - Create.Column("passwordConfig").OnTable("umbracoUser").AsString(500).Nullable().Do(); - //Check if we have a known config, we only want to store config for hashing - var membershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider(); - if (membershipProvider.PasswordFormat == MembershipPasswordFormat.Hashed) - { - var json = JsonConvert.SerializeObject(new { hashAlgorithm = Membership.HashAlgorithmType }); - Database.Execute("UPDATE umbracoUser SET passwordConfig = '" + json + "'"); - } - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs deleted file mode 100644 index ddb084a609..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 -{ - internal class AddIndexToPropertyTypeAliasColumn : MigrationBase - { - public AddIndexToPropertyTypeAliasColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsPropertyTypeAlias")) == false) - { - //we can apply the index - Create.Index("IX_cmsPropertyTypeAlias").OnTable(Constants.DatabaseSchema.Tables.PropertyType) - .OnColumn("Alias") - .Ascending().WithOptions().NonClustered() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs deleted file mode 100644 index 0ce2c91f2e..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 -{ - internal class AddInstructionCountColumn : MigrationBase - { - public AddInstructionCountColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.CacheInstruction) && x.ColumnName.InvariantEquals("instructionCount")) == false) - AddColumn("instructionCount"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs deleted file mode 100644 index b4c0062770..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Factories; -using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 -{ - internal class AddMediaVersionTable : MigrationBase - { - public AddMediaVersionTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains(Constants.DatabaseSchema.Tables.MediaVersion)) return; - - Create.Table().Do(); - MigrateMediaPaths(); - } - - private void MigrateMediaPaths() - { - // this may not be the most efficient way to do it, compared to how it's done in v7, but this - // migration should only run for v8 sites that are being developed, before v8 is released, so - // no big sites and performances don't matter here - keep it simple - - var sql = Sql() - .Select(x => x.VarcharValue, x => x.TextValue) - .AndSelect(x => Alias(x.Id, "versionId")) - .From() - .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) - .InnerJoin().On((left, right) => left.VersionId == right.Id) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .Where(x => x.Alias == "umbracoFile") - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media); - - var paths = new List(); - - //using QUERY = a db cursor, we won't load this all into memory first, just row by row - foreach (var row in Database.Query(sql)) - { - // if there's values then ensure there's a media path match and extract it - string mediaPath = null; - if ( - (row.varcharValue != null && ContentBaseFactory.TryMatch((string) row.varcharValue, out mediaPath)) - || (row.textValue != null && ContentBaseFactory.TryMatch((string) row.textValue, out mediaPath))) - { - paths.Add(new MediaVersionDto - { - Id = (int) row.versionId, - Path = mediaPath - }); - } - } - - // bulk insert - Database.BulkInsertRecords(paths); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs deleted file mode 100644 index cd2678205f..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 -{ - internal class AddTourDataUserColumn : MigrationBase - { - public AddTourDataUserColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.User) && x.ColumnName.InvariantEquals("tourData")) == false) - AddColumn("tourData"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs deleted file mode 100644 index 7a55362072..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 -{ - internal class AddUserLoginTable : MigrationBase - { - public AddUserLoginTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains(Constants.DatabaseSchema.Tables.UserLogin) == false) - { - Create.Table().Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs deleted file mode 100644 index 2b5f481769..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 -{ - internal class AddIsSensitiveMemberTypeColumn : MigrationBase - { - public AddIsSensitiveMemberTypeColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.MemberPropertyType) && x.ColumnName.InvariantEquals("isSensitive")) == false) - AddColumn("isSensitive"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs deleted file mode 100644 index e7880dfc73..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 -{ - internal class AddUmbracoAuditTable : MigrationBase - { - public AddUmbracoAuditTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains(Constants.DatabaseSchema.Tables.AuditEntry)) - return; - - Create.Table().Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs deleted file mode 100644 index e3656f69ac..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 -{ - internal class AddUmbracoConsentTable : MigrationBase - { - public AddUmbracoConsentTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains(Constants.DatabaseSchema.Tables.Consent)) - return; - - Create.Table().Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs deleted file mode 100644 index ce656aa0c1..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 -{ - internal class CreateSensitiveDataUserGroup : MigrationBase - { - public CreateSensitiveDataUserGroup(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var sql = Sql() - .SelectCount() - .From() - .Where(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); - - var exists = Database.ExecuteScalar(sql) > 0; - if (exists) return; - - var groupId = Database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", new UserGroupDto { StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); - Database.Insert(new User2UserGroupDto { UserGroupId = Convert.ToInt32(groupId), UserId = Constants.Security.SuperUserId }); // add super to sensitive data - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs index efa54a0f59..5342755ebf 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs @@ -1,6 +1,7 @@ using System.Data; using System.Linq; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { @@ -12,31 +13,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + var tables = SqlSyntax.GetTablesInSchema(Context.Database); if (tables.InvariantContains("cmsContentNu")) return; - var textType = SqlSyntax.GetSpecialDbType(SpecialDbTypes.NTEXT); - - Create.Table("cmsContentNu") - .WithColumn("nodeId").AsInt32().NotNullable() - .WithColumn("published").AsBoolean().NotNullable() - .WithColumn("data").AsCustom(textType).NotNullable() - .WithColumn("rv").AsInt64().NotNullable().WithDefaultValue(0) - .Do(); - - Create.PrimaryKey("PK_cmsContentNu") - .OnTable("cmsContentNu") - .Columns(new[] { "nodeId", "published" }) - .Do(); - - Create.ForeignKey("FK_cmsContentNu_umbracoNode_id") - .FromTable("cmsContentNu") - .ForeignColumn("nodeId") - .ToTable("umbracoNode") - .PrimaryColumn("id") - .OnDelete(Rule.Cascade) - .OnUpdate(Rule.None) - .Do(); + Create.Table(true).Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockTable.cs deleted file mode 100644 index aa14bc81c8..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockTable.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 -{ - public class AddLockTable : MigrationBase - { - public AddLockTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoLock") == false) - { - Create.Table("umbracoLock") - .WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock") - .WithColumn("value").AsInt32().NotNullable() - .WithColumn("name").AsString(64).NotNullable() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs deleted file mode 100644 index 0ccc2d93ff..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 -{ - public class AddUserLoginDtoDateIndex : MigrationBase - { - public AddUserLoginDtoDateIndex(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - if (!IndexExists("IX_umbracoUserLogin_lastValidatedUtc")) - Create.Index("IX_umbracoUserLogin_lastValidatedUtc") - .OnTable(UserLoginDto.TableName) - .OnColumn("lastValidatedUtc") - .Ascending() - .WithOptions().NonClustered() - .Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddVariationTables2.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddVariationTables2.cs index c6cad2eac1..4044b5a173 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddVariationTables2.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddVariationTables2.cs @@ -11,8 +11,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - Create.Table().Do(); - Create.Table().Do(); + Create.Table(true).Do(); + Create.Table(true).Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs index ed1c08f0f8..4802750f4b 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs @@ -19,8 +19,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 var sqlDataTypes = Sql() .Select() .From() - .Where(x => x.EditorAlias == "Umbraco.RelatedLinks" - || x.EditorAlias == "Umbraco.RelatedLinks2"); + .Where(x => x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks + || x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2); var dataTypes = Database.Fetch(sqlDataTypes); var dataTypeIds = dataTypes.Select(x => x.NodeId).ToList(); @@ -50,10 +50,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 var properties = Database.Fetch(sqlPropertyData); // Create a Multi URL Picker datatype for the converted RelatedLinks data - + foreach (var property in properties) { - var value = property.Value.ToString(); + var value = property.Value?.ToString(); if (string.IsNullOrWhiteSpace(value)) continue; @@ -89,7 +89,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 Name = relatedLink.Caption, Target = relatedLink.NewWindow ? "_blank" : null, Udi = udi, - // Should only have a URL if it's an external link otherwise it wil be a UDI + // Should only have a URL if it's an external link otherwise it wil be a UDI Url = relatedLink.IsInternal == false ? relatedLink.Link : null }; @@ -103,7 +103,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 Database.Update(property); } - + } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs index d7180385f0..7b2daa99ef 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs @@ -1,28 +1,46 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using Newtonsoft.Json; -using NPoco; -using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { public class DataTypeMigration : MigrationBase { - public DataTypeMigration(IMigrationContext context) + private readonly PreValueMigratorCollection _preValueMigrators; + private readonly PropertyEditorCollection _propertyEditors; + private readonly ILogger _logger; + + private static readonly ISet LegacyAliases = new HashSet() + { + Constants.PropertyEditors.Legacy.Aliases.Date, + Constants.PropertyEditors.Legacy.Aliases.Textbox, + Constants.PropertyEditors.Legacy.Aliases.ContentPicker2, + Constants.PropertyEditors.Legacy.Aliases.MediaPicker2, + Constants.PropertyEditors.Legacy.Aliases.MemberPicker2, + Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2, + Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple, + Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2, + }; + + public DataTypeMigration(IMigrationContext context, PreValueMigratorCollection preValueMigrators, PropertyEditorCollection propertyEditors, ILogger logger) : base(context) - { } + { + _preValueMigrators = preValueMigrators; + _propertyEditors = propertyEditors; + _logger = logger; + } public override void Migrate() { - // delete *all* keys and indexes - because of FKs - Delete.KeysAndIndexes().Do(); - // drop and create columns Delete.Column("pk").FromTable("cmsDataType").Do(); @@ -31,21 +49,17 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 // create column AddColumn(Constants.DatabaseSchema.Tables.DataType, "config"); - Execute.Sql(Sql().Update(u => u.Set(x => x.Configuration, string.Empty)).SQL).Do(); - - // re-create *all* keys and indexes - foreach (var x in DatabaseSchemaCreator.OrderedTables) - Create.KeysAndIndexes(x).Do(); + Execute.Sql(Sql().Update(u => u.Set(x => x.Configuration, string.Empty))).Do(); // renames Execute.Sql(Sql() .Update(u => u.Set(x => x.EditorAlias, "Umbraco.ColorPicker")) - .Where(x => x.EditorAlias == "Umbraco.ColorPickerAlias").SQL).Do(); + .Where(x => x.EditorAlias == "Umbraco.ColorPickerAlias")).Do(); // from preValues to configuration... var sql = Sql() .Select() - .AndSelect(x => x.Alias, x => x.SortOrder, x => x.Value) + .AndSelect(x => x.Id, x => x.Alias, x => x.SortOrder, x => x.Value) .From() .InnerJoin().On((left, right) => left.NodeId == right.NodeId) .OrderBy(x => x.NodeId) @@ -60,43 +74,52 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 .From() .Where(x => x.NodeId == group.Key)).First(); - var aliases = group.Select(x => x.Alias).Distinct().ToArray(); - if (aliases.Length == 1 && string.IsNullOrWhiteSpace(aliases[0])) + // migrate the preValues to configuration + var migrator = _preValueMigrators.GetMigrator(dataType.EditorAlias) ?? new DefaultPreValueMigrator(); + var config = migrator.GetConfiguration(dataType.NodeId, dataType.EditorAlias, group.ToDictionary(x => x.Alias, x => x)); + var json = JsonConvert.SerializeObject(config); + + // validate - and kill the migration if it fails + var newAlias = migrator.GetNewAlias(dataType.EditorAlias); + if (newAlias == null) { - // array-based prevalues - var values = new Dictionary { ["values"] = group.OrderBy(x => x.SortOrder).Select(x => x.Value).ToArray() }; - dataType.Configuration = JsonConvert.SerializeObject(values); + if (!LegacyAliases.Contains(dataType.EditorAlias)) + { + _logger.Warn( + "Skipping validation of configuration for data type {NodeId} : {EditorAlias}." + + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.", + dataType.NodeId, dataType.EditorAlias); + } + } + else if (!_propertyEditors.TryGet(newAlias, out var propertyEditor)) + { + if (!LegacyAliases.Contains(newAlias)) + { + _logger.Warn("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})" + + " because no property editor with that alias was found." + + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.", + dataType.NodeId, newAlias, dataType.EditorAlias); + } } else { - // assuming we don't want to fall back to array - if (aliases.Length != group.Count() || aliases.Any(string.IsNullOrWhiteSpace)) - throw new InvalidOperationException($"Cannot migrate datatype w/ id={dataType.NodeId} preValues: duplicate or null/empty alias."); - - // dictionary-base prevalues - var values = group.ToDictionary(x => x.Alias, x => x.Value); - dataType.Configuration = JsonConvert.SerializeObject(values); + var configEditor = propertyEditor.GetConfigurationEditor(); + try + { + var _ = configEditor.FromDatabase(json); + } + catch (Exception e) + { + _logger.Warn(e, "Failed to validate configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", + dataType.NodeId, newAlias, dataType.EditorAlias); + } } + // update + dataType.Configuration = JsonConvert.SerializeObject(config); Database.Update(dataType); } } - - [TableName("cmsDataTypePreValues")] - [ExplicitColumns] - public class PreValueDto - { - [Column("datatypeNodeId")] - public int NodeId { get; set; } - - [Column("alias")] - public string Alias { get; set; } - - [Column("sortorder")] - public int SortOrder { get; set; } - - [Column("value")] - public string Value { get; set; } - } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs new file mode 100644 index 0000000000..f445742aa9 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class ContentPickerPreValueMigrator : DefaultPreValueMigrator + { + public override bool CanMigrate(string editorAlias) + => editorAlias == Constants.PropertyEditors.Legacy.Aliases.ContentPicker2; + + public override string GetNewAlias(string editorAlias) + => null; + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "showOpenButton" || + preValue.Alias == "ignoreUserStartNodes") + return preValue.Value == "1"; + + return base.GetPreValueValue(preValue); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DecimalPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DecimalPreValueMigrator.cs new file mode 100644 index 0000000000..8dc0f1dcd5 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DecimalPreValueMigrator.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class DecimalPreValueMigrator : DefaultPreValueMigrator + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.Decimal"; + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "min" || + preValue.Alias == "step" || + preValue.Alias == "max") + return decimal.TryParse(preValue.Value, out var d) ? (decimal?) d : null; + + return preValue.Value.DetectIsJson() ? JsonConvert.DeserializeObject(preValue.Value) : preValue.Value; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DefaultPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DefaultPreValueMigrator.cs new file mode 100644 index 0000000000..7112679de2 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DefaultPreValueMigrator.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class DefaultPreValueMigrator : IPreValueMigrator + { + public virtual bool CanMigrate(string editorAlias) + => true; + + public virtual string GetNewAlias(string editorAlias) + => editorAlias; + + public object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + var preValuesA = preValues.Values.ToList(); + var aliases = preValuesA.Select(x => x.Alias).Distinct().ToArray(); + if (aliases.Length == 1 && string.IsNullOrWhiteSpace(aliases[0])) + { + // array-based prevalues + return new Dictionary { ["values"] = preValuesA.OrderBy(x => x.SortOrder).Select(x => x.Value).ToArray() }; + } + + // assuming we don't want to fall back to array + if (aliases.Length != preValuesA.Count || aliases.Any(string.IsNullOrWhiteSpace)) + throw new InvalidOperationException($"Cannot migrate datatype w/ id={dataTypeId} preValues: duplicate or null/empty alias."); + + // dictionary-base prevalues + return GetPreValues(preValuesA).ToDictionary(x => x.Alias, GetPreValueValue); + } + + protected virtual IEnumerable GetPreValues(IEnumerable preValues) + => preValues; + + protected virtual object GetPreValueValue(PreValueDto preValue) + { + return preValue.Value.DetectIsJson() ? JsonConvert.DeserializeObject(preValue.Value) : preValue.Value; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DropDownFlexiblePreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DropDownFlexiblePreValueMigrator.cs new file mode 100644 index 0000000000..35ca574bab --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DropDownFlexiblePreValueMigrator.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class DropDownFlexiblePreValueMigrator : IPreValueMigrator + { + public bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.DropDown.Flexible"; + + public virtual string GetNewAlias(string editorAlias) + => null; + + public object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + var config = new DropDownFlexibleConfiguration(); + foreach (var preValue in preValues.Values) + { + if (preValue.Alias == "multiple") + { + config.Multiple = (preValue.Value == "1"); + } + else + { + config.Items.Add(new ValueListConfiguration.ValueListItem { Id = preValue.Id, Value = preValue.Value }); + } + } + return config; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/IPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/IPreValueMigrator.cs new file mode 100644 index 0000000000..01e0ed3875 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/IPreValueMigrator.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + /// + /// Defines a service migrating preValues. + /// + public interface IPreValueMigrator + { + /// + /// Determines whether this migrator can migrate a data type. + /// + /// The data type editor alias. + bool CanMigrate(string editorAlias); + + /// + /// Gets the v8 codebase data type editor alias. + /// + /// The original v7 codebase editor alias. + /// + /// This is used to validate that the migrated configuration can be parsed + /// by the new property editor. Return null to bypass this validation, + /// when for instance we know it will fail, and another, later migration will + /// deal with it. + /// + string GetNewAlias(string editorAlias); + + /// + /// Gets the configuration object corresponding to preValue. + /// + /// The data type identifier. + /// The data type editor alias. + /// PreValues. + object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues); + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ListViewPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ListViewPreValueMigrator.cs new file mode 100644 index 0000000000..d8daf9b5e6 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ListViewPreValueMigrator.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class ListViewPreValueMigrator : DefaultPreValueMigrator + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.ListView"; + + protected override IEnumerable GetPreValues(IEnumerable preValues) + { + return preValues.Where(preValue => preValue.Alias != "displayAtTabNumber"); + } + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "pageSize") + return int.TryParse(preValue.Value, out var i) ? (int?)i : null; + + return preValue.Value.DetectIsJson() ? JsonConvert.DeserializeObject(preValue.Value) : preValue.Value; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs new file mode 100644 index 0000000000..922d886595 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs @@ -0,0 +1,37 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class MediaPickerPreValueMigrator : DefaultPreValueMigrator //PreValueMigratorBase + { + private readonly string[] _editors = + { + Constants.PropertyEditors.Legacy.Aliases.MediaPicker2, + Constants.PropertyEditors.Aliases.MediaPicker + }; + + public override bool CanMigrate(string editorAlias) + => _editors.Contains(editorAlias); + + public override string GetNewAlias(string editorAlias) + => Constants.PropertyEditors.Aliases.MediaPicker; + + // you wish - but MediaPickerConfiguration lives in Umbraco.Web + /* + public override object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + return new MediaPickerConfiguration { ... }; + } + */ + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "multiPicker" || + preValue.Alias == "onlyImages" || + preValue.Alias == "disableFolderSelect") + return preValue.Value == "1"; + + return base.GetPreValueValue(preValue); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/NestedContentPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/NestedContentPreValueMigrator.cs new file mode 100644 index 0000000000..22c87f8503 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/NestedContentPreValueMigrator.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class NestedContentPreValueMigrator : DefaultPreValueMigrator //PreValueMigratorBase + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.NestedContent"; + + // you wish - but NestedContentConfiguration lives in Umbraco.Web + /* + public override object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + return new NestedContentConfiguration { ... }; + } + */ + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "confirmDeletes" || + preValue.Alias == "showIcons" || + preValue.Alias == "hideLabel") + return preValue.Value == "1"; + + if (preValue.Alias == "minItems" || + preValue.Alias == "maxItems") + return int.TryParse(preValue.Value, out var i) ? (int?)i : null; + + return preValue.Value.DetectIsJson() ? JsonConvert.DeserializeObject(preValue.Value) : preValue.Value; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueDto.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueDto.cs new file mode 100644 index 0000000000..b6ab622510 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueDto.cs @@ -0,0 +1,24 @@ +using NPoco; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + [TableName("cmsDataTypePreValues")] + [ExplicitColumns] + public class PreValueDto + { + [Column("id")] + public int Id { get; set; } + + [Column("datatypeNodeId")] + public int NodeId { get; set; } + + [Column("alias")] + public string Alias { get; set; } + + [Column("sortorder")] + public int SortOrder { get; set; } + + [Column("value")] + public string Value { get; set; } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorBase.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorBase.cs new file mode 100644 index 0000000000..62e2b2347b --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorBase.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + public abstract class PreValueMigratorBase : IPreValueMigrator + { + public abstract bool CanMigrate(string editorAlias); + + public virtual string GetNewAlias(string editorAlias) + => editorAlias; + + public abstract object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues); + + protected bool GetBoolValue(Dictionary preValues, string alias, bool defaultValue = false) + => preValues.TryGetValue(alias, out var preValue) ? preValue.Value == "1" : defaultValue; + + protected decimal GetDecimalValue(Dictionary preValues, string alias, decimal defaultValue = 0) + => preValues.TryGetValue(alias, out var preValue) && decimal.TryParse(preValue.Value, out var value) ? value : defaultValue; + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollection.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollection.cs new file mode 100644 index 0000000000..06f5d45deb --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollection.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + public class PreValueMigratorCollection : BuilderCollectionBase + { + private readonly ILogger _logger; + + public PreValueMigratorCollection(IEnumerable items, ILogger logger) + : base(items) + { + _logger = logger; + _logger.Debug(GetType(), "Migrators: " + string.Join(", ", items.Select(x => x.GetType().Name))); + } + + public IPreValueMigrator GetMigrator(string editorAlias) + { + var migrator = this.FirstOrDefault(x => x.CanMigrate(editorAlias)); + _logger.Debug(GetType(), "Getting migrator for \"{EditorAlias}\" = {MigratorType}", editorAlias, migrator == null ? "" : migrator.GetType().Name); + return migrator; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollectionBuilder.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollectionBuilder.cs new file mode 100644 index 0000000000..3859e63767 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollectionBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + public class PreValueMigratorCollectionBuilder : OrderedCollectionBuilderBase + { + protected override PreValueMigratorCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs new file mode 100644 index 0000000000..8dfa464508 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs @@ -0,0 +1,26 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ +[RuntimeLevel(MinLevel = RuntimeLevel.Upgrade, MaxLevel = RuntimeLevel.Upgrade)] // only on upgrades +public class PreValueMigratorComposer : ICoreComposer +{ + public void Compose(Composition composition) + { + // do NOT add DefaultPreValueMigrator to this list! + // it will be automatically used if nothing matches + + composition.WithCollectionBuilder() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + } +} +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs new file mode 100644 index 0000000000..c04e7c8fda --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class RenamingPreValueMigrator : DefaultPreValueMigrator + { + private readonly string[] _editors = + { + "Umbraco.NoEdit" + }; + + public override bool CanMigrate(string editorAlias) + => _editors.Contains(editorAlias); + + public override string GetNewAlias(string editorAlias) + { + switch (editorAlias) + { + case "Umbraco.NoEdit": + return Constants.PropertyEditors.Aliases.Label; + default: + throw new Exception("panic"); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RichTextPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RichTextPreValueMigrator.cs new file mode 100644 index 0000000000..3670c52c64 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RichTextPreValueMigrator.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class RichTextPreValueMigrator : DefaultPreValueMigrator + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.TinyMCEv3"; + + public override string GetNewAlias(string editorAlias) + => Constants.PropertyEditors.Aliases.TinyMce; + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "hideLabel") + return preValue.Value == "1"; + + return preValue.Value.DetectIsJson() ? JsonConvert.DeserializeObject(preValue.Value) : preValue.Value; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/UmbracoSliderPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/UmbracoSliderPreValueMigrator.cs new file mode 100644 index 0000000000..dc6de5e493 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/UmbracoSliderPreValueMigrator.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class UmbracoSliderPreValueMigrator : PreValueMigratorBase + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.Slider"; + + public override object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + return new SliderConfiguration + { + EnableRange = GetBoolValue(preValues, "enableRange"), + InitialValue = GetDecimalValue(preValues, "initVal1"), + InitialValue2 = GetDecimalValue(preValues, "initVal2"), + MaximumValue = GetDecimalValue(preValues, "maxVal"), + MinimumValue = GetDecimalValue(preValues, "minVal"), + StepIncrements = GetDecimalValue(preValues, "step") + }; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs new file mode 100644 index 0000000000..07fefc8e85 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class ValueListPreValueMigrator : IPreValueMigrator + { + private readonly string[] _editors = + { + "Umbraco.RadioButtonList", + "Umbraco.CheckBoxList", + "Umbraco.DropDown", + "Umbraco.DropdownlistPublishingKeys", + "Umbraco.DropDownMultiple", + "Umbraco.DropdownlistMultiplePublishKeys" + }; + + public bool CanMigrate(string editorAlias) + => _editors.Contains(editorAlias); + + public virtual string GetNewAlias(string editorAlias) + => null; + + public object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + var config = new ValueListConfiguration(); + foreach (var preValue in preValues.Values) + config.Items.Add(new ValueListConfiguration.ValueListItem { Id = preValue.Id, Value = preValue.Value }); + return config; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropDownPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropDownPropertyEditorsMigration.cs index ed2990aff7..d30719231a 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropDownPropertyEditorsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropDownPropertyEditorsMigration.cs @@ -1,39 +1,35 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Cache; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.PostMigrations; using Umbraco.Core.Models; -using Umbraco.Core.Sync; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { - public class DropDownPropertyEditorsMigration : MigrationBase + public class DropDownPropertyEditorsMigration : PropertyEditorsMigrationBase { - private readonly CacheRefresherCollection _cacheRefreshers; - private readonly IServerMessenger _serverMessenger; - - public DropDownPropertyEditorsMigration(IMigrationContext context, CacheRefresherCollection cacheRefreshers, IServerMessenger serverMessenger) + public DropDownPropertyEditorsMigration(IMigrationContext context) : base(context) - { - _cacheRefreshers = cacheRefreshers; - _serverMessenger = serverMessenger; - } - - // dummy editor for deserialization - private class ValueListConfigurationEditor : ConfigurationEditor { } public override void Migrate() { - //need to convert the old drop down data types to use the new one - var dataTypes = Database.Fetch(Sql() - .Select() - .From() - .Where(x => x.EditorAlias.Contains(".DropDown"))); + var refreshCache = Migrate(GetDataTypes(".DropDown", false)); + + // if some data types have been updated directly in the database (editing DataTypeDto and/or PropertyDataDto), + // bypassing the services, then we need to rebuild the cache entirely, including the umbracoContentNu table + if (refreshCache) + Context.AddPostMigration(); + } + + private bool Migrate(IEnumerable dataTypes) + { + var refreshCache = false; + ConfigurationEditor configurationEditor = null; foreach (var dataType in dataTypes) { @@ -42,14 +38,16 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 if (!dataType.Configuration.IsNullOrWhiteSpace()) { // parse configuration, and update everything accordingly + if (configurationEditor == null) + configurationEditor = new ValueListConfigurationEditor(); try { - config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase(dataType.Configuration); + config = (ValueListConfiguration) configurationEditor.FromDatabase(dataType.Configuration); } catch (Exception ex) { Logger.Error( - ex, "Invalid drop down configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared", + ex, "Invalid configuration: \"{Configuration}\", cannot convert editor.", dataType.Configuration); // reset @@ -65,7 +63,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 .Where(x => x.DataTypeId == dataType.NodeId)); // update dtos - var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config)); + var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config, true)); // persist changes foreach (var propertyDataDto in updatedDtos) @@ -77,7 +75,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 config = new ValueListConfiguration(); } - var requiresCacheRebuild = false; switch (dataType.EditorAlias) { case string ea when ea.InvariantEquals("Umbraco.DropDown"): @@ -85,29 +82,25 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 break; case string ea when ea.InvariantEquals("Umbraco.DropdownlistPublishingKeys"): UpdateDataType(dataType, config, false); - requiresCacheRebuild = true; break; case string ea when ea.InvariantEquals("Umbraco.DropDownMultiple"): UpdateDataType(dataType, config, true); break; case string ea when ea.InvariantEquals("Umbraco.DropdownlistMultiplePublishKeys"): UpdateDataType(dataType, config, true); - requiresCacheRebuild = true; break; } - if (requiresCacheRebuild) - { - var dataTypeCacheRefresher = _cacheRefreshers[Guid.Parse("35B16C25-A17E-45D7-BC8F-EDAB1DCC28D2")]; - _serverMessenger.PerformRefreshAll(dataTypeCacheRefresher); - } + refreshCache = true; } + + return refreshCache; } private void UpdateDataType(DataTypeDto dataType, ValueListConfiguration config, bool isMultiple) { - dataType.EditorAlias = Constants.PropertyEditors.Aliases.DropDownListFlexible; dataType.DbType = ValueStorageType.Nvarchar.ToString(); + dataType.EditorAlias = Constants.PropertyEditors.Aliases.DropDownListFlexible; var flexConfig = new DropDownFlexibleConfiguration { @@ -118,68 +111,5 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 Database.Update(dataType); } - - private bool UpdatePropertyDataDto(PropertyDataDto propData, ValueListConfiguration config) - { - //Get the INT ids stored for this property/drop down - int[] ids = null; - if (!propData.VarcharValue.IsNullOrWhiteSpace()) - { - ids = ConvertStringValues(propData.VarcharValue); - } - else if (!propData.TextValue.IsNullOrWhiteSpace()) - { - ids = ConvertStringValues(propData.TextValue); - } - else if (propData.IntegerValue.HasValue) - { - ids = new[] { propData.IntegerValue.Value }; - } - - //if there are INT ids, convert them to values based on the configured pre-values - if (ids != null && ids.Length > 0) - { - //map the ids to values - var vals = new List(); - var canConvert = true; - foreach (var id in ids) - { - var val = config.Items.FirstOrDefault(x => x.Id == id); - if (val != null) - vals.Add(val.Value); - else - { - Logger.Warn( - "Could not find associated data type configuration for stored Id {DataTypeId}", id); - canConvert = false; - } - } - if (canConvert) - { - propData.VarcharValue = string.Join(",", vals); - propData.TextValue = null; - propData.IntegerValue = null; - return true; - } - } - - return false; - } - - private int[] ConvertStringValues(string val) - { - var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - var intVals = splitVals - .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) - .Where(x => x != int.MinValue) - .ToArray(); - - //only return if the number of values are the same (i.e. All INTs) - if (splitVals.Length == intVals.Length) - return intVals; - - return null; - } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs index 26668361bd..def6a93400 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs @@ -8,7 +8,8 @@ public override void Migrate() { - Delete.Table("umbracoMigration").Do(); + if (TableExists("umbracoMigration")) + Delete.Table("umbracoMigration").Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropPreValueTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropPreValueTable.cs index fa6e47fac7..918510d13c 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropPreValueTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropPreValueTable.cs @@ -3,8 +3,7 @@ public class DropPreValueTable : MigrationBase { public DropPreValueTable(IMigrationContext context) : base(context) - { - } + { } public override void Migrate() { diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLanguageIsoCodeLength.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLanguageIsoCodeLength.cs new file mode 100644 index 0000000000..8de06c9a6f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLanguageIsoCodeLength.cs @@ -0,0 +1,21 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class FixLanguageIsoCodeLength : MigrationBase + { + public FixLanguageIsoCodeLength(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // there is some confusion here when upgrading from v7 + // it should be 14 already but that's not always the case + + Alter.Table("umbracoLanguage") + .AlterColumn("languageISOCode") + .AsString(14) + .Nullable() + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs deleted file mode 100644 index fbb233927b..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 -{ - public class FixLockTablePrimaryKey : MigrationBase - { - public FixLockTablePrimaryKey(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - // at some point, the KeyValueService dropped the PK and failed to re-create it, - // so the PK is gone - make sure we have one, and create if needed - - var constraints = SqlSyntax.GetConstraintsPerTable(Database); - var exists = constraints.Any(x => x.Item2 == "PK_umbracoLock"); - - if (!exists) - Create.PrimaryKey("PK_umbracoLock").OnTable(Constants.DatabaseSchema.Tables.Lock).Column("id").Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs index 2e366c7c14..651c95e6bd 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs @@ -11,19 +11,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { AddColumn("culture"); - - Delete.Index("IX_umbracoRedirectUrl").OnTable(Constants.DatabaseSchema.Tables.RedirectUrl).Do(); - Create.Index("IX_umbracoRedirectUrl").OnTable(Constants.DatabaseSchema.Tables.RedirectUrl) - .OnColumn("urlHash") - .Ascending() - .OnColumn("contentKey") - .Ascending() - .OnColumn("culture") - .Ascending() - .OnColumn("createDateUtc") - .Ascending() - .WithOptions().Unique() - .Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeTagsVariant.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeTagsVariant.cs index 9ccd6d5e76..c898187884 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeTagsVariant.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeTagsVariant.cs @@ -11,17 +11,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { AddColumn("languageId"); - - Delete.Index($"IX_{Constants.DatabaseSchema.Tables.Tag}").OnTable(Constants.DatabaseSchema.Tables.Tag).Do(); - Create.Index($"IX_{Constants.DatabaseSchema.Tables.Tag}").OnTable(Constants.DatabaseSchema.Tables.Tag) - .OnColumn("group") - .Ascending() - .OnColumn("tag") - .Ascending() - .OnColumn("languageId") - .Ascending() - .WithOptions().Unique() - .Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs index a434b9f1c1..0d451e8460 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - var dataTypes = GetDataTypes("Umbraco.Date"); + var dataTypes = GetDataTypes(Constants.PropertyEditors.Legacy.Aliases.Date); foreach (var dataType in dataTypes) { @@ -25,6 +25,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { config = (DateTimeConfiguration) new CustomDateTimeConfigurationEditor().FromDatabase( dataType.Configuration); + + // If the Umbraco.Date type is the default from V7 and it has never been updated, then the + // configuration is empty, and the format stuff is handled by in JS by moment.js. - We can't do that + // after the migration, so we force the format to the default from V7. + if (string.IsNullOrEmpty(dataType.Configuration)) + { + config.Format = "YYYY-MM-DD"; + }; } catch (Exception ex) { diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs index 064ffc7228..89a8f010ec 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs @@ -12,12 +12,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - RenameDataType(Constants.PropertyEditors.Aliases.ContentPicker + "2", Constants.PropertyEditors.Aliases.ContentPicker); - RenameDataType(Constants.PropertyEditors.Aliases.MediaPicker + "2", Constants.PropertyEditors.Aliases.MediaPicker); - RenameDataType(Constants.PropertyEditors.Aliases.MemberPicker + "2", Constants.PropertyEditors.Aliases.MemberPicker); - RenameDataType(Constants.PropertyEditors.Aliases.MultiNodeTreePicker + "2", Constants.PropertyEditors.Aliases.MultiNodeTreePicker); - RenameDataType("Umbraco.TextboxMultiple", Constants.PropertyEditors.Aliases.TextArea, false); - RenameDataType("Umbraco.Textbox", Constants.PropertyEditors.Aliases.TextBox, false); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.ContentPicker2, Constants.PropertyEditors.Aliases.ContentPicker); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MediaPicker2, Constants.PropertyEditors.Aliases.MediaPicker); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MemberPicker2, Constants.PropertyEditors.Aliases.MemberPicker); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2, Constants.PropertyEditors.Aliases.MultiNodeTreePicker); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple, Constants.PropertyEditors.Aliases.TextArea, false); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.Textbox, Constants.PropertyEditors.Aliases.TextBox, false); } private void RenameDataType(string fromAlias, string toAlias, bool checkCollision = true) @@ -30,7 +30,20 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 .Where(x => x.EditorAlias == toAlias)); if (oldCount > 0) - throw new InvalidOperationException($"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used."); + { + // If we throw it means that the upgrade will exit and cannot continue. + // This will occur if a v7 site has the old "Obsolete" property editors that are already named with the `toAlias` name. + // TODO: We should have an additional upgrade step when going from 7 -> 8 like we did with 6 -> 7 that shows a compatibility report, + // this would include this check and then we can provide users with information on what they should do (i.e. before upgrading to v8 they will + // need to migrate these old obsolete editors to non-obsolete editors) + + throw new InvalidOperationException( + $"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used." + + $"This is generally because when upgrading from a v7 to v8 site, the v7 site contains Data Types that reference old and already Obsolete " + + $"Property Editors. Before upgrading to v8, any Data Types using property editors that are named with the prefix '(Obsolete)' must be migrated " + + $"to the non-obsolete v7 property editors of the same type."); + } + } Database.Execute(Sql() diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs new file mode 100644 index 0000000000..605f8a9eed --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public abstract class PropertyEditorsMigrationBase : MigrationBase + { + protected PropertyEditorsMigrationBase(IMigrationContext context) + : base(context) + { } + + internal List GetDataTypes(string editorAlias, bool strict = true) + { + var sql = Sql() + .Select() + .From(); + + sql = strict + ? sql.Where(x => x.EditorAlias == editorAlias) + : sql.Where(x => x.EditorAlias.Contains(editorAlias)); + + return Database.Fetch(sql); + } + + protected int[] ConvertStringValues(string val) + { + var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + var intVals = splitVals + .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) + .Where(x => x != int.MinValue) + .ToArray(); + + //only return if the number of values are the same (i.e. All INTs) + if (splitVals.Length == intVals.Length) + return intVals; + + return null; + } + + internal bool UpdatePropertyDataDto(PropertyDataDto propData, ValueListConfiguration config, bool isMultiple) + { + //Get the INT ids stored for this property/drop down + int[] ids = null; + if (!propData.VarcharValue.IsNullOrWhiteSpace()) + { + ids = ConvertStringValues(propData.VarcharValue); + } + else if (!propData.TextValue.IsNullOrWhiteSpace()) + { + ids = ConvertStringValues(propData.TextValue); + } + else if (propData.IntegerValue.HasValue) + { + ids = new[] { propData.IntegerValue.Value }; + } + + // if there are INT ids, convert them to values based on the configuration + if (ids == null || ids.Length <= 0) return false; + + // map ids to values + var values = new List(); + var canConvert = true; + + foreach (var id in ids) + { + var val = config.Items.FirstOrDefault(x => x.Id == id); + if (val != null) + { + values.Add(val.Value); + continue; + } + + Logger.Warn(GetType(), "Could not find PropertyData {PropertyDataId} value '{PropertyValue}' in the datatype configuration: {Values}.", + propData.Id, id, string.Join(", ", config.Items.Select(x => x.Id + ":" + x.Value))); + canConvert = false; + } + + if (!canConvert) return false; + + propData.VarcharValue = isMultiple ? JsonConvert.SerializeObject(values) : values[0]; + propData.TextValue = null; + propData.IntegerValue = null; + return true; + } + + // dummy editor for deserialization + protected class ValueListConfigurationEditor : ConfigurationEditor + { } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs deleted file mode 100644 index 1944c8079f..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Umbraco.Core.Logging; -using Umbraco.Core.Migrations.PostMigrations; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 -{ - public class RadioAndCheckboxAndDropdownPropertyEditorsMigration : MigrationBase - { - public RadioAndCheckboxAndDropdownPropertyEditorsMigration(IMigrationContext context) - : base(context) - { - } - - public override void Migrate() - { - var refreshCache = false; - - refreshCache |= Migrate(Constants.PropertyEditors.Aliases.RadioButtonList, (dto, configuration) => UpdateRadioOrCheckboxPropertyDataDto(dto, configuration, true)); - refreshCache |= Migrate(Constants.PropertyEditors.Aliases.CheckBoxList, (dto, configuration) => UpdateRadioOrCheckboxPropertyDataDto(dto, configuration, false)); - refreshCache |= Migrate(Constants.PropertyEditors.Aliases.DropDownListFlexible, UpdateDropDownPropertyDataDto); - - if (refreshCache) - { - Context.AddPostMigration(); - } - } - - private bool Migrate(string editorAlias, Func updateRadioPropertyDataFunc) - { - var refreshCache = false; - var dataTypes = GetDataTypes(editorAlias); - - foreach (var dataType in dataTypes) - { - ValueListConfiguration config; - - if (dataType.Configuration.IsNullOrWhiteSpace()) - continue; - - // parse configuration, and update everything accordingly - try - { - config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase( - dataType.Configuration); - } - catch (Exception ex) - { - Logger.Error( - ex, - "Invalid property editor configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared", - dataType.Configuration); - - continue; - } - - // get property data dtos - var propertyDataDtos = Database.Fetch(Sql() - .Select() - .From() - .InnerJoin() - .On((pt, pd) => pt.Id == pd.PropertyTypeId) - .InnerJoin() - .On((dt, pt) => dt.NodeId == pt.DataTypeId) - .Where(x => x.DataTypeId == dataType.NodeId)); - - // update dtos - var updatedDtos = propertyDataDtos.Where(x => updateRadioPropertyDataFunc(x, config)); - - // persist changes - foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto); - - UpdateDataType(dataType); - refreshCache = true; - } - - return refreshCache; - } - - private List GetDataTypes(string editorAlias) - { - //need to convert the old drop down data types to use the new one - var dataTypes = Database.Fetch(Sql() - .Select() - .From() - .Where(x => x.EditorAlias == editorAlias)); - return dataTypes; - } - - private void UpdateDataType(DataTypeDto dataType) - { - dataType.DbType = ValueStorageType.Nvarchar.ToString(); - Database.Update(dataType); - } - - private bool UpdateRadioOrCheckboxPropertyDataDto(PropertyDataDto propData, ValueListConfiguration config, bool singleValue) - { - //Get the INT ids stored for this property/drop down - int[] ids = null; - if (!propData.VarcharValue.IsNullOrWhiteSpace()) - { - ids = ConvertStringValues(propData.VarcharValue); - } - else if (!propData.TextValue.IsNullOrWhiteSpace()) - { - ids = ConvertStringValues(propData.TextValue); - } - else if (propData.IntegerValue.HasValue) - { - ids = new[] {propData.IntegerValue.Value}; - } - - //if there are INT ids, convert them to values based on the configuration - if (ids == null || ids.Length <= 0) return false; - - //map the ids to values - var values = new List(); - var canConvert = true; - - foreach (var id in ids) - { - var val = config.Items.FirstOrDefault(x => x.Id == id); - if (val != null) - values.Add(val.Value); - else - { - Logger.Warn( - "Could not find associated data type configuration for stored Id {DataTypeId}", id); - canConvert = false; - } - } - - if (!canConvert) return false; - - //The radio button only supports selecting a single value, so if there are multiple for some insane reason we can only use the first - propData.VarcharValue = singleValue ? values[0] : JsonConvert.SerializeObject(values); - propData.TextValue = null; - propData.IntegerValue = null; - return true; - } - - private bool UpdateDropDownPropertyDataDto(PropertyDataDto propData, ValueListConfiguration config) - { - //Get the INT ids stored for this property/drop down - var values = propData.VarcharValue.Split(new []{","}, StringSplitOptions.RemoveEmptyEntries); - - //if there are INT ids, convert them to values based on the configuration - if (values == null || values.Length <= 0) return false; - - //The radio button only supports selecting a single value, so if there are multiple for some insane reason we can only use the first - propData.VarcharValue = JsonConvert.SerializeObject(values); - propData.TextValue = null; - propData.IntegerValue = null; - return true; - } - - private int[] ConvertStringValues(string val) - { - var splitVals = val.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); - - var intVals = splitVals - .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) - .Where(x => x != int.MinValue) - .ToArray(); - - //only return if the number of values are the same (i.e. All INTs) - if (splitVals.Length == intVals.Length) - return intVals; - - return null; - } - - private class ValueListConfigurationEditor : ConfigurationEditor - { - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxPropertyEditorsMigration.cs new file mode 100644 index 0000000000..e96fa1f7e9 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxPropertyEditorsMigration.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.PostMigrations; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class RadioAndCheckboxPropertyEditorsMigration : PropertyEditorsMigrationBase + { + public RadioAndCheckboxPropertyEditorsMigration(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var refreshCache = false; + + refreshCache |= Migrate(GetDataTypes(Constants.PropertyEditors.Aliases.RadioButtonList), false); + refreshCache |= Migrate(GetDataTypes(Constants.PropertyEditors.Aliases.CheckBoxList), true); + + // if some data types have been updated directly in the database (editing DataTypeDto and/or PropertyDataDto), + // bypassing the services, then we need to rebuild the cache entirely, including the umbracoContentNu table + if (refreshCache) + Context.AddPostMigration(); + } + + private bool Migrate(IEnumerable dataTypes, bool isMultiple) + { + var refreshCache = false; + ConfigurationEditor configurationEditor = null; + + foreach (var dataType in dataTypes) + { + ValueListConfiguration config; + + if (dataType.Configuration.IsNullOrWhiteSpace()) + continue; + + // parse configuration, and update everything accordingly + if (configurationEditor == null) + configurationEditor = new ValueListConfigurationEditor(); + try + { + config = (ValueListConfiguration) configurationEditor.FromDatabase(dataType.Configuration); + } + catch (Exception ex) + { + Logger.Error( + ex, "Invalid configuration: \"{Configuration}\", cannot convert editor.", + dataType.Configuration); + + continue; + } + + // get property data dtos + var propertyDataDtos = Database.Fetch(Sql() + .Select() + .From() + .InnerJoin().On((pt, pd) => pt.Id == pd.PropertyTypeId) + .InnerJoin().On((dt, pt) => dt.NodeId == pt.DataTypeId) + .Where(x => x.DataTypeId == dataType.NodeId)); + + // update dtos + var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config, isMultiple)); + + // persist changes + foreach (var propertyDataDto in updatedDtos) + Database.Update(propertyDataDto); + + UpdateDataType(dataType); + refreshCache = true; + } + + return refreshCache; + } + + private void UpdateDataType(DataTypeDto dataType) + { + dataType.DbType = ValueStorageType.Nvarchar.ToString(); + Database.Update(dataType); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorXmlColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorXmlColumns.cs deleted file mode 100644 index c683940f60..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorXmlColumns.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 -{ - public class RefactorXmlColumns : MigrationBase - { - public RefactorXmlColumns(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - if (ColumnExists("cmsContentXml", "Rv") == false) - Alter.Table("cmsContentXml").AddColumn("Rv").AsInt64().NotNullable().WithDefaultValue(0).Do(); - - if (ColumnExists("cmsPreviewXml", "Rv") == false) - Alter.Table("cmsPreviewXml").AddColumn("Rv").AsInt64().NotNullable().WithDefaultValue(0).Do(); - - // remove the any PK_ and the FK_ to cmsContentVersion.VersionId - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - var dups = new List(); - foreach (var c in constraints.Where(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantStartsWith("PK_"))) - { - var keyName = c.Item3.ToLowerInvariant(); - if (dups.Contains(keyName)) - { - Logger.Warn("Duplicate constraint '{Constraint}'", c.Item3); - continue; - } - dups.Add(keyName); - Delete.PrimaryKey(c.Item3).FromTable(c.Item1).Do(); - } - foreach (var c in constraints.Where(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantStartsWith("FK_cmsPreviewXml_cmsContentVersion"))) - { - Delete.ForeignKey().FromTable("cmsPreviewXml").ForeignColumn("VersionId") - .ToTable("cmsContentVersion").PrimaryColumn("VersionId") - .Do(); - } - - if (ColumnExists("cmsPreviewXml", "Timestamp")) - Delete.Column("Timestamp").FromTable("cmsPreviewXml").Do(); - - if (ColumnExists("cmsPreviewXml", "VersionId")) - { - RemoveDuplicates(); - Delete.Column("VersionId").FromTable("cmsPreviewXml").Do(); - } - - // re-create the primary key - Create.PrimaryKey("PK_cmsPreviewXml") - .OnTable("cmsPreviewXml") - .Columns(new[] { "nodeId" }) - .Do(); - } - - private void RemoveDuplicates() - { - const string sql = @"delete from cmsPreviewXml where versionId in ( -select cmsPreviewXml.versionId from cmsPreviewXml -join cmsDocument on cmsPreviewXml.versionId=cmsDocument.versionId -where cmsDocument.newest <> 1)"; - - Context.Database.Execute(sql); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs new file mode 100644 index 0000000000..b60923fcba --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs @@ -0,0 +1,46 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class RenameMediaVersionTable : MigrationBase + { + public RenameMediaVersionTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Rename.Table("cmsMedia").To(Constants.DatabaseSchema.Tables.MediaVersion).Do(); + + // that is not supported on SqlCE + //Rename.Column("versionId").OnTable(Constants.DatabaseSchema.Tables.MediaVersion).To("id").Do(); + + AddColumn("id", out var sqls); + + // SQLCE does not support UPDATE...FROM + var temp2 = Database.Fetch($@"SELECT v.versionId, v.id +FROM cmsContentVersion v +JOIN umbracoNode n on v.contentId=n.id +WHERE n.nodeObjectType='{Constants.ObjectTypes.Media}'"); + foreach (var t in temp2) + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id={t.id} WHERE versionId='{t.versionId}'").Do(); + + foreach (var sql in sqls) + Execute.Sql(sql).Do(); + + AddColumn("path", out sqls); + + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET path=mediaPath").Do(); + + foreach (var sql in sqls) + Execute.Sql(sql).Do(); + + // we had to run sqls to get the NULL constraints, but we need to get rid of most + Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.MediaVersion).Do(); + + Delete.Column("mediaPath").FromTable(Constants.DatabaseSchema.Tables.MediaVersion).Do(); + Delete.Column("versionId").FromTable(Constants.DatabaseSchema.Tables.MediaVersion).Do(); + Delete.Column("nodeId").FromTable(Constants.DatabaseSchema.Tables.MediaVersion).Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs index 9bbccf368c..0543b57fcc 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs @@ -2,7 +2,9 @@ { public class RenameUmbracoDomainsTable : MigrationBase { - public RenameUmbracoDomainsTable(IMigrationContext context) : base(context) { } + public RenameUmbracoDomainsTable(IMigrationContext context) + : base(context) + { } public override void Migrate() { diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs index ba29880e79..64ac20d175 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs @@ -11,20 +11,27 @@ var exists = Database.Fetch("select id from umbracoUser where id=-1;").Count > 0; if (exists) return; - Database.Execute("update umbracoUser set userLogin = userLogin + '__' where userId=0"); + Database.Execute("update umbracoUser set userLogin = userLogin + '__' where id=0"); Database.Execute("set identity_insert umbracoUser on;"); Database.Execute(@" - insert into umbracoUser select -1, - userDisabled, userNoConsole, userName, substring(userLogin, 1, len(userLogin) - 2), userPassword, passwordConfig, + insert into umbracoUser (id, + userDisabled, userNoConsole, userName, userLogin, userPassword, passwordConfig, + userEmail, userLanguage, securityStampToken, failedLoginAttempts, lastLockoutDate, + lastPasswordChangeDate, lastLoginDate, emailConfirmedDate, invitedDate, + createDate, updateDate, avatar, tourData) + select + -1 id, + userDisabled, userNoConsole, userName, substring(userLogin, 1, len(userLogin) - 2) userLogin, userPassword, passwordConfig, userEmail, userLanguage, securityStampToken, failedLoginAttempts, lastLockoutDate, lastPasswordChangeDate, lastLoginDate, emailConfirmedDate, invitedDate, - createDate, updateDate, avatar + createDate, updateDate, avatar, tourData from umbracoUser where id=0;"); Database.Execute("set identity_insert umbracoUser off;"); Database.Execute("update umbracoUser2UserGroup set userId=-1 where userId=0;"); Database.Execute("update umbracoNode set nodeUser=-1 where nodeUser=0;"); + Database.Execute("update umbracoUserLogin set userId=-1 where userId=0;"); Database.Execute($"update {Constants.DatabaseSchema.Tables.ContentVersion} set userId=-1 where userId=0;"); Database.Execute("delete from umbracoUser where id=0;"); } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs index cd4de179bd..2f7ffe8679 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs @@ -14,33 +14,45 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { //Get anything currently scheduled - var scheduleSql = new Sql() - .Select("nodeId", "releaseDate", "expireDate") + var releaseSql = new Sql() + .Select("nodeId", "releaseDate") .From("umbracoDocument") - .Where("releaseDate IS NOT NULL OR expireDate IS NOT NULL"); - var schedules = Database.Dictionary (scheduleSql); + .Where("releaseDate IS NOT NULL"); + var releases = Database.Dictionary (releaseSql); + + var expireSql = new Sql() + .Select("nodeId", "expireDate") + .From("umbracoDocument") + .Where("expireDate IS NOT NULL"); + var expires = Database.Dictionary(expireSql); + //drop old cols Delete.Column("releaseDate").FromTable("umbracoDocument").Do(); Delete.Column("expireDate").FromTable("umbracoDocument").Do(); //add new table - Create.Table().Do(); + Create.Table(true).Do(); //migrate the schedule - foreach(var s in schedules) + foreach(var s in releases) { - var date = s.Value.releaseDate; + var date = s.Value; var action = ContentScheduleAction.Release.ToString(); - if (!date.HasValue) - { - date = s.Value.expireDate; - action = ContentScheduleAction.Expire.ToString(); - } Insert.IntoTable(ContentScheduleDto.TableName) - .Row(new { nodeId = s.Key, date = date.Value, action = action }) + .Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action }) .Do(); } + + foreach (var s in expires) + { + var date = s.Value; + var action = ContentScheduleAction.Expire.ToString(); + + Insert.IntoTable(ContentScheduleDto.TableName) + .Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action }) + .Do(); + } } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs index 5dc5e0b6fe..d6a5380e31 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.Dtos; +using System.Linq; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { @@ -14,13 +15,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 AlterColumn(Constants.DatabaseSchema.Tables.Tag, "group"); AlterColumn(Constants.DatabaseSchema.Tables.Tag, "tag"); - //AddColumn(Constants.DatabaseSchema.Tables.Tag, "key"); - // kill unused parentId column - Delete.ForeignKey("FK_cmsTags_cmsTags").OnTable(Constants.DatabaseSchema.Tables.Tag).Do(); Delete.Column("ParentId").FromTable(Constants.DatabaseSchema.Tables.Tag).Do(); } } - - // fixes TagsMigration that... originally failed to properly drop the ParentId column } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdatePickerIntegerValuesToUdi.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdatePickerIntegerValuesToUdi.cs index 2e37c79632..7f7cf8474c 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdatePickerIntegerValuesToUdi.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdatePickerIntegerValuesToUdi.cs @@ -2,6 +2,7 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Core.Composing; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs index 68a68ec53e..f16c9cd761 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs @@ -1,6 +1,4 @@ -using NPoco; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { @@ -15,24 +13,19 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - //first allow NULL-able + // first allow NULL-able Alter.Table(ContentVersionCultureVariationDto.TableName).AlterColumn("availableUserId").AsInt32().Nullable().Do(); Alter.Table(ContentVersionDto.TableName).AlterColumn("userId").AsInt32().Nullable().Do(); Alter.Table(Constants.DatabaseSchema.Tables.Log).AlterColumn("userId").AsInt32().Nullable().Do(); Alter.Table(NodeDto.TableName).AlterColumn("nodeUser").AsInt32().Nullable().Do(); - //then we can update any non existing users to NULL + // then we can update any non existing users to NULL Execute.Sql($"UPDATE {ContentVersionCultureVariationDto.TableName} SET availableUserId = NULL WHERE availableUserId NOT IN (SELECT id FROM {UserDto.TableName})").Do(); Execute.Sql($"UPDATE {ContentVersionDto.TableName} SET userId = NULL WHERE userId NOT IN (SELECT id FROM {UserDto.TableName})").Do(); Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Log} SET userId = NULL WHERE userId NOT IN (SELECT id FROM {UserDto.TableName})").Do(); Execute.Sql($"UPDATE {NodeDto.TableName} SET nodeUser = NULL WHERE nodeUser NOT IN (SELECT id FROM {UserDto.TableName})").Do(); - //now NULL-able with FKs - Alter.Table(ContentVersionCultureVariationDto.TableName).AlterColumn("availableUserId").AsInt32().Nullable().ForeignKey(UserDto.TableName, "id").Do(); - Alter.Table(ContentVersionDto.TableName).AlterColumn("userId").AsInt32().Nullable().ForeignKey(UserDto.TableName, "id").Do(); - Alter.Table(Constants.DatabaseSchema.Tables.Log).AlterColumn("userId").AsInt32().Nullable().ForeignKey(UserDto.TableName, "id").Do(); - Alter.Table(NodeDto.TableName).AlterColumn("nodeUser").AsInt32().Nullable().ForeignKey(UserDto.TableName, "id").Do(); + // FKs will be created after migrations } - } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs index c193fdeb1f..8c60d30680 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs @@ -18,9 +18,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - // delete *all* keys and indexes - because of FKs - Delete.KeysAndIndexes().Do(); - MigratePropertyData(); MigrateContentAndPropertyTypes(); MigrateContent(); @@ -46,10 +43,6 @@ HAVING COUNT(v2.id) <> 1").Any()) Debugger.Break(); throw new Exception("Migration failed: missing or duplicate 'current' content versions."); } - - // re-create *all* keys and indexes - foreach (var x in DatabaseSchemaCreator.OrderedTables) - Create.KeysAndIndexes(x).Do(); } private void MigratePropertyData() @@ -220,8 +213,10 @@ WHERE versionId NOT IN (SELECT (versionId) FROM {PreTables.ContentVersion} WHERE Delete.Column("text").FromTable(PreTables.Document).Do(); Delete.Column("templateId").FromTable(PreTables.Document).Do(); Delete.Column("documentUser").FromTable(PreTables.Document).Do(); + Delete.DefaultConstraint().OnTable(PreTables.Document).OnColumn("updateDate").Do(); Delete.Column("updateDate").FromTable(PreTables.Document).Do(); Delete.Column("versionId").FromTable(PreTables.Document).Do(); + Delete.DefaultConstraint().OnTable(PreTables.Document).OnColumn("newest").Do(); Delete.Column("newest").FromTable(PreTables.Document).Do(); // add and populate edited column @@ -320,7 +315,7 @@ WHERE v1.propertyTypeId=v2.propertyTypeId AND v1.languageId=v2.languageId AND v1 public const string Tag = "cmsTags"; public const string TagRelationship = "cmsTagRelationship"; - + // ReSharper restore UnusedMember.Local } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs deleted file mode 100644 index 0ceb366e1c..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Core.Migrations.PostMigrations; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 -{ - public class ChangeNuCacheJsonFormat : MigrationBase - { - public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context) - { } - - public override void Migrate() - { - // nothing - just adding the post-migration - Context.AddPostMigration(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs index 50c7daf65d..1b6597a660 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Newtonsoft.Json; @@ -26,8 +27,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); var sqlPropertyData = Sql() - .Select(x => x.Id, x => x.TextValue) - .AndSelect(x => x.EditorAlias) + .Select(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto))) .From() .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) @@ -37,6 +37,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 var properties = Database.Fetch(sqlPropertyData); + var exceptions = new List(); foreach (var property in properties) { var value = property.TextValue; @@ -44,26 +45,47 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 if (property.PropertyTypeDto.DataTypeDto.EditorAlias == Constants.PropertyEditors.Aliases.Grid) { - var obj = JsonConvert.DeserializeObject(value); - var allControls = obj.SelectTokens("$.sections..rows..areas..controls"); - - foreach (var control in allControls.SelectMany(c => c)) + try { - var controlValue = control["value"]; - if (controlValue.Type == JTokenType.String) + var obj = JsonConvert.DeserializeObject(value); + var allControls = obj.SelectTokens("$.sections..rows..areas..controls"); + + foreach (var control in allControls.SelectMany(c => c)) { - control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value()); + var controlValue = control["value"]; + if (controlValue.Type == JTokenType.String) + { + control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value()); + } } + + property.TextValue = JsonConvert.SerializeObject(obj); + } + catch (JsonException e) + { + exceptions.Add(new InvalidOperationException( + "Cannot deserialize the value as json. This can be because the property editor " + + "type is changed from another type into a grid. Old versions of the value in this " + + "property can have the structure from the old property editor type. This needs to be " + + "changed manually before updating the database.\n" + + $"Property info: Id = {property.Id}, LanguageId = {property.LanguageId}, VersionId = {property.VersionId}, Value = {property.Value}" + , e)); + continue; } - property.TextValue = JsonConvert.SerializeObject(obj); } else { property.TextValue = UpdateMediaUrls(mediaLinkPattern, value); } - Database.Update(property, x => x.TextValue); + Database.Update(property); + } + + + if (exceptions.Any()) + { + throw new AggregateException("One or more errors related to unexpected data in grid values occurred.", exceptions); } Context.AddPostMigration(); diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/FixContentNuCascade.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/FixContentNuCascade.cs new file mode 100644 index 0000000000..09ea941742 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/FixContentNuCascade.cs @@ -0,0 +1,17 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 +{ + public class FixContentNuCascade : MigrationBase + { + public FixContentNuCascade(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Delete.KeysAndIndexes().Do(); + Create.KeysAndIndexes().Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/RenameUserLoginDtoDateIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/RenameUserLoginDtoDateIndex.cs new file mode 100644 index 0000000000..c0b9c8f2db --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/RenameUserLoginDtoDateIndex.cs @@ -0,0 +1,36 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 +{ + public class RenameUserLoginDtoDateIndex : MigrationBase + { + public RenameUserLoginDtoDateIndex(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // there has been some confusion with an index name, resulting in + // different names depending on which migration path was followed, + // and discrepancies between an upgraded or an installed database. + // better normalize + + if (IndexExists("IX_umbracoUserLogin_lastValidatedUtc")) + return; + + if (IndexExists("IX_userLoginDto_lastValidatedUtc")) + Delete + .Index("IX_userLoginDto_lastValidatedUtc") + .OnTable(UserLoginDto.TableName) + .Do(); + + Create + .Index("IX_umbracoUserLogin_lastValidatedUtc") + .OnTable(UserLoginDto.TableName) + .OnColumn("lastValidatedUtc") + .Ascending() + .WithOptions().NonClustered() + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index 8745e6dbec..c237f6381c 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -48,7 +48,16 @@ namespace Umbraco.Core.Models var configuration = Configuration; var json = JsonConvert.SerializeObject(configuration); _editor = value; - Configuration = _editor.GetConfigurationEditor().FromDatabase(json); + + try + { + Configuration = _editor.GetConfigurationEditor().FromDatabase(json); + } + catch (Exception e) + { + throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e); + } } } @@ -76,7 +85,16 @@ namespace Umbraco.Core.Models if (_hasConfiguration) return _configuration; - _configuration = _editor.GetConfigurationEditor().FromDatabase(_configurationJson); + try + { + _configuration = _editor.GetConfigurationEditor().FromDatabase(_configurationJson); + } + catch (Exception e) + { + throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e); + } + _hasConfiguration = true; _configurationJson = null; @@ -158,7 +176,18 @@ namespace Umbraco.Core.Models // else, create a Lazy de-serializer var capturedConfiguration = _configurationJson; var capturedEditor = _editor; - return new Lazy(() => capturedEditor.GetConfigurationEditor().FromDatabase(capturedConfiguration)); + return new Lazy(() => + { + try + { + return capturedEditor.GetConfigurationEditor().FromDatabase(capturedConfiguration); + } + catch (Exception e) + { + throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e); + } + }); } } } diff --git a/src/Umbraco.Core/Models/DataTypeExtensions.cs b/src/Umbraco.Core/Models/DataTypeExtensions.cs index df8a3caea8..f460edbde7 100644 --- a/src/Umbraco.Core/Models/DataTypeExtensions.cs +++ b/src/Umbraco.Core/Models/DataTypeExtensions.cs @@ -35,5 +35,58 @@ namespace Umbraco.Core.Models throw new InvalidCastException($"Cannot cast dataType configuration, of type {configuration.GetType().Name}, to {typeof(T).Name}."); } + + private static readonly ISet IdsOfBuildInDataTypes = new HashSet() + { + Constants.DataTypes.Guids.ContentPickerGuid, + Constants.DataTypes.Guids.MemberPickerGuid, + Constants.DataTypes.Guids.MediaPickerGuid, + Constants.DataTypes.Guids.MultipleMediaPickerGuid, + Constants.DataTypes.Guids.RelatedLinksGuid, + Constants.DataTypes.Guids.MemberGuid, + Constants.DataTypes.Guids.ImageCropperGuid, + Constants.DataTypes.Guids.TagsGuid, + Constants.DataTypes.Guids.ListViewContentGuid, + Constants.DataTypes.Guids.ListViewMediaGuid, + Constants.DataTypes.Guids.ListViewMembersGuid, + Constants.DataTypes.Guids.DatePickerWithTimeGuid, + Constants.DataTypes.Guids.ApprovedColorGuid, + Constants.DataTypes.Guids.DropdownMultipleGuid, + Constants.DataTypes.Guids.RadioboxGuid, + Constants.DataTypes.Guids.DatePickerGuid, + Constants.DataTypes.Guids.DropdownGuid, + Constants.DataTypes.Guids.CheckboxListGuid, + Constants.DataTypes.Guids.CheckboxGuid, + Constants.DataTypes.Guids.NumericGuid, + Constants.DataTypes.Guids.RichtextEditorGuid, + Constants.DataTypes.Guids.TextstringGuid, + Constants.DataTypes.Guids.TextareaGuid, + Constants.DataTypes.Guids.UploadGuid, + Constants.DataTypes.Guids.LabelStringGuid, + Constants.DataTypes.Guids.LabelDecimalGuid, + Constants.DataTypes.Guids.LabelDateTimeGuid, + Constants.DataTypes.Guids.LabelBigIntGuid, + Constants.DataTypes.Guids.LabelTimeGuid, + Constants.DataTypes.Guids.LabelDateTimeGuid, + }; + + /// + /// Returns true if this date type is build-in/default. + /// + /// The data type definition. + /// + internal static bool IsBuildInDataType(this IDataType dataType) + { + return IsBuildInDataType(dataType.Key); + } + + /// + /// Returns true if this date type is build-in/default. + /// + internal static bool IsBuildInDataType(Guid key) + { + return IdsOfBuildInDataTypes.Contains(key); + } + } } diff --git a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs index 39ece5fa10..8536b1ded3 100644 --- a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs @@ -3,6 +3,7 @@ using System.Linq; namespace Umbraco.Core.Models.Entities { + /// /// Implements . /// diff --git a/src/Umbraco.Core/Models/Entities/EntityBase.cs b/src/Umbraco.Core/Models/Entities/EntityBase.cs index 43837fa776..cdb3ecebc5 100644 --- a/src/Umbraco.Core/Models/Entities/EntityBase.cs +++ b/src/Umbraco.Core/Models/Entities/EntityBase.cs @@ -81,37 +81,7 @@ namespace Umbraco.Core.Models.Entities _key = Guid.Empty; _hasIdentity = false; } - - /// - /// Updates the entity when it is being saved for the first time. - /// - internal virtual void AddingEntity() - { - var now = DateTime.Now; - - // set the create and update dates, if not already set - if (IsPropertyDirty("CreateDate") == false || _createDate == default) - CreateDate = now; - if (IsPropertyDirty("UpdateDate") == false || _updateDate == default) - UpdateDate = now; - } - - /// - /// Updates the entity when it is being saved. - /// - internal virtual void UpdatingEntity() - { - var now = DateTime.Now; - - // just in case - if (_createDate == default) - CreateDate = now; - - // set the update date if not already set - if (IsPropertyDirty("UpdateDate") == false || _updateDate == default) - UpdateDate = now; - } - + public virtual bool Equals(EntityBase other) { return other != null && (ReferenceEquals(this, other) || SameIdentityAs(other)); diff --git a/src/Umbraco.Core/Models/Entities/EntityExtensions.cs b/src/Umbraco.Core/Models/Entities/EntityExtensions.cs new file mode 100644 index 0000000000..2ee6a2d5ed --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/EntityExtensions.cs @@ -0,0 +1,46 @@ +using System; + +namespace Umbraco.Core.Models.Entities +{ + internal static class EntityExtensions + { + /// + /// Updates the entity when it is being saved. + /// + internal static void UpdatingEntity(this IEntity entity) + { + var now = DateTime.Now; + + // just in case + if (entity.CreateDate == default) + { + entity.CreateDate = now; + } + + // set the update date if not already set + if (entity.UpdateDate == default || (entity is ICanBeDirty canBeDirty && canBeDirty.IsPropertyDirty("UpdateDate") == false)) + { + entity.UpdateDate = now; + } + } + + /// + /// Updates the entity when it is being saved for the first time. + /// + internal static void AddingEntity(this IEntity entity) + { + var now = DateTime.Now; + var canBeDirty = entity as ICanBeDirty; + + // set the create and update dates, if not already set + if (entity.CreateDate == default || canBeDirty?.IsPropertyDirty("CreateDate") == false) + { + entity.CreateDate = now; + } + if (entity.UpdateDate == default || canBeDirty?.IsPropertyDirty("UpdateDate") == false) + { + entity.UpdateDate = now; + } + } + } +} diff --git a/src/Umbraco.Core/Models/Entities/EntitySlim.cs b/src/Umbraco.Core/Models/Entities/EntitySlim.cs index 3b8f997602..b095965056 100644 --- a/src/Umbraco.Core/Models/Entities/EntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/EntitySlim.cs @@ -111,52 +111,6 @@ namespace Umbraco.Core.Models.Entities public virtual bool IsContainer { get; set; } - /// - /// Represents a lightweight property. - /// - public class PropertySlim - { - /// - /// Initializes a new instance of the class. - /// - public PropertySlim(string editorAlias, object value) - { - PropertyEditorAlias = editorAlias; - Value = value; - } - - /// - /// Gets the property editor alias. - /// - public string PropertyEditorAlias { get; } - - /// - /// Gets the property value. - /// - public object Value { get; } - - protected bool Equals(PropertySlim other) - { - return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && Equals(Value, other.Value); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((PropertySlim) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0); - } - } - } - #region IDeepCloneable /// diff --git a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs index 38fd9a02f1..0258d49114 100644 --- a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs @@ -2,6 +2,7 @@ namespace Umbraco.Core.Models.Entities { + /// /// Represents a lightweight document entity, managed by the entity service. /// diff --git a/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs new file mode 100644 index 0000000000..9440000146 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Models.Entities +{ + /// + /// Represents a lightweight media entity, managed by the entity service. + /// + public interface IMediaEntitySlim : IContentEntitySlim + { + + /// + /// The media file's path/url + /// + string MediaPath { get; } + } +} diff --git a/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs b/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs new file mode 100644 index 0000000000..6292a5a35a --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Models.Entities +{ + /// + /// Implements . + /// + public class MediaEntitySlim : ContentEntitySlim, IMediaEntitySlim + { + public string MediaPath { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 0e91065d56..b473a154f1 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -478,19 +478,6 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _providerUserKey, nameof(ProviderUserKey)); } - - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if (ProviderUserKey == null) - ProviderUserKey = Key; - } - /* Internal experiment - only used for mapping queries. * Adding these to have first level properties instead of the Properties collection. */ diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 2fb293c349..3d071b0a18 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -54,7 +54,6 @@ namespace Umbraco.Core.Models.Membership _isLockedOut = false; _startContentIds = new int[] { }; _startMediaIds = new int[] { }; - } /// diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 1e950a876c..61736607c6 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -21,6 +21,7 @@ namespace Umbraco.Core.Models private string _alias; private string _description; private int _dataTypeId; + private Guid _dataTypeKey; private Lazy _propertyGroupId; private string _propertyEditorAlias; private ValueStorageType _valueStorageType; @@ -113,7 +114,7 @@ namespace Umbraco.Core.Models /// Gets of sets the alias of the property type. /// [DataMember] - public string Alias + public virtual string Alias { get => _alias; set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias, nameof(Alias)); @@ -139,6 +140,13 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, nameof(DataTypeId)); } + [DataMember] + public Guid DataTypeKey + { + get => _dataTypeKey; + set => SetPropertyValueAndDetectChanges(value, ref _dataTypeKey, nameof(DataTypeKey)); + } + /// /// Gets or sets the alias of the property editor for this property type. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs index 4027184f3c..0810f2207b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs @@ -13,6 +13,10 @@ /// /// Refreshes the factory. /// + /// + /// This will typically re-compiled models/classes into a new DLL that are used to populate the cache. + /// This is called prior to refreshing the cache. + /// void Refresh(); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index d38c8eb721..1c0d39a8b8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; namespace Umbraco.Core.Models.PublishedContent { - /// /// /// Represents a published content item. @@ -27,21 +26,13 @@ namespace Umbraco.Core.Models.PublishedContent int Id { get; } /// - /// Gets the name of the content item. + /// Gets the name of the content item for the current culture. /// - /// - /// The value of this property is contextual. When the content type is multi-lingual, - /// this is the name for the 'current' culture. Otherwise, it is the invariant name. - /// string Name { get; } /// - /// Gets the url segment of the content item. + /// Gets the url segment of the content item for the current culture. /// - /// - /// The value of this property is contextual. When the content type is multi-lingual, - /// this is the name for the 'current' culture. Otherwise, it is the invariant url segment. - /// string UrlSegment { get; } /// @@ -94,43 +85,28 @@ namespace Umbraco.Core.Models.PublishedContent /// /// /// For published content items, this is also the date the item was published. - /// This date is always global to the content item, see GetCulture().Date for the + /// This date is always global to the content item, see CultureDate() for the /// date each culture was published. /// DateTime UpdateDate { get; } /// - /// Gets the url of the content item. + /// Gets the url of the content item for the current culture. /// /// /// The value of this property is contextual. It depends on the 'current' request uri, - /// if any. In addition, when the content type is multi-lingual, this is the url for the - /// 'current' culture. Otherwise, it is the invariant url. + /// if any. /// string Url { get; } /// - /// Gets the url of the content item. - /// - /// - /// The value of this property is contextual. It depends on the 'current' request uri, - /// if any. In addition, when the content type is multi-lingual, this is the url for the - /// specified culture. Otherwise, it is the invariant url. - /// - string GetUrl(string culture = null); - - /// - /// Gets culture infos for a culture. - /// - PublishedCultureInfo GetCulture(string culture = null); - - /// - /// Gets culture infos. + /// Gets available culture infos. /// /// /// Contains only those culture that are available. For a published content, these are /// the cultures that are published. For a draft content, those that are 'available' ie /// have a non-empty content name. + /// Does not contain the invariant culture. // fixme? /// IReadOnlyDictionary Cultures { get; } @@ -178,11 +154,15 @@ namespace Umbraco.Core.Models.PublishedContent IPublishedContent Parent { get; } /// - /// Gets the children of the content item. + /// Gets the children of the content item that are available for the current culture. /// - /// Children are sorted by their sortOrder. IEnumerable Children { get; } + /// + /// Gets all the children of the content item, regardless of whether they are available for the current culture. + /// + IEnumerable ChildrenForAllCultures { get; } + #endregion } } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs new file mode 100644 index 0000000000..ab6920377c --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents an type. + /// + /// Instances implementing the interface should be + /// immutable, ie if the content type changes, then a new instance needs to be created. + public interface IPublishedContentType + { + /// + /// Gets the content type identifier. + /// + int Id { get; } + + /// + /// Gets the content type alias. + /// + string Alias { get; } + + /// + /// Gets the content item type. + /// + PublishedItemType ItemType { get; } + + /// + /// Gets the aliases of the content types participating in the composition. + /// + HashSet CompositionAliases { get; } + + /// + /// Gets the content variations of the content type. + /// + ContentVariation Variations { get; } + + /// + /// Gets a value indicating whether this content type is for an element. + /// + bool IsElement { get; } + + /// + /// Gets the content type properties. + /// + IEnumerable PropertyTypes { get; } + + /// + /// Gets a property type index. + /// + /// The alias is case-insensitive. This is the only place where alias strings are compared. + int GetPropertyIndex(string alias); + + /// + /// Gets a property type. + /// + IPublishedPropertyType GetPropertyType(string alias); + + /// + /// Gets a property type. + /// + IPublishedPropertyType GetPropertyType(int index); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs index e75e8a4eb9..816bfdbb01 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs @@ -10,7 +10,7 @@ /// /// An content type. /// A published content type corresponding to the item type and content type. - PublishedContentType CreateContentType(IContentTypeComposition contentType); + IPublishedContentType CreateContentType(IContentTypeComposition contentType); /// /// Creates a published property type. @@ -18,7 +18,7 @@ /// The published content type owning the property. /// A property type. /// Is used by constructor to create property types. - PublishedPropertyType CreatePropertyType(PublishedContentType contentType, PropertyType propertyType); + IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, PropertyType propertyType); /// /// Creates a published property type. @@ -28,7 +28,7 @@ /// The datatype identifier. /// The variations. /// Is used by constructor to create special property types. - PublishedPropertyType CreatePropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations); + IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations); /// /// Gets a published datatype. diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs index 4b579d824b..4c72dc914a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the content type. /// - PublishedContentType ContentType { get; } + IPublishedContentType ContentType { get; } #endregion diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs index 9a00e94d3e..2ee7dcb28f 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs @@ -5,7 +5,7 @@ /// public interface IPublishedProperty { - PublishedPropertyType PropertyType { get; } + IPublishedPropertyType PropertyType { get; } /// /// Gets the alias of the property. diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs new file mode 100644 index 0000000000..40f2bf3df2 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs @@ -0,0 +1,108 @@ +using System; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents a published property type. + /// + /// Instances implementing the interface should be + /// immutable, ie if the property type changes, then a new instance needs to be created. + public interface IPublishedPropertyType + { + /// + /// Gets the published content type containing the property type. + /// + IPublishedContentType ContentType { get; } + + /// + /// Gets the data type. + /// + PublishedDataType DataType { get; } + + /// + /// Gets property type alias. + /// + string Alias { get; } + + /// + /// Gets the property editor alias. + /// + string EditorAlias { get; } + + /// + /// Gets a value indicating whether the property is a user content property. + /// + /// A non-user content property is a property that has been added to a + /// published content type by Umbraco but does not corresponds to a user-defined + /// published property. + bool IsUserProperty { get; } + + /// + /// Gets the content variations of the property type. + /// + ContentVariation Variations { get; } + + /// + /// Determines whether a value is an actual value, or not a value. + /// + /// Used by property.HasValue and, for instance, in fallback scenarios. + bool? IsValue(object value, PropertyValueLevel level); + + /// + /// Gets the property cache level. + /// + PropertyCacheLevel CacheLevel { get; } + + /// + /// Converts the source value into the intermediate value. + /// + /// The published element owning the property. + /// The source value. + /// A value indicating whether content should be considered draft. + /// The intermediate value. + object ConvertSourceToInter(IPublishedElement owner, object source, bool preview); + + /// + /// Converts the intermediate value into the object value. + /// + /// The published element owning the property. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether content should be considered draft. + /// The object value. + object ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); + + /// + /// Converts the intermediate value into the XPath value. + /// + /// The published element owning the property. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether content should be considered draft. + /// The XPath value. + /// + /// The XPath value can be either a string or an XPathNavigator. + /// + object ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); + + /// + /// Gets the property model CLR type. + /// + /// + /// The model CLR type may be a type, or may contain types. + /// For the actual CLR type, see . + /// + Type ModelClrType { get; } + + /// + /// Gets the property CLR type. + /// + /// + /// Returns the actual CLR type which does not contain types. + /// Mapping from may throw if some instances + /// could not be mapped to actual CLR types. + /// + Type ClrType { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 0798e9a4e0..3b03cfc9ea 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -5,13 +5,13 @@ using System.Linq; namespace Umbraco.Core.Models.PublishedContent { /// - /// Represents an type. + /// Represents an type. /// /// Instances of the class are immutable, ie /// if the content type changes, then a new class needs to be created. - public class PublishedContentType + public class PublishedContentType : IPublishedContentType { - private readonly PublishedPropertyType[] _propertyTypes; + private readonly IPublishedPropertyType[] _propertyTypes; // fast alias-to-index xref containing both the raw alias and its lowercase version private readonly Dictionary _indexes = new Dictionary(); @@ -35,11 +35,10 @@ namespace Umbraco.Core.Models.PublishedContent } /// - /// Initializes a new instance of the with specific values. + /// This constructor is for tests and is not intended to be used directly from application code. /// /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// Values are assumed to be consisted and are not checked. + /// Values are assumed to be consistent and are not checked. /// public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) : this (id, alias, itemType, compositionAliases, variations, isElement) @@ -52,6 +51,20 @@ namespace Umbraco.Core.Models.PublishedContent InitializeIndexes(); } + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consistent and are not checked. + /// + public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) + : this(id, alias, itemType, compositionAliases, variations, isElement) + { + _propertyTypes = propertyTypes(this).ToArray(); + + InitializeIndexes(); + } + private PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations, bool isElement) { Id = id; @@ -75,7 +88,7 @@ namespace Umbraco.Core.Models.PublishedContent // Members have properties such as IMember LastLoginDate which are plain C# properties and not content // properties; they are exposed as pseudo content properties, as long as a content property with the // same alias does not exist already. - private void EnsureMemberProperties(List propertyTypes, IPublishedContentTypeFactory factory) + private void EnsureMemberProperties(List propertyTypes, IPublishedContentTypeFactory factory) { var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase); @@ -103,44 +116,29 @@ namespace Umbraco.Core.Models.PublishedContent #region Content type - /// - /// Gets the content type identifier. - /// + /// public int Id { get; } - /// - /// Gets the content type alias. - /// + /// public string Alias { get; } - /// - /// Gets the content item type. - /// + /// public PublishedItemType ItemType { get; } - /// - /// Gets the aliases of the content types participating in the composition. - /// + /// public HashSet CompositionAliases { get; } - /// - /// Gets the content variations of the content type. - /// + /// public ContentVariation Variations { get; } #endregion #region Properties - /// - /// Gets the content type properties. - /// - public IEnumerable PropertyTypes => _propertyTypes; + /// + public IEnumerable PropertyTypes => _propertyTypes; - /// - /// Gets a property type index. - /// - /// The alias is case-insensitive. This is the only place where alias strings are compared. + /// public int GetPropertyIndex(string alias) { if (_indexes.TryGetValue(alias, out var index)) return index; // fastest @@ -150,10 +148,8 @@ namespace Umbraco.Core.Models.PublishedContent // virtual for unit tests // TODO: explain why - /// - /// Gets a property type. - /// - public virtual PublishedPropertyType GetPropertyType(string alias) + /// + public virtual IPublishedPropertyType GetPropertyType(string alias) { var index = GetPropertyIndex(alias); return GetPropertyType(index); @@ -161,17 +157,13 @@ namespace Umbraco.Core.Models.PublishedContent // virtual for unit tests // TODO: explain why - /// - /// Gets a property type. - /// - public virtual PublishedPropertyType GetPropertyType(int index) + /// + public virtual IPublishedPropertyType GetPropertyType(int index) { return index >= 0 && index < _propertyTypes.Length ? _propertyTypes[index] : null; } - /// - /// Gets a value indicating whether this content type is for an element. - /// + /// public bool IsElement { get; } #endregion diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index 2ca3593b55..17a15a2536 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -26,37 +26,46 @@ namespace Umbraco.Core.Models.PublishedContent } /// - public PublishedContentType CreateContentType(IContentTypeComposition contentType) + public IPublishedContentType CreateContentType(IContentTypeComposition contentType) { return new PublishedContentType(contentType, this); } - // for tests - internal PublishedContentType CreateContentType(int id, string alias, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) + /// + /// This method is for tests and is not intended to be used directly from application code. + /// + /// Values are assumed to be consisted and are not checked. + internal IPublishedContentType CreateContentType(int id, string alias, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); } - // for tests - internal PublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) + /// + /// This method is for tests and is not intended to be used directly from application code. + /// + /// Values are assumed to be consisted and are not checked. + internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); } /// - public PublishedPropertyType CreatePropertyType(PublishedContentType contentType, PropertyType propertyType) + public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, PropertyType propertyType) { return new PublishedPropertyType(contentType, propertyType, _propertyValueConverters, _publishedModelFactory, this); } /// - public PublishedPropertyType CreatePropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing) + public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing) { return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this); } - // for tests - internal PublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, ContentVariation variations = ContentVariation.Nothing) + /// + /// This method is for tests and is not intended to be used directly from application code. + /// + /// Values are assumed to be consisted and are not checked. + internal IPublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, ContentVariation variations = ContentVariation.Nothing) { return new PublishedPropertyType(propertyTypeAlias, dataTypeId, umbraco, variations, _propertyValueConverters, _publishedModelFactory, this); } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 8bf8cec244..fb41c95419 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Models.PublishedContent #region ContentType /// - public virtual PublishedContentType ContentType => _content.ContentType; + public virtual IPublishedContentType ContentType => _content.ContentType; #endregion @@ -96,12 +96,6 @@ namespace Umbraco.Core.Models.PublishedContent /// public virtual string Url => _content.Url; - /// - public virtual string GetUrl(string culture = null) => _content.GetUrl(culture); - - /// - public PublishedCultureInfo GetCulture(string culture = null) => _content.GetCulture(culture); - /// public IReadOnlyDictionary Cultures => _content.Cultures; @@ -124,6 +118,9 @@ namespace Umbraco.Core.Models.PublishedContent /// public virtual IEnumerable Children => _content.Children; + /// + public virtual IEnumerable ChildrenForAllCultures => _content.ChildrenForAllCultures; + #endregion #region Properties diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index 749b37a41a..d5096158a7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -13,10 +13,9 @@ namespace Umbraco.Core.Models.PublishedContent /// public PublishedCultureInfo(string culture, string name, string urlSegment, DateTime date) { - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentNullOrEmptyException(nameof(culture)); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - Culture = culture; + Culture = culture ?? throw new ArgumentNullException(nameof(culture)); Name = name; UrlSegment = urlSegment; Date = date; @@ -30,12 +29,12 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the name of the item. /// - public string Name { get; } + internal string Name { get; } /// /// Gets the url segment of the item. /// - public string UrlSegment { get; } + internal string UrlSegment { get; } /// /// Gets the date associated with the culture. diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs index 1989ac2caf..481b9bd5d2 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs @@ -28,7 +28,7 @@ namespace Umbraco.Core.Models.PublishedContent public IPublishedElement Unwrap() => _content; /// - public PublishedContentType ContentType => _content.ContentType; + public IPublishedContentType ContentType => _content.ContentType; /// public Guid Key => _content.Key; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs index 5f374f8bc8..e11d2391ec 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Initializes a new instance of the class. /// - protected PublishedPropertyBase(PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel) + protected PublishedPropertyBase(IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel) { PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); ReferenceCacheLevel = referenceCacheLevel; @@ -42,7 +42,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the property type. /// - public PublishedPropertyType PropertyType { get; } + public IPublishedPropertyType PropertyType { get; } /// /// Gets the property reference cache level. diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 68892fd79a..0c2e62770e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -10,10 +10,8 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Instances of the class are immutable, ie /// if the property type changes, then a new class needs to be created. - public class PublishedPropertyType + public class PublishedPropertyType : IPublishedPropertyType { - // TODO: API design review, should this be an interface? - private readonly IPublishedModelFactory _publishedModelFactory; private readonly PropertyValueConverterCollection _propertyValueConverters; private readonly object _locker = new object(); @@ -32,7 +30,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// The new published property type belongs to the published content type. /// - public PublishedPropertyType(PublishedContentType contentType, PropertyType propertyType, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + public PublishedPropertyType(IPublishedContentType contentType, PropertyType propertyType, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, publishedModelFactory, factory) { ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); @@ -45,7 +43,7 @@ namespace Umbraco.Core.Models.PublishedContent /// Values are assumed to be consisted and are not checked. /// The new published property type belongs to the published content type. /// - public PublishedPropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + public PublishedPropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, publishedModelFactory, factory) { ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); @@ -75,37 +73,22 @@ namespace Umbraco.Core.Models.PublishedContent #region Property type - /// - /// Gets the published content type containing the property type. - /// - public PublishedContentType ContentType { get; internal set; } // internally set by PublishedContentType constructor + /// + public IPublishedContentType ContentType { get; internal set; } // internally set by PublishedContentType constructor - /// - /// Gets the data type. - /// + /// public PublishedDataType DataType { get; } - /// - /// Gets property type alias. - /// + /// public string Alias { get; } - /// - /// Gets the property editor alias. - /// + /// public string EditorAlias => DataType.EditorAlias; - /// - /// Gets a value indicating whether the property is a user content property. - /// - /// A non-user content property is a property that has been added to a - /// published content type by Umbraco but does not corresponds to a user-defined - /// published property. + /// public bool IsUserProperty { get; } - /// - /// Gets the content variations of the property type. - /// + /// public ContentVariation Variations { get; } #endregion @@ -193,10 +176,7 @@ namespace Umbraco.Core.Models.PublishedContent _modelClrType = _converter == null ? typeof (object) : _converter.GetPropertyValueType(this); } - /// - /// Determines whether a value is an actual value, or not a value. - /// - /// Used by property.HasValue and, for instance, in fallback scenarios. + /// public bool? IsValue(object value, PropertyValueLevel level) { if (!_initialized) Initialize(); @@ -209,9 +189,7 @@ namespace Umbraco.Core.Models.PublishedContent return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); } - /// - /// Gets the property cache level. - /// + /// public PropertyCacheLevel CacheLevel { get @@ -221,13 +199,7 @@ namespace Umbraco.Core.Models.PublishedContent } } - /// - /// Converts the source value into the intermediate value. - /// - /// The published element owning the property. - /// The source value. - /// A value indicating whether content should be considered draft. - /// The intermediate value. + /// public object ConvertSourceToInter(IPublishedElement owner, object source, bool preview) { if (!_initialized) Initialize(); @@ -238,14 +210,7 @@ namespace Umbraco.Core.Models.PublishedContent : source; } - /// - /// Converts the intermediate value into the object value. - /// - /// The published element owning the property. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether content should be considered draft. - /// The object value. + /// public object ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { if (!_initialized) Initialize(); @@ -256,17 +221,7 @@ namespace Umbraco.Core.Models.PublishedContent : inter; } - /// - /// Converts the intermediate value into the XPath value. - /// - /// The published element owning the property. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether content should be considered draft. - /// The XPath value. - /// - /// The XPath value can be either a string or an XPathNavigator. - /// + /// public object ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { if (!_initialized) Initialize(); @@ -282,13 +237,7 @@ namespace Umbraco.Core.Models.PublishedContent return inter.ToString().Trim(); } - /// - /// Gets the property model CLR type. - /// - /// - /// The model CLR type may be a type, or may contain types. - /// For the actual CLR type, see . - /// + /// public Type ModelClrType { get @@ -298,14 +247,7 @@ namespace Umbraco.Core.Models.PublishedContent } } - /// - /// Gets the property CLR type. - /// - /// - /// Returns the actual CLR type which does not contain types. - /// Mapping from may throw if some instances - /// could not be mapped to actual CLR types. - /// + /// public Type ClrType { get diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs index 7469222ab0..10f999532f 100644 --- a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -38,7 +38,7 @@ namespace Umbraco.Core.Models.PublishedContent public override object GetXPathValue(string culture = null, string segment = null) => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _xpathValue.Value : null; - public RawValueProperty(PublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false) + public RawValueProperty(IPublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { if (propertyType.Variations != ContentVariation.Nothing) diff --git a/src/Umbraco.Web/Routing/UrlProviderMode.cs b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs similarity index 63% rename from src/Umbraco.Web/Routing/UrlProviderMode.cs rename to src/Umbraco.Core/Models/PublishedContent/UrlMode.cs index ac29aed3fb..4cd6a680f4 100644 --- a/src/Umbraco.Web/Routing/UrlProviderMode.cs +++ b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs @@ -1,14 +1,14 @@ -namespace Umbraco.Web.Routing +namespace Umbraco.Core.Models.PublishedContent { /// - /// Specifies the type of urls that the url provider should produce, Auto is the default + /// Specifies the type of urls that the url provider should produce, Auto is the default. /// - /// - /// The Relative option can lead to invalid results when combined with hostnames, but it is the only way to reproduce - /// the true, pre-4.10, always-relative behavior of Umbraco. - /// - public enum UrlProviderMode + public enum UrlMode { + /// + /// Indicates that the url provider should do what it has been configured to do. + /// + Default = 0, /// /// Indicates that the url provider should produce relative urls exclusively. diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 0f83cf78a4..cf7df4fb86 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -193,19 +193,6 @@ namespace Umbraco.Core.Models return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); } - internal static bool IsInBranchOfStartNode(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId, out bool hasPathAccess) - { - switch (recycleBinId) - { - case Constants.System.RecycleBinMedia: - return ContentPermissionsHelper.IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess); - case Constants.System.RecycleBinContent: - return ContentPermissionsHelper.IsInBranchOfStartNode(entity.Path, user.CalculateContentStartNodeIds(entityService), user.GetContentStartNodePaths(entityService), out hasPathAccess); - default: - throw new NotSupportedException("Path access is only determined on content or media"); - } - } - /// /// Determines whether this user has access to view sensitive data /// diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs index fceb32d609..2137b2ff44 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Data; namespace Umbraco.Core.Persistence.DatabaseAnnotations { @@ -9,11 +10,17 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations public class ForeignKeyAttribute : ReferencesAttribute { public ForeignKeyAttribute(Type type) : base(type) - { - } + { } - internal string OnDelete { get; set; } - internal string OnUpdate { get; set; } + /// + /// Gets or sets the cascade rule for deletions. + /// + public Rule OnDelete { get; set; } = Rule.None; + + /// + /// Gets or sets the cascade rule for updates. + /// + public Rule OnUpdate { get; set; } = Rule.None; /// /// Gets or sets the name of the foreign key reference diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs index e2d3ac97ae..5925e58afc 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs @@ -134,7 +134,9 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { Name = foreignKeyName, ForeignTable = tableName, - PrimaryTable = referencedTable.Value + PrimaryTable = referencedTable.Value, + OnDelete = attribute.OnDelete, + OnUpdate = attribute.OnUpdate }; definition.ForeignColumns.Add(columnName); definition.PrimaryColumns.Add(referencedColumn); diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs index eea3d5d537..c6269d5317 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs @@ -1,4 +1,5 @@ -using NPoco; +using System.Data; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Persistence.Dtos @@ -10,7 +11,7 @@ namespace Umbraco.Core.Persistence.Dtos { [Column("nodeId")] [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsContentNu", OnColumns = "nodeId, published")] - [ForeignKey(typeof(ContentDto), Column = "nodeId")] + [ForeignKey(typeof(ContentDto), Column = "nodeId", OnDelete = Rule.Cascade)] public int NodeId { get; set; } [Column("published")] diff --git a/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs index d7d02631b7..8bf254ea31 100644 --- a/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs @@ -37,7 +37,7 @@ namespace Umbraco.Core.Persistence.Dtos /// [Column("lastValidatedUtc")] [NullSetting(NullSetting = NullSettings.NotNull)] - [Index(IndexTypes.NonClustered, Name = "IX_userLoginDto_lastValidatedUtc")] + [Index(IndexTypes.NonClustered, Name = "IX_umbracoUserLogin_lastValidatedUtc")] public DateTime LastValidatedUtc { get; set; } /// diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs index f5144e34eb..f189d38d05 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -15,7 +15,8 @@ namespace Umbraco.Core.Persistence.Factories { if (!editors.TryGet(dto.EditorAlias, out var editor)) { - logger.Warn(typeof(DataTypeFactory), "Could not find an editor with alias {EditorAlias}, converting to label", dto.EditorAlias); + logger.Warn(typeof(DataType), "Could not find an editor with alias {EditorAlias}, treating as Label." + +" The site may fail to boot and / or load data types and run.", dto.EditorAlias); //convert to label editor = new LabelPropertyEditor(logger); } diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index c134748047..db8e2b20d9 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -54,6 +54,7 @@ namespace Umbraco.Core.Persistence.Factories propertyType.Alias = typeDto.Alias; propertyType.DataTypeId = typeDto.DataTypeId; + propertyType.DataTypeKey = typeDto.DataTypeDto.NodeDto.UniqueId; propertyType.Description = typeDto.Description; propertyType.Id = typeDto.Id; propertyType.Key = typeDto.UniqueId; diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index dae70d502f..c64eb14bb7 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -36,6 +36,13 @@ namespace Umbraco.Core.Persistence.Factories user.InvitedDate = dto.InvitedDate; user.TourData = dto.TourData; + // we should never get user with ID zero from database, except + // when upgrading from v7 - mark that user so that we do not + // save it back to database (as that would create a *new* user) + // see also: UserRepository.PersistNewItem + if (dto.Id == 0) + user.AdditionalData["IS_V7_ZERO"] = true; + // reset dirty initial properties (U4-1946) user.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs index 0236fc4bd5..c2d65b824f 100644 --- a/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs @@ -10,22 +10,35 @@ namespace Umbraco.Core.Persistence /// /// Creates a new database. /// - /// The new database must be disposed after being used. + /// + /// The new database must be disposed after being used. + /// Creating a database causes the factory to initialize if it is not already initialized. + /// IUmbracoDatabase CreateDatabase(); /// - /// Gets a value indicating whether the database factory is configured. + /// Gets a value indicating whether the database factory is configured, i.e. whether + /// its connection string and provider name have been set. The factory may however not + /// be initialized (see ). /// bool Configured { get; } + /// + /// Gets a value indicating whether the database factory is initialized, i.e. whether + /// its internal state is ready and it has been possible to connect to the database. + /// + bool Initialized { get; } + /// /// Gets the connection string. /// - /// Throws if the factory is not configured. + /// May return null if the database factory is not configured. string ConnectionString { get; } /// - /// Gets a value indicating whether the database can connect. + /// Gets a value indicating whether the database factory is configured (see ), + /// and it is possible to connect to the database. The factory may however not be initialized (see + /// ). /// bool CanConnect { get; } @@ -37,6 +50,9 @@ namespace Umbraco.Core.Persistence /// /// Gets the Sql context. /// + /// + /// Getting the Sql context causes the factory to initialize if it is not already initialized. + /// ISqlContext SqlContext { get; } /// diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs index 853cd9f99e..48e7afdc7e 100644 --- a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs @@ -18,7 +18,8 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(AuditItem.Id), nameof(LogDto.NodeId)); DefineMap(nameof(AuditItem.CreateDate), nameof(LogDto.Datestamp)); DefineMap(nameof(AuditItem.UserId), nameof(LogDto.UserId)); - DefineMap(nameof(AuditItem.AuditType), nameof(LogDto.Header)); + // we cannot map that one - because AuditType is an enum but Header is a string + //DefineMap(nameof(AuditItem.AuditType), nameof(LogDto.Header)); DefineMap(nameof(AuditItem.Comment), nameof(LogDto.Comment)); } } diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs index c9d85feb25..0574e37c4c 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs @@ -232,6 +232,14 @@ namespace Umbraco.Core.Persistence using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction) { BulkCopyTimeout = 10000, DestinationTableName = tableName }) using (var bulkReader = new PocoDataDataReader(records, pocoData, syntax)) { + //we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared + //to the order in which they are declared in the model then this will not work, so instead we will add column mappings by name so that this explicitly uses + //the names instead of their ordering. + foreach(var col in bulkReader.ColumnMappings) + { + copy.ColumnMappings.Add(col.DestinationColumn, col.DestinationColumn); + } + copy.WriteToServer(bulkReader); return bulkReader.RecordsAffected; } diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs index 03d82a345f..c87937e41e 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs @@ -85,6 +85,17 @@ namespace Umbraco.Core.Persistence.Querying // I'm just unsure right now due to time constraints how to make it correct. It won't matter right now and has been working already with this bug but I've // only just discovered what it is actually doing. + // TODO + // in most cases we want to convert the value to a plain object, + // but for in some rare cases, we may want to do it differently, + // for instance a Models.AuditType (an enum) may in some cases + // need to be converted to its string value. + // but - we cannot have specific code here, really - and how would + // we configure this? is it even possible? + /* + var toString = typeof(object).GetMethod("ToString"); + var member = Expression.Call(m, toString); + */ var member = Expression.Convert(m, typeof(object)); var lambda = Expression.Lambda>(member); var getter = lambda.Compile(); diff --git a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs index 7c8a82bb85..b2dd6a3297 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs @@ -33,5 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories Direction orderDirection, AuditType[] auditTypeFilter, IQuery customFilter); + + IEnumerable Get(AuditType type, IQuery query); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs index 1486935e2a..c3d34cc3e9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs @@ -100,7 +100,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected override void PersistNewItem(IAuditEntry entity) { - ((EntityBase) entity).AddingEntity(); + entity.AddingEntity(); var dto = AuditEntryFactory.BuildDto(entity); Database.Insert(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs index cda89fd89a..c25328b10c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs @@ -74,6 +74,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList(); } + public IEnumerable Get(AuditType type, IQuery query) + { + var sqlClause = GetBaseQuery(false) + .Where(x => x.Header == type.ToString()); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + var dtos = Database.Fetch(sql); + + return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList(); + } + protected override Sql GetBaseQuery(bool isCount) { var sql = SqlContext.Sql(); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs index 8df9bf686d..57d5dfa864 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs @@ -69,7 +69,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected override void PersistNewItem(IConsent entity) { - ((EntityBase) entity).AddingEntity(); + entity.AddingEntity(); var dto = ConsentFactory.BuildDto(entity); Database.Insert(dto); @@ -80,7 +80,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected override void PersistUpdatedItem(IConsent entity) { - ((EntityBase) entity).UpdatingEntity(); + entity.UpdatingEntity(); var dto = ConsentFactory.BuildDto(entity); Database.Update(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index 645ab9f924..6b751eb8ff 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Scoping; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -140,10 +141,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement while (templateDtoIx < templateDtos.Count && templateDtos[templateDtoIx].ContentTypeNodeId == contentType.Id) { var allowedDto = templateDtos[templateDtoIx]; + templateDtoIx++; if (!templates.TryGetValue(allowedDto.TemplateNodeId, out var template)) continue; allowedTemplates.Add(template); - templateDtoIx++; - + if (allowedDto.IsDefault) defaultTemplateId = template.Id; } @@ -188,10 +189,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var groupDtos = Database.Fetch(sql1); var sql2 = Sql() - .Select(r => r.Select(x => x.DataTypeDto)) + .Select(r => r.Select(x => x.DataTypeDto, r1 => r1.Select(x => x.NodeDto))) .AndSelect() .From() .InnerJoin().On((pt, dt) => pt.DataTypeId == dt.NodeId) + .InnerJoin().On((dt, n) => dt.NodeId == n.NodeId) .InnerJoin().On((pt, ct) => pt.ContentTypeId == ct.NodeId) .LeftJoin().On((pt, ptg) => pt.PropertyTypeGroupId == ptg.Id) .LeftJoin().On((pt, mpt) => pt.Id == mpt.PropertyTypeId) @@ -290,6 +292,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { Description = dto.Description, DataTypeId = dto.DataTypeId, + DataTypeKey = dto.DataTypeDto.NodeDto.UniqueId, Id = dto.Id, Key = dto.UniqueId, Mandatory = dto.Mandatory, diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs index 98ddcdcb17..9d77eb0990 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -5,6 +5,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; @@ -230,7 +231,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement throw ex; } - ((ContentType)entity).AddingEntity(); + entity.AddingEntity(); PersistNewBaseContentType(entity); PersistTemplates(entity, false); @@ -270,7 +271,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ValidateAlias(entity); //Updates Modified date - ((ContentType)entity).UpdatingEntity(); + entity.UpdatingEntity(); //Look up parent to get and set the correct Path if ParentId has changed if (entity.IsPropertyDirty("ParentId")) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 591fa2b660..22c9244d8f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -1013,8 +1013,9 @@ AND umbracoNode.id <> @id", if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false) { var sql = Sql() - .SelectAll() + .Select(dt => dt.Select(x => x.NodeDto)) .From() + .InnerJoin().On((dt, n) => dt.NodeId == n.NodeId) .Where("propertyEditorAlias = @propertyEditorAlias", new { propertyEditorAlias = propertyType.PropertyEditorAlias }) .OrderBy(typeDto => typeDto.NodeId); var datatype = Database.FirstOrDefault(sql); @@ -1022,6 +1023,7 @@ AND umbracoNode.id <> @id", if (datatype != null) { propertyType.DataTypeId = datatype.NodeId; + propertyType.DataTypeKey = datatype.NodeDto.UniqueId; } else { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs index 02ba4d1c13..dac8fda5ec 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -106,7 +107,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IDataType entity) { - ((DataType)entity).AddingEntity(); + entity.AddingEntity(); //ensure a datatype has a unique name before creating it entity.Name = EnsureUniqueNodeName(entity.Name); @@ -174,7 +175,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } //Updates Modified date - ((DataType)entity).UpdatingEntity(); + entity.UpdatingEntity(); //Look up parent to get and set the correct Path if ParentId has changed if (entity.IsPropertyDirty("ParentId")) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs index be1e28fcc1..0b58663952 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs @@ -148,7 +148,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IDictionaryItem entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); foreach (var translation in entity.Translations) translation.Value = translation.Value.ToValidXmlString(); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 6c08e05995..30a2927cc8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; @@ -205,7 +206,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersionCultureVariation + " WHERE versionId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)", "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.AccessRule + " WHERE accessId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE nodeId = @id OR loginNodeId = @id OR noAccessNodeId = @id)", "DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE nodeId = @id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE loginNodeId = @id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE noAccessNodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Node + " WHERE id = @id" }; return list; @@ -260,21 +264,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IContent entity) { - // TODO: https://github.com/umbraco/Umbraco-CMS/issues/4234 - sort out IContent vs Content - // however, it's not just so we have access to AddingEntity - // there are tons of things at the end of the methods, that can only work with a true Content - // and basically, the repository requires a Content, not an IContent - var content = (Content)entity; + entity.AddingEntity(); - content.AddingEntity(); - var publishing = content.PublishedState == PublishedState.Publishing; + var publishing = entity.PublishedState == PublishedState.Publishing; // ensure that the default template is assigned if (entity.TemplateId.HasValue == false) entity.TemplateId = entity.ContentType.DefaultTemplate?.Id; // sanitize names - SanitizeNames(content, publishing); + SanitizeNames(entity, publishing); // ensure that strings don't contain characters that are invalid in xml // TODO: do we really want to keep doing this here? @@ -324,11 +323,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement contentVersionDto.NodeId = nodeDto.NodeId; contentVersionDto.Current = !publishing; Database.Insert(contentVersionDto); - content.VersionId = contentVersionDto.Id; + entity.VersionId = contentVersionDto.Id; // persist the document version dto var documentVersionDto = dto.DocumentVersionDto; - documentVersionDto.Id = content.VersionId; + documentVersionDto.Id = entity.VersionId; if (publishing) documentVersionDto.Published = true; Database.Insert(documentVersionDto); @@ -336,62 +335,62 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // and again in case we're publishing immediately if (publishing) { - content.PublishedVersionId = content.VersionId; + entity.PublishedVersionId = entity.VersionId; contentVersionDto.Id = 0; contentVersionDto.Current = true; - contentVersionDto.Text = content.Name; + contentVersionDto.Text = entity.Name; Database.Insert(contentVersionDto); - content.VersionId = contentVersionDto.Id; + entity.VersionId = contentVersionDto.Id; - documentVersionDto.Id = content.VersionId; + documentVersionDto.Id = entity.VersionId; documentVersionDto.Published = false; Database.Insert(documentVersionDto); } // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(content.ContentType.Variations, content.VersionId, content.PublishedVersionId, entity.Properties, LanguageRepository, out var edited, out var editedCultures); + var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, entity.PublishedVersionId, entity.Properties, LanguageRepository, out var edited, out var editedCultures); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); // if !publishing, we may have a new name != current publish name, // also impacts 'edited' - if (!publishing && content.PublishName != content.Name) + if (!publishing && entity.PublishName != entity.Name) edited = true; // persist the document dto // at that point, when publishing, the entity still has its old Published value // so we need to explicitly update the dto to persist the correct value - if (content.PublishedState == PublishedState.Publishing) + if (entity.PublishedState == PublishedState.Publishing) dto.Published = true; dto.NodeId = nodeDto.NodeId; - content.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited + entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited Database.Insert(dto); //insert the schedule - PersistContentSchedule(content, false); + PersistContentSchedule(entity, false); // persist the variations - if (content.ContentType.VariesByCulture()) + if (entity.ContentType.VariesByCulture()) { // bump dates to align cultures to version if (publishing) - content.AdjustDates(contentVersionDto.VersionDate); + entity.AdjustDates(contentVersionDto.VersionDate); // names also impact 'edited' // ReSharper disable once UseDeconstruction - foreach (var cultureInfo in content.CultureInfos) - if (cultureInfo.Name != content.GetPublishName(cultureInfo.Culture)) + foreach (var cultureInfo in entity.CultureInfos) + if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture)) (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture); // insert content variations - Database.BulkInsertRecords(GetContentVariationDtos(content, publishing)); + Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing)); // insert document variations - Database.BulkInsertRecords(GetDocumentVariationDtos(content, publishing, editedCultures)); + Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures)); } // refresh content - content.SetCultureEdited(editedCultures); + entity.SetCultureEdited(editedCultures); // trigger here, before we reset Published etc OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity)); @@ -399,23 +398,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // flip the entity's published property // this also flips its published state // note: what depends on variations (eg PublishNames) is managed directly by the content - if (content.PublishedState == PublishedState.Publishing) + if (entity.PublishedState == PublishedState.Publishing) { - content.Published = true; - content.PublishTemplateId = content.TemplateId; - content.PublisherId = content.WriterId; - content.PublishName = content.Name; - content.PublishDate = content.UpdateDate; + entity.Published = true; + entity.PublishTemplateId = entity.TemplateId; + entity.PublisherId = entity.WriterId; + entity.PublishName = entity.Name; + entity.PublishDate = entity.UpdateDate; SetEntityTags(entity, _tagRepository); } - else if (content.PublishedState == PublishedState.Unpublishing) + else if (entity.PublishedState == PublishedState.Unpublishing) { - content.Published = false; - content.PublishTemplateId = null; - content.PublisherId = null; - content.PublishName = null; - content.PublishDate = null; + entity.Published = false; + entity.PublishTemplateId = null; + entity.PublisherId = null; + entity.PublishName = null; + entity.PublishDate = null; ClearEntityTags(entity, _tagRepository); } @@ -437,34 +436,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IContent entity) { - // however, it's not just so we have access to AddingEntity - // there are tons of things at the end of the methods, that can only work with a true Content - // and basically, the repository requires a Content, not an IContent - var content = (Content)entity; + var entityBase = entity as EntityBase; + var isEntityDirty = entityBase != null && entityBase.IsDirty(); // check if we need to make any database changes at all - if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) - && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty()) + if ((entity.PublishedState == PublishedState.Published || entity.PublishedState == PublishedState.Unpublished) + && !isEntityDirty && !entity.IsAnyUserPropertyDirty()) return; // no change to save, do nothing, don't even update dates // whatever we do, we must check that we are saving the current version - var version = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.Id == content.VersionId)).FirstOrDefault(); + var version = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.Id == entity.VersionId)).FirstOrDefault(); if (version == null || !version.Current) throw new InvalidOperationException("Cannot save a non-current version."); // update - content.UpdatingEntity(); - var publishing = content.PublishedState == PublishedState.Publishing; + entity.UpdatingEntity(); + + var publishing = entity.PublishedState == PublishedState.Publishing; // check if we need to create a new version - if (publishing && content.PublishedVersionId > 0) + if (publishing && entity.PublishedVersionId > 0) { // published version is not published anymore - Database.Execute(Sql().Update(u => u.Set(x => x.Published, false)).Where(x => x.Id == content.PublishedVersionId)); + Database.Execute(Sql().Update(u => u.Set(x => x.Published, false)).Where(x => x.Id == entity.PublishedVersionId)); } // sanitize names - SanitizeNames(content, publishing); + SanitizeNames(entity, publishing); // ensure that strings don't contain characters that are invalid in xml // TODO: do we really want to keep doing this here? @@ -504,13 +502,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // and, if publishing, insert new content & document version dtos if (publishing) { - content.PublishedVersionId = content.VersionId; + entity.PublishedVersionId = entity.VersionId; contentVersionDto.Id = 0; // want a new id contentVersionDto.Current = true; // current version - contentVersionDto.Text = content.Name; + contentVersionDto.Text = entity.Name; Database.Insert(contentVersionDto); - content.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id + entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id documentVersionDto.Published = false; // non-published version Database.Insert(documentVersionDto); @@ -518,31 +516,31 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // replace the property data (rather than updating) // only need to delete for the version that existed, the new version (if any) has no property data yet - var versionToDelete = publishing ? content.PublishedVersionId : content.VersionId; + var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId; var deletePropertyDataSql = Sql().Delete().Where(x => x.VersionId == versionToDelete); Database.Execute(deletePropertyDataSql); // insert property data - var propertyDataDtos = PropertyFactory.BuildDtos(content.ContentType.Variations, content.VersionId, publishing ? content.PublishedVersionId : 0, + var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishing ? entity.PublishedVersionId : 0, entity.Properties, LanguageRepository, out var edited, out var editedCultures); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); // if !publishing, we may have a new name != current publish name, // also impacts 'edited' - if (!publishing && content.PublishName != content.Name) + if (!publishing && entity.PublishName != entity.Name) edited = true; - if (content.ContentType.VariesByCulture()) + if (entity.ContentType.VariesByCulture()) { // bump dates to align cultures to version if (publishing) - content.AdjustDates(contentVersionDto.VersionDate); + entity.AdjustDates(contentVersionDto.VersionDate); // names also impact 'edited' // ReSharper disable once UseDeconstruction - foreach (var cultureInfo in content.CultureInfos) - if (cultureInfo.Name != content.GetPublishName(cultureInfo.Culture)) + foreach (var cultureInfo in entity.CultureInfos) + if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture)) { edited = true; (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture); @@ -560,7 +558,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Execute(deleteContentVariations); // replace the document version variations (rather than updating) - var deleteDocumentVariations = Sql().Delete().Where(x => x.NodeId == content.Id); + var deleteDocumentVariations = Sql().Delete().Where(x => x.NodeId == entity.Id); Database.Execute(deleteDocumentVariations); // TODO: NPoco InsertBulk issue? @@ -570,32 +568,32 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // (same in PersistNewItem above) // insert content variations - Database.BulkInsertRecords(GetContentVariationDtos(content, publishing)); + Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing)); // insert document variations - Database.BulkInsertRecords(GetDocumentVariationDtos(content, publishing, editedCultures)); + Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures)); } // refresh content - content.SetCultureEdited(editedCultures); + entity.SetCultureEdited(editedCultures); // update the document dto // at that point, when un/publishing, the entity still has its old Published value // so we need to explicitly update the dto to persist the correct value - if (content.PublishedState == PublishedState.Publishing) + if (entity.PublishedState == PublishedState.Publishing) dto.Published = true; - else if (content.PublishedState == PublishedState.Unpublishing) + else if (entity.PublishedState == PublishedState.Unpublishing) dto.Published = false; - content.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited + entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited Database.Update(dto); //update the schedule - if (content.IsPropertyDirty("ContentSchedule")) - PersistContentSchedule(content, true); + if (entity.IsPropertyDirty("ContentSchedule")) + PersistContentSchedule(entity, true); // if entity is publishing, update tags, else leave tags there // means that implicitly unpublished, or trashed, entities *still* have tags in db - if (content.PublishedState == PublishedState.Publishing) + if (entity.PublishedState == PublishedState.Publishing) SetEntityTags(entity, _tagRepository); // trigger here, before we reset Published etc @@ -603,23 +601,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // flip the entity's published property // this also flips its published state - if (content.PublishedState == PublishedState.Publishing) + if (entity.PublishedState == PublishedState.Publishing) { - content.Published = true; - content.PublishTemplateId = content.TemplateId; - content.PublisherId = content.WriterId; - content.PublishName = content.Name; - content.PublishDate = content.UpdateDate; + entity.Published = true; + entity.PublishTemplateId = entity.TemplateId; + entity.PublisherId = entity.WriterId; + entity.PublishName = entity.Name; + entity.PublishDate = entity.UpdateDate; SetEntityTags(entity, _tagRepository); } - else if (content.PublishedState == PublishedState.Unpublishing) + else if (entity.PublishedState == PublishedState.Unpublishing) { - content.Published = false; - content.PublishTemplateId = null; - content.PublisherId = null; - content.PublishName = null; - content.PublishDate = null; + entity.Published = false; + entity.PublishTemplateId = null; + entity.PublisherId = null; + entity.PublishName = null; + entity.PublishDate = null; ClearEntityTags(entity, _tagRepository); } @@ -1335,7 +1333,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Utilities - private void SanitizeNames(Content content, bool publishing) + private void SanitizeNames(IContent content, bool publishing) { // a content item *must* have an invariant name, and invariant published name // else we just cannot write the invariant rows (node, content version...) to the database @@ -1400,7 +1398,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement x.NodeId != SqlTemplate.Arg("id")) .OrderBy(x => x.LanguageId)); - private void EnsureVariantNamesAreUnique(Content content, bool publishing) + private void EnsureVariantNamesAreUnique(IContent content, bool publishing) { if (!EnsureUniqueNaming || !content.ContentType.VariesByCulture() || content.CultureInfos.Count == 0) return; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs index 69523a860a..9aa28fb18a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs @@ -6,6 +6,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; @@ -101,7 +102,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value); } - ((UmbracoDomain)entity).AddingEntity(); + entity.AddingEntity(); var factory = new DomainModelFactory(); var dto = factory.BuildDto(entity); @@ -120,7 +121,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IDomain entity) { - ((UmbracoDomain)entity).UpdatingEntity(); + entity.UpdatingEntity(); var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomain WHERE domainName = @domainName AND umbracoDomain.id <> @id", new { domainName = entity.DomainName, id = entity.Id }); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index b4c0e51c22..4a32e373c1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -75,6 +75,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement dtos = page.Items; totalRecords = page.TotalItems; } + else if (isMedia) + { + var page = Database.Page(pageIndexToFetch, pageSize, sql); + dtos = page.Items; + totalRecords = page.TotalItems; + } else { var page = Database.Page(pageIndexToFetch, pageSize, sql); @@ -87,10 +93,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent) BuildVariants(entities.Cast()); - // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - if (isMedia) - BuildProperties(entities, dtos.ToList()); - return entities; } @@ -112,14 +114,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return cdtos.Count == 0 ? null : BuildVariants(BuildDocumentEntity(cdtos[0])); } - var dto = Database.FirstOrDefault(sql); + var dto = isMedia + ? Database.FirstOrDefault(sql) + : Database.FirstOrDefault(sql); + if (dto == null) return null; var entity = BuildEntity(false, isMedia, dto); - if (isMedia) - BuildProperties(entity, dto); - return entity; } @@ -162,7 +164,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : PerformGetAll(objectType); } - private IEnumerable GetEntities(Sql sql, bool isContent, bool isMedia, bool loadMediaProperties) + private IEnumerable GetEntities(Sql sql, bool isContent, bool isMedia) { //isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) @@ -174,15 +176,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : BuildVariants(cdtos.Select(BuildDocumentEntity)).ToList(); } - var dtos = Database.Fetch(sql); - if (dtos.Count == 0) return Enumerable.Empty(); + var dtos = isMedia + ? (IEnumerable)Database.Fetch(sql) + : Database.Fetch(sql); var entities = dtos.Select(x => BuildEntity(false, isMedia, x)).ToArray(); - // TODO: See https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - if (isMedia && loadMediaProperties) - BuildProperties(entities, dtos); - return entities; } @@ -192,7 +191,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectType == Constants.ObjectTypes.Media; var sql = GetFullSqlForEntityType(isContent, isMedia, objectType, filter); - return GetEntities(sql, isContent, isMedia, true); + return GetEntities(sql, isContent, isMedia); } public IEnumerable GetAllPaths(Guid objectType, params int[] ids) @@ -238,22 +237,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql = translator.Translate(); sql = AddGroupBy(isContent, isMedia, sql, true); - return GetEntities(sql, isContent, isMedia, true); - } - - // TODO: See https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - internal IEnumerable GetMediaByQueryWithoutPropertyData(IQuery query) - { - var isContent = false; - var isMedia = true; - - var sql = GetBaseWhere(isContent, isMedia, false, null, Constants.ObjectTypes.Media); - - var translator = new SqlTranslator(sql, query); - sql = translator.Translate(); - sql = AddGroupBy(isContent, isMedia, sql, true); - - return GetEntities(sql, isContent, isMedia, false); + return GetEntities(sql, isContent, isMedia); } public UmbracoObjectTypes GetObjectType(int id) @@ -280,41 +264,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return Database.ExecuteScalar(sql) > 0; } - // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - private void BuildProperties(EntitySlim entity, BaseDto dto) - { - var pdtos = Database.Fetch(GetPropertyData(dto.VersionId)); - foreach (var pdto in pdtos) - BuildProperty(entity, pdto); - } - - // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - private void BuildProperties(EntitySlim[] entities, List dtos) - { - var versionIds = dtos.Select(x => x.VersionId).Distinct().ToList(); - var pdtos = Database.FetchByGroups(versionIds, 2000, GetPropertyData); - - var xentity = entities.ToDictionary(x => x.Id, x => x); // nodeId -> entity - var xdto = dtos.ToDictionary(x => x.VersionId, x => x.NodeId); // versionId -> nodeId - foreach (var pdto in pdtos) - { - var nodeId = xdto[pdto.VersionId]; - var entity = xentity[nodeId]; - BuildProperty(entity, pdto); - } - } - - // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - private void BuildProperty(EntitySlim entity, PropertyDataDto pdto) - { - // explain ?! - var value = string.IsNullOrWhiteSpace(pdto.TextValue) - ? pdto.VarcharValue - : pdto.TextValue.ConvertToJsonIfPossible(); - - entity.AdditionalData[pdto.PropertyTypeDto.Alias] = new EntitySlim.PropertySlim(pdto.PropertyTypeDto.DataTypeDto.EditorAlias, value); - } - private DocumentEntitySlim BuildVariants(DocumentEntitySlim entity) => BuildVariants(new[] { entity }).First(); @@ -400,31 +349,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return AddGroupBy(isContent, isMedia, sql, true); } - private Sql GetPropertyData(int versionId) - { - return Sql() - .Select(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto))) - .From() - .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) - .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) - .Where(x => x.VersionId == versionId); - } - - private Sql GetPropertyData(IEnumerable versionIds) - { - return Sql() - .Select(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto))) - .From() - .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) - .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) - .WhereIn(x => x.VersionId, versionIds) - .OrderBy(x => x.VersionId); - } - // gets the base SELECT + FROM [+ filter] sql // always from the 'current' content version protected Sql GetBase(bool isContent, bool isMedia, Action> filter, bool isCount = false) - { + { var sql = Sql(); if (isCount) @@ -448,6 +376,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql .AndSelect(x => x.Published, x => x.Edited); } + + if (isMedia) + { + sql + .AndSelect(x => Alias(x.Path, "MediaPath")); + } } sql @@ -467,6 +401,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin().On((left, right) => left.NodeId == right.NodeId); } + if (isMedia) + { + sql + .InnerJoin().On((left, right) => left.Id == right.Id); + } + //Any LeftJoin statements need to come last if (isCount == false) { @@ -536,6 +476,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .AndBy(x => x.Published, x => x.Edited); } + if (isMedia) + { + sql + .AndBy(x => Alias(x.Path, "MediaPath")); + } + if (isContent || isMedia) sql @@ -566,23 +512,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Classes - [ExplicitColumns] - internal class UmbracoPropertyDto - { - [Column("propertyEditorAlias")] - public string PropertyEditorAlias { get; set; } - - [Column("propertyTypeAlias")] - public string PropertyAlias { get; set; } - - [Column("varcharValue")] - public string VarcharValue { get; set; } - - [Column("textValue")] - public string TextValue { get; set; } - } - - /// /// The DTO used to fetch results for a content item with its variation info /// @@ -594,6 +523,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public bool Edited { get; set; } } + private class MediaEntityDto : BaseDto + { + public string MediaPath { get; set; } + } + public class VariantInfoDto { public int NodeId { get; set; } @@ -645,7 +579,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent) return BuildDocumentEntity(dto); if (isMedia) - return BuildContentEntity(dto); + return BuildMediaEntity(dto); // EntitySlim does not track changes var entity = new EntitySlim(); @@ -678,11 +612,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.ContentTypeThumbnail = dto.Thumbnail; } - private static EntitySlim BuildContentEntity(BaseDto dto) + private MediaEntitySlim BuildMediaEntity(BaseDto dto) { // EntitySlim does not track changes - var entity = new ContentEntitySlim(); + var entity = new MediaEntitySlim(); BuildContentEntity(entity, dto); + + if (dto is MediaEntityDto contentDto) + { + // fill in the media info + entity.MediaPath = contentDto.MediaPath; + } + return entity; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 0fa48e5521..f708590ea8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -139,7 +139,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IIdentityUserLogin entity) { - ((EntityBase)entity).AddingEntity(); + entity.AddingEntity(); var dto = ExternalLoginFactory.BuildDto(entity); @@ -151,7 +151,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IIdentityUserLogin entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); var dto = ExternalLoginFactory.BuildDto(entity); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index 5a62c25df7..8429532b01 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -129,7 +129,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace()) throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name."); - ((EntityBase) entity).AddingEntity(); + entity.AddingEntity(); // deal with entity becoming the new default entity if (entity.IsDefault) @@ -156,7 +156,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace()) throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name."); - ((EntityBase) entity).UpdatingEntity(); + entity.UpdatingEntity(); if (entity.IsDefault) { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs index 565917e078..f0044e225d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs @@ -132,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMacro entity) { - ((EntityBase)entity).AddingEntity(); + entity.AddingEntity(); var dto = MacroFactory.BuildDto(entity); @@ -152,7 +152,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IMacro entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); ; var dto = MacroFactory.BuildDto(entity); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 65043c4c67..25828b8126 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -217,7 +218,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMedia entity) { var media = (Models.Media) entity; - media.AddingEntity(); + entity.AddingEntity(); // ensure unique name on the same level entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs index 281255e755..1abc75cf3a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -5,6 +5,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; @@ -102,7 +103,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMediaType entity) { - ((MediaType)entity).AddingEntity(); + entity.AddingEntity(); PersistNewBaseContentType(entity); @@ -114,7 +115,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ValidateAlias(entity); //Updates Modified date - ((MediaType)entity).UpdatingEntity(); + entity.UpdatingEntity(); //Look up parent to get and set the correct Path if ParentId has changed if (entity.IsPropertyDirty("ParentId")) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs index ff7a79f98e..c138550de5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -91,8 +92,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMemberGroup entity) { //Save to db + entity.AddingEntity(); var group = (MemberGroup)entity; - group.AddingEntity(); var dto = MemberGroupFactory.BuildDto(group); var o = Database.IsNew(dto) ? Convert.ToInt32(Database.Insert(dto)) : Database.Update(dto); group.Id = dto.NodeId; //Set Id on entity to ensure an Id is set diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 808f61305a..1fc3568fc0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -6,6 +6,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -232,8 +233,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMember entity) { + if (entity.ProviderUserKey == null) + { + entity.ProviderUserKey = entity.Key; + } + entity.AddingEntity(); + var member = (Member) entity; - member.AddingEntity(); // ensure that strings don't contain characters that are invalid in xml // TODO: do we really want to keep doing this here? diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index a32ec1b422..d96854743e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -5,6 +5,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -131,12 +132,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { ValidateAlias(entity); - ((MemberType)entity).AddingEntity(); + entity.AddingEntity(); //set a default icon if one is not specified if (entity.Icon.IsNullOrWhiteSpace()) { - entity.Icon = "icon-user"; + entity.Icon = Constants.Icons.Member; } //By Convention we add 9 standard PropertyTypes to an Umbraco MemberType @@ -165,7 +166,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ValidateAlias(entity); //Updates Modified date - ((MemberType)entity).UpdatingEntity(); + entity.UpdatingEntity(); //Look up parent to get and set the correct Path if ParentId has changed if (entity.IsPropertyDirty("ParentId")) @@ -224,6 +225,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { //this reset's its current data type reference which will be re-assigned based on the property editor assigned on the next line propertyType.DataTypeId = 0; + propertyType.DataTypeKey = default; } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs index bd2580b38f..1dc7aa478d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs @@ -5,6 +5,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index c5ba24f385..4b4af505b8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -134,7 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IRelation entity) { - ((EntityBase)entity).AddingEntity(); + entity.AddingEntity(); var factory = new RelationFactory(entity.RelationType); var dto = factory.BuildDto(entity); @@ -147,7 +147,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IRelation entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); var factory = new RelationFactory(entity.RelationType); var dto = factory.BuildDto(entity); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs index 4faf78bd0a..075d4aa769 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs @@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IRelationType entity) { - ((EntityBase)entity).AddingEntity(); + entity.AddingEntity(); var dto = RelationTypeFactory.BuildDto(entity); @@ -145,7 +145,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IRelationType entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); var dto = RelationTypeFactory.BuildDto(entity); Database.Update(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs index 6b2dfddaeb..1497c2857c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs @@ -5,6 +5,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -96,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IServerRegistration entity) { - ((ServerRegistration)entity).AddingEntity(); + entity.AddingEntity(); var dto = ServerRegistrationFactory.BuildDto(entity); @@ -108,7 +109,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IServerRegistration entity) { - ((ServerRegistration)entity).UpdatingEntity(); + entity.UpdatingEntity(); var dto = ServerRegistrationFactory.BuildDto(entity); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs index 9f27b6b9e3..99e824757d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs @@ -99,7 +99,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (uniqueing) { - if (name.NumPos > 0 && name.Name.StartsWith(nodeName) && name.NumVal == uniqueNumber) + if (name.NumPos > 0 && name.Name.InvariantStartsWith(nodeName) && name.NumVal == uniqueNumber) uniqueNumber++; else break; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs index f26fcca81b..279e03b19e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs @@ -85,7 +85,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected override void PersistNewItem(ITag entity) { - ((EntityBase)entity).AddingEntity(); + entity.AddingEntity(); var dto = TagFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(dto)); @@ -97,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected override void PersistUpdatedItem(ITag entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); var dto = TagFactory.BuildDto(entity); Database.Update(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs index 3935027ada..0701a0996e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -290,7 +290,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IUserGroup entity) { - ((UserGroup) entity).AddingEntity(); + entity.AddingEntity(); var userGroupDto = UserGroupFactory.BuildDto(entity); @@ -304,7 +304,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IUserGroup entity) { - ((UserGroup) entity).UpdatingEntity(); + entity.UpdatingEntity(); var userGroupDto = UserGroupFactory.BuildDto(entity); @@ -411,8 +411,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return; //now the user association - RemoveAllUsersFromGroup(entity.UserGroup.Id); - AddUsersToGroup(entity.UserGroup.Id, entity.UserIds); + RefreshUsersInGroup(entity.UserGroup.Id, entity.UserIds); } protected override void PersistUpdatedItem(UserGroupWithUsers entity) @@ -424,8 +423,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return; //now the user association - RemoveAllUsersFromGroup(entity.UserGroup.Id); - AddUsersToGroup(entity.UserGroup.Id, entity.UserIds); + RefreshUsersInGroup(entity.UserGroup.Id, entity.UserIds); + } + + /// + /// Adds a set of users to a group, first removing any that exist + /// + /// Id of group + /// Ids of users + private void RefreshUsersInGroup(int groupId, int[] userIds) + { + RemoveAllUsersFromGroup(groupId); + AddUsersToGroup(groupId, userIds); } /// @@ -444,7 +453,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// Ids of users private void AddUsersToGroup(int groupId, int[] userIds) { - // TODO: Check if the user exists? foreach (var userId in userIds) { var dto = new User2UserGroupDto diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 9027e9269c..96abc37662 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -434,7 +434,18 @@ ORDER BY colName"; protected override void PersistNewItem(IUser entity) { - ((User) entity).AddingEntity(); + // the use may have no identity, ie ID is zero, and be v7 super + // user - then it has been marked - and we must not persist it + // as new, as we do not want to create a new user - instead, persist + // it as updated + // see also: UserFactory.BuildEntity + if (((User) entity).AdditionalData.ContainsKey("IS_V7_ZERO")) + { + PersistUpdatedItem(entity); + return; + } + + entity.AddingEntity(); // ensure security stamp if missing if (entity.SecurityStamp.IsNullOrWhiteSpace()) @@ -484,7 +495,7 @@ ORDER BY colName"; protected override void PersistUpdatedItem(IUser entity) { // updates Modified date - ((User) entity).UpdatingEntity(); + entity.UpdatingEntity(); // ensure security stamp if missing if (entity.SecurityStamp.IsNullOrWhiteSpace()) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index c352e312ac..55625ff04e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -44,7 +44,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string TruncateTable { get; } string CreateConstraint { get; } string DeleteConstraint { get; } - + string DeleteDefaultConstraint { get; } string FormatDateTime(DateTime date, bool includeTime = true); string Format(TableDefinition table); @@ -106,5 +106,20 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// A Tuple containing: TableName, IndexName, ColumnName, IsUnique /// IEnumerable> GetDefinedIndexes(IDatabase db); + + /// + /// Tries to gets the name of the default constraint on a column. + /// + /// The database. + /// The table name. + /// The column name. + /// The constraint name. + /// A value indicating whether a default constraint was found. + /// + /// Some database engines (e.g. SqlCe) may not have names for default constraints, + /// in which case the function may return true, but is + /// unspecified. + /// + bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName); } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 8f39e36fb9..cb4b7a5176 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -132,6 +132,17 @@ ORDER BY TABLE_NAME, INDEX_NAME"); item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE)); } + /// + public override bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName) + { + // cannot return a true default constraint name (does not exist on SqlCe) + // but we won't really need it anyways - just check whether there is a constraint + constraintName = null; + var hasDefault = db.Fetch(@"select column_hasdefault from information_schema.columns +where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault(); + return hasDefault; + } + public override bool DoesTableExist(IDatabase db, string tableName) { var result = @@ -175,7 +186,7 @@ ORDER BY TABLE_NAME, INDEX_NAME"); { get { - return "ALTER TABLE [{0}] ALTER COLUMN [{1}] DROP DEFAULT"; + return "ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT"; } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index e51aa547b8..fab7526a6b 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -225,6 +225,18 @@ order by T.name, I.name"); } + /// + public override bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName) + { + constraintName = db.Fetch(@"select con.[name] as [constraintName] +from sys.default_constraints con +join sys.columns col on con.object_id=col.default_object_id +join sys.tables tbl on col.object_id=tbl.object_id +where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) + .FirstOrDefault(); + return !constraintName.IsNullOrWhiteSpace(); + } + public override bool DoesTableExist(IDatabase db, string tableName) { var result = @@ -276,7 +288,7 @@ order by T.name, I.name"); return null; } - public override string DeleteDefaultConstraint => "ALTER TABLE [{0}] DROP CONSTRAINT [DF_{0}_{1}]"; + public override string DeleteDefaultConstraint => "ALTER TABLE {0} DROP CONSTRAINT {2}"; public override string DropIndex => "DROP INDEX {0} ON {1}"; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 5b6a9afb04..0c27ac2d50 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -223,6 +223,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax public abstract IEnumerable> GetDefinedIndexes(IDatabase db); + public abstract bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName); + public virtual bool DoesTableExist(IDatabase db, string tableName) { return false; @@ -552,6 +554,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax public virtual string CreateConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; public virtual string DeleteConstraint => "ALTER TABLE {0} DROP CONSTRAINT {1}"; public virtual string CreateForeignKeyConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; + public virtual string CreateDefaultConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} DEFAULT ({2}) FOR {3}"; public virtual string ConvertIntegerToOrderableString => "REPLACE(STR({0}, 8), SPACE(1), '0')"; public virtual string ConvertDateToOrderableString => "CONVERT(nvarchar, {0}, 102)"; diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index dc86ff060c..13422f43b1 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -28,7 +28,8 @@ namespace Umbraco.Core.Persistence { private readonly Lazy _mappers; private readonly ILogger _logger; - private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + + private object _lock = new object(); private DatabaseFactory _npocoDatabaseFactory; private IPocoDataFactory _pocoDataFactory; @@ -36,12 +37,13 @@ namespace Umbraco.Core.Persistence private string _providerName; private DbProviderFactory _dbProviderFactory; private DatabaseType _databaseType; - private bool _serverVersionDetected; private ISqlSyntaxProvider _sqlSyntax; private RetryPolicy _connectionRetryPolicy; private RetryPolicy _commandRetryPolicy; private NPoco.MapperCollection _pocoMappers; + private SqlContext _sqlContext; private bool _upgrading; + private bool _initialized; #region Constructors @@ -106,36 +108,30 @@ namespace Umbraco.Core.Persistence #endregion /// - public bool Configured { get; private set; } - - /// - public string ConnectionString + public bool Configured { get { - EnsureConfigured(); - return _connectionString; + lock (_lock) + { + return !_connectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace(); + } } } /// - public bool CanConnect - { - get - { - if (!Configured || !DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName)) return false; + public bool Initialized => Volatile.Read(ref _initialized); - if (_serverVersionDetected) return true; + /// + public string ConnectionString => _connectionString; - if (_databaseType.IsSqlServer()) - DetectSqlServerVersion(); - _serverVersionDetected = true; + /// + public bool CanConnect => + // actually tries to connect to the database (regardless of configured/initialized) + !_connectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace() && + DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName); - return true; - } - } - - private void DetectSqlServerVersion() + private void UpdateSqlServerDatabaseType() { // replace NPoco database type by a more efficient one @@ -171,7 +167,15 @@ namespace Umbraco.Core.Persistence } /// - public ISqlContext SqlContext { get; private set; } + public ISqlContext SqlContext + { + get + { + // must be initialized to have a context + EnsureInitialized(); + return _sqlContext; + } + } /// public void ConfigureForUpgrade() @@ -182,63 +186,79 @@ namespace Umbraco.Core.Persistence /// public void Configure(string connectionString, string providerName) { - try + if (connectionString.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(connectionString)); + if (providerName.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(providerName)); + + lock (_lock) { - _lock.EnterWriteLock(); - - _logger.Debug("Configuring."); - - if (Configured) throw new InvalidOperationException("Already configured."); - - if (connectionString.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(connectionString)); - if (providerName.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(providerName)); + if (Volatile.Read(ref _initialized)) + throw new InvalidOperationException("Already initialized."); _connectionString = connectionString; _providerName = providerName; - - _connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(_connectionString); - _commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(_connectionString); - - _dbProviderFactory = DbProviderFactories.GetFactory(_providerName); - if (_dbProviderFactory == null) - throw new Exception($"Can't find a provider factory for provider name \"{_providerName}\"."); - _databaseType = DatabaseType.Resolve(_dbProviderFactory.GetType().Name, _providerName); - if (_databaseType == null) - throw new Exception($"Can't find an NPoco database type for provider name \"{_providerName}\"."); - - _sqlSyntax = GetSqlSyntaxProvider(_providerName); - if (_sqlSyntax == null) - throw new Exception($"Can't find a sql syntax provider for provider name \"{_providerName}\"."); - - // ensure we have only 1 set of mappers, and 1 PocoDataFactory, for all database - // so that everything NPoco is properly cached for the lifetime of the application - _pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; - var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver); - _pocoDataFactory = factory; - var config = new FluentConfig(xmappers => factory); - - // create the database factory - _npocoDatabaseFactory = DatabaseFactory.Config(x => x - .UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances - .WithFluentConfig(config)); // with proper configuration - - if (_npocoDatabaseFactory == null) throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance."); - - SqlContext = new SqlContext(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers); - - _logger.Debug("Configured."); - Configured = true; - } - finally - { - if (_lock.IsWriteLockHeld) - _lock.ExitWriteLock(); } + + // rest to be lazy-initialized + } + + private void EnsureInitialized() + { + LazyInitializer.EnsureInitialized(ref _sqlContext, ref _initialized, ref _lock, Initialize); + } + + private SqlContext Initialize() + { + _logger.Debug("Initializing."); + + if (_connectionString.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper connection string."); + if (_providerName.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper provider name."); + + // cannot initialize without being able to talk to the database + if (!DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName)) + throw new Exception("Cannot connect to the database."); + + _connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(_connectionString); + _commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(_connectionString); + + _dbProviderFactory = DbProviderFactories.GetFactory(_providerName); + if (_dbProviderFactory == null) + throw new Exception($"Can't find a provider factory for provider name \"{_providerName}\"."); + _databaseType = DatabaseType.Resolve(_dbProviderFactory.GetType().Name, _providerName); + if (_databaseType == null) + throw new Exception($"Can't find an NPoco database type for provider name \"{_providerName}\"."); + + _sqlSyntax = GetSqlSyntaxProvider(_providerName); + if (_sqlSyntax == null) + throw new Exception($"Can't find a sql syntax provider for provider name \"{_providerName}\"."); + + if (_databaseType.IsSqlServer()) + UpdateSqlServerDatabaseType(); + + // ensure we have only 1 set of mappers, and 1 PocoDataFactory, for all database + // so that everything NPoco is properly cached for the lifetime of the application + _pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; + var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver); + _pocoDataFactory = factory; + var config = new FluentConfig(xmappers => factory); + + // create the database factory + _npocoDatabaseFactory = DatabaseFactory.Config(x => x + .UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances + .WithFluentConfig(config)); // with proper configuration + + if (_npocoDatabaseFactory == null) + throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance."); + + _logger.Debug("Initialized."); + + return new SqlContext(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers); } /// public IUmbracoDatabase CreateDatabase() { + // must be initialized to create a database + EnsureInitialized(); return (IUmbracoDatabase) _npocoDatabaseFactory.GetDatabase(); } @@ -260,22 +280,6 @@ namespace Umbraco.Core.Persistence } } - // ensures that the database is configured, else throws - private void EnsureConfigured() - { - _lock.EnterReadLock(); - try - { - if (Configured == false) - throw new InvalidOperationException("Not configured."); - } - finally - { - if (_lock.IsReadLockHeld) - _lock.ExitReadLock(); - } - } - // method used by NPoco's UmbracoDatabaseFactory to actually create the database instance private UmbracoDatabase CreateDatabaseInstance() { @@ -292,7 +296,7 @@ namespace Umbraco.Core.Persistence //var db = _umbracoDatabaseAccessor.UmbracoDatabase; //_umbracoDatabaseAccessor.UmbracoDatabase = null; //db?.Dispose(); - Configured = false; + Volatile.Write(ref _initialized, false); } // during tests, the thread static var can leak between tests diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs index 9d1193ac82..1654bb3f20 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs @@ -113,7 +113,7 @@ namespace Umbraco.Core.PropertyEditors } catch (Exception e) { - throw new Exception($"Failed to parse configuration \"{configuration}\" as \"{typeof(TConfiguration).Name}\" (see inner exception).", e); + throw new InvalidOperationException($"Failed to parse configuration \"{configuration}\" as \"{typeof(TConfiguration).Name}\" (see inner exception).", e); } } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index 43f4b68b99..dbb2fc467e 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.PropertyEditors // defaults Type = type; Icon = Constants.Icons.PropertyEditor; - Group = "common"; + Group = Constants.PropertyEditors.Groups.Common; // assign properties based on the attribute, if it is found Attribute = GetType().GetCustomAttribute(false); diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs index ca08127d51..821f06513e 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs @@ -121,7 +121,7 @@ namespace Umbraco.Core.PropertyEditors /// Gets or sets an optional group. /// /// The group can be used for example to group the editors by category. - public string Group { get; set; } = "common"; + public string Group { get; set; } = Constants.PropertyEditors.Groups.Common; /// /// Gets or sets a value indicating whether the value editor is deprecated. diff --git a/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs b/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs new file mode 100644 index 0000000000..bef3f42f46 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Marker interface for any editor configuration that supports Ignoring user start nodes + /// + internal interface IIgnoreUserStartNodesConfig + { + bool IgnoreUserStartNodes { get; set; } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs index f6a7cbf32f..0a9cf632bc 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.PropertyEditors /// /// The property type. /// A value indicating whether the converter supports a property type. - bool IsConverter(PublishedPropertyType propertyType); + bool IsConverter(IPublishedPropertyType propertyType); /// /// Determines whether a value is an actual value, or not a value. @@ -36,14 +36,14 @@ namespace Umbraco.Core.PropertyEditors /// The CLR type of values returned by the converter. /// Some of the CLR types may be generated, therefore this method cannot directly return /// a Type object (which may not exist yet). In which case it needs to return a ModelType instance. - Type GetPropertyValueType(PublishedPropertyType propertyType); + Type GetPropertyValueType(IPublishedPropertyType propertyType); /// /// Gets the property cache level. /// /// The property type. /// The property cache level. - PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType); + PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType); /// /// Converts a property source value to an intermediate value. @@ -64,7 +64,7 @@ namespace Umbraco.Core.PropertyEditors /// strings, and xml-whitespace strings appropriately, ie it should know whether to preserve /// white spaces. /// - object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview); + object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview); /// /// Converts a property intermediate value to an Object value. @@ -83,7 +83,7 @@ namespace Umbraco.Core.PropertyEditors /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage /// the cache levels of property values. It is not meant to be used by the converter. /// - object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); + object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); /// /// Converts a property intermediate value to an XPath value. @@ -107,6 +107,6 @@ namespace Umbraco.Core.PropertyEditors /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage /// the cache levels of property values. It is not meant to be used by the converter. /// - object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); + object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); } } diff --git a/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs index d0c40b1e63..60b7d55c01 100644 --- a/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs @@ -5,7 +5,11 @@ namespace Umbraco.Core.PropertyEditors /// /// Represents a property editor for label properties. /// - [DataEditor(Constants.PropertyEditors.Aliases.Label, "Label", "readonlyvalue", Icon = "icon-readonly")] + [DataEditor( + Constants.PropertyEditors.Aliases.Label, + "Label", + "readonlyvalue", + Icon = "icon-readonly")] public class LabelPropertyEditor : DataEditor { /// diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs index 48bfc49ed9..3b6ebc610c 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.PropertyEditors /// public abstract class PropertyValueConverterBase : IPropertyValueConverter { - public virtual bool IsConverter(PublishedPropertyType propertyType) + public virtual bool IsConverter(IPublishedPropertyType propertyType) => false; public virtual bool? IsValue(object value, PropertyValueLevel level) @@ -30,19 +30,19 @@ namespace Umbraco.Core.PropertyEditors return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); } - public virtual Type GetPropertyValueType(PublishedPropertyType propertyType) + public virtual Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (object); - public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public virtual PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; - public virtual object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public virtual object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => source; - public virtual object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public virtual object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => inter; - public virtual object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public virtual object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => inter?.ToString() ?? string.Empty; } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs index 3d69c37b8b..dd2dfb49e7 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs @@ -9,16 +9,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class CheckboxListValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.CheckBoxList); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IEnumerable); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { var sourceString = source?.ToString() ?? string.Empty; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs index 9f260fc973..46dae3e4f0 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs @@ -8,16 +8,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class ColorPickerValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.ColorPicker); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => UseLabel(propertyType) ? typeof(PickedColor) : typeof(string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { var useLabel = UseLabel(propertyType); @@ -39,7 +39,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return ssource; } - private bool UseLabel(PublishedPropertyType propertyType) + private bool UseLabel(IPublishedPropertyType propertyType) { return ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration).UseLabel; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs index ffe9feb653..0206528bf7 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs @@ -8,16 +8,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class DatePickerValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.DateTime); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (DateTime); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return DateTime.MinValue; @@ -39,7 +39,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // default ConvertSourceToObject just returns source ie a DateTime value - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a DateTime already return XmlConvert.ToString((DateTime) inter, XmlDateTimeSerializationMode.Unspecified); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs index 6f7888aee3..7d6e7c0ce9 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs @@ -7,16 +7,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class DecimalValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.Decimal.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (decimal); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) { diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs index e4ef3a50a3..88061a559e 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs @@ -6,16 +6,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class EmailAddressValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.EmailAddress); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { return source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs index 29f6de0271..b3685457ec 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs @@ -28,16 +28,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters _config = config; } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Grid); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (JToken); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs index 79cb748960..6f5bd571b7 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs @@ -14,19 +14,19 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters public class ImageCropperValueConverter : PropertyValueConverterBase { /// - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.ImageCropper); /// - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (ImageCropperValue); /// - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; /// - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs index e0abf17a7e..ca8f23bca2 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs @@ -6,16 +6,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class IntegerValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.Integer.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (int); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { return source.TryConvertTo().Result; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs index e04893716a..12e6238705 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs @@ -31,19 +31,19 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters /// /// /// - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) { return _propertyEditors.TryGet(propertyType.EditorAlias, out var editor) && editor.GetValueEditor().ValueType.InvariantEquals(ValueTypes.Json); } - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (JToken); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs index 05a5f15aaf..84baf226cf 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs @@ -16,10 +16,10 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class LabelValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.Label.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) { var valueType = ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration); switch (valueType.ValueType) @@ -40,10 +40,10 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters } } - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { var valueType = ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration); switch (valueType.ValueType) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index aeacf33eef..a062561ab1 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -7,17 +7,17 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MarkdownEditorValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.MarkdownEditor.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IHtmlString); // PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { // in xml a string is: string // in the database a string is: string @@ -25,13 +25,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return source; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return new HtmlString(inter == null ? string.Empty : (string) inter); } - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return inter?.ToString() ?? string.Empty; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs index bdd09ea33b..cd7f48f510 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs @@ -6,16 +6,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MemberGroupPickerValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.MemberGroupPicker); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { return source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index 1d5f0b1ca3..15e7ce4caf 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -9,18 +9,18 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MultipleTextStringValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.MultipleTextstring.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IEnumerable); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; private static readonly string[] NewLineDelimiters = { "\r\n", "\r", "\n" }; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { // data is (both in database and xml): // @@ -58,7 +58,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters : values.ToArray(); } - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { var d = new XmlDocument(); var e = d.CreateElement("values"); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs index c9528c3e8b..b9c61bb169 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs @@ -22,16 +22,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters Constants.PropertyEditors.Aliases.MultiNodeTreePicker }; - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Aliases.Contains(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { return source?.ToString(); } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs index b99cc7e0e3..61adc9a93e 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs @@ -6,16 +6,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class RadioButtonListValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.RadioButtonList); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { var attempt = source.TryConvertTo(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs index 31ab47223f..11502687b7 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs @@ -18,16 +18,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Slider); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => IsRangeDataType(propertyType.DataType.Id) ? typeof (Range) : typeof (decimal); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { if (source == null) return null; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs index 9b857c2dff..b54c693c14 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs @@ -19,16 +19,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Tags); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IEnumerable); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return Array.Empty(); @@ -43,7 +43,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return source.ToString().Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { return (string[]) source; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs index 2368a1d034..7caa9a90cc 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -13,16 +13,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters Constants.PropertyEditors.Aliases.TextArea }; - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => PropertyTypeAliases.Contains(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { // in xml a string is: string // in the database a string is: string @@ -30,13 +30,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return source; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return inter ?? string.Empty; } - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return inter; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs index 46f660d829..9938af671d 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs @@ -10,17 +10,17 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class TinyMceValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IHtmlString); // PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { // in xml a string is: string // in the database a string is: string @@ -28,13 +28,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return source; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return new HtmlString(inter == null ? string.Empty : (string)inter); } - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return inter; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs index cfa247edaa..407ed13ddf 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs @@ -9,16 +9,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class UploadPropertyConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.UploadField); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { return source?.ToString() ?? ""; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs index 8ad09733f8..153462ccf5 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs @@ -6,16 +6,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class YesNoValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.Boolean; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (bool); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { // in xml a boolean is: string // in the database a boolean is: string "1" or "0" or empty @@ -49,7 +49,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // default ConvertSourceToObject just returns source ie a boolean value - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a boolean already return (bool)inter ? "1" : "0"; diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs new file mode 100644 index 0000000000..921883b822 --- /dev/null +++ b/src/Umbraco.Core/PublishedContentExtensions.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core +{ + public static class PublishedContentExtensions + { + private static IVariationContextAccessor VariationContextAccessor => Current.VariationContextAccessor; + + /// + /// Determines whether the content has a culture. + /// + /// Culture is case-insensitive. + public static bool HasCulture(this IPublishedContent content, string culture) + => content.Cultures.ContainsKey(culture ?? string.Empty); + + /// + /// Determines whether the content is invariant, or has a culture. + /// + /// Culture is case-insensitive. + public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture) + => !content.ContentType.VariesByCulture() || content.Cultures.ContainsKey(culture ?? ""); + + /// + /// Filters a sequence of to return invariant items, and items that are published for the specified culture. + /// + /// The content items. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null). + internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable contents, string culture = null) + where T : class, IPublishedContent + { + if (contents == null) throw new ArgumentNullException(nameof(contents)); + + culture = culture ?? Current.VariationContextAccessor.VariationContext?.Culture ?? ""; + + // either does not vary by culture, or has the specified culture + return contents.Where(x => !x.ContentType.VariesByCulture() || HasCulture(x, culture)); + } + + /// + /// Gets the name of the content item. + /// + /// The content item. + /// The specific culture to get the name for. If null is used the current culture is used (Default is null). + public static string Name(this IPublishedContent content, string culture = null) + { + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) + return content.Cultures.TryGetValue("", out var invariantInfos) ? invariantInfos.Name : null; + + // handle context culture for variant + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + + // get + return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.Name : null; + } + + + /// + /// Gets the url segment of the content item. + /// + /// The content item. + /// The specific culture to get the url segment for. If null is used the current culture is used (Default is null). + public static string UrlSegment(this IPublishedContent content, string culture = null) + { + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) + return content.Cultures.TryGetValue("", out var invariantInfos) ? invariantInfos.UrlSegment : null; + + // handle context culture for variant + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + + // get + return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.UrlSegment : null; + } + + /// + /// Gets the culture date of the content item. + /// + /// The content item. + /// The specific culture to get the name for. If null is used the current culture is used (Default is null). + public static DateTime CultureDate(this IPublishedContent content, string culture = null) + { + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) + return content.UpdateDate; + + // handle context culture for variant + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + + // get + return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.Date : DateTime.MinValue; + } + + + /// + /// Gets the children of the content item. + /// + /// The content item. + /// + /// The specific culture to get the url children for. Default is null which will use the current culture in + /// + /// + /// Gets children that are available for the specified culture. + /// Children are sorted by their sortOrder. + /// + /// For culture, + /// if null is used the current culture is used. + /// If an empty string is used only invariant children are returned. + /// If "*" is used all children are returned. + /// + /// + /// If a variant culture is specified or there is a current culture in the then the Children returned + /// will include both the variant children matching the culture AND the invariant children because the invariant children flow with the current culture. + /// However, if an empty string is specified only invariant children are returned. + /// + /// + public static IEnumerable Children(this IPublishedContent content, string culture = null) + { + // handle context culture for variant + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + + var children = content.ChildrenForAllCultures; + return culture == "*" + ? children + : children.Where(x => x.IsInvariantOrHasCulture(culture)); + } + } +} diff --git a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs index 4e026490a4..de6eeb6a42 100644 --- a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs +++ b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs @@ -9,7 +9,14 @@ namespace Umbraco.Core public static class PublishedModelFactoryExtensions { /// - /// Executes an action with a safe live factory/ + /// Returns true if the current is an implementation of + /// + /// + /// + public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory; + + /// + /// Executes an action with a safe live factory /// /// /// If the factory is a live factory, ensures it is refreshed and locked while executing the action. @@ -20,6 +27,7 @@ namespace Umbraco.Core { lock (liveFactory.SyncRoot) { + //Call refresh on the live factory to re-compile the models liveFactory.Refresh(); action(); } diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index f32a46f3f8..1f004846d0 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -58,7 +58,7 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(); // register our predefined validators - composition.WithCollectionBuilder() + composition.ManifestValueValidators() .Add() .Add() .Add() @@ -66,6 +66,9 @@ namespace Umbraco.Core.Runtime .Add() .Add(); + // register the manifest filter collection builder (collection is empty by default) + composition.ManifestFilters(); + // properties and parameters derive from data editors composition.WithCollectionBuilder() .Add(() => composition.TypeLoader.GetDataEditors()); diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index f9a41b4f66..5b069641c4 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -139,7 +139,7 @@ namespace Umbraco.Core.Runtime // there should be none, really - this is here "just in case" Compose(composition); - // acquire the main domain + // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate AcquireMainDom(mainDom); // determine our runtime level @@ -218,13 +218,13 @@ namespace Umbraco.Core.Runtime IOHelper.SetRootDirectory(path); } - private void AcquireMainDom(MainDom mainDom) + private bool AcquireMainDom(MainDom mainDom) { using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) { try { - mainDom.Acquire(); + return mainDom.Acquire(); } catch { diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 6fb8a04c0d..5d34fe70a1 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -97,6 +97,11 @@ namespace Umbraco.Core /// internal void EnsureApplicationUrl(HttpRequestBase request = null) { + //Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that + // it changes the URL to `localhost:80` which actually doesn't work for pinging itself, it only works internally in Azure. The ironic part + // about this is that this is here specifically for the slot swap scenario https://issues.umbraco.org/issue/U4-10626 + + // see U4-10626 - in some cases we want to reset the application url // (this is a simplified version of what was in 7.x) // note: should this be optional? is it expensive? diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs index 497f7d47a9..bf7f87fd1a 100644 --- a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs +++ b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs @@ -6,9 +6,25 @@ namespace Umbraco.Core.Services.Changes public enum ContentTypeChangeTypes : byte { None = 0, - Create = 1, // item type has been created, no impact - RefreshMain = 2, // changed, impacts content (adding property or composition does NOT) - RefreshOther = 4, // changed, other changes - Remove = 8 // item type has been removed + + /// + /// Item type has been created, no impact + /// + Create = 1, + + /// + /// Content type changes impact only the Content type being saved + /// + RefreshMain = 2, + + /// + /// Content type changes impacts the content type being saved and others used that are composed of it + /// + RefreshOther = 4, // changed, other change + + /// + /// Content type was removed + /// + Remove = 8 } } diff --git a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs index b3d15bd612..a5f5efdba9 100644 --- a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs +++ b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs @@ -2,9 +2,9 @@ namespace Umbraco.Core.Services.Changes { - internal static class TreeChangeExtensions + public static class TreeChangeExtensions { - public static TreeChange.EventArgs ToEventArgs(this IEnumerable> changes) + internal static TreeChange.EventArgs ToEventArgs(this IEnumerable> changes) { return new TreeChange.EventArgs(changes); } diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index 1175df81dc..dfe02ba690 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -11,6 +12,42 @@ namespace Umbraco.Core.Services /// public static class ContentServiceExtensions { + #region RTE Anchor values + + private static readonly Regex AnchorRegex = new Regex("", RegexOptions.Compiled); + + internal static IEnumerable GetAnchorValuesFromRTEs(this IContentService contentService, int id, string culture = "*") + { + var result = new List(); + var content = contentService.GetById(id); + + foreach (var contentProperty in content.Properties) + { + if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.TinyMce)) + { + var value = contentProperty.GetValue(culture)?.ToString(); + if (!string.IsNullOrEmpty(value)) + { + result.AddRange(contentService.GetAnchorValuesFromRTEContent(value)); + } + } + } + return result; + } + + + internal static IEnumerable GetAnchorValuesFromRTEContent(this IContentService contentService, string rteContent) + { + var result = new List(); + var matches = AnchorRegex.Matches(rteContent); + foreach (Match match in matches) + { + result.Add(match.Value.Split('\"')[1]); + } + return result; + } + #endregion + public static IEnumerable GetByIds(this IContentService contentService, IEnumerable ids) { var guids = new List(); diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs new file mode 100644 index 0000000000..3b72a6f258 --- /dev/null +++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Services +{ + internal static class DateTypeServiceExtensions + { + public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTypeService, Guid key) + { + if (DataTypeExtensions.IsBuildInDataType(key)) return false; //built in ones can never be ignoring start nodes + + var dataType = dataTypeService.GetDataType(key); + + if (dataType != null && dataType.Configuration is IIgnoreUserStartNodesConfig ignoreStartNodesConfig) + return ignoreStartNodesConfig.IgnoreUserStartNodes; + + return false; + } + } +} diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 48e577a8f0..6f9ca58821 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -526,5 +526,6 @@ namespace Umbraco.Core.Services OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId); #endregion + } } diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 3dc530e250..bb56e110cd 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Services { + /// /// Defines the DataType Service, which is an easy access to operations involving /// diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index 9d0399f324..f03bc640ec 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -15,92 +15,40 @@ namespace Umbraco.Core.Services /// The identifier of the entity. IEntitySlim Get(int id); - /// - /// Gets an entity. - /// - /// The identifier of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(int id, bool full); - /// /// Gets an entity. /// /// The unique key of the entity. IEntitySlim Get(Guid key); - /// - /// Gets an entity. - /// - /// The unique key of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(Guid key, bool full); - /// /// Gets an entity. /// /// The identifier of the entity. /// The object type of the entity. IEntitySlim Get(int id, UmbracoObjectTypes objectType); - - /// - /// Gets an entity. - /// - /// The identifier of the entity. - /// The object type of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(int id, UmbracoObjectTypes objectType, bool full); - + /// /// Gets an entity. /// /// The unique key of the entity. /// The object type of the entity. IEntitySlim Get(Guid key, UmbracoObjectTypes objectType); - - /// - /// Gets an entity. - /// - /// The unique key of the entity. - /// The object type of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(Guid key, UmbracoObjectTypes objectType, bool full); - + /// /// Gets an entity. /// /// The type used to determine the object type of the entity. /// The identifier of the entity. IEntitySlim Get(int id) where T : IUmbracoEntity; - - /// - /// Gets an entity. - /// - /// The type used to determine the object type of the entity. - /// The identifier of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(int id, bool full) where T : IUmbracoEntity; - + /// /// Gets an entity. /// /// The type used to determine the object type of the entity. /// The unique key of the entity. IEntitySlim Get(Guid key) where T : IUmbracoEntity; - - /// - /// Gets an entity. - /// - /// The type used to determine the object type of the entity. - /// The unique key of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(Guid key, bool full) where T : IUmbracoEntity; - + /// /// Determines whether an entity exists. /// diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index e2733a311d..ef22632d6e 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -142,52 +142,43 @@ namespace Umbraco.Core.Services /// Gets the Child object from a Relation as an /// /// Relation to retrieve child object from - /// Optional bool to load the complete object graph when set to False /// An - IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false); + IUmbracoEntity GetChildEntityFromRelation(IRelation relation); /// /// Gets the Parent object from a Relation as an /// /// Relation to retrieve parent object from - /// Optional bool to load the complete object graph when set to False /// An - IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false); + IUmbracoEntity GetParentEntityFromRelation(IRelation relation); /// /// Gets the Parent and Child objects from a Relation as a "/> with . /// /// Relation to retrieve parent and child object from - /// Optional bool to load the complete object graph when set to False /// Returns a Tuple with Parent (item1) and Child (item2) - Tuple GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false); + Tuple GetEntitiesFromRelation(IRelation relation); /// /// Gets the Child objects from a list of Relations as a list of objects. /// /// List of relations to retrieve child objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of - IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false); + IEnumerable GetChildEntitiesFromRelations(IEnumerable relations); /// /// Gets the Parent objects from a list of Relations as a list of objects. /// /// List of relations to retrieve parent objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of - IEnumerable GetParentEntitiesFromRelations(IEnumerable relations, - bool loadBaseType = false); + IEnumerable GetParentEntitiesFromRelations(IEnumerable relations); /// /// Gets the Parent and Child objects from a list of Relations as a list of objects. /// /// List of relations to retrieve parent and child objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of with - IEnumerable> GetEntitiesFromRelations( - IEnumerable relations, - bool loadBaseType = false); + IEnumerable> GetEntitiesFromRelations(IEnumerable relations); /// /// Relates two objects by their entity Ids. diff --git a/src/Umbraco.Core/Services/Implement/AuditService.cs b/src/Umbraco.Core/Services/Implement/AuditService.cs index 46c851a789..5eb08f2dea 100644 --- a/src/Umbraco.Core/Services/Implement/AuditService.cs +++ b/src/Umbraco.Core/Services/Implement/AuditService.cs @@ -51,8 +51,8 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { var result = sinceDate.HasValue == false - ? _auditRepository.Get(Query().Where(x => x.UserId == userId && x.AuditType == type)) - : _auditRepository.Get(Query().Where(x => x.UserId == userId && x.AuditType == type && x.CreateDate >= sinceDate.Value)); + ? _auditRepository.Get(type, Query().Where(x => x.UserId == userId)) + : _auditRepository.Get(type, Query().Where(x => x.UserId == userId && x.CreateDate >= sinceDate.Value)); scope.Complete(); return result; } @@ -63,8 +63,8 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { var result = sinceDate.HasValue == false - ? _auditRepository.Get(Query().Where(x => x.AuditType == type)) - : _auditRepository.Get(Query().Where(x => x.AuditType == type && x.CreateDate >= sinceDate.Value)); + ? _auditRepository.Get(type, Query()) + : _auditRepository.Get(type, Query().Where(x => x.CreateDate >= sinceDate.Value)); scope.Complete(); return result; } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index bd77daffbe..e49dcf4a12 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; @@ -31,6 +32,7 @@ namespace Umbraco.Core.Services.Implement private IQuery _queryNotTrashed; //TODO: The non-lazy object should be injected private readonly Lazy _propertyValidationService = new Lazy(() => new PropertyValidationService()); + #region Constructors @@ -757,6 +759,11 @@ namespace Umbraco.Core.Services.Implement if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) throw new InvalidOperationException("Cannot save (un)publishing content, use the dedicated SavePublished method."); + if (content.Name != null && content.Name.Length > 255) + { + throw new InvalidOperationException("Name cannot be more than 255 characters in length."); + } + var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) @@ -870,6 +877,11 @@ namespace Umbraco.Core.Services.Implement throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types."); } + if(content.Name != null && content.Name.Length > 255) + { + throw new InvalidOperationException("Name cannot be more than 255 characters in length."); + } + using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.ContentTree); @@ -900,6 +912,11 @@ namespace Umbraco.Core.Services.Implement if (content == null) throw new ArgumentNullException(nameof(content)); if (cultures == null) throw new ArgumentNullException(nameof(cultures)); + if (content.Name != null && content.Name.Length > 255) + { + throw new InvalidOperationException("Name cannot be more than 255 characters in length."); + } + using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.ContentTree); @@ -1680,12 +1697,11 @@ namespace Umbraco.Core.Services.Implement } const int pageSize = 500; - var page = 0; var total = long.MaxValue; - while (page * pageSize < total) + while (total > 0) { //get descendants - ordered from deepest to shallowest - var descendants = GetPagedDescendants(content.Id, page, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); + var descendants = GetPagedDescendants(content.Id, 0, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); foreach (var c in descendants) DoDelete(c); } @@ -1911,11 +1927,10 @@ namespace Umbraco.Core.Services.Implement paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString) : parent.Path) + "," + content.Id; const int pageSize = 500; - var page = 0; var total = long.MaxValue; - while (page * pageSize < total) + while (total > 0) { - var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); + var descendants = GetPagedDescendantsLocked(originalPath, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); foreach (var descendant in descendants) { moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path @@ -3011,5 +3026,8 @@ namespace Umbraco.Core.Services.Implement } #endregion + + + } } diff --git a/src/Umbraco.Core/Services/Implement/EntityService.cs b/src/Umbraco.Core/Services/Implement/EntityService.cs index 00edde48f3..04e2624592 100644 --- a/src/Umbraco.Core/Services/Implement/EntityService.cs +++ b/src/Umbraco.Core/Services/Implement/EntityService.cs @@ -7,11 +7,9 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; namespace Umbraco.Core.Services.Implement @@ -19,30 +17,25 @@ namespace Umbraco.Core.Services.Implement public class EntityService : ScopeRepositoryService, IEntityService { private readonly IEntityRepository _entityRepository; - private readonly Dictionary GetById, Func GetByKey)> _objectTypes; + private readonly Dictionary _objectTypes; private IQuery _queryRootEntity; private readonly IdkMap _idkMap; - public EntityService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, - IContentService contentService, IContentTypeService contentTypeService, - IMediaService mediaService, IMediaTypeService mediaTypeService, - IDataTypeService dataTypeService, - IMemberService memberService, IMemberTypeService memberTypeService, IdkMap idkMap, - IEntityRepository entityRepository) + public EntityService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IdkMap idkMap, IEntityRepository entityRepository) : base(provider, logger, eventMessagesFactory) { _idkMap = idkMap; _entityRepository = entityRepository; - _objectTypes = new Dictionary, Func)> + _objectTypes = new Dictionary { - { typeof (IDataType).FullName, (UmbracoObjectTypes.DataType, dataTypeService.GetDataType, dataTypeService.GetDataType) }, - { typeof (IContent).FullName, (UmbracoObjectTypes.Document, contentService.GetById, contentService.GetById) }, - { typeof (IContentType).FullName, (UmbracoObjectTypes.DocumentType, contentTypeService.Get, contentTypeService.Get) }, - { typeof (IMedia).FullName, (UmbracoObjectTypes.Media, mediaService.GetById, mediaService.GetById) }, - { typeof (IMediaType).FullName, (UmbracoObjectTypes.MediaType, mediaTypeService.Get, mediaTypeService.Get) }, - { typeof (IMember).FullName, (UmbracoObjectTypes.Member, memberService.GetById, memberService.GetByKey) }, - { typeof (IMemberType).FullName, (UmbracoObjectTypes.MemberType, memberTypeService.Get, memberTypeService.Get) }, + { typeof (IDataType).FullName, UmbracoObjectTypes.DataType }, + { typeof (IContent).FullName, UmbracoObjectTypes.Document }, + { typeof (IContentType).FullName, UmbracoObjectTypes.DocumentType }, + { typeof (IMedia).FullName, UmbracoObjectTypes.Media }, + { typeof (IMediaType).FullName, UmbracoObjectTypes.MediaType }, + { typeof (IMember).FullName, UmbracoObjectTypes.Member }, + { typeof (IMemberType).FullName, UmbracoObjectTypes.MemberType }, }; } @@ -54,162 +47,68 @@ namespace Umbraco.Core.Services.Implement #endregion - // gets the getters, throws if not supported - private (UmbracoObjectTypes ObjectType, Func GetById, Func GetByKey) GetGetters(Type type) + // gets the object type, throws if not supported + private UmbracoObjectTypes GetObjectType(Type type) { - if (type?.FullName == null || !_objectTypes.TryGetValue(type.FullName, out var getters)) + if (type?.FullName == null || !_objectTypes.TryGetValue(type.FullName, out var objType)) throw new NotSupportedException($"Type \"{type?.FullName ?? ""}\" is not supported here."); - return getters; + return objType; } /// public IEntitySlim Get(int id) { - return (IEntitySlim) Get(id, false); - } - - /// - public IUmbracoEntity Get(int id, bool full) - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(id); - } + return _entityRepository.Get(id); } - - // get the full entity - var objectType = GetObjectType(id); - var entityType = objectType.GetClrType(); - var getters = GetGetters(entityType); - return getters.GetById(id); } /// public IEntitySlim Get(Guid key) { - return (IEntitySlim) Get(key, false); - } - - /// - public IUmbracoEntity Get(Guid key, bool full) - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(key); - } + return _entityRepository.Get(key); } - - // get the full entity - var objectType = GetObjectType(key); - var entityType = objectType.GetClrType(); - var getters = GetGetters(entityType); - return getters.GetByKey(key); } /// public virtual IEntitySlim Get(int id, UmbracoObjectTypes objectType) { - return (IEntitySlim) Get(id, objectType, false); - } - - /// - public virtual IUmbracoEntity Get(int id, UmbracoObjectTypes objectType, bool full) - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(id, objectType.GetGuid()); - } + return _entityRepository.Get(id, objectType.GetGuid()); } - - // get the full entity - var entityType = objectType.GetClrType(); - var getters = GetGetters(entityType); - return getters.GetById(id); } /// public IEntitySlim Get(Guid key, UmbracoObjectTypes objectType) { - return (IEntitySlim) Get(key, objectType, false); - } - - /// - public IUmbracoEntity Get(Guid key, UmbracoObjectTypes objectType, bool full) - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(key, objectType.GetGuid()); - } + return _entityRepository.Get(key, objectType.GetGuid()); } - - // get the full entity - var entityType = objectType.GetClrType(); - var getters = GetGetters(entityType); - return getters.GetByKey(key); } /// public virtual IEntitySlim Get(int id) where T : IUmbracoEntity { - return (IEntitySlim) Get(id, false); - } - - /// - public virtual IUmbracoEntity Get(int id, bool full) - where T : IUmbracoEntity - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(id); - } + return _entityRepository.Get(id); } - - // get the full entity - var entityType = typeof (T); - var getters = GetGetters(entityType); - return getters.GetById(id); } /// public virtual IEntitySlim Get(Guid key) where T : IUmbracoEntity { - return (IEntitySlim) Get(key, false); - } - - /// - public IUmbracoEntity Get(Guid key, bool full) - where T : IUmbracoEntity - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(key); - } + return _entityRepository.Get(key); } - - // get the full entity - var entityType = typeof (T); - var getters = GetGetters(entityType); - return getters.GetByKey(key); } /// @@ -240,8 +139,7 @@ namespace Umbraco.Core.Services.Implement where T : IUmbracoEntity { var entityType = typeof (T); - var getters = GetGetters(entityType); - var objectType = getters.ObjectType; + var objectType = GetObjectType(entityType); var objectTypeId = objectType.GetGuid(); using (ScopeProvider.CreateScope(autoComplete: true)) @@ -261,7 +159,7 @@ namespace Umbraco.Core.Services.Implement if (entityType == null) throw new NotSupportedException($"Type \"{objectType}\" is not supported here."); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -277,7 +175,7 @@ namespace Umbraco.Core.Services.Implement public virtual IEnumerable GetAll(Guid objectType, params int[] ids) { var entityType = ObjectTypes.GetClrType(objectType); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -290,8 +188,7 @@ namespace Umbraco.Core.Services.Implement where T : IUmbracoEntity { var entityType = typeof (T); - var getters = GetGetters(entityType); - var objectType = getters.ObjectType; + var objectType = GetObjectType(entityType); var objectTypeId = objectType.GetGuid(); using (ScopeProvider.CreateScope(autoComplete: true)) @@ -304,7 +201,7 @@ namespace Umbraco.Core.Services.Implement public IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] keys) { var entityType = objectType.GetClrType(); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -316,7 +213,7 @@ namespace Umbraco.Core.Services.Implement public virtual IEnumerable GetAll(Guid objectType, params Guid[] keys) { var entityType = ObjectTypes.GetClrType(objectType); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -377,22 +274,6 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of children by the parent's Id and UmbracoObjectType without adding property data - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - internal IEnumerable GetMediaChildrenWithoutPropertyData(int parentId) - { - using (ScopeProvider.CreateScope(autoComplete: true)) - { - var query = Query().Where(x => x.ParentId == parentId); - - // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - return ((EntityRepository)_entityRepository).GetMediaByQueryWithoutPropertyData(query); - } - } - /// public virtual IEnumerable GetDescendants(int id) { @@ -578,7 +459,7 @@ namespace Umbraco.Core.Services.Implement public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params int[] ids) { var entityType = objectType.GetClrType(); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -590,7 +471,7 @@ namespace Umbraco.Core.Services.Implement public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys) { var entityType = objectType.GetClrType(); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { diff --git a/src/Umbraco.Core/Services/Implement/KeyValueService.cs b/src/Umbraco.Core/Services/Implement/KeyValueService.cs index eb68cea25c..b3f3f2468d 100644 --- a/src/Umbraco.Core/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Core/Services/Implement/KeyValueService.cs @@ -28,7 +28,6 @@ namespace Umbraco.Core.Services.Implement { if (_initialized) return; Initialize(); - _initialized = true; } } @@ -41,7 +40,10 @@ namespace Umbraco.Core.Services.Implement // then everything should be ok (the table should exist, etc) if (UmbracoVersion.LocalVersion != null && UmbracoVersion.LocalVersion.Major >= 8) + { + _initialized = true; return; + } // else we are upgrading from 7, we can assume that the locks table // exists, but we need to create everything for key/value @@ -53,6 +55,10 @@ namespace Umbraco.Core.Services.Implement initMigration.Migrate(); scope.Complete(); } + + // but don't assume we are initializing + // we are upgrading from v7 and if anything goes wrong, + // the table and everything will be rolled back } /// diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index 2ff39f7f7d..ab075c4ade 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -201,7 +201,7 @@ namespace Umbraco.Core.Services.Implement var mediaType = GetMediaType(mediaTypeAlias); if (mediaType == null) - throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback // causes rollback + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback var media = new Models.Media(name, parent, mediaType); CreateMedia(scope, media, parent, userId, false); @@ -227,13 +227,13 @@ namespace Umbraco.Core.Services.Implement // locking the media tree secures media types too scope.WriteLock(Constants.Locks.MediaTree); - var mediaType = GetMediaType(mediaTypeAlias); // + locks // + locks + var mediaType = GetMediaType(mediaTypeAlias); // + locks if (mediaType == null) - throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback // causes rollback + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback - var parent = parentId > 0 ? GetById(parentId) : null; // + locks // + locks + var parent = parentId > 0 ? GetById(parentId) : null; // + locks if (parentId > 0 && parent == null) - throw new ArgumentException("No media with that id.", nameof(parentId)); // causes rollback // causes rollback + throw new ArgumentException("No media with that id.", nameof(parentId)); // causes rollback var media = parentId > 0 ? new Models.Media(name, parent, mediaType) : new Models.Media(name, parentId, mediaType); CreateMedia(scope, media, parent, userId, true); @@ -261,9 +261,9 @@ namespace Umbraco.Core.Services.Implement // locking the media tree secures media types too scope.WriteLock(Constants.Locks.MediaTree); - var mediaType = GetMediaType(mediaTypeAlias); // + locks // + locks + var mediaType = GetMediaType(mediaTypeAlias); // + locks if (mediaType == null) - throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback // causes rollback + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback var media = new Models.Media(name, parent, mediaType); CreateMedia(scope, media, parent, userId, true); @@ -645,8 +645,6 @@ namespace Umbraco.Core.Services.Implement } // poor man's validation? - // poor man's validation? - if (string.IsNullOrWhiteSpace(media.Name)) throw new ArgumentException("Media has no name.", nameof(media)); @@ -934,7 +932,7 @@ namespace Umbraco.Core.Services.Implement var parent = parentId == Constants.System.Root ? null : GetById(parentId); if (parentId != Constants.System.Root && (parent == null || parent.Trashed)) - throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback // causes rollback + throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback var moveEventInfo = new MoveEventInfo(media, media.Path, parentId); var moveEventArgs = new MoveEventArgs(true, evtMsgs, moveEventInfo); @@ -947,12 +945,6 @@ namespace Umbraco.Core.Services.Implement // if media was trashed, and since we're not moving to the recycle bin, // indicate that the trashed status should be changed to false, else just // leave it unchanged - // if media was trashed, and since we're not moving to the recycle bin, - - // indicate that the trashed status should be changed to false, else just - - // leave it unchanged - var trashed = media.Trashed ? false : (bool?) null; PerformMoveLocked(media, parentId, parent, userId, moves, trashed); @@ -1042,17 +1034,11 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.MediaTree); + // no idea what those events are for, keep a simplified version + // v7 EmptyingRecycleBin and EmptiedRecycleBin events are greatly simplified since // each deleted items will have its own deleting/deleted events. so, files and such // are managed by Delete, and not here. - - // no idea what those events are for, keep a simplified version - // v7 EmptyingRecycleBin and EmptiedRecycleBin events are greatly simplified since - // each deleted items will have its own deleting/deleted events. so, files and such - - // emptying the recycle bin means deleting whatever is in there - do it properly! - // are managed by Delete, and not here. - // no idea what those events are for, keep a simplified version var args = new RecycleBinEventArgs(nodeObjectType, evtMsgs); if (scope.Events.DispatchCancelable(EmptyingRecycleBin, this, args)) @@ -1113,11 +1099,6 @@ namespace Umbraco.Core.Services.Implement { // if the current sort order equals that of the media we don't // need to update it, so just increment the sort order and continue. - // if the current sort order equals that of the media we don't - - // else update - // need to update it, so just increment the sort order and continue. - // save if (media.SortOrder == sortOrder) { sortOrder++; diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index a316d04f8e..405c3a2800 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -285,39 +285,36 @@ namespace Umbraco.Core.Services.Implement /// Gets the Child object from a Relation as an /// /// Relation to retrieve child object from - /// Optional bool to load the complete object graph when set to False /// An - public IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false) + public IUmbracoEntity GetChildEntityFromRelation(IRelation relation) { var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - return _entityService.Get(relation.ChildId, objectType, loadBaseType); + return _entityService.Get(relation.ChildId, objectType); } /// /// Gets the Parent object from a Relation as an /// /// Relation to retrieve parent object from - /// Optional bool to load the complete object graph when set to False /// An - public IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false) + public IUmbracoEntity GetParentEntityFromRelation(IRelation relation) { var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - return _entityService.Get(relation.ParentId, objectType, loadBaseType); + return _entityService.Get(relation.ParentId, objectType); } /// /// Gets the Parent and Child objects from a Relation as a "/> with . /// /// Relation to retrieve parent and child object from - /// Optional bool to load the complete object graph when set to False /// Returns a Tuple with Parent (item1) and Child (item2) - public Tuple GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false) + public Tuple GetEntitiesFromRelation(IRelation relation) { var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType); - var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType); + var child = _entityService.Get(relation.ChildId, childObjectType); + var parent = _entityService.Get(relation.ParentId, parentObjectType); return new Tuple(parent, child); } @@ -326,14 +323,13 @@ namespace Umbraco.Core.Services.Implement /// Gets the Child objects from a list of Relations as a list of objects. /// /// List of relations to retrieve child objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of - public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) + public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations) { foreach (var relation in relations) { var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - yield return _entityService.Get(relation.ChildId, objectType, loadBaseType); + yield return _entityService.Get(relation.ChildId, objectType); } } @@ -341,14 +337,13 @@ namespace Umbraco.Core.Services.Implement /// Gets the Parent objects from a list of Relations as a list of objects. /// /// List of relations to retrieve parent objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of - public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) + public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations) { foreach (var relation in relations) { var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - yield return _entityService.Get(relation.ParentId, objectType, loadBaseType); + yield return _entityService.Get(relation.ParentId, objectType); } } @@ -356,17 +351,16 @@ namespace Umbraco.Core.Services.Implement /// Gets the Parent and Child objects from a list of Relations as a list of objects. /// /// List of relations to retrieve parent and child objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of with - public IEnumerable> GetEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) + public IEnumerable> GetEntitiesFromRelations(IEnumerable relations) { foreach (var relation in relations) { var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType); - var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType); + var child = _entityService.Get(relation.ChildId, childObjectType); + var parent = _entityService.Get(relation.ParentId, parentObjectType); yield return new Tuple(parent, child); } diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs index 51fdb7e8e3..ce5b3ef8c4 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs @@ -83,10 +83,10 @@ namespace Umbraco.Core.Services internal static string UmbracoDictionaryTranslate(this ILocalizedTextService manager, string text) { var cultureDictionary = CultureDictionary; - return UmbracoDictionaryTranslate(text, cultureDictionary); + return manager.UmbracoDictionaryTranslate(text, cultureDictionary); } - private static string UmbracoDictionaryTranslate(string text, ICultureDictionary cultureDictionary) + private static string UmbracoDictionaryTranslate(this ILocalizedTextService manager, string text, ICultureDictionary cultureDictionary) { if (text == null) return null; @@ -95,7 +95,14 @@ namespace Umbraco.Core.Services return text; text = text.Substring(1); - return cultureDictionary[text].IfNullOrWhiteSpace(text); + var value = cultureDictionary[text]; + if (value.IsNullOrWhiteSpace() == false) + { + return value; + } + + value = manager.Localize(text.Replace('_', '/')); + return value.StartsWith("[") ? text : value; } private static ICultureDictionary CultureDictionary diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8eeb8773ea..6adce1944f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -13,7 +13,7 @@ true - full + portable false bin\Debug\ TRACE;DEBUG @@ -23,7 +23,7 @@ latest - pdbonly + portable true bin\Release\ TRACE @@ -55,6 +55,11 @@ + + 1.0.0-beta2-19324-01 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + 1.3.0 @@ -218,16 +223,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -235,6 +275,7 @@ + @@ -386,23 +427,6 @@ - - - - - - - - - - - - - - - - - @@ -415,14 +439,13 @@ - - + @@ -432,6 +455,7 @@ + @@ -704,7 +728,6 @@ - @@ -1138,30 +1161,7 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Core/WaitHandleExtensions.cs b/src/Umbraco.Core/WaitHandleExtensions.cs index 0d840a2496..7a9294c113 100644 --- a/src/Umbraco.Core/WaitHandleExtensions.cs +++ b/src/Umbraco.Core/WaitHandleExtensions.cs @@ -23,6 +23,8 @@ namespace Umbraco.Core handle, (state, timedOut) => { + //TODO: We aren't checking if this is timed out + tcs.SetResult(null); // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. diff --git a/src/Umbraco.Examine/BaseValueSetBuilder.cs b/src/Umbraco.Examine/BaseValueSetBuilder.cs index 22d379d148..93cee88231 100644 --- a/src/Umbraco.Examine/BaseValueSetBuilder.cs +++ b/src/Umbraco.Examine/BaseValueSetBuilder.cs @@ -45,7 +45,7 @@ namespace Umbraco.Examine continue; case string strVal: { - if (strVal.IsNullOrWhiteSpace()) return; + if (strVal.IsNullOrWhiteSpace()) continue; var key = $"{keyVal.Key}{cultureSuffix}"; if (values.TryGetValue(key, out var v)) values[key] = new List(v) { val }.ToArray(); diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 43a3ccc196..d97278f31c 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -48,6 +48,31 @@ namespace Umbraco.Examine } } + /// + /// Returns all index fields that are culture specific (suffixed) or invariant + /// + /// + /// + /// + public static IEnumerable GetCultureAndInvariantFields(this IUmbracoIndex index, string culture) + { + var allFields = index.GetFields(); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var field in allFields) + { + var match = CultureIsoCodeFieldNameMatchExpression.Match(field); + if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value)) + { + yield return field; //matches this culture field + } + else if (!match.Success) + { + yield return field; //matches no culture field (invariant) + } + + } + } + internal static bool TryParseLuceneQuery(string query) { // TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll @@ -77,7 +102,7 @@ namespace Umbraco.Examine /// /// This is not thread safe, use with care /// - internal static void UnlockLuceneIndexes(this IExamineManager examineManager, ILogger logger) + internal static void ConfigureLuceneIndexes(this IExamineManager examineManager, ILogger logger, bool disableExamineIndexing) { foreach (var luceneIndexer in examineManager.Indexes.OfType()) { @@ -86,6 +111,8 @@ namespace Umbraco.Examine //that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems. luceneIndexer.WaitForIndexQueueOnShutdown = false; + if (disableExamineIndexing) continue; //exit if not enabled, we don't need to unlock them if we're not maindom + //we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that //the indexes are not operational unless MainDom is true var dir = luceneIndexer.GetLuceneDirectory(); diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 05b209f927..95690c17e4 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -13,7 +13,7 @@ true - full + portable false bin\Debug\ DEBUG;TRACE @@ -23,7 +23,7 @@ latest - pdbonly + portable true bin\Release\ TRACE @@ -49,6 +49,11 @@ + + 1.0.0-beta2-19324-01 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index b9a5890d57..60ef944a8c 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -14,7 +14,7 @@ true - full + portable false bin\Debug\ DEBUG;TRACE @@ -22,7 +22,7 @@ 4 - pdbonly + portable true bin\Release\ TRACE @@ -103,6 +103,11 @@ 2.8.0 + + 1.0.0-beta2-19324-01 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs index fbf828ad20..be160a483c 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs @@ -68,9 +68,9 @@ namespace Umbraco.Tests.Cache.PublishedCache var appCache = new DictionaryAppCache(); var domainCache = new DomainCache(ServiceContext.DomainService, DefaultCultureAccessor); var publishedShapshot = new PublishedSnapshot( - new PublishedContentCache(xmlStore, domainCache, appCache, globalSettings, new SiteDomainHelper(), umbracoContextAccessor, ContentTypesCache, null, null), + new PublishedContentCache(xmlStore, domainCache, appCache, globalSettings, ContentTypesCache, null, null), new PublishedMediaCache(xmlStore, ServiceContext.MediaService, ServiceContext.UserService, appCache, ContentTypesCache, Factory.GetInstance(), umbracoContextAccessor), - new PublishedMemberCache(null, appCache, Current.Services.MemberService, ContentTypesCache, umbracoContextAccessor), + new PublishedMemberCache(null, appCache, Current.Services.MemberService, ContentTypesCache), domainCache); var publishedSnapshotService = new Mock(); publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedShapshot); @@ -85,7 +85,7 @@ namespace Umbraco.Tests.Cache.PublishedCache globalSettings, new TestVariationContextAccessor()); - _cache = _umbracoContext.ContentCache; + _cache = _umbracoContext.Content; } [Test] diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index 08eeb8ef4d..f3d9f895ef 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -323,8 +323,7 @@ namespace Umbraco.Tests.Cache.PublishedCache // no xpath null, // not from examine - false, - _umbracoContextAccessor), + false), //callback to get the children (dd, n) => children, // callback to get a property @@ -334,8 +333,7 @@ namespace Umbraco.Tests.Cache.PublishedCache // no xpath null, // not from examine - false, - _umbracoContextAccessor); + false); return dicDoc; } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index d3cbf1f183..db8dc38d6d 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -8,7 +8,6 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web.Models; using Umbraco.Web.PublishedCache; @@ -40,9 +39,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache IAppCache appCache, PublishedContentTypeCache contentTypeCache, XPathNavigator nav, - bool fromExamine, - IUmbracoContextAccessor umbracoContextAccessor) - :base(umbracoContextAccessor) + bool fromExamine) { if (valueDictionary == null) throw new ArgumentNullException(nameof(valueDictionary)); if (getParent == null) throw new ArgumentNullException(nameof(getParent)); @@ -158,8 +155,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache public override string Name => _name; - public override PublishedCultureInfo GetCulture(string culture = null) => null; - private static readonly Lazy> NoCultures = new Lazy>(() => new Dictionary()); public override IReadOnlyDictionary Cultures => NoCultures.Value; @@ -189,12 +184,14 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache public override IEnumerable Children => _getChildren.Value; + public override IEnumerable ChildrenForAllCultures => Children; + public override IPublishedProperty GetProperty(string alias) { return _getProperty(this, alias); } - public override PublishedContentType ContentType => _contentType; + public override IPublishedContentType ContentType => _contentType; private readonly List _keysAdded = new List(); private int _id; @@ -215,7 +212,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache //private Guid _version; private int _level; private readonly ICollection _properties; - private readonly PublishedContentType _contentType; + private readonly IPublishedContentType _contentType; private void ValidateAndSetProperty(IReadOnlyDictionary valueDictionary, Action setProperty, params string[] potentialKeys) { diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs index cde2077551..abaa239598 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs @@ -27,13 +27,18 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } /// - public IEnumerable GetAssigned(int contentId, bool includeWildcards) + public IEnumerable GetAssigned(int documentId, bool includeWildcards = false) { - return _domainService.GetAssignedDomains(contentId, includeWildcards) + return _domainService.GetAssignedDomains(documentId, includeWildcards) .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard)); } + /// + public bool HasAssigned(int documentId, bool includeWildcards = false) + => documentId > 0 && GetAssigned(documentId, includeWildcards).Any(); + + /// public string DefaultCulture { get; } } } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index 2a144f3aaa..8ce6b10983 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -9,7 +9,6 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; -using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; @@ -19,10 +18,8 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache { private readonly IAppCache _appCache; private readonly IGlobalSettings _globalSettings; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly RoutesCache _routesCache; private readonly IDomainCache _domainCache; - private readonly DomainHelper _domainHelper; private readonly PublishedContentTypeCache _contentTypeCache; // initialize a PublishedContentCache instance with @@ -35,8 +32,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache IDomainCache domainCache, // an IDomainCache implementation IAppCache appCache, // an IAppCache that should be at request-level IGlobalSettings globalSettings, - ISiteDomainHelper siteDomainHelper, - IUmbracoContextAccessor umbracoContextAccessor, PublishedContentTypeCache contentTypeCache, // a PublishedContentType cache RoutesCache routesCache, // a RoutesCache string previewToken) // a preview token string (or null if not previewing) @@ -44,11 +39,9 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache { _appCache = appCache; _globalSettings = globalSettings; - _umbracoContextAccessor = umbracoContextAccessor; _routesCache = routesCache; // may be null for unit-testing _contentTypeCache = contentTypeCache; _domainCache = domainCache; - _domainHelper = new DomainHelper(_domainCache, siteDomainHelper); _xmlStore = xmlStore; _xml = _xmlStore.Xml; // capture - because the cache has to remain consistent @@ -107,7 +100,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // that would be returned - the "deepest" route - and that is the route we want to cache, *not* the // longer one - so make sure we don't cache the wrong route - var deepest = DomainHelper.ExistsDomainInPath(_domainCache.GetAll(false), content.Path, domainRootNodeId) == false; + var deepest = DomainUtilities.ExistsDomainInPath(_domainCache.GetAll(false), content.Path, domainRootNodeId) == false; if (deepest) _routesCache.Store(content.Id, route, true); // trusted route @@ -267,16 +260,16 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // or we reach the content root, collecting urls in the way var pathParts = new List(); var n = node; - var hasDomains = _domainHelper.NodeHasDomains(n.Id); + var hasDomains = _domainCache.HasAssigned(n.Id); while (hasDomains == false && n != null) // n is null at root { // get the url - var urlName = n.UrlSegment; + var urlName = n.UrlSegment(); pathParts.Add(urlName); // move to parent node n = n.Parent; - hasDomains = n != null && _domainHelper.NodeHasDomains(n.Id); + hasDomains = n != null && _domainCache.HasAssigned(n.Id); } // no domain, respect HideTopLevelNodeFromPath for legacy purposes @@ -320,13 +313,13 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing) { - return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache,_umbracoContextAccessor); + return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache); } private IEnumerable ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing) { return xmlNodes.Cast() - .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache, _umbracoContextAccessor)); + .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache)); } #endregion @@ -389,7 +382,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return GetXml(preview).CreateNavigator().MoveToId(contentId.ToString(CultureInfo.InvariantCulture)); } - public override IEnumerable GetAtRoot(bool preview) + public override IEnumerable GetAtRoot(bool preview, string culture = null) { return ConvertToDocuments(GetXml(preview).SelectNodes(XPathStrings.RootDocuments), preview); } @@ -539,12 +532,12 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Content types - public override PublishedContentType GetContentType(int id) + public override IPublishedContentType GetContentType(int id) { return _contentTypeCache.Get(PublishedItemType.Content, id); } - public override PublishedContentType GetContentType(string alias) + public override IPublishedContentType GetContentType(string alias) { return _contentTypeCache.Get(PublishedItemType.Content, alias); } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs index 0c7ee98c6d..999d7f040d 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs @@ -105,7 +105,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return GetUmbracoMedia(contentId) != null; } - public override IEnumerable GetAtRoot(bool preview) + public override IEnumerable GetAtRoot(bool preview, string culture = null) { var searchProvider = GetSearchProviderSafe(); @@ -612,17 +612,17 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Content types - public override PublishedContentType GetContentType(int id) + public override IPublishedContentType GetContentType(int id) { return _contentTypeCache.Get(PublishedItemType.Media, id); } - public override PublishedContentType GetContentType(string alias) + public override IPublishedContentType GetContentType(string alias) { return _contentTypeCache.Get(PublishedItemType.Media, alias); } - public override IEnumerable GetByContentType(PublishedContentType contentType) + public override IEnumerable GetByContentType(IPublishedContentType contentType) { throw new NotSupportedException(); } @@ -674,8 +674,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _appCache, _contentTypeCache, cacheValues.XPath, // though, outside of tests, that should be null - cacheValues.FromExamine, - _umbracoContextAccessor + cacheValues.FromExamine ); return content.CreateModel(); } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs index c882488f20..19328c241e 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs @@ -6,7 +6,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Security; @@ -18,16 +17,13 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private readonly IAppCache _requestCache; private readonly XmlStore _xmlStore; private readonly PublishedContentTypeCache _contentTypeCache; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public PublishedMemberCache(XmlStore xmlStore, IAppCache requestCache, IMemberService memberService, - PublishedContentTypeCache contentTypeCache, IUmbracoContextAccessor umbracoContextAccessor) + public PublishedMemberCache(XmlStore xmlStore, IAppCache requestCache, IMemberService memberService, PublishedContentTypeCache contentTypeCache) { _requestCache = requestCache; _memberService = memberService; _xmlStore = xmlStore; _contentTypeCache = contentTypeCache; - _umbracoContextAccessor = umbracoContextAccessor; } public IPublishedContent GetByProviderKey(object key) @@ -44,7 +40,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var result = _memberService.GetByProviderKey(key); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); + return new PublishedMember(result, type).CreateModel(); }); } @@ -62,7 +58,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var result = _memberService.GetById(memberId); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); + return new PublishedMember(result, type).CreateModel(); }); } @@ -80,7 +76,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var result = _memberService.GetByUsername(username); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); + return new PublishedMember(result, type).CreateModel(); }); } @@ -98,14 +94,14 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var result = _memberService.GetByEmail(email); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); + return new PublishedMember(result, type).CreateModel(); }); } public IPublishedContent GetByMember(IMember member) { var type = _contentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId); - return new PublishedMember(member, type, _umbracoContextAccessor).CreateModel(); + return new PublishedMember(member, type).CreateModel(); } public XPathNavigator CreateNavigator() @@ -138,12 +134,12 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Content types - public PublishedContentType GetContentType(int id) + public IPublishedContentType GetContentType(int id) { return _contentTypeCache.Get(PublishedItemType.Member, id); } - public PublishedContentType GetContentType(string alias) + public IPublishedContentType GetContentType(string alias) { return _contentTypeCache.Get(PublishedItemType.Member, alias); } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs index 4a201ae44c..394a33d777 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs @@ -145,9 +145,9 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var domainCache = new DomainCache(_domainService, _defaultCultureAccessor); return new PublishedSnapshot( - new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _siteDomainHelper,_umbracoContextAccessor, _contentTypeCache, _routesCache, previewToken), + new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _contentTypeCache, _routesCache, previewToken), new PublishedMediaCache(_xmlStore, _mediaService, _userService, _requestCache, _contentTypeCache, _entitySerializer, _umbracoContextAccessor), - new PublishedMemberCache(_xmlStore, _requestCache, _memberService, _contentTypeCache, _umbracoContextAccessor), + new PublishedMemberCache(_xmlStore, _requestCache, _memberService, _contentTypeCache), domainCache); } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index e1819bf0be..3697863cb4 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -26,23 +26,19 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache XmlNode xmlNode, bool isPreviewing, IAppCache appCache, - PublishedContentTypeCache contentTypeCache, - IUmbracoContextAccessor umbracoContextAccessor) - :base(umbracoContextAccessor) + PublishedContentTypeCache contentTypeCache) { _xmlNode = xmlNode; _isPreviewing = isPreviewing; _appCache = appCache; _contentTypeCache = contentTypeCache; - _umbracoContextAccessor = umbracoContextAccessor; } private readonly XmlNode _xmlNode; private readonly bool _isPreviewing; private readonly IAppCache _appCache; // at snapshot/request level (see PublishedContentCache) private readonly PublishedContentTypeCache _contentTypeCache; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly object _initializeLock = new object(); @@ -53,7 +49,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private IEnumerable _children = Enumerable.Empty(); private IPublishedContent _parent; - private PublishedContentType _contentType; + private IPublishedContentType _contentType; private Dictionary _properties; private int _id; @@ -84,6 +80,8 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } } + public override IEnumerable ChildrenForAllCultures => Children; + public override IPublishedProperty GetProperty(string alias) { EnsureNodeInitialized(); @@ -147,10 +145,15 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } } - public override PublishedCultureInfo GetCulture(string culture = null) => null; + private Dictionary _cultures; - private static readonly Lazy> NoCultures = new Lazy>(() => new Dictionary()); - public override IReadOnlyDictionary Cultures => NoCultures.Value; + private Dictionary GetCultures() + { + EnsureNodeInitialized(); + return new Dictionary { { "", new PublishedCultureInfo("", _name, _urlName, _updateDate) } }; + } + + public override IReadOnlyDictionary Cultures => _cultures ?? (_cultures = GetCultures()); public override string WriterName { @@ -254,7 +257,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } } - public override PublishedContentType ContentType + public override IPublishedContentType ContentType { get { @@ -269,7 +272,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache if (parent == null) return; if (parent.Attributes?.GetNamedItem("isDoc") != null) - _parent = Get(parent, _isPreviewing, _appCache, _contentTypeCache, _umbracoContextAccessor); + _parent = Get(parent, _isPreviewing, _appCache, _contentTypeCache); _parentInitialized = true; } @@ -308,8 +311,8 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache out int id, out Guid key, out int template, out int sortOrder, out string name, out string writerName, out string urlName, out string creatorName, out int creatorId, out int writerId, out string docTypeAlias, out int docTypeId, out string path, out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft, - out PublishedContentType contentType, out Dictionary properties, - Func getPublishedContentType) + out IPublishedContentType contentType, out Dictionary properties, + Func getPublishedContentType) { //initialize the out params with defaults: writerName = null; @@ -426,7 +429,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var iterator = nav.Select(expr); _children = iterator.Cast() - .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing, _appCache, _contentTypeCache, _umbracoContextAccessor)) + .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing, _appCache, _contentTypeCache)) .OrderBy(x => x.SortOrder) .ToList(); @@ -446,7 +449,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache /// sure that we create only one instance of each for the duration of a request. The /// returned IPublishedContent is a model, if models are enabled. public static IPublishedContent Get(XmlNode node, bool isPreviewing, IAppCache appCache, - PublishedContentTypeCache contentTypeCache, IUmbracoContextAccessor umbracoContextAccessor) + PublishedContentTypeCache contentTypeCache) { // only 1 per request @@ -454,7 +457,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var id = attrs?.GetNamedItem("id").Value; if (id.IsNullOrWhiteSpace()) throw new InvalidOperationException("Node has no ID attribute."); var key = CacheKeyPrefix + id; // dont bother with preview, wont change during request in Xml cache - return (IPublishedContent) appCache.Get(key, () => (new XmlPublishedContent(node, isPreviewing, appCache, contentTypeCache, umbracoContextAccessor)).CreateModel()); + return (IPublishedContent) appCache.Get(key, () => (new XmlPublishedContent(node, isPreviewing, appCache, contentTypeCache)).CreateModel()); } public static void ClearRequest() diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs index 0c90c8d1ff..7d2fa74aa6 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs @@ -50,7 +50,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache public override object GetXPathValue(string culture = null, string segment = null) { throw new NotImplementedException(); } - public XmlPublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, XmlNode propertyXmlData) + public XmlPublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, XmlNode propertyXmlData) : this(propertyType, content, isPreviewing) { if (propertyXmlData == null) @@ -58,7 +58,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _sourceValue = XmlHelper.GetNodeValue(propertyXmlData); } - public XmlPublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, string propertyData) + public XmlPublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, string propertyData) : this(propertyType, content, isPreviewing) { if (propertyData == null) @@ -66,7 +66,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _sourceValue = propertyData; } - public XmlPublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing) + public XmlPublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { _sourceValue = string.Empty; diff --git a/src/Umbraco.Tests/Logging/LogviewerTests.cs b/src/Umbraco.Tests/Logging/LogviewerTests.cs index 35981f5368..ed9deec177 100644 --- a/src/Umbraco.Tests/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests/Logging/LogviewerTests.cs @@ -23,9 +23,10 @@ namespace Umbraco.Tests.Logging private string _newSearchfilePath; private string _newSearchfileDirPath; - private DateTimeOffset _startDate = new DateTime(year: 2018, month: 11, day: 12, hour:0, minute:0, second:0); - private DateTimeOffset _endDate = new DateTime(year: 2018, month: 11, day: 13, hour: 0, minute: 0, second: 0); - + private LogTimePeriod _logTimePeriod = new LogTimePeriod( + new DateTime(year: 2018, month: 11, day: 12, hour:0, minute:0, second:0), + new DateTime(year: 2018, month: 11, day: 13, hour: 0, minute: 0, second: 0) + ); [OneTimeSetUp] public void Setup() { @@ -67,7 +68,7 @@ namespace Umbraco.Tests.Logging [Test] public void Logs_Contain_Correct_Error_Count() { - var numberOfErrors = _logViewer.GetNumberOfErrors(startDate: _startDate, endDate: _endDate); + var numberOfErrors = _logViewer.GetNumberOfErrors(_logTimePeriod); //Our dummy log should contain 2 errors Assert.AreEqual(2, numberOfErrors); @@ -76,8 +77,8 @@ namespace Umbraco.Tests.Logging [Test] public void Logs_Contain_Correct_Log_Level_Counts() { - var logCounts = _logViewer.GetLogLevelCounts(startDate: _startDate, endDate: _endDate); - + var logCounts = _logViewer.GetLogLevelCounts(_logTimePeriod); + Assert.AreEqual(1954, logCounts.Debug); Assert.AreEqual(2, logCounts.Error); Assert.AreEqual(0, logCounts.Fatal); @@ -88,7 +89,7 @@ namespace Umbraco.Tests.Logging [Test] public void Logs_Contains_Correct_Message_Templates() { - var templates = _logViewer.GetMessageTemplates(startDate: _startDate, endDate: _endDate); + var templates = _logViewer.GetMessageTemplates(_logTimePeriod); //Count no of templates Assert.AreEqual(43, templates.Count()); @@ -112,7 +113,7 @@ namespace Umbraco.Tests.Logging { //We are just testing a return value (as we know the example file is less than 200MB) //But this test method does not test/check that - var canOpenLogs = _logViewer.CheckCanOpenLogs(startDate: _startDate, endDate: _endDate); + var canOpenLogs = _logViewer.CheckCanOpenLogs(_logTimePeriod); Assert.IsTrue(canOpenLogs); } @@ -120,7 +121,7 @@ namespace Umbraco.Tests.Logging public void Logs_Can_Be_Queried() { //Should get me the most 100 recent log entries & using default overloads for remaining params - var allLogs = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1); + var allLogs = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1); //Check we get 100 results back for a page & total items all good :) Assert.AreEqual(100, allLogs.Items.Count()); @@ -138,7 +139,7 @@ namespace Umbraco.Tests.Logging //Check we call method again with a smaller set of results & in ascending - var smallQuery = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, pageSize: 10, orderDirection: Direction.Ascending); + var smallQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, pageSize: 10, orderDirection: Direction.Ascending); Assert.AreEqual(10, smallQuery.Items.Count()); Assert.AreEqual(241, smallQuery.TotalPages); @@ -152,17 +153,17 @@ namespace Umbraco.Tests.Logging //Check invalid log levels //Rather than expect 0 items - get all items back & ignore the invalid levels string[] invalidLogLevels = { "Invalid", "NotALevel" }; - var queryWithInvalidLevels = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, logLevels: invalidLogLevels); + var queryWithInvalidLevels = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, logLevels: invalidLogLevels); Assert.AreEqual(2410, queryWithInvalidLevels.TotalItems); //Check we can call method with an array of logLevel (error & warning) string [] logLevels = { "Warning", "Error" }; - var queryWithLevels = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, logLevels: logLevels); + var queryWithLevels = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, logLevels: logLevels); Assert.AreEqual(9, queryWithLevels.TotalItems); - + //Query @Level='Warning' BUT we pass in array of LogLevels for Debug & Info (Expect to get 0 results) string[] logLevelMismatch = { "Debug", "Information" }; - var filterLevelQuery = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); ; + var filterLevelQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); ; Assert.AreEqual(0, filterLevelQuery.TotalItems); } @@ -177,10 +178,10 @@ namespace Umbraco.Tests.Logging [Test] public void Logs_Can_Query_With_Expressions(string queryToVerify, int expectedCount) { - var testQuery = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, filterExpression: queryToVerify); + var testQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: queryToVerify); Assert.AreEqual(expectedCount, testQuery.TotalItems); } - + [Test] public void Log_Search_Can_Persist() { diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 6605bc4546..1c90f68d62 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Manifest new RequiredValidator(Mock.Of()), new RegexValidator(Mock.Of(), null) }; - _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), Mock.Of()); + _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), new ManifestFilterCollection(Array.Empty()), Mock.Of()); } [Test] diff --git a/src/Umbraco.Tests/Mapping/MappingTests.cs b/src/Umbraco.Tests/Mapping/MappingTests.cs index 79d383857a..e6a382692c 100644 --- a/src/Umbraco.Tests/Mapping/MappingTests.cs +++ b/src/Umbraco.Tests/Mapping/MappingTests.cs @@ -172,6 +172,48 @@ namespace Umbraco.Tests.Mapping } } + [Test] + public void EnumMap() + { + var definitions = new MapDefinitionCollection(new IMapDefinition[] + { + new MapperDefinition4(), + }); + var mapper = new UmbracoMapper(definitions); + + var thing5 = new Thing5() + { + Fruit1 = Thing5Enum.Apple, + Fruit2 = Thing5Enum.Banana, + Fruit3= Thing5Enum.Cherry + }; + + var thing6 = mapper.Map(thing5); + + Assert.IsNotNull(thing6); + Assert.AreEqual(Thing6Enum.Apple, thing6.Fruit1); + Assert.AreEqual(Thing6Enum.Banana, thing6.Fruit2); + Assert.AreEqual(Thing6Enum.Cherry, thing6.Fruit3); + } + + [Test] + public void NullPropertyMap() + { + var definitions = new MapDefinitionCollection(new IMapDefinition[] + { + new MapperDefinition5(), + }); + var mapper = new UmbracoMapper(definitions); + + var thing7 = new Thing7(); + + var thing8 = mapper.Map(thing7); + + Assert.IsNotNull(thing8); + Assert.IsNull(thing8.Things); + } + + private class Thing1 { public string Value { get; set; } @@ -188,6 +230,44 @@ namespace Umbraco.Tests.Mapping private class Thing4 { } + private class Thing5 + { + public Thing5Enum Fruit1 { get; set; } + public Thing5Enum Fruit2 { get; set; } + public Thing5Enum Fruit3 { get; set; } + } + + private enum Thing5Enum + { + Apple = 0, + Banana = 1, + Cherry = 2 + } + + private class Thing6 + { + public Thing6Enum Fruit1 { get; set; } + public Thing6Enum Fruit2 { get; set; } + public Thing6Enum Fruit3 { get; set; } + } + + private enum Thing6Enum + { + Apple = 0, + Banana = 1, + Cherry = 2 + } + + private class Thing7 + { + public IEnumerable Things { get; set; } + } + + private class Thing8 + { + public IEnumerable Things { get; set; } + } + private class MapperDefinition1 : IMapDefinition { public void DefineMaps(UmbracoMapper mapper) @@ -224,5 +304,41 @@ namespace Umbraco.Tests.Mapping mapper.Define(); } } + + private class MapperDefinition4 : IMapDefinition + { + public void DefineMaps(UmbracoMapper mapper) + { + mapper.Define((source, context) => new Thing6(), Map); + mapper.Define( + (source, context) => (Thing6Enum)source); + } + + private void Map(Thing5 source, Thing6 target, MapperContext context) + { + target.Fruit1 = context.Map(source.Fruit1); + target.Fruit2 = context.Map(source.Fruit2); + target.Fruit3 = context.Map(source.Fruit3); + } + } + + private class MapperDefinition5 : IMapDefinition + { + public void DefineMaps(UmbracoMapper mapper) + { + mapper.Define((source, context) => new Thing2(), Map1); + mapper.Define((source, context) => new Thing8(), Map2); + } + + private void Map1(Thing1 source, Thing2 target, MapperContext context) + { + target.Value = source.Value; + } + + private void Map2(Thing7 source, Thing8 target, MapperContext context) + { + target.Things = context.Map>(source.Things); + } + } } } diff --git a/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs b/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs index 5ff0dcffc1..c13d141fa5 100644 --- a/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs +++ b/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs @@ -217,7 +217,11 @@ namespace Umbraco.Tests.Migrations //Execute.DropKeysAndIndexes("umbracoUser"); // drops *all* tables keys and indexes - Delete.KeysAndIndexes().Do(); + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToList(); + foreach (var table in tables) + Delete.KeysAndIndexes(table, false, true).Do(); + foreach (var table in tables) + Delete.KeysAndIndexes(table, true, false).Do(); } } @@ -262,7 +266,7 @@ namespace Umbraco.Tests.Migrations public override void Migrate() { // cannot delete the column without this, of course - Delete.KeysAndIndexes().Do(); + Delete.KeysAndIndexes("umbracoUser").Do(); Delete.Column("id").FromTable("umbracoUser").Do(); diff --git a/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs b/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs index add278f9eb..ab065eb0a9 100644 --- a/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs @@ -119,7 +119,7 @@ namespace Umbraco.Tests.Migrations .To("bbb") .From("ccc") .To("ddd"); - Assert.Throws(() => plan.Validate()); + Assert.Throws(() => plan.Validate()); } [Test] @@ -131,7 +131,7 @@ namespace Umbraco.Tests.Migrations .To("bbb") .To("ccc") .To("aaa"); - Assert.Throws(() => plan.Validate()); + Assert.Throws(() => plan.Validate()); } [Test] diff --git a/src/Umbraco.Tests/Models/LightEntityTest.cs b/src/Umbraco.Tests/Models/LightEntityTest.cs index f1752a7681..41ce830cff 100644 --- a/src/Umbraco.Tests/Models/LightEntityTest.cs +++ b/src/Umbraco.Tests/Models/LightEntityTest.cs @@ -37,9 +37,7 @@ namespace Umbraco.Tests.Models }; item.AdditionalData.Add("test1", 3); item.AdditionalData.Add("test2", "valuie"); - item.AdditionalData.Add("test3", new EntitySlim.PropertySlim("TestPropertyEditor", "test")); - item.AdditionalData.Add("test4", new EntitySlim.PropertySlim("TestPropertyEditor2", "test2")); - + var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); Debug.Print(json); // FIXME: compare with v7 diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index c276dc35ca..fb451b1d5c 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -43,16 +43,6 @@ namespace Umbraco.Tests.Persistence _databaseFactory = null; } - [Test] - public void GetDatabaseType() - { - using (var database = _databaseFactory.CreateDatabase()) - { - var databaseType = database.DatabaseType; - Assert.AreEqual(DatabaseType.SQLCe, databaseType); - } - } - [Test] public void CreateDatabase() // FIXME: move to DatabaseBuilderTest! { @@ -79,6 +69,13 @@ namespace Umbraco.Tests.Persistence // re-create the database factory and database context with proper connection string _databaseFactory = new UmbracoDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _logger, new Lazy(() => Mock.Of())); + // test get database type (requires an actual database) + using (var database = _databaseFactory.CreateDatabase()) + { + var databaseType = database.DatabaseType; + Assert.AreEqual(DatabaseType.SQLCe, databaseType); + } + // create application context //var appCtx = new ApplicationContext( // _databaseFactory, diff --git a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs index 3c23223c9f..582e5a4815 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs @@ -36,6 +36,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsTrue(result > 0, "Expected >0 but was " + result); } + + [Test] public void OrderByTest() { @@ -75,6 +77,7 @@ namespace Umbraco.Tests.Persistence.Repositories [TestCase(0, "Alpha", "Alpha (3)")] [TestCase(0, "Kilo (1)", "Kilo (1) (1)")] // though... we might consider "Kilo (2)" [TestCase(6, "Kilo (1)", "Kilo (1)")] // because of the id + [TestCase(0, "alpha", "alpha (3)")] [TestCase(0, "", " (1)")] [TestCase(0, null, " (1)")] public void Test(int nodeId, string nodeName, string expected) @@ -95,5 +98,22 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(expected, SimilarNodeName.GetUniqueName(names, nodeId, nodeName)); } + + [Test] + [Explicit("This test fails! We need to fix up the logic")] + public void TestMany() + { + var names = new[] + { + new SimilarNodeName { Id = 1, Name = "Alpha (2)" }, + new SimilarNodeName { Id = 2, Name = "Test" }, + new SimilarNodeName { Id = 3, Name = "Test (1)" }, + new SimilarNodeName { Id = 4, Name = "Test (2)" }, + new SimilarNodeName { Id = 22, Name = "Test (1) (1)" }, + }; + + //fixme - this will yield "Test (2)" which is already in use + Assert.AreEqual("Test (3)", SimilarNodeName.GetUniqueName(names, 0, "Test")); + } } } diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index 809354bd74..44879eae2f 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -327,7 +327,7 @@ AnotherContentFinder public void Resolves_RestExtensions() { var types = _manager.ResolveRestExtensions(); - Assert.AreEqual(3, types.Count()); + Assert.AreEqual(2, types.Count()); } [Test] diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 0fce8ebfc3..671129848c 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -4,16 +4,14 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.Components; +using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.PublishedCache; @@ -37,10 +35,12 @@ namespace Umbraco.Tests.Published var contentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", new[] + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - contentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1); + } + + var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", CreatePropertyTypes); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); @@ -70,22 +70,22 @@ namespace Umbraco.Tests.Published } } - public bool IsConverter(PublishedPropertyType propertyType) + public bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); - public Type GetPropertyValueType(PublishedPropertyType propertyType) + public Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (int); - public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => int.TryParse(source as string, out int i) ? i : 0; - public object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => (int) inter; - public object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => ((int) inter).ToString(); } @@ -115,15 +115,17 @@ namespace Umbraco.Tests.Published var contentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", new[] + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - contentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1); + } + + var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", CreatePropertyTypes); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); - var cntType1 = contentTypeFactory.CreateContentType(1001, "cnt1", Array.Empty()); - var cnt1 = new TestPublishedContent(cntType1, 1234, Guid.NewGuid(), new Dictionary(), false); + var cntType1 = contentTypeFactory.CreateContentType(1001, "cnt1", t => Enumerable.Empty()); + var cnt1 = new SolidPublishedContent(cntType1) { Id = 1234 }; cacheContent[cnt1.Id] = cnt1; Assert.AreSame(cnt1, element1.Value("prop1")); @@ -143,26 +145,26 @@ namespace Umbraco.Tests.Published public bool? IsValue(object value, PropertyValueLevel level) => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); - public bool IsConverter(PublishedPropertyType propertyType) + public bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); - public Type GetPropertyValueType(PublishedPropertyType propertyType) + public Type GetPropertyValueType(IPublishedPropertyType propertyType) // the first version would be the "generic" version, but say we want to be more precise // and return: whatever Clr type is generated for content type with alias "cnt1" -- which // we cannot really typeof() at the moment because it has not been generated, hence ModelType. // => typeof (IPublishedContent); => ModelType.For("cnt1"); - public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => _cacheLevel; - public object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => int.TryParse(source as string, out int i) ? i : -1; - public object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById((int) inter); - public object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => ((int) inter).ToString(); } @@ -208,30 +210,28 @@ namespace Umbraco.Tests.Published var contentTypeFactory = new PublishedContentTypeFactory(factory, converters, dataTypeService); - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", new[] + IEnumerable CreatePropertyTypes(IPublishedContentType contentType, int i) { - contentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return contentTypeFactory.CreatePropertyType(contentType, "prop" + i, i); + } - var elementType2 = contentTypeFactory.CreateContentType(1001, "element2", new[] - { - contentTypeFactory.CreatePropertyType("prop2", 2), - }); - - var contentType1 = contentTypeFactory.CreateContentType(1002, "content1", new[] - { - contentTypeFactory.CreatePropertyType("prop1", 1), - }); - - var contentType2 = contentTypeFactory.CreateContentType(1003, "content2", new[] - { - contentTypeFactory.CreatePropertyType("prop2", 2), - }); + var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", t => CreatePropertyTypes(t, 1)); + var elementType2 = contentTypeFactory.CreateContentType(1001, "element2", t => CreatePropertyTypes(t, 2)); + var contentType1 = contentTypeFactory.CreateContentType(1002, "content1", t => CreatePropertyTypes(t, 1)); + var contentType2 = contentTypeFactory.CreateContentType(1003, "content2", t => CreatePropertyTypes(t, 2)); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "val1" } }, false); var element2 = new PublishedElement(elementType2, Guid.NewGuid(), new Dictionary { { "prop2", "1003" } }, false); - var cnt1 = new TestPublishedContent(contentType1, 1003, Guid.NewGuid(), new Dictionary { { "prop1", "val1" } }, false); - var cnt2 = new TestPublishedContent(contentType2, 1004, Guid.NewGuid(), new Dictionary { { "prop2", "1003" } }, false); + var cnt1 = new SolidPublishedContent(contentType1) + { + Id = 1003, + Properties = new[] { new SolidPublishedProperty { Alias = "prop1", SolidHasValue = true, SolidValue = "val1" } } + }; + var cnt2 = new SolidPublishedContent(contentType1) + { + Id = 1004, + Properties = new[] { new SolidPublishedProperty { Alias = "prop2", SolidHasValue = true, SolidValue = "1003" } } + }; cacheContent[cnt1.Id] = cnt1.CreateModel(); cacheContent[cnt2.Id] = cnt2.CreateModel(); @@ -267,13 +267,13 @@ namespace Umbraco.Tests.Published public class SimpleConverter3A : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias == "Umbraco.Void"; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; } @@ -286,22 +286,22 @@ namespace Umbraco.Tests.Published _publishedSnapshotAccessor = publishedSnapshotAccessor; } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias == "Umbraco.Void.2"; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IEnumerable<>).MakeGenericType(ModelType.For("content1")); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Elements; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { var s = source as string; return s?.Split(',').Select(int.Parse).ToArray() ?? Array.Empty(); } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { return ((int[]) inter).Select(x => (PublishedSnapshotTestObjects.TestContentModel1) _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(x)).ToArray(); } diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 8f3b9a1df9..9385b8955a 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Models; @@ -23,7 +24,7 @@ namespace Umbraco.Tests.Published [TestFixture] public class NestedContentTests { - private (PublishedContentType, PublishedContentType) CreateContentTypes() + private (IPublishedContentType, IPublishedContentType) CreateContentTypes() { Current.Reset(); @@ -125,13 +126,24 @@ namespace Umbraco.Tests.Published var factory = new PublishedContentTypeFactory(publishedModelFactory.Object, converters, dataTypeService); - var propertyType1 = factory.CreatePropertyType("property1", 1); - var propertyType2 = factory.CreatePropertyType("property2", 2); - var propertyTypeN1 = factory.CreatePropertyType("propertyN1", 3); + IEnumerable CreatePropertyTypes1(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "property1", 1); + } - var contentType1 = factory.CreateContentType(1, "content1", new[] { propertyType1 }); - var contentType2 = factory.CreateContentType(2, "content2", new[] { propertyType2 }); - var contentTypeN1 = factory.CreateContentType(2, "contentN1", new[] { propertyTypeN1 }, isElement: true); + IEnumerable CreatePropertyTypes2(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "property2", 2); + } + + IEnumerable CreatePropertyTypesN1(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "propertyN1", 3); + } + + var contentType1 = factory.CreateContentType(1, "content1", CreatePropertyTypes1); + var contentType2 = factory.CreateContentType(2, "content2", CreatePropertyTypes2); + var contentTypeN1 = factory.CreateContentType(2, "contentN1", CreatePropertyTypesN1, isElement: true); // mocked content cache returns content types contentCache @@ -156,12 +168,16 @@ namespace Umbraco.Tests.Published var key = Guid.NewGuid(); var keyA = Guid.NewGuid(); - var content = new TestPublishedContent(contentType1, key, new[] + var content = new SolidPublishedContent(contentType1) { - new TestPublishedProperty(contentType1.GetPropertyType("property1"), $@"[ + Key = key, + Properties = new [] + { + new TestPublishedProperty(contentType1.GetPropertyType("property1"), $@"[ {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }} ]") - }, Mock.Of()); + } + }; var value = content.Value("property1"); // nested single converter returns proper TestModel value @@ -183,14 +199,17 @@ namespace Umbraco.Tests.Published var key = Guid.NewGuid(); var keyA = Guid.NewGuid(); var keyB = Guid.NewGuid(); - var content = new TestPublishedContent(contentType2, key, new[] + var content = new SolidPublishedContent(contentType2) { - new TestPublishedProperty(contentType2.GetPropertyType("property2"), $@"[ + Key = key, + Properties = new[] + { + new TestPublishedProperty(contentType2.GetPropertyType("property2"), $@"[ {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }}, {{ ""key"": ""{keyB}"", ""propertyN1"": ""bar"", ""ncContentTypeAlias"": ""contentN1"" }} ]") - }, - Mock.Of()); + } + }; var value = content.Value("property2"); // nested many converter returns proper IEnumerable value @@ -219,14 +238,14 @@ namespace Umbraco.Tests.Published private readonly bool _hasValue; private IPublishedElement _owner; - public TestPublishedProperty(PublishedPropertyType propertyType, object source) + public TestPublishedProperty(IPublishedPropertyType propertyType, object source) : base(propertyType, PropertyCacheLevel.Element) // initial reference cache level always is .Content { _sourceValue = source; _hasValue = source != null && (!(source is string ssource) || !string.IsNullOrWhiteSpace(ssource)); } - public TestPublishedProperty(PublishedPropertyType propertyType, IPublishedElement element, bool preview, PropertyCacheLevel referenceCacheLevel, object source) + public TestPublishedProperty(IPublishedPropertyType propertyType, IPublishedElement element, bool preview, PropertyCacheLevel referenceCacheLevel, object source) : base(propertyType, referenceCacheLevel) { _sourceValue = source; @@ -247,52 +266,5 @@ namespace Umbraco.Tests.Published public override object GetValue(string culture = null, string segment = null) => PropertyType.ConvertInterToObject(_owner, ReferenceCacheLevel, InterValue, _preview); public override object GetXPathValue(string culture = null, string segment = null) => throw new WontImplementException(); } - - class TestPublishedContent : PublishedContentBase - { - public TestPublishedContent(PublishedContentType contentType, Guid key, IEnumerable properties, IUmbracoContextAccessor umbracoContextAccessor): base(umbracoContextAccessor) - { - ContentType = contentType; - Key = key; - var propertiesA = properties.ToArray(); - Properties = propertiesA; - foreach (var property in propertiesA) - property.SetOwner(this); - } - - // ReSharper disable UnassignedGetOnlyAutoProperty - public override PublishedItemType ItemType { get; } - public override bool IsDraft(string culture = null) => false; - public override bool IsPublished(string culture = null) => true; - public override IPublishedContent Parent { get; } - public override IEnumerable Children { get; } - public override PublishedContentType ContentType { get; } - // ReSharper restore UnassignedGetOnlyAutoProperty - - // ReSharper disable UnassignedGetOnlyAutoProperty - public override int Id { get; } - public override int? TemplateId { get; } - public override int SortOrder { get; } - public override string Name { get; } - public override PublishedCultureInfo GetCulture(string culture = ".") => throw new NotSupportedException(); - public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); - public override string UrlSegment { get; } - public override string WriterName { get; } - public override string CreatorName { get; } - public override int WriterId { get; } - public override int CreatorId { get; } - public override string Path { get; } - public override DateTime CreateDate { get; } - public override DateTime UpdateDate { get; } - public override int Level { get; } - public override Guid Key { get; } - // ReSharper restore UnassignedGetOnlyAutoProperty - - public override IEnumerable Properties { get; } - public override IPublishedProperty GetProperty(string alias) - { - return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - } } } diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 76fdd81ec2..9db539d142 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -35,10 +35,13 @@ namespace Umbraco.Tests.Published new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", new[] + + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - publishedContentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); + } + + var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); // PublishedElementPropertyBase.GetCacheLevels: // @@ -113,10 +116,13 @@ namespace Umbraco.Tests.Published new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", new[] + + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - publishedContentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); + } + + var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); var elementsCache = new FastDictionaryAppCache(); var snapshotCache = new FastDictionaryAppCache(); @@ -187,10 +193,13 @@ namespace Umbraco.Tests.Published new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", new[] + + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - publishedContentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); + } + + var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); Assert.Throws(() => { @@ -213,28 +222,28 @@ namespace Umbraco.Tests.Published public bool? IsValue(object value, PropertyValueLevel level) => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); - public bool IsConverter(PublishedPropertyType propertyType) + public bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); - public Type GetPropertyValueType(PublishedPropertyType propertyType) + public Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(int); - public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => _cacheLevel; - public object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { SourceConverts++; return int.TryParse(source as string, out int i) ? i : 0; } - public object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { InterConverts++; return (int) inter; } - public object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => ((int) inter).ToString(); } } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs new file mode 100644 index 0000000000..eb6e7725c0 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -0,0 +1,963 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing.Objects; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; +using Umbraco.Web.Cache; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.PublishedCache.NuCache; +using Umbraco.Web.PublishedCache.NuCache.DataSource; + +namespace Umbraco.Tests.PublishedContent +{ + [TestFixture] + public class NuCacheChildrenTests + { + private IPublishedSnapshotService _snapshotService; + private IVariationContextAccessor _variationAccesor; + private IPublishedSnapshotAccessor _snapshotAccessor; + private ContentType _contentTypeInvariant; + private ContentType _contentTypeVariant; + private TestDataSource _source; + + private void Init(IEnumerable kits) + { + Current.Reset(); + + var factory = Mock.Of(); + Current.Factory = factory; + + var configs = new Configs(); + Mock.Get(factory).Setup(x => x.GetInstance(typeof(Configs))).Returns(configs); + var globalSettings = new GlobalSettings(); + configs.Add(SettingsForTests.GenerateMockUmbracoSettings); + configs.Add(() => globalSettings); + + var publishedModelFactory = new NoopPublishedModelFactory(); + Mock.Get(factory).Setup(x => x.GetInstance(typeof(IPublishedModelFactory))).Returns(publishedModelFactory); + + var runtime = Mock.Of(); + Mock.Get(runtime).Setup(x => x.Level).Returns(RuntimeLevel.Run); + + // create data types, property types and content types + var dataType = new DataType(new VoidEditor("Editor", Mock.Of())) { Id = 3 }; + + var dataTypes = new[] + { + dataType + }; + + var propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Nothing }; + _contentTypeInvariant = new ContentType(-1) { Id = 2, Alias = "itype", Variations = ContentVariation.Nothing }; + _contentTypeInvariant.AddPropertyType(propertyType); + + propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Culture }; + _contentTypeVariant = new ContentType(-1) { Id = 3, Alias = "vtype", Variations = ContentVariation.Culture }; + _contentTypeVariant.AddPropertyType(propertyType); + + var contentTypes = new[] + { + _contentTypeInvariant, + _contentTypeVariant + }; + + var contentTypeService = Mock.Of(); + Mock.Get(contentTypeService).Setup(x => x.GetAll()).Returns(contentTypes); + Mock.Get(contentTypeService).Setup(x => x.GetAll(It.IsAny())).Returns(contentTypes); + + var contentTypeServiceBaseFactory = Mock.Of(); + Mock.Get(contentTypeServiceBaseFactory).Setup(x => x.For(It.IsAny())).Returns(contentTypeService); + + var dataTypeService = Mock.Of(); + Mock.Get(dataTypeService).Setup(x => x.GetAll()).Returns(dataTypes); + + // create a service context + var serviceContext = ServiceContext.CreatePartial( + dataTypeService: dataTypeService, + memberTypeService: Mock.Of(), + memberService: Mock.Of(), + contentTypeService: contentTypeService, + localizationService: Mock.Of() + ); + + // create a scope provider + var scopeProvider = Mock.Of(); + Mock.Get(scopeProvider) + .Setup(x => x.CreateScope( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(Mock.Of); + + // create a published content type factory + var contentTypeFactory = new PublishedContentTypeFactory( + Mock.Of(), + new PropertyValueConverterCollection(Array.Empty()), + dataTypeService); + + // create accessors + _variationAccesor = new TestVariationContextAccessor(); + _snapshotAccessor = new TestPublishedSnapshotAccessor(); + + // create a data source for NuCache + _source = new TestDataSource(kits); + + // at last, create the complete NuCache snapshot service! + var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; + _snapshotService = new PublishedSnapshotService(options, + null, + runtime, + serviceContext, + contentTypeFactory, + null, + _snapshotAccessor, + _variationAccesor, + Mock.Of(), + scopeProvider, + Mock.Of(), + Mock.Of(), + Mock.Of(), + new TestDefaultCultureAccessor(), + _source, + globalSettings, + Mock.Of(), + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + + // invariant is the current default + _variationAccesor.VariationContext = new VariationContext(); + + Mock.Get(factory).Setup(x => x.GetInstance(typeof(IVariationContextAccessor))).Returns(_variationAccesor); + } + + private IEnumerable GetNestedVariantKits() + { + var paths = new Dictionary { { -1, "-1" } }; + + //1x variant (root) + yield return CreateVariantKit(1, -1, 1, paths); + + //1x invariant under root + yield return CreateInvariantKit(4, 1, 1, paths); + + //1x variant under root + yield return CreateVariantKit(7, 1, 4, paths); + + //2x mixed under invariant + yield return CreateVariantKit(10, 4, 1, paths); + yield return CreateInvariantKit(11, 4, 2, paths); + + //2x mixed under variant + yield return CreateVariantKit(12, 7, 1, paths); + yield return CreateInvariantKit(13, 7, 2, paths); + } + + private IEnumerable GetInvariantKits() + { + var paths = new Dictionary { { -1, "-1" } }; + + yield return CreateInvariantKit(1, -1, 1, paths); + yield return CreateInvariantKit(2, -1, 2, paths); + yield return CreateInvariantKit(3, -1, 3, paths); + + yield return CreateInvariantKit(4, 1, 1, paths); + yield return CreateInvariantKit(5, 1, 2, paths); + yield return CreateInvariantKit(6, 1, 3, paths); + + yield return CreateInvariantKit(7, 2, 3, paths); + yield return CreateInvariantKit(8, 2, 2, paths); + yield return CreateInvariantKit(9, 2, 1, paths); + + yield return CreateInvariantKit(10, 3, 1, paths); + + yield return CreateInvariantKit(11, 4, 1, paths); + yield return CreateInvariantKit(12, 4, 2, paths); + } + + private ContentNodeKit CreateInvariantKit(int id, int parentId, int sortOrder, Dictionary paths) + { + if (!paths.TryGetValue(parentId, out var parentPath)) + throw new Exception("Unknown parent."); + + var path = paths[id] = parentPath + "," + id; + var level = path.Count(x => x == ','); + var now = DateTime.Now; + + return new ContentNodeKit + { + ContentTypeId = _contentTypeInvariant.Id, + Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = "N" + id, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + } + + private IEnumerable GetVariantKits() + { + var paths = new Dictionary { { -1, "-1" } }; + + yield return CreateVariantKit(1, -1, 1, paths); + yield return CreateVariantKit(2, -1, 2, paths); + yield return CreateVariantKit(3, -1, 3, paths); + + yield return CreateVariantKit(4, 1, 1, paths); + yield return CreateVariantKit(5, 1, 2, paths); + yield return CreateVariantKit(6, 1, 3, paths); + + yield return CreateVariantKit(7, 2, 3, paths); + yield return CreateVariantKit(8, 2, 2, paths); + yield return CreateVariantKit(9, 2, 1, paths); + + yield return CreateVariantKit(10, 3, 1, paths); + + yield return CreateVariantKit(11, 4, 1, paths); + yield return CreateVariantKit(12, 4, 2, paths); + } + + private static Dictionary GetCultureInfos(int id, DateTime now) + { + var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 }; + + var infos = new Dictionary(); + if (en.Contains(id)) + infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false }; + if (fr.Contains(id)) + infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false }; + return infos; + } + + private ContentNodeKit CreateVariantKit(int id, int parentId, int sortOrder, Dictionary paths) + { + if (!paths.TryGetValue(parentId, out var parentPath)) + throw new Exception("Unknown parent."); + + var path = paths[id] = parentPath + "," + id; + var level = path.Count(x => x == ','); + var now = DateTime.Now; + + return new ContentNodeKit + { + ContentTypeId = _contentTypeVariant.Id, + Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = "N" + id, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = GetCultureInfos(id, now) + } + }; + } + + private IEnumerable GetVariantWithDraftKits() + { + var paths = new Dictionary { { -1, "-1" } }; + + Dictionary GetCultureInfos(int id, DateTime now) + { + var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 }; + + var infos = new Dictionary(); + if (en.Contains(id)) + infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false }; + if (fr.Contains(id)) + infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false }; + return infos; + } + + ContentNodeKit CreateKit(int id, int parentId, int sortOrder) + { + if (!paths.TryGetValue(parentId, out var parentPath)) + throw new Exception("Unknown parent."); + + var path = paths[id] = parentPath + "," + id; + var level = path.Count(x => x == ','); + var now = DateTime.Now; + + ContentData CreateContentData(bool published) => new ContentData + { + Name = "N" + id, + Published = published, + TemplateId = 0, + VersionId = 1, + VersionDate = now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = GetCultureInfos(id, now) + }; + + var withDraft = id%2==0; + var withPublished = !withDraft; + + return new ContentNodeKit + { + ContentTypeId = _contentTypeVariant.Id, + Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), + DraftData = withDraft ? CreateContentData(false) : null, + PublishedData = withPublished ? CreateContentData(true) : null + }; + } + + yield return CreateKit(1, -1, 1); + yield return CreateKit(2, -1, 2); + yield return CreateKit(3, -1, 3); + + yield return CreateKit(4, 1, 1); + yield return CreateKit(5, 1, 2); + yield return CreateKit(6, 1, 3); + + yield return CreateKit(7, 2, 3); + yield return CreateKit(8, 2, 2); + yield return CreateKit(9, 2, 1); + + yield return CreateKit(10, 3, 1); + + yield return CreateKit(11, 4, 1); + yield return CreateKit(12, 4, 2); + } + + [Test] + public void EmptyTest() + { + Init(Enumerable.Empty()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + var documents = snapshot.Content.GetAtRoot().ToArray(); + Assert.AreEqual(0, documents.Length); + } + + [Test] + public void ChildrenTest() + { + Init(GetInvariantKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2", "N3"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N5", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9", "N8", "N7"); + + documents = snapshot.Content.GetById(3).Children().ToArray(); + AssertDocuments(documents, "N10"); + + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N11", "N12"); + + documents = snapshot.Content.GetById(10).Children().ToArray(); + AssertDocuments(documents); + } + + [Test] + public void ParentTest() + { + Init(GetInvariantKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + Assert.IsNull(snapshot.Content.GetById(1).Parent); + Assert.IsNull(snapshot.Content.GetById(2).Parent); + Assert.IsNull(snapshot.Content.GetById(3).Parent); + + Assert.AreEqual(1, snapshot.Content.GetById(4).Parent?.Id); + Assert.AreEqual(1, snapshot.Content.GetById(5).Parent?.Id); + Assert.AreEqual(1, snapshot.Content.GetById(6).Parent?.Id); + + Assert.AreEqual(2, snapshot.Content.GetById(7).Parent?.Id); + Assert.AreEqual(2, snapshot.Content.GetById(8).Parent?.Id); + Assert.AreEqual(2, snapshot.Content.GetById(9).Parent?.Id); + + Assert.AreEqual(3, snapshot.Content.GetById(10).Parent?.Id); + + Assert.AreEqual(4, snapshot.Content.GetById(11).Parent?.Id); + Assert.AreEqual(4, snapshot.Content.GetById(12).Parent?.Id); + } + + [Test] + public void MoveToRootTest() + { + Init(GetInvariantKits()); + + // get snapshot + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + // do some changes + var kit = _source.Kits[10]; + _source.Kits[10] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), 1, "-1,10", 4, -1, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + // notify + _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(10, TreeChangeTypes.RefreshBranch) }, out _, out _); + + // changes that *I* make are immediately visible on the current snapshot + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2", "N3", "N10"); + + documents = snapshot.Content.GetById(3).Children().ToArray(); + AssertDocuments(documents); + + Assert.IsNull(snapshot.Content.GetById(10).Parent); + } + + [Test] + public void MoveFromRootTest() + { + Init(GetInvariantKits()); + + // get snapshot + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + // do some changes + var kit = _source.Kits[1]; + _source.Kits[1] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), 1, "-1,3,10,1", 1, 10, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + // notify + _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.RefreshBranch) }, out _, out _); + + // changes that *I* make are immediately visible on the current snapshot + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N2", "N3"); + + documents = snapshot.Content.GetById(10).Children().ToArray(); + AssertDocuments(documents, "N1"); + + Assert.AreEqual(10, snapshot.Content.GetById(1).Parent?.Id); + } + + [Test] + public void ReOrderTest() + { + Init(GetInvariantKits()); + + // get snapshot + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + // do some changes + var kit = _source.Kits[7]; + _source.Kits[7] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 1, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + kit = _source.Kits[8]; + _source.Kits[8] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 3, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + kit = _source.Kits[9]; + _source.Kits[9] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 2, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + // notify + _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(kit.Node.ParentContentId, TreeChangeTypes.RefreshBranch) }, out _, out _); + + // changes that *I* make are immediately visible on the current snapshot + var documents = snapshot.Content.GetById(kit.Node.ParentContentId).Children().ToArray(); + AssertDocuments(documents, "N7", "N9", "N8"); + } + + [Test] + public void MoveTest() + { + Init(GetInvariantKits()); + + // get snapshot + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + // do some changes + var kit = _source.Kits[4]; + _source.Kits[4] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 2, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + kit = _source.Kits[5]; + _source.Kits[5] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 3, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + kit = _source.Kits[6]; + _source.Kits[6] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 4, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + kit = _source.Kits[7]; + _source.Kits[7] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, "-1,1,7", 1, 1, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + // notify + _snapshotService.Notify(new[] + { + // removal must come first + new ContentCacheRefresher.JsonPayload(2, TreeChangeTypes.RefreshBranch), + new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.RefreshBranch) + }, out _, out _); + + // changes that *I* make are immediately visible on the current snapshot + var documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N7", "N4", "N5", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9", "N8"); + + Assert.AreEqual(1, snapshot.Content.GetById(7).Parent?.Id); + } + + [Test] + public void NestedVariationChildrenTest() + { + var mixedKits = GetNestedVariantKits(); + Init(mixedKits); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + //TEST with en-us variation context + + _variationAccesor.VariationContext = new VariationContext("en-US"); + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1-en-US"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N7-en-US"); + + //Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N10-en-US", "N11"); + + //Get the variant and list children, there's a variation context so it should return invariant AND en-us variants + documents = snapshot.Content.GetById(7).Children().ToArray(); + AssertDocuments(documents, "N12-en-US", "N13"); + + //TEST with fr-fr variation context + + _variationAccesor.VariationContext = new VariationContext("fr-FR"); + + documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1-fr-FR"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N7-fr-FR"); + + //Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N10-fr-FR", "N11"); + + //Get the variant and list children, there's a variation context so it should return invariant AND en-us variants + documents = snapshot.Content.GetById(7).Children().ToArray(); + AssertDocuments(documents, "N12-fr-FR", "N13"); + + //TEST specific cultures + + documents = snapshot.Content.GetAtRoot("fr-FR").ToArray(); + AssertDocuments(documents, "N1-fr-FR"); + + documents = snapshot.Content.GetById(1).Children("fr-FR").ToArray(); + AssertDocuments(documents, "N4", "N7-fr-FR"); //NOTE: Returns invariant, this is expected + documents = snapshot.Content.GetById(1).Children("").ToArray(); + AssertDocuments(documents, "N4"); //Only returns invariant since that is what was requested + + documents = snapshot.Content.GetById(4).Children("fr-FR").ToArray(); + AssertDocuments(documents, "N10-fr-FR", "N11"); //NOTE: Returns invariant, this is expected + documents = snapshot.Content.GetById(4).Children("").ToArray(); + AssertDocuments(documents, "N11"); //Only returns invariant since that is what was requested + + documents = snapshot.Content.GetById(7).Children("fr-FR").ToArray(); + AssertDocuments(documents, "N12-fr-FR", "N13"); //NOTE: Returns invariant, this is expected + documents = snapshot.Content.GetById(7).Children("").ToArray(); + AssertDocuments(documents, "N13"); //Only returns invariant since that is what was requested + + //TEST without variation context + // This will actually convert the culture to "" which will be invariant since that's all it will know how to do + // This will return a NULL name for culture specific entities because there is no variation context + + _variationAccesor.VariationContext = null; + + documents = snapshot.Content.GetAtRoot().ToArray(); + //will return nothing because there's only variant at root + Assert.AreEqual(0, documents.Length); + //so we'll continue to getting the known variant, do not fully assert this because the Name will NULL + documents = snapshot.Content.GetAtRoot("fr-FR").ToArray(); + Assert.AreEqual(1, documents.Length); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4"); + + //Get the invariant and list children + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N11"); + + //Get the variant and list children + documents = snapshot.Content.GetById(7).Children().ToArray(); + AssertDocuments(documents, "N13"); + } + + [Test] + public void VariantChildrenTest() + { + Init(GetVariantKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + _variationAccesor.VariationContext = new VariationContext("en-US"); + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1-en-US", "N2-en-US", "N3-en-US"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4-en-US", "N5-en-US", "N6-en-US"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9-en-US", "N8-en-US", "N7-en-US"); + + documents = snapshot.Content.GetById(3).Children().ToArray(); + AssertDocuments(documents, "N10-en-US"); + + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N11-en-US", "N12-en-US"); + + documents = snapshot.Content.GetById(10).Children().ToArray(); + AssertDocuments(documents); + + + _variationAccesor.VariationContext = new VariationContext("fr-FR"); + + documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1-fr-FR", "N3-fr-FR"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4-fr-FR", "N6-fr-FR"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9-fr-FR", "N7-fr-FR"); + + documents = snapshot.Content.GetById(3).Children().ToArray(); + AssertDocuments(documents, "N10-fr-FR"); + + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N12-fr-FR"); + + documents = snapshot.Content.GetById(10).Children().ToArray(); + AssertDocuments(documents); + + documents = snapshot.Content.GetById(1).Children("*").ToArray(); + AssertDocuments(documents, "N4-fr-FR", null, "N6-fr-FR"); + AssertDocuments("en-US", documents, "N4-en-US", "N5-en-US", "N6-en-US"); + + documents = snapshot.Content.GetById(1).Children("en-US").ToArray(); + AssertDocuments(documents, "N4-fr-FR", null, "N6-fr-FR"); + AssertDocuments("en-US", documents, "N4-en-US", "N5-en-US", "N6-en-US"); + + documents = snapshot.Content.GetById(1).ChildrenForAllCultures.ToArray(); + AssertDocuments(documents, "N4-fr-FR", null, "N6-fr-FR"); + AssertDocuments("en-US", documents, "N4-en-US", "N5-en-US", "N6-en-US"); + + + documents = snapshot.Content.GetAtRoot("*").ToArray(); + AssertDocuments(documents, "N1-fr-FR", null, "N3-fr-FR"); + + documents = snapshot.Content.GetById(1).DescendantsOrSelf().ToArray(); + AssertDocuments(documents, "N1-fr-FR", "N4-fr-FR", "N12-fr-FR", "N6-fr-FR"); + + documents = snapshot.Content.GetById(1).DescendantsOrSelf("*").ToArray(); + AssertDocuments(documents, "N1-fr-FR", "N4-fr-FR", null /*11*/, "N12-fr-FR", null /*5*/, "N6-fr-FR"); + } + + [Test] + public void RemoveTest() + { + Init(GetInvariantKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2", "N3"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N5", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9", "N8", "N7"); + + // notify + _snapshotService.Notify(new[] + { + new ContentCacheRefresher.JsonPayload(3, TreeChangeTypes.Remove), // remove last + new ContentCacheRefresher.JsonPayload(5, TreeChangeTypes.Remove), // remove middle + new ContentCacheRefresher.JsonPayload(9, TreeChangeTypes.Remove), // remove first + }, out _, out _); + + documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N8", "N7"); + + // notify + _snapshotService.Notify(new[] + { + new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.Remove), // remove first + new ContentCacheRefresher.JsonPayload(8, TreeChangeTypes.Remove), // remove + new ContentCacheRefresher.JsonPayload(7, TreeChangeTypes.Remove), // remove + }, out _, out _); + + documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N2"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents); + } + + [Test] + public void UpdateTest() + { + Init(GetInvariantKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2", "N3"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N5", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9", "N8", "N7"); + + // notify + _snapshotService.Notify(new[] + { + new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.RefreshBranch), + new ContentCacheRefresher.JsonPayload(2, TreeChangeTypes.RefreshNode), + }, out _, out _); + + documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2", "N3"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N5", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9", "N8", "N7"); + } + + [Test] + public void AtRootTest() + { + Init(GetVariantWithDraftKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + _variationAccesor.VariationContext = new VariationContext("en-US"); + + // N2 is draft only + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1-en-US", /*"N2-en-US",*/ "N3-en-US"); + + documents = snapshot.Content.GetAtRoot(true).ToArray(); + AssertDocuments(documents, "N1-en-US", "N2-en-US", "N3-en-US"); + } + + private void AssertDocuments(IPublishedContent[] documents, params string[] names) + { + Assert.AreEqual(names.Length, documents.Length); + for (var i = 0; i < names.Length; i++) + Assert.AreEqual(names[i], documents[i].Name); + } + + private void AssertDocuments(string culture, IPublishedContent[] documents, params string[] names) + { + Assert.AreEqual(names.Length, documents.Length); + for (var i = 0; i < names.Length; i++) + Assert.AreEqual(names[i], documents[i].Name(culture)); + } + } +} diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index ad2b0220bb..399b0c1342 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -24,7 +24,6 @@ using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.PublishedCache.NuCache.DataSource; -using Umbraco.Web.Routing; namespace Umbraco.Tests.PublishedContent { @@ -39,10 +38,18 @@ namespace Umbraco.Tests.PublishedContent private void Init() { Current.Reset(); - Current.UnlockConfigs(); - Current.Configs.Add(SettingsForTests.GenerateMockUmbracoSettings); - Current.Configs.Add(() => new GlobalSettings()); - var globalSettings = Current.Configs.Global(); + + var factory = Mock.Of(); + Current.Factory = factory; + + var configs = new Configs(); + Mock.Get(factory).Setup(x => x.GetInstance(typeof(Configs))).Returns(configs); + var globalSettings = new GlobalSettings(); + configs.Add(SettingsForTests.GenerateMockUmbracoSettings); + configs.Add(() => globalSettings); + + var publishedModelFactory = new NoopPublishedModelFactory(); + Mock.Get(factory).Setup(x => x.GetInstance(typeof(IPublishedModelFactory))).Returns(publishedModelFactory); // create a content node kit var kit = new ContentNodeKit @@ -162,7 +169,7 @@ namespace Umbraco.Tests.PublishedContent _variationAccesor = new TestVariationContextAccessor(); // at last, create the complete NuCache snapshot service! - var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; + var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; _snapshotService = new PublishedSnapshotService(options, null, runtime, @@ -171,7 +178,6 @@ namespace Umbraco.Tests.PublishedContent null, new TestPublishedSnapshotAccessor(), _variationAccesor, - Mock.Of(), Mock.Of(), scopeProvider, Mock.Of(), @@ -180,13 +186,14 @@ namespace Umbraco.Tests.PublishedContent new TestDefaultCultureAccessor(), dataSource, globalSettings, - new SiteDomainHelper(), Mock.Of(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); + + Mock.Get(factory).Setup(x => x.GetInstance(typeof(IVariationContextAccessor))).Returns(_variationAccesor); } [Test] @@ -202,36 +209,34 @@ namespace Umbraco.Tests.PublishedContent var publishedContent = snapshot.Content.GetById(1); Assert.IsNotNull(publishedContent); - Assert.AreEqual("It Works1!", publishedContent.Name); Assert.AreEqual("val1", publishedContent.Value("prop")); Assert.AreEqual("val-fr1", publishedContent.Value("prop", "fr-FR")); Assert.AreEqual("val-uk1", publishedContent.Value("prop", "en-UK")); - Assert.AreEqual("name-fr1", publishedContent.GetCulture("fr-FR").Name); - Assert.AreEqual("name-uk1", publishedContent.GetCulture("en-UK").Name); + Assert.IsNull(publishedContent.Name()); // no invariant name for varying content + Assert.AreEqual("name-fr1", publishedContent.Name("fr-FR")); + Assert.AreEqual("name-uk1", publishedContent.Name("en-UK")); var draftContent = snapshot.Content.GetById(true, 1); - Assert.AreEqual("It Works2!", draftContent.Name); Assert.AreEqual("val2", draftContent.Value("prop")); Assert.AreEqual("val-fr2", draftContent.Value("prop", "fr-FR")); Assert.AreEqual("val-uk2", draftContent.Value("prop", "en-UK")); - Assert.AreEqual("name-fr2", draftContent.GetCulture("fr-FR").Name); - Assert.AreEqual("name-uk2", draftContent.GetCulture("en-UK").Name); + Assert.IsNull(draftContent.Name()); // no invariant name for varying content + Assert.AreEqual("name-fr2", draftContent.Name("fr-FR")); + Assert.AreEqual("name-uk2", draftContent.Name("en-UK")); // now french is default _variationAccesor.VariationContext = new VariationContext("fr-FR"); Assert.AreEqual("val-fr1", publishedContent.Value("prop")); - Assert.AreEqual("name-fr1", publishedContent.GetCulture().Name); - Assert.AreEqual("name-fr1", publishedContent.Name); - Assert.AreEqual(new DateTime(2018, 01, 01, 01, 00, 00), publishedContent.GetCulture().Date); + Assert.AreEqual("name-fr1", publishedContent.Name()); + Assert.AreEqual(new DateTime(2018, 01, 01, 01, 00, 00), publishedContent.CultureDate()); // now uk is default _variationAccesor.VariationContext = new VariationContext("en-UK"); Assert.AreEqual("val-uk1", publishedContent.Value("prop")); - Assert.AreEqual("name-uk1", publishedContent.GetCulture().Name); - Assert.AreEqual("name-uk1", publishedContent.Name); - Assert.AreEqual(new DateTime(2018, 01, 02, 01, 00, 00), publishedContent.GetCulture().Date); + Assert.AreEqual("name-uk1", publishedContent.Name()); + Assert.AreEqual(new DateTime(2018, 01, 02, 01, 00, 00), publishedContent.CultureDate()); // invariant needs to be retrieved explicitly, when it's not default Assert.AreEqual("val1", publishedContent.Value("prop", culture: "")); @@ -251,7 +256,7 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.GetPropertyType("prop").Variations); // now, "no culture" means "invariant" - Assert.AreEqual("It Works1!", againContent.Name); + Assert.AreEqual("It Works1!", againContent.Name()); Assert.AreEqual("val1", againContent.Value("prop")); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 283ed1edd9..cc455b8e5d 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -10,9 +10,9 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Web; +using PublishedContentExtensions = Umbraco.Web.PublishedContentExtensions; namespace Umbraco.Tests.PublishedContent { @@ -97,7 +97,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetContent(true, 1); //change a doc type alias - var c = (TestPublishedContent)doc.Children.ElementAt(0); + var c = (SolidPublishedContent)doc.Children.ElementAt(0); c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var dt = doc.ChildrenAsTable(Current.Services, "Child"); @@ -129,7 +129,8 @@ namespace Umbraco.Tests.PublishedContent var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); var contentTypeAlias = createChildren ? "Parent" : "Child"; - var d = new TestPublishedContent + var contentType = new PublishedContentType(22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var d = new SolidPublishedContent(contentType) { CreateDate = DateTime.Now, CreatorId = 1, @@ -140,7 +141,7 @@ namespace Umbraco.Tests.PublishedContent UpdateDate = DateTime.Now, Path = "-1,3", UrlSegment = "home-page", - Name = "Page" + Guid.NewGuid().ToString(), + Name = "Page" + Guid.NewGuid(), Version = Guid.NewGuid(), WriterId = 1, WriterName = "Shannon", @@ -175,75 +176,7 @@ namespace Umbraco.Tests.PublishedContent new RawValueProperty(factory.CreatePropertyType("property3", 1), d, "value" + (indexVals + 2))); } - d.ContentType = new PublishedContentType(22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); return d; } - - // note - could probably rewrite those tests using SolidPublishedContentCache - // l8tr... - private class TestPublishedContent : IPublishedContent - { - public string Url { get; set; } - public string GetUrl(string culture = null) => throw new NotSupportedException(); - - public PublishedItemType ItemType { get; set; } - - IPublishedContent IPublishedContent.Parent - { - get { return Parent; } - } - - IEnumerable IPublishedContent.Children - { - get { return Children; } - } - - public IPublishedContent Parent { get; set; } - public int Id { get; set; } - public Guid Key { get; set; } - public int? TemplateId { get; set; } - public int SortOrder { get; set; } - public string Name { get; set; } - public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); - public IReadOnlyDictionary Cultures => throw new NotSupportedException(); - public string UrlSegment { get; set; } - public string WriterName { get; set; } - public string CreatorName { get; set; } - public int WriterId { get; set; } - public int CreatorId { get; set; } - public string Path { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public Guid Version { get; set; } - public int Level { get; set; } - public bool IsDraft(string culture = null) => false; - public bool IsPublished(string culture = null) => true; - - public IEnumerable Properties { get; set; } - - public IEnumerable Children { get; set; } - - public IPublishedProperty GetProperty(string alias) - { - return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - - public IPublishedProperty GetProperty(string alias, bool recurse) - { - var property = GetProperty(alias); - if (recurse == false) return property; - - IPublishedContent content = this; - while (content != null && (property == null || property.HasValue() == false)) - { - content = content.Parent; - property = content == null ? null : content.GetProperty(alias); - } - - return property; - } - - public PublishedContentType ContentType { get; set; } - } } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs index acbad002ee..ced4c012f8 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.PublishedContent { InitializeInheritedContentTypes(); - var publishedContent = _ctx.ContentCache.GetById(1100); + var publishedContent = _ctx.Content.GetById(1100); Assert.That(publishedContent.IsDocumentType("inherited", false)); } @@ -36,7 +36,7 @@ namespace Umbraco.Tests.PublishedContent { InitializeInheritedContentTypes(); - var publishedContent = _ctx.ContentCache.GetById(1100); + var publishedContent = _ctx.Content.GetById(1100); Assert.That(publishedContent.IsDocumentType("base", false), Is.False); } @@ -45,7 +45,7 @@ namespace Umbraco.Tests.PublishedContent { InitializeInheritedContentTypes(); - var publishedContent = _ctx.ContentCache.GetById(1100); + var publishedContent = _ctx.Content.GetById(1100); Assert.That(publishedContent.IsDocumentType("inherited", true)); } @@ -55,7 +55,7 @@ namespace Umbraco.Tests.PublishedContent InitializeInheritedContentTypes(); ContentTypesCache.GetPublishedContentTypeByAlias = null; - var publishedContent = _ctx.ContentCache.GetById(1100); + var publishedContent = _ctx.Content.GetById(1100); Assert.That(publishedContent.IsDocumentType("base", true)); } @@ -64,7 +64,7 @@ namespace Umbraco.Tests.PublishedContent { InitializeInheritedContentTypes(); - var publishedContent = _ctx.ContentCache.GetById(1100); + var publishedContent = _ctx.Content.GetById(1100); Assert.That(publishedContent.IsDocumentType("invalidbase", true), Is.False); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 108bfb9f18..62447742ff 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -65,17 +65,22 @@ namespace Umbraco.Tests.PublishedContent var welcome2Type = factory.CreatePropertyType("welcomeText2", 1, variations: ContentVariation.Culture); var nopropType = factory.CreatePropertyType("noprop", 1, variations: ContentVariation.Culture); - var props = new[] - { - prop1Type, - welcomeType, - welcome2Type, - nopropType - }; - var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), props); + IEnumerable CreatePropertyTypes1(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "prop1", 1, variations: ContentVariation.Culture); + yield return factory.CreatePropertyType(contentType, "welcomeText", 1, variations: ContentVariation.Culture); + yield return factory.CreatePropertyType(contentType, "welcomeText2", 1, variations: ContentVariation.Culture); + yield return factory.CreatePropertyType(contentType, "noprop", 1, variations: ContentVariation.Culture); + } - var prop3Type = factory.CreatePropertyType("prop3", 1, variations: ContentVariation.Culture); - var contentType2 = factory.CreateContentType(2, "contentType2", Enumerable.Empty(), new[] { prop3Type }); + var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes1); + + IEnumerable CreatePropertyTypes2(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "prop3", 1, variations: ContentVariation.Culture); + } + + var contentType2 = factory.CreateContentType(2, "contentType2", Enumerable.Empty(), CreatePropertyTypes2); var prop1 = new SolidPublishedPropertyWithLanguageVariants { @@ -150,7 +155,7 @@ namespace Umbraco.Tests.PublishedContent var prop4 = new SolidPublishedPropertyWithLanguageVariants { Alias = "prop3", - PropertyType = prop3Type + PropertyType = contentType2.GetPropertyType("prop3") }; prop4.SetSourceValue("en-US", "Oxxo", true); prop4.SetValue("en-US", "Oxxo", true); @@ -186,7 +191,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Populated_Requested_Language() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "en-US"); Assert.AreEqual("Welcome", value); } @@ -194,7 +199,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Populated_Requested_Non_Default_Language() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "de"); Assert.AreEqual("Willkommen", value); } @@ -202,7 +207,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_Without_Fallback() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "fr"); Assert.IsNull(value); } @@ -210,7 +215,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Unless_Requested() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "es"); Assert.IsNull(value); } @@ -218,7 +223,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "es", fallback: Fallback.ToLanguage); Assert.AreEqual("Welcome", value); } @@ -226,7 +231,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Over_Two_Levels() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)); Assert.AreEqual("Welcome", value); } @@ -234,7 +239,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "no", fallback: Fallback.ToLanguage); Assert.IsNull(value); } @@ -242,7 +247,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_Recursively_Unless_Requested() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText2"); Assert.IsNull(value); } @@ -250,7 +255,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); Assert.AreEqual("Welcome", value); } @@ -258,7 +263,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_Recursively_Unless_Requested2() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First().Children.First(); Assert.IsNull(content.GetProperty("welcomeText2")); var value = content.Value("welcomeText2"); Assert.IsNull(value); @@ -267,7 +272,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively2() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First().Children.First(); Assert.IsNull(content.GetProperty("welcomeText2")); var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); Assert.AreEqual("Welcome", value); @@ -276,7 +281,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively3() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First().Children.First(); Assert.IsNull(content.GetProperty("noprop")); var value = content.Value("noprop", fallback: Fallback.ToAncestors); // property has no value but we still get the value (ie, the converter would do something) @@ -287,7 +292,7 @@ namespace Umbraco.Tests.PublishedContent public void Can_Get_Content_With_Recursive_Priority() { Current.VariationContextAccessor.VariationContext = new VariationContext("nl"); - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language)); @@ -298,7 +303,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_With_Fallback_Language_Priority() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText", "nl", fallback: Fallback.ToLanguage); // No Dutch value is directly assigned. Check has fallen back to English value from language variant. @@ -308,14 +313,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Throws_For_Non_Supported_Fallback() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); Assert.Throws(() => content.Value("welcomeText", "nl", fallback: Fallback.To(999))); } [Test] public void Can_Fallback_To_Default_Value() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); // no Dutch value is assigned, so getting null var value = content.Value("welcomeText", "nl"); @@ -333,7 +338,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Have_Custom_Default_Value() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); // HACK: the value, pretend the converter would return something var prop = content.GetProperty("welcomeText") as SolidPublishedPropertyWithLanguageVariants; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index b2f1f311c3..440474ae74 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -1,4 +1,5 @@ -using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models.PublishedContent; @@ -15,15 +16,16 @@ namespace Umbraco.Tests.PublishedContent { internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache) { - var props = new[] - { - factory.CreatePropertyType("prop1", 1), - }; - var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), props); - var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty(), props); - var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty(), props); + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "prop1", 1); + } - cache.Add(new SolidPublishedContent(contentType1) + var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes); + var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty(), CreatePropertyTypes); + var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty(), CreatePropertyTypes); + + var content = new SolidPublishedContent(contentType1) { Id = 1, SortOrder = 0, @@ -35,18 +37,19 @@ namespace Umbraco.Tests.PublishedContent ParentId = -1, ChildIds = new int[] { }, Properties = new Collection + { + new SolidPublishedProperty { - new SolidPublishedProperty - { - Alias = "prop1", - SolidHasValue = true, - SolidValue = 1234, - SolidSourceValue = "1234" - } + Alias = "prop1", + SolidHasValue = true, + SolidValue = 1234, + SolidSourceValue = "1234" } - }); + } + }; + cache.Add(content); - cache.Add(new SolidPublishedContent(contentType2) + content = new SolidPublishedContent(contentType2) { Id = 2, SortOrder = 1, @@ -58,18 +61,19 @@ namespace Umbraco.Tests.PublishedContent ParentId = -1, ChildIds = new int[] { }, Properties = new Collection + { + new SolidPublishedProperty { - new SolidPublishedProperty - { - Alias = "prop1", - SolidHasValue = true, - SolidValue = 1234, - SolidSourceValue = "1234" - } + Alias = "prop1", + SolidHasValue = true, + SolidValue = 1234, + SolidSourceValue = "1234" } - }); + } + }; + cache.Add(content); - cache.Add(new SolidPublishedContent(contentType2Sub) + content = new SolidPublishedContent(contentType2Sub) { Id = 3, SortOrder = 2, @@ -90,20 +94,21 @@ namespace Umbraco.Tests.PublishedContent SolidSourceValue = "1234" } } - }); + }; + cache.Add(content); } [Test] public void First() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); - Assert.AreEqual("Content 1", content.Name); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); + Assert.AreEqual("Content 1", content.Name()); } [Test] public void Distinct() { - var items = Current.UmbracoContext.ContentCache.GetAtRoot() + var items = Current.UmbracoContext.Content.GetAtRoot() .Distinct() .Distinct() .ToIndexedArray(); @@ -127,7 +132,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void OfType1() { - var items = Current.UmbracoContext.ContentCache.GetAtRoot() + var items = Current.UmbracoContext.Content.GetAtRoot() .OfType() .Distinct() .ToIndexedArray(); @@ -138,7 +143,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void OfType2() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot() + var content = Current.UmbracoContext.Content.GetAtRoot() .OfType() .Distinct() .ToIndexedArray(); @@ -149,7 +154,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void OfType() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot() + var content = Current.UmbracoContext.Content.GetAtRoot() .OfType() .First(x => x.Prop1 == 1234); Assert.AreEqual("Content 2", content.Name); @@ -159,7 +164,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Position() { - var items = Current.UmbracoContext.ContentCache.GetAtRoot() + var items = Current.UmbracoContext.Content.GetAtRoot() .Where(x => x.Value("prop1") == 1234) .ToIndexedArray(); @@ -174,7 +179,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Issue() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot() + var content = Current.UmbracoContext.Content.GetAtRoot() .Distinct() .OfType(); @@ -182,12 +187,12 @@ namespace Umbraco.Tests.PublishedContent var first = where.First(); Assert.AreEqual(1234, first.Prop1); - var content2 = Current.UmbracoContext.ContentCache.GetAtRoot() + var content2 = Current.UmbracoContext.Content.GetAtRoot() .OfType() .First(x => x.Prop1 == 1234); Assert.AreEqual(1234, content2.Prop1); - var content3 = Current.UmbracoContext.ContentCache.GetAtRoot() + var content3 = Current.UmbracoContext.Content.GetAtRoot() .OfType() .First(); Assert.AreEqual(1234, content3.Prop1); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index c5bcd29589..6907f6936c 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -1,4 +1,5 @@ -using Umbraco.Core; +using System.Collections.Generic; +using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; @@ -41,13 +42,12 @@ namespace Umbraco.Tests.PublishedContent var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - // need to specify a custom callback for unit tests - var propertyTypes = new[] + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - // AutoPublishedContentType will auto-generate other properties - publishedContentTypeFactory.CreatePropertyType("content", 1), - }; - var type = new AutoPublishedContentType(0, "anything", propertyTypes); + yield return publishedContentTypeFactory.CreatePropertyType(contentType, "content", 1); + } + + var type = new AutoPublishedContentType(0, "anything", CreatePropertyTypes); ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; var umbracoContext = GetUmbracoContext("/test"); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index de641a99a2..f54971a197 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Web; @@ -62,18 +63,19 @@ namespace Umbraco.Tests.PublishedContent // when they are requested, but we must declare those that we // explicitely want to be here... - var propertyTypes = new[] + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { // AutoPublishedContentType will auto-generate other properties - factory.CreatePropertyType("umbracoNaviHide", 1001), - factory.CreatePropertyType("selectedNodes", 1), - factory.CreatePropertyType("umbracoUrlAlias", 1), - factory.CreatePropertyType("content", 1002), - factory.CreatePropertyType("testRecursive", 1), - }; + yield return factory.CreatePropertyType(contentType, "umbracoNaviHide", 1001); + yield return factory.CreatePropertyType(contentType, "selectedNodes", 1); + yield return factory.CreatePropertyType(contentType, "umbracoUrlAlias", 1); + yield return factory.CreatePropertyType(contentType, "content", 1002); + yield return factory.CreatePropertyType(contentType, "testRecursive", 1); + } + var compositionAliases = new[] { "MyCompositionAlias" }; - var anythingType = new AutoPublishedContentType(0, "anything", compositionAliases, propertyTypes); - var homeType = new AutoPublishedContentType(0, "home", compositionAliases, propertyTypes); + var anythingType = new AutoPublishedContentType(0, "anything", compositionAliases, CreatePropertyTypes); + var homeType = new AutoPublishedContentType(0, "home", compositionAliases, CreatePropertyTypes); ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType; } @@ -140,7 +142,7 @@ namespace Umbraco.Tests.PublishedContent internal IPublishedContent GetNode(int id) { var ctx = GetUmbracoContext("/test"); - var doc = ctx.ContentCache.GetById(id); + var doc = ctx.Content.GetById(id); Assert.IsNotNull(doc); return doc; } @@ -149,16 +151,16 @@ namespace Umbraco.Tests.PublishedContent public void GetNodeByIds() { var ctx = GetUmbracoContext("/test"); - var contentById = ctx.ContentCache.GetById(1173); + var contentById = ctx.Content.GetById(1173); Assert.IsNotNull(contentById); - var contentByGuid = ctx.ContentCache.GetById(_node1173Guid); + var contentByGuid = ctx.Content.GetById(_node1173Guid); Assert.IsNotNull(contentByGuid); Assert.AreEqual(contentById.Id, contentByGuid.Id); Assert.AreEqual(contentById.Key, contentByGuid.Key); - contentById = ctx.ContentCache.GetById(666); + contentById = ctx.Content.GetById(666); Assert.IsNull(contentById); - contentByGuid = ctx.ContentCache.GetById(Guid.NewGuid()); + contentByGuid = ctx.Content.GetById(Guid.NewGuid()); Assert.IsNull(contentByGuid); } @@ -167,7 +169,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - var items = doc.Children.Where(x => x.IsVisible()).ToIndexedArray(); + var items = doc.Children().Where(x => x.IsVisible()).ToIndexedArray(); foreach (var item in items) { @@ -188,7 +190,7 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1173); var items = doc - .Children + .Children() .Where(x => x.IsVisible()) .ToIndexedArray(); @@ -243,7 +245,7 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1173); var ct = doc.ContentType; - var items = doc.Children + var items = doc.Children() .Select(x => x.CreateModel()) // linq, returns IEnumerable // only way around this is to make sure every IEnumerable extension @@ -275,7 +277,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - var items = doc.Children.Take(4).ToIndexedArray(); + var items = doc.Children().Take(4).ToIndexedArray(); foreach (var item in items) { @@ -295,7 +297,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - foreach (var d in doc.Children.Skip(1).ToIndexedArray()) + foreach (var d in doc.Children().Skip(1).ToIndexedArray()) { if (d.Content.Id != 1176) { @@ -313,7 +315,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - var items = doc.Children + var items = doc.Children() .Concat(new[] { GetNode(1175), GetNode(4444) }) .ToIndexedArray(); @@ -398,7 +400,7 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1046); - var found1 = doc.Children.GroupBy(x => x.ContentType.Alias).ToArray(); + var found1 = doc.Children().GroupBy(x => x.ContentType.Alias).ToArray(); Assert.AreEqual(2, found1.Length); Assert.AreEqual(2, found1.Single(x => x.Key.ToString() == "Home").Count()); @@ -419,8 +421,8 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1046); - var found1 = doc.Children.Where(x => x.ContentType.Alias == "CustomDocument"); - var found2 = doc.Children.Where(x => x.ContentType.Alias == "Home"); + var found1 = doc.Children().Where(x => x.ContentType.Alias == "CustomDocument"); + var found2 = doc.Children().Where(x => x.ContentType.Alias == "Home"); Assert.AreEqual(1, found1.Count()); Assert.AreEqual(2, found2.Count()); @@ -431,7 +433,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - var ordered = doc.Children.OrderBy(x => x.UpdateDate); + var ordered = doc.Children().OrderBy(x => x.UpdateDate); var correctOrder = new[] { 1178, 1177, 1174, 1176 }; for (var i = 0; i < correctOrder.Length; i++) @@ -819,7 +821,7 @@ namespace Umbraco.Tests.PublishedContent var level1_2 = GetNode(1175); var level1_3 = GetNode(4444); - _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot()).Returns(new []{root}); + _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot(It.IsAny())).Returns(new []{root}); CollectionAssertAreEqual(new []{root}, root.SiblingsAndSelf()); @@ -858,7 +860,7 @@ namespace Umbraco.Tests.PublishedContent var level1_2 = GetNode(1175); var level1_3 = GetNode(4444); - _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot()).Returns(new []{root}); + _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot(It.IsAny())).Returns(new []{root}); CollectionAssertAreEqual(new IPublishedContent[0], root.Siblings()); @@ -887,8 +889,13 @@ namespace Umbraco.Tests.PublishedContent { var factory = Factory.GetInstance() as PublishedContentTypeFactory; - var pt = factory.CreatePropertyType("detached", 1003); - var ct = factory.CreateContentType(0, "alias", new[] { pt }); + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "detached", 1003); + } + + var ct = factory.CreateContentType(0, "alias", CreatePropertyTypes); + var pt = ct.GetPropertyType("detached"); var prop = new PublishedElementPropertyBase(pt, null, false, PropertyCacheLevel.None, 5548); Assert.IsInstanceOf(prop.GetValue()); Assert.AreEqual(5548, prop.GetValue()); @@ -906,16 +913,20 @@ namespace Umbraco.Tests.PublishedContent { var factory = Factory.GetInstance() as PublishedContentTypeFactory; - var pt1 = factory.CreatePropertyType("legend", 1004); - var pt2 = factory.CreatePropertyType("image", 1005); - var pt3 = factory.CreatePropertyType("size", 1003); + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "legend", 1004); + yield return factory.CreatePropertyType(contentType, "image", 1005); + yield return factory.CreatePropertyType(contentType, "size", 1003); + } + const string val1 = "boom bam"; const int val2 = 0; const int val3 = 666; var guid = Guid.NewGuid(); - var ct = factory.CreateContentType(0, "alias", new[] { pt1, pt2, pt3 }); + var ct = factory.CreateContentType(0, "alias", CreatePropertyTypes); var c = new ImageWithLegendModel(ct, guid, new Dictionary { @@ -930,7 +941,7 @@ namespace Umbraco.Tests.PublishedContent class ImageWithLegendModel : PublishedElement { - public ImageWithLegendModel(PublishedContentType contentType, Guid fragmentKey, Dictionary values, bool previewing) + public ImageWithLegendModel(IPublishedContentType contentType, Guid fragmentKey, Dictionary values, bool previewing) : base(contentType, fragmentKey, values, previewing) { } diff --git a/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs b/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs index 77eab1dbb7..4aad3d0acb 100644 --- a/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs +++ b/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs @@ -13,17 +13,17 @@ namespace Umbraco.Tests.PublishedContent var ctx = GetUmbracoContext("/test"); // there is no content node with ID -1 - var content = ctx.ContentCache.GetById(-1); + var content = ctx.Content.GetById(-1); Assert.IsNull(content); // content at root has null parent - content = ctx.ContentCache.GetById(1046); + content = ctx.Content.GetById(1046); Assert.IsNotNull(content); Assert.AreEqual(1, content.Level); Assert.IsNull(content.Parent); // non-existing content is null - content = ctx.ContentCache.GetById(666); + content = ctx.Content.GetById(666); Assert.IsNull(content); } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 9828a14597..860b9b9179 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -100,7 +100,7 @@ namespace Umbraco.Tests.PublishedContent return _content.ContainsKey(contentId); } - public override IEnumerable GetAtRoot(bool preview) + public override IEnumerable GetAtRoot(bool preview, string culture = null) { return _content.Values.Where(x => x.Parent == null); } @@ -140,27 +140,27 @@ namespace Umbraco.Tests.PublishedContent return _content.Count > 0; } - public override PublishedContentType GetContentType(int id) + public override IPublishedContentType GetContentType(int id) { throw new NotImplementedException(); } - public override PublishedContentType GetContentType(string alias) + public override IPublishedContentType GetContentType(string alias) { throw new NotImplementedException(); } - public override IEnumerable GetByContentType(PublishedContentType contentType) + public override IEnumerable GetByContentType(IPublishedContentType contentType) { throw new NotImplementedException(); } } - class SolidPublishedContent : IPublishedContent + internal class SolidPublishedContent : IPublishedContent { #region Constructor - public SolidPublishedContent(PublishedContentType contentType) + public SolidPublishedContent(IPublishedContentType contentType) { // initialize boring stuff TemplateId = 0; @@ -176,13 +176,19 @@ namespace Umbraco.Tests.PublishedContent #region Content + private Dictionary _cultures; + + private Dictionary GetCultures() + { + return new Dictionary { { "", new PublishedCultureInfo("", Name, UrlSegment, UpdateDate) } }; + } + public int Id { get; set; } public Guid Key { get; set; } public int? TemplateId { get; set; } public int SortOrder { get; set; } public string Name { get; set; } - public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); - public IReadOnlyDictionary Cultures => throw new NotSupportedException(); + public IReadOnlyDictionary Cultures => _cultures ?? (_cultures = GetCultures()); public string UrlSegment { get; set; } public string WriterName { get; set; } public string CreatorName { get; set; } @@ -194,9 +200,8 @@ namespace Umbraco.Tests.PublishedContent public Guid Version { get; set; } public int Level { get; set; } public string Url { get; set; } - public string GetUrl(string culture = null) => throw new NotSupportedException(); - public PublishedItemType ItemType { get { return PublishedItemType.Content; } } + public PublishedItemType ItemType => PublishedItemType.Content; public bool IsDraft(string culture = null) => false; public bool IsPublished(string culture = null) => true; @@ -209,12 +214,13 @@ namespace Umbraco.Tests.PublishedContent public IPublishedContent Parent { get; set; } public IEnumerable Children { get; set; } + public IEnumerable ChildrenForAllCultures => Children; #endregion #region ContentType - public PublishedContentType ContentType { get; private set; } + public IPublishedContentType ContentType { get; set; } #endregion @@ -236,7 +242,7 @@ namespace Umbraco.Tests.PublishedContent while (content != null && (property == null || property.HasValue() == false)) { content = content.Parent; - property = content == null ? null : content.GetProperty(alias); + property = content?.GetProperty(alias); } return property; @@ -256,7 +262,7 @@ namespace Umbraco.Tests.PublishedContent internal class SolidPublishedProperty : IPublishedProperty { - public PublishedPropertyType PropertyType { get; set; } + public IPublishedPropertyType PropertyType { get; set; } public string Alias { get; set; } public object SolidSourceValue { get; set; } public object SolidValue { get; set; } @@ -400,7 +406,7 @@ namespace Umbraco.Tests.PublishedContent class AutoPublishedContentType : PublishedContentType { - private static readonly PublishedPropertyType Default; + private static readonly IPublishedPropertyType Default; static AutoPublishedContentType() { @@ -415,11 +421,19 @@ namespace Umbraco.Tests.PublishedContent : base(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) { } + public AutoPublishedContentType(int id, string alias, Func> propertyTypes) + : base(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) + { } + public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) : base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) { } - public override PublishedPropertyType GetPropertyType(string alias) + public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, Func> propertyTypes) + : base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) + { } + + public override IPublishedPropertyType GetPropertyType(string alias) { var propertyType = base.GetPropertyType(alias); return propertyType ?? Default; diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 5de99fdd38..6489417dc7 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using Moq; using Newtonsoft.Json; @@ -11,7 +10,6 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; using Umbraco.Web.Routing; @@ -45,7 +43,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Auto, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Auto); Assert.AreEqual(expected, resolvedUrl); } @@ -56,15 +54,15 @@ namespace Umbraco.Tests.Routing const string expected = "/media/rfeiw584/test.jpg"; var configuration = new ImageCropperConfiguration(); - var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue + var imageCropperValue = new ImageCropperValue { Src = expected - }); + }; var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Auto, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Auto); Assert.AreEqual(expected, resolvedUrl); } @@ -78,7 +76,20 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, mediaUrl, null); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Absolute, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Absolute); + + Assert.AreEqual(expected, resolvedUrl); + } + + [Test] + public void Get_Media_Url_Returns_Absolute_Url_If_Stored_Url_Is_Absolute() + { + const string expected = "http://localhost/media/rfeiw584/test.jpg"; + + var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider }); + var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null); + + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Relative); Assert.AreEqual(expected, resolvedUrl); } @@ -89,7 +100,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.Boolean, "0", null); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "test", UrlProviderMode.Absolute, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Absolute, propertyAlias: "test"); Assert.AreEqual(string.Empty, resolvedUrl); } @@ -116,19 +127,32 @@ namespace Umbraco.Tests.Routing var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}}; - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Auto, "da", null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Auto, "da"); Assert.AreEqual(daMediaUrl, resolvedUrl); } - private static TestPublishedContent CreatePublishedContent(string propertyEditorAlias, object propertyValue, object dataTypeConfiguration) + private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, object propertyValue, object dataTypeConfiguration) { var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), new[] {umbracoFilePropertyType}, ContentVariation.Nothing); - return new TestPublishedContent(contentType, 1234, Guid.NewGuid(), - new Dictionary {{"umbracoFile", propertyValue } }, false); + return new SolidPublishedContent(contentType) + { + Id = 1234, + Key = Guid.NewGuid(), + Properties = new[] + { + new SolidPublishedProperty + { + Alias = "umbracoFile", + SolidValue = propertyValue, + SolidHasValue = true, + PropertyType = umbracoFilePropertyType + } + } + }; } private static PublishedPropertyType CreatePropertyType(string propertyEditorAlias, object dataTypeConfiguration, ContentVariation variation) diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index f1f38504f1..935417c510 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -100,7 +100,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("~/dummy-page", template.Id, routeData); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.PublishedContent = umbracoContext.ContentCache.GetById(1174); + frequest.PublishedContent = umbracoContext.Content.GetById(1174); frequest.TemplateModel = template; var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); @@ -136,7 +136,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("~/dummy-page", template.Id, routeData, true); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.PublishedContent = umbracoContext.ContentCache.GetById(1172); + frequest.PublishedContent = umbracoContext.Content.GetById(1172); frequest.TemplateModel = template; var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index 236c198b3a..02aa95cd2e 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -10,8 +10,8 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.LegacyXmlPublishedCache; +using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; @@ -78,7 +78,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(randomSample.Value, result); } - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); Assert.AreEqual(8, cachedRoutes.Count); @@ -142,7 +142,7 @@ namespace Umbraco.Tests.Routing new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings: globalSettings.Object); - + var result = umbracoContext.UrlProvider.GetUrl(nodeId); Assert.AreEqual(niceUrlMatch, result); } @@ -159,7 +159,7 @@ namespace Umbraco.Tests.Routing var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); - var publishedContent = new TestPublishedContent(contentType, 1234, Guid.NewGuid(), new Dictionary(), false); + var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) @@ -185,7 +185,7 @@ namespace Umbraco.Tests.Routing snapshotService: snapshotService.Object); //even though we are asking for a specific culture URL, there are no domains assigned so all that can be returned is a normal relative url. - var url = umbracoContext.UrlProvider.GetUrl(1234, "fr-FR"); + var url = umbracoContext.UrlProvider.GetUrl(1234, culture: "fr-FR"); Assert.AreEqual("/home/test-fr/", url); } @@ -204,7 +204,7 @@ namespace Umbraco.Tests.Routing var umbracoSettings = Current.Configs.Settings(); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); - var publishedContent = new TestPublishedContent(contentType, 1234, Guid.NewGuid(), new Dictionary(), false); + var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) @@ -239,7 +239,7 @@ namespace Umbraco.Tests.Routing snapshotService: snapshotService.Object); - var url = umbracoContext.UrlProvider.GetUrl(1234, "fr-FR"); + var url = umbracoContext.UrlProvider.GetUrl(1234, culture: "fr-FR"); Assert.AreEqual("/home/test-fr/", url); } @@ -258,7 +258,7 @@ namespace Umbraco.Tests.Routing var umbracoSettings = Current.Configs.Settings(); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); - var publishedContent = new TestPublishedContent(contentType, 1234, Guid.NewGuid(), new Dictionary(), false); + var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) @@ -293,7 +293,7 @@ namespace Umbraco.Tests.Routing snapshotService: snapshotService.Object); - var url = umbracoContext.UrlProvider.GetUrl(1234, "fr-FR"); + var url = umbracoContext.UrlProvider.GetUrl(1234, culture: "fr-FR"); //the current uri is not the culture specific domain we want, so the result is an absolute path to the culture specific domain Assert.AreEqual("http://example.fr/home/test-fr/", url); @@ -314,7 +314,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("/home/sub1/custom-sub-1/", umbracoContext.UrlProvider.GetUrl(1177)); - umbracoContext.UrlProvider.Mode = UrlProviderMode.Absolute; + umbracoContext.UrlProvider.Mode = UrlMode.Absolute; Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", umbracoContext.UrlProvider.GetUrl(1177)); } @@ -332,10 +332,10 @@ namespace Umbraco.Tests.Routing }, globalSettings: globalSettings.Object); //mock the Umbraco settings that we need - + Assert.AreEqual("#", umbracoContext.UrlProvider.GetUrl(999999)); - umbracoContext.UrlProvider.Mode = UrlProviderMode.Absolute; + umbracoContext.UrlProvider.Mode = UrlMode.Absolute; Assert.AreEqual("#", umbracoContext.UrlProvider.GetUrl(999999)); } diff --git a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs index 2e944211ca..4b8d708df6 100644 --- a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs +++ b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs @@ -200,7 +200,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings: globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var route = cache.GetRouteById(false, id); @@ -224,7 +224,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings: globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var route = cache.GetRouteById(false, id); @@ -238,7 +238,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var route = cache.GetRouteById(false, 1000); @@ -269,7 +269,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); const bool preview = false; // make sure we don't cache - but HOW? should be some sort of switch?! @@ -300,7 +300,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); const bool preview = false; // make sure we don't cache - but HOW? should be some sort of switch?! @@ -323,7 +323,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var content = cache.GetByRoute(false, "/a/b/c"); diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index c01ee83d6a..0a34fb8041 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -7,6 +7,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers; @@ -188,7 +189,8 @@ namespace Umbraco.Tests.Routing SetDomains1(); var currentUri = new Uri(currentUrl); - var result = umbracoContext.UrlProvider.GetUrl(nodeId, absolute, current: currentUri); + var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; + var result = umbracoContext.UrlProvider.GetUrl(nodeId, mode, current: currentUri); Assert.AreEqual(expected, result); } @@ -220,7 +222,8 @@ namespace Umbraco.Tests.Routing SetDomains2(); var currentUri = new Uri(currentUrl); - var result = umbracoContext.UrlProvider.GetUrl(nodeId, absolute, current : currentUri); + var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; + var result = umbracoContext.UrlProvider.GetUrl(nodeId, mode, current : currentUri); Assert.AreEqual(expected, result); } @@ -244,7 +247,8 @@ namespace Umbraco.Tests.Routing SetDomains3(); var currentUri = new Uri(currentUrl); - var result = umbracoContext.UrlProvider.GetUrl(nodeId, absolute, current : currentUri); + var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; + var result = umbracoContext.UrlProvider.GetUrl(nodeId, mode, current : currentUri); Assert.AreEqual(expected, result); } @@ -274,7 +278,8 @@ namespace Umbraco.Tests.Routing SetDomains4(); var currentUri = new Uri(currentUrl); - var result = umbracoContext.UrlProvider.GetUrl(nodeId, absolute, current : currentUri); + var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; + var result = umbracoContext.UrlProvider.GetUrl(nodeId, mode, current : currentUri); Assert.AreEqual(expected, result); } @@ -294,19 +299,19 @@ namespace Umbraco.Tests.Routing SetDomains4(); string ignore; - ignore = umbracoContext.UrlProvider.GetUrl(1001, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(10011, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(100111, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(10012, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(100121, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(10013, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(1002, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(1001, false, current: new Uri("http://domain2.com")); - ignore = umbracoContext.UrlProvider.GetUrl(10011, false, current: new Uri("http://domain2.com")); - ignore = umbracoContext.UrlProvider.GetUrl(100111, false, current: new Uri("http://domain2.com")); - ignore = umbracoContext.UrlProvider.GetUrl(1002, false, current: new Uri("http://domain2.com")); + ignore = umbracoContext.UrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain2.com")); + ignore = umbracoContext.UrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain2.com")); + ignore = umbracoContext.UrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain2.com")); + ignore = umbracoContext.UrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain2.com")); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); Assert.AreEqual(7, cachedRoutes.Count); @@ -323,15 +328,15 @@ namespace Umbraco.Tests.Routing CheckRoute(cachedRoutes, cachedIds, 1002, "/1002"); // use the cache - Assert.AreEqual("/", umbracoContext.UrlProvider.GetUrl(1001, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/en/", umbracoContext.UrlProvider.GetUrl(10011, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/en/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/fr/", umbracoContext.UrlProvider.GetUrl(10012, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/fr/1001-2-1/", umbracoContext.UrlProvider.GetUrl(100121, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/1001-3/", umbracoContext.UrlProvider.GetUrl(10013, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/1002/", umbracoContext.UrlProvider.GetUrl(1002, false, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/", umbracoContext.UrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/en/", umbracoContext.UrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/en/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/fr/", umbracoContext.UrlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/fr/1001-2-1/", umbracoContext.UrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/1001-3/", umbracoContext.UrlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/1002/", umbracoContext.UrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com"))); - Assert.AreEqual("http://domain1.com/fr/1001-2-1/", umbracoContext.UrlProvider.GetUrl(100121, false, current: new Uri("http://domain2.com"))); + Assert.AreEqual("http://domain1.com/fr/1001-2-1/", umbracoContext.UrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain2.com"))); } private static void CheckRoute(IDictionary routes, IDictionary ids, int id, string route) @@ -359,7 +364,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("/en/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111)); Assert.AreEqual("http://domain3.com/en/1003-1-1/", umbracoContext.UrlProvider.GetUrl(100311)); - umbracoContext.UrlProvider.Mode = UrlProviderMode.Absolute; + umbracoContext.UrlProvider.Mode = UrlMode.Absolute; Assert.AreEqual("http://domain1.com/en/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111)); Assert.AreEqual("http://domain3.com/en/1003-1-1/", umbracoContext.UrlProvider.GetUrl(100311)); @@ -380,7 +385,7 @@ namespace Umbraco.Tests.Routing SetDomains5(); - var url = umbracoContext.UrlProvider.GetUrl(100111, true); + var url = umbracoContext.UrlProvider.GetUrl(100111, UrlMode.Absolute); Assert.AreEqual("http://domain1.com/en/1001-1-1/", url); var result = umbracoContext.UrlProvider.GetOtherUrls(100111).ToArray(); diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index 0eb621bd93..6587b2e4f6 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -5,6 +5,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; using Umbraco.Core.Services; @@ -44,10 +45,10 @@ namespace Umbraco.Tests.Routing { new DefaultUrlProvider(settings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings:globalSettings.Object); - Assert.AreEqual("http://domain2.com/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111, true)); + Assert.AreEqual("http://domain2.com/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111, UrlMode.Absolute)); // check that the proper route has been cached - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index fc9e0e6166..d0258a100f 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -191,17 +191,17 @@ namespace Umbraco.Tests.Runtimes var umbracoContext = umbracoContextReference.UmbracoContext; // assert that there is no published document - var pcontent = umbracoContext.ContentCache.GetById(content.Id); + var pcontent = umbracoContext.Content.GetById(content.Id); Assert.IsNull(pcontent); // but a draft document - pcontent = umbracoContext.ContentCache.GetById(true, content.Id); + pcontent = umbracoContext.Content.GetById(true, content.Id); Assert.IsNotNull(pcontent); - Assert.AreEqual("test", pcontent.Name); + Assert.AreEqual("test", pcontent.Name()); Assert.IsTrue(pcontent.IsDraft()); // no published url - Assert.AreEqual("#", pcontent.GetUrl()); + Assert.AreEqual("#", pcontent.Url()); // now publish the document + make some unpublished changes contentService.SaveAndPublish(content); @@ -209,22 +209,22 @@ namespace Umbraco.Tests.Runtimes contentService.Save(content); // assert that snapshot has been updated and there is now a published document - pcontent = umbracoContext.ContentCache.GetById(content.Id); + pcontent = umbracoContext.Content.GetById(content.Id); Assert.IsNotNull(pcontent); - Assert.AreEqual("test", pcontent.Name); + Assert.AreEqual("test", pcontent.Name()); Assert.IsFalse(pcontent.IsDraft()); // but the url is the published one - no draft url - Assert.AreEqual("/test/", pcontent.GetUrl()); + Assert.AreEqual("/test/", pcontent.Url()); // and also an updated draft document - pcontent = umbracoContext.ContentCache.GetById(true, content.Id); + pcontent = umbracoContext.Content.GetById(true, content.Id); Assert.IsNotNull(pcontent); - Assert.AreEqual("testx", pcontent.Name); + Assert.AreEqual("testx", pcontent.Name()); Assert.IsTrue(pcontent.IsDraft()); // and the published document has a url - Assert.AreEqual("/test/", pcontent.GetUrl()); + Assert.AreEqual("/test/", pcontent.Url()); umbracoContextReference.Dispose(); mainDom.Stop(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index d969356ce9..c974fed99e 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -72,7 +72,7 @@ namespace Umbraco.Tests.Scoping protected override IPublishedSnapshotService CreatePublishedSnapshotService() { - var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; + var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor); var runtimeStateMock = new Mock(); runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run); @@ -92,13 +92,12 @@ namespace Umbraco.Tests.Scoping null, publishedSnapshotAccessor, Mock.Of(), - Mock.Of(), Logger, ScopeProvider, documentRepository, mediaRepository, memberRepository, DefaultCultureAccessor, new DatabaseDataSource(), - Factory.GetInstance(), new SiteDomainHelper(), + Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); @@ -149,11 +148,11 @@ namespace Umbraco.Tests.Scoping { evented++; - var e = umbracoContext.ContentCache.GetById(item.Id); + var e = umbracoContext.Content.GetById(item.Id); // during events, due to LiveSnapshot, we see the changes Assert.IsNotNull(e); - Assert.AreEqual("changed", e.Name); + Assert.AreEqual("changed", e.Name()); }; using (var scope = ScopeProvider.CreateScope()) @@ -163,9 +162,9 @@ namespace Umbraco.Tests.Scoping } // been created - var x = umbracoContext.ContentCache.GetById(item.Id); + var x = umbracoContext.Content.GetById(item.Id); Assert.IsNotNull(x); - Assert.AreEqual("name", x.Name); + Assert.AreEqual("name", x.Name()); ContentService.Published += OnPublishedAssert; @@ -185,9 +184,9 @@ namespace Umbraco.Tests.Scoping // after the scope, // if completed, we see the changes // else changes have been rolled back - x = umbracoContext.ContentCache.GetById(item.Id); + x = umbracoContext.Content.GetById(item.Id); Assert.IsNotNull(x); - Assert.AreEqual(complete ? "changed" : "name", x.Name); + Assert.AreEqual(complete ? "changed" : "name", x.Name()); } } } diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 044965bc79..34482a79fa 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Scoping private static XmlStore XmlStore => (Current.Factory.GetInstance() as PublishedSnapshotService).XmlStore; private static XmlDocument XmlMaster => XmlStore.Xml; - private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.ContentCache).GetXml(false); + private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.Content).GetXml(false); [TestCase(true)] [TestCase(false)] @@ -85,7 +85,7 @@ namespace Umbraco.Tests.Scoping // sanity checks Assert.AreSame(umbracoContext, Umbraco.Web.Composing.Current.UmbracoContext); - Assert.AreSame(XmlStore, ((PublishedContentCache) umbracoContext.ContentCache).XmlStore); + Assert.AreSame(XmlStore, ((PublishedContentCache) umbracoContext.Content).XmlStore); // create document type, document var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" }; @@ -199,7 +199,7 @@ namespace Umbraco.Tests.Scoping // sanity checks Assert.AreSame(umbracoContext, Umbraco.Web.Composing.Current.UmbracoContext); - Assert.AreSame(XmlStore, ((PublishedContentCache)umbracoContext.ContentCache).XmlStore); + Assert.AreSame(XmlStore, ((PublishedContentCache)umbracoContext.Content).XmlStore); // create document type var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" }; diff --git a/src/Umbraco.Tests/Services/AuditServiceTests.cs b/src/Umbraco.Tests/Services/AuditServiceTests.cs index 6064fe4acc..bfec246e61 100644 --- a/src/Umbraco.Tests/Services/AuditServiceTests.cs +++ b/src/Umbraco.Tests/Services/AuditServiceTests.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using Umbraco.Core.Services.Implement; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using Umbraco.Core.Models; namespace Umbraco.Tests.Services { @@ -48,5 +49,19 @@ namespace Umbraco.Tests.Services Assert.AreEqual(123 + 5, entries[0].PerformingUserId); Assert.AreEqual(123 + 4, entries[1].PerformingUserId); } + + [Test] + public void CanReadEntries() + { + var yesterday = DateTime.UtcNow.AddDays(-1); + + for (var i = 0; i < 10; i++) + { + yesterday = yesterday.AddMinutes(1); + ServiceContext.AuditService.Add(AuditType.Unpublish, -1, 33, "", "blah"); + } + + var logs = ServiceContext.AuditService.GetUserLogs(-1, AuditType.Unpublish); + } } } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index c70b96a175..18ea95cd98 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -18,11 +18,9 @@ using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Tests.Testing; -using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.PublishedCache.NuCache.DataSource; -using Umbraco.Web.Routing; namespace Umbraco.Tests.Services { @@ -44,7 +42,7 @@ namespace Umbraco.Tests.Services protected override IPublishedSnapshotService CreatePublishedSnapshotService() { - var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; + var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor); var runtimeStateMock = new Mock(); runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run); @@ -65,13 +63,12 @@ namespace Umbraco.Tests.Services null, publishedSnapshotAccessor, Mock.Of(), - Mock.Of(), Logger, ScopeProvider, documentRepository, mediaRepository, memberRepository, DefaultCultureAccessor, new DatabaseDataSource(), - Factory.GetInstance(), new SiteDomainHelper(), + Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index 2425f8b74a..0598b8cea2 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -675,15 +675,10 @@ namespace Umbraco.Tests.Services foreach (var entity in entities) { - Console.WriteLine(); - foreach (var data in entity.AdditionalData) - { - Console.WriteLine($"{entity.Id} {data.Key} {data.Value} {(data.Value is EntitySlim.PropertySlim p ? p.PropertyEditorAlias : "")}"); - } + Assert.IsTrue(entity.GetType().Implements()); + Console.WriteLine(((IMediaEntitySlim)entity).MediaPath); + Assert.IsNotEmpty(((IMediaEntitySlim)entity).MediaPath); } - - Assert.That(entities.Any(x => - x.AdditionalData.Any(y => y.Value is EntitySlim.PropertySlim && ((EntitySlim.PropertySlim)y.Value).PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField)), Is.True); } [Test] diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index b5712a52aa..e52d338ca6 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -147,7 +147,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting umbracoContextAccessor.UmbracoContext = umbCtx; var urlHelper = new Mock(); - urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/hello/world/1234")); var membershipHelper = new MembershipHelper(umbCtx.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()); diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs index 8c598281dd..9f9f933d72 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs @@ -25,19 +25,23 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting public async Task> Execute(string controllerName, string actionName, HttpMethod method, HttpContent content = null, MediaTypeWithQualityHeaderValue mediaTypeHeader = null, - bool assertOkResponse = true) + bool assertOkResponse = true, object routeDefaults = null, string url = null) { if (mediaTypeHeader == null) { mediaTypeHeader = new MediaTypeWithQualityHeaderValue("application/json"); } + if (routeDefaults == null) + { + routeDefaults = new { controller = controllerName, action = actionName, id = RouteParameter.Optional }; + } var startup = new TestStartup( configuration => { configuration.Routes.MapHttpRoute("Default", routeTemplate: "{controller}/{action}/{id}", - defaults: new { controller = controllerName, action = actionName, id = RouteParameter.Optional }); + defaults: routeDefaults); }, _controllerFactory); @@ -45,7 +49,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting { var request = new HttpRequestMessage { - RequestUri = new Uri("https://testserver/"), + RequestUri = new Uri("https://testserver/" + (url ?? "")), Method = method }; diff --git a/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs b/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs new file mode 100644 index 0000000000..34904db1ae --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs @@ -0,0 +1,22 @@ +using Lucene.Net.Store; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Tests.TestHelpers +{ + + /// + /// Used for tests with Lucene so that each RAM directory is unique + /// + public class RandomIdRAMDirectory : RAMDirectory + { + private readonly string _lockId = Guid.NewGuid().ToString(); + public override string GetLockId() + { + return _lockId; + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs deleted file mode 100644 index a9abe96232..0000000000 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.PublishedCache; - -namespace Umbraco.Tests.TestHelpers.Stubs -{ - internal class TestPublishedContent : PublishedElement, IPublishedContent - { - public TestPublishedContent(PublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) - : base(contentType, key, values, previewing) - { - Id = id; - Cultures = cultures; - } - - public int Id { get; } - public int? TemplateId { get; set; } - public int SortOrder { get; set; } - public string Name { get; set; } - public IVariationContextAccessor VariationContextAccessor { get; set; } - public PublishedCultureInfo GetCulture(string culture = null) - { - // handle context culture - if (culture == null) - culture = VariationContextAccessor?.VariationContext?.Culture; - - // no invariant culture infos - if (culture == "" || Cultures == null) return null; - - // get - return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos : null; - } - public IReadOnlyDictionary Cultures { get; set; } - public string UrlSegment { get; set; } - public string DocumentTypeAlias => ContentType.Alias; - public int DocumentTypeId { get; set; } - public string WriterName { get; set; } - public string CreatorName { get; set; } - public int WriterId { get; set; } - public int CreatorId { get; set; } - public string Path { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public Guid Version { get; set; } - public int Level { get; set; } - public string Url { get; set; } - public string GetUrl(string culture = null) => throw new NotSupportedException(); - public PublishedItemType ItemType => ContentType.ItemType; - public bool IsDraft(string culture = null) => false; - public bool IsPublished(string culture = null) => true; - public IPublishedContent Parent { get; set; } - public IEnumerable Children { get; set; } - - // copied from PublishedContentBase - public IPublishedProperty GetProperty(string alias, bool recurse) - { - var property = GetProperty(alias); - if (recurse == false) return property; - - IPublishedContent content = this; - var firstNonNullProperty = property; - while (content != null && (property == null || property.HasValue() == false)) - { - content = content.Parent; - property = content?.GetProperty(alias); - if (firstNonNullProperty == null && property != null) firstNonNullProperty = property; - } - - // if we find a content with the property with a value, return that property - // if we find no content with the property, return null - // if we find a content with the property without a value, return that property - // have to save that first property while we look further up, hence firstNonNullProperty - - return property != null && property.HasValue() ? property : firstNonNullProperty; - } - } -} diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 647824ab66..dc8e35bb52 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Services; @@ -147,7 +148,7 @@ namespace Umbraco.Tests.TestHelpers var umbracoSettingsMock = new Mock(); var webRoutingSectionMock = new Mock(); - webRoutingSectionMock.Setup(x => x.UrlProviderMode).Returns(UrlProviderMode.Auto.ToString()); + webRoutingSectionMock.Setup(x => x.UrlProviderMode).Returns(UrlMode.Auto.ToString()); umbracoSettingsMock.Setup(x => x.WebRouting).Returns(webRoutingSectionMock.Object); return umbracoSettingsMock.Object; } diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 110accdecf..31d11952ef 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -164,11 +164,7 @@ namespace Umbraco.Tests.TestHelpers var fileService = GetLazyService(factory, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var memberTypeService = GetLazyService(factory, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); - var entityService = GetLazyService(factory, c => new EntityService( - scopeProvider, logger, eventMessagesFactory, - contentService.Value, contentTypeService.Value, mediaService.Value, mediaTypeService.Value, dataTypeService.Value, memberService.Value, memberTypeService.Value, - idkMap, - GetRepo(c))); + var entityService = GetLazyService(factory, c => new EntityService(scopeProvider, logger, eventMessagesFactory, idkMap, GetRepo(c))); var macroService = GetLazyService(factory, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); var packagingService = GetLazyService(factory, c => diff --git a/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs b/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs index 26bfff0e1a..72fb89ab82 100644 --- a/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs +++ b/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs @@ -9,30 +9,38 @@ namespace Umbraco.Tests.Testing.Objects { internal class TestDataSource : IDataSource { - private readonly Dictionary _kits; - public TestDataSource(params ContentNodeKit[] kits) : this((IEnumerable) kits) { } public TestDataSource(IEnumerable kits) { - _kits = kits.ToDictionary(x => x.Node.Id, x => x); + Kits = kits.ToDictionary(x => x.Node.Id, x => x); } + public Dictionary Kits { get; } + + // note: it is important to clone the returned kits, as the inner + // ContentNode is directly reused and modified by the snapshot service + public ContentNodeKit GetContentSource(IScope scope, int id) - => _kits.TryGetValue(id, out var kit) ? kit : default; + => Kits.TryGetValue(id, out var kit) ? kit.Clone() : default; public IEnumerable GetAllContentSources(IScope scope) - => _kits.Values; + => Kits.Values + .OrderBy(x => x.Node.Level) + .Select(x => x.Clone()); public IEnumerable GetBranchContentSources(IScope scope, int id) - { - throw new NotImplementedException(); - } + => Kits.Values + .Where(x => x.Node.Path.EndsWith("," + id) || x.Node.Path.Contains("," + id + ",")) + .OrderBy(x => x.Node.Level).ThenBy(x => x.Node.SortOrder) + .Select(x => x.Clone()); public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids) - => _kits.Values.Where(x => ids.Contains(x.ContentTypeId)); + => Kits.Values + .Where(x => ids.Contains(x.ContentTypeId)) + .Select(x => x.Clone()); public ContentNodeKit GetMediaSource(IScope scope, int id) { diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index d85f610236..28fecd6b2b 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -76,7 +76,7 @@ namespace Umbraco.Tests.Testing.TestingTests var umbracoContext = TestObjects.GetUmbracoContextMock(); var urlProviderMock = new Mock(); - urlProviderMock.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + urlProviderMock.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/hello/world/1234")); var urlProvider = urlProviderMock.Object; diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 2d2cbaa3fd..7e72a5aefb 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -248,6 +248,11 @@ namespace Umbraco.Tests.Testing // register empty content apps collection Composition.WithCollectionBuilder(); + + // manifest + Composition.ManifestValueValidators(); + Composition.ManifestFilters(); + } protected virtual void ComposeMapper(bool configure) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ddd67df6e5..f788168ddc 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -139,6 +139,7 @@ + @@ -156,6 +157,7 @@ + @@ -205,7 +207,6 @@ - @@ -242,6 +243,7 @@ + @@ -267,6 +269,8 @@ + + diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs new file mode 100644 index 0000000000..3d264663b5 --- /dev/null +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Security.Cryptography; +using System.Threading; +using System.Web; +using System.Web.Hosting; +using System.Web.Http; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.ControllerTesting; +using Umbraco.Tests.Testing; +using Umbraco.Web; +using Umbraco.Web.Editors; +using Umbraco.Web.Features; +using Umbraco.Web.Models.ContentEditing; +using IUser = Umbraco.Core.Models.Membership.IUser; + +namespace Umbraco.Tests.Web.Controllers +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.None)] + public class AuthenticationControllerTests : TestWithDatabaseBase + { + protected override void ComposeApplication(bool withApplication) + { + base.ComposeApplication(withApplication); + //if (!withApplication) return; + + // replace the true IUserService implementation with a mock + // so that each test can configure the service to their liking + Composition.RegisterUnique(f => Mock.Of()); + + // kill the true IEntityService too + Composition.RegisterUnique(f => Mock.Of()); + + Composition.RegisterUnique(); + } + + + [Test] + public async System.Threading.Tasks.Task GetCurrentUser_Fips() + { + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) + { + //setup some mocks + var userServiceMock = Mock.Get(Current.Services.UserService); + userServiceMock.Setup(service => service.GetUserById(It.IsAny())) + .Returns(() => null); + + if (Thread.GetDomain().GetData(".appPath") != null) + { + HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter())); + } + else + { + var baseDir = IOHelper.MapPath("", false).TrimEnd(IOHelper.DirSepChar); + HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter())); + } + IOHelper.ForceNotHosted = true; + var usersController = new AuthenticationController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + return usersController; + } + + Mock.Get(Current.SqlContext) + .Setup(x => x.Query()) + .Returns(new Query(Current.SqlContext)); + + var syntax = new SqlCeSyntaxProvider(); + + Mock.Get(Current.SqlContext) + .Setup(x => x.SqlSyntax) + .Returns(syntax); + + var mappers = new MapperCollection(new[] + { + new UserMapper(new Lazy(() => Current.SqlContext), new ConcurrentDictionary>()) + }); + + Mock.Get(Current.SqlContext) + .Setup(x => x.Mappers) + .Returns(mappers); + + // Testing what happens if the system were configured to only use FIPS-compliant algorithms + var typ = typeof(CryptoConfig); + var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic); + var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy"); + var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy"); + var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms; + + try + { + if (!originalFipsValue) + { + haveFld.SetValue(null, true); + isFld.SetValue(null, true); + } + + var runner = new TestRunner(CtrlFactory); + var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get); + + var obj = JsonConvert.DeserializeObject(response.Item2); + Assert.AreEqual(-1, obj.UserId); + } + finally + { + if (!originalFipsValue) + { + haveFld.SetValue(null, false); + isFld.SetValue(null, false); + } + } + } + } +} diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs index a4213b4f0e..d77867152a 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs @@ -77,6 +77,19 @@ namespace Umbraco.Tests.Web.Controllers var entityService = new Mock(); entityService.Setup(x => x.GetAllPaths(UmbracoObjectTypes.Document, It.IsAny())) .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = $"-1,{x}", Id = x }).ToList()); + entityService.Setup(x => x.GetKey(It.IsAny(), UmbracoObjectTypes.DataType)) + .Returns((int id, UmbracoObjectTypes objType) => + { + switch (id) + { + case Constants.DataTypes.Textbox: + return Attempt.Succeed(Constants.DataTypes.Guids.TextstringGuid); + case Constants.DataTypes.RichtextEditor: + return Attempt.Succeed(Constants.DataTypes.Guids.RichtextEditorGuid); + } + return Attempt.Fail(); + }); + var dataTypeService = new Mock(); dataTypeService.Setup(service => service.GetDataType(It.IsAny())) diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index a4c3078b8f..85dd303432 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; +using System.Reflection; +using System.Security.Cryptography; using System.Web.Http; using Moq; using Newtonsoft.Json; @@ -155,7 +157,7 @@ namespace Umbraco.Tests.Web.Controllers var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); - var obj = JsonConvert.DeserializeObject>(response.Item2); + var obj = JsonConvert.DeserializeObject>(response.Item2); Assert.AreEqual(0, obj.TotalItems); } @@ -190,9 +192,100 @@ namespace Umbraco.Tests.Web.Controllers var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); - var obj = JsonConvert.DeserializeObject>(response.Item2); + var obj = JsonConvert.DeserializeObject>(response.Item2); Assert.AreEqual(10, obj.TotalItems); Assert.AreEqual(10, obj.Items.Count()); } + + [Test] + public async System.Threading.Tasks.Task GetPagedUsers_Fips() + { + await RunFipsTest("GetPagedUsers", mock => + { + var users = MockedUser.CreateMulipleUsers(10); + long outVal = 10; + mock.Setup(service => service.GetAll( + It.IsAny(), It.IsAny(), out outVal, It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(() => users); + }, response => + { + var obj = JsonConvert.DeserializeObject>(response.Item2); + Assert.AreEqual(10, obj.TotalItems); + Assert.AreEqual(10, obj.Items.Count()); + }); + } + + [Test] + public async System.Threading.Tasks.Task GetById_Fips() + { + const int mockUserId = 1234; + var user = MockedUser.CreateUser(); + + await RunFipsTest("GetById", mock => + { + mock.Setup(service => service.GetUserById(1234)) + .Returns((int i) => i == mockUserId ? user : null); + }, response => + { + var obj = JsonConvert.DeserializeObject(response.Item2); + Assert.AreEqual(user.Username, obj.Username); + Assert.AreEqual(user.Email, obj.Email); + }, new { controller = "Users", action = "GetById" }, $"Users/GetById/{mockUserId}"); + } + + + private async System.Threading.Tasks.Task RunFipsTest(string action, Action> userServiceSetup, + Action> verification, + object routeDefaults = null, string url = null) + { + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) + { + //setup some mocks + var userServiceMock = Mock.Get(Current.Services.UserService); + userServiceSetup(userServiceMock); + + var usersController = new UsersController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + return usersController; + } + + // Testing what happens if the system were configured to only use FIPS-compliant algorithms + var typ = typeof(CryptoConfig); + var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic); + var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy"); + var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy"); + var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms; + + try + { + if (!originalFipsValue) + { + haveFld.SetValue(null, true); + isFld.SetValue(null, true); + } + + MockForGetPagedUsers(); + + var runner = new TestRunner(CtrlFactory); + var response = await runner.Execute("Users", action, HttpMethod.Get, routeDefaults: routeDefaults, url: url); + verification(response); + } + finally + { + if (!originalFipsValue) + { + haveFld.SetValue(null, false); + isFld.SetValue(null, false); + } + } + } } } diff --git a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs index a301dfbd04..1a4220c83b 100644 --- a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs @@ -4,6 +4,7 @@ using Umbraco.Web; namespace Umbraco.Tests.Web.Mvc { + [TestFixture] public class HtmlHelperExtensionMethodsTests { diff --git a/src/Umbraco.Tests/Web/Mvc/ValidateUmbracoFormRouteStringAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/ValidateUmbracoFormRouteStringAttributeTests.cs new file mode 100644 index 0000000000..d4c3b7c887 --- /dev/null +++ b/src/Umbraco.Tests/Web/Mvc/ValidateUmbracoFormRouteStringAttributeTests.cs @@ -0,0 +1,37 @@ +using NUnit.Framework; +using Umbraco.Web; +using Umbraco.Web.Mvc; + +namespace Umbraco.Tests.Web.Mvc +{ + [TestFixture] + public class ValidateUmbracoFormRouteStringAttributeTests + { + [Test] + public void Validate_Route_String() + { + var attribute = new ValidateUmbracoFormRouteStringAttribute(); + + Assert.Throws(() => attribute.ValidateRouteString(null, null, null, null)); + + const string ControllerName = "Test"; + const string ControllerAction = "Index"; + const string Area = "MyArea"; + var validUfprt = UrlHelperRenderExtensions.CreateEncryptedRouteString(ControllerName, ControllerAction, Area); + + var invalidUfprt = validUfprt + "z"; + Assert.Throws(() => attribute.ValidateRouteString(invalidUfprt, null, null, null)); + + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, "doesntMatch")); + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, null)); + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, ControllerName, "doesntMatch", Area)); + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, ControllerName, null, Area)); + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, "doesntMatch", ControllerAction, Area)); + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, null, ControllerAction, Area)); + + Assert.DoesNotThrow(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, Area)); + Assert.DoesNotThrow(() => attribute.ValidateRouteString(validUfprt, ControllerName.ToLowerInvariant(), ControllerAction.ToLowerInvariant(), Area.ToLowerInvariant())); + } + + } +} diff --git a/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs new file mode 100644 index 0000000000..a3505aeb0e --- /dev/null +++ b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Examine; +using Examine.LuceneEngine.Providers; +using Lucene.Net.Store; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Examine; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Tests.Web +{ + [TestFixture] + public class PublishedContentQueryTests + { + + private class TestIndex : LuceneIndex, IUmbracoIndex + { + private readonly string[] _fieldNames; + + public TestIndex(string name, Directory luceneDirectory, string[] fieldNames) + : base(name, luceneDirectory, null, null, null, null) + { + _fieldNames = fieldNames; + } + public bool EnableDefaultEventHandler => throw new NotImplementedException(); + public bool PublishedValuesOnly => throw new NotImplementedException(); + public IEnumerable GetFields() => _fieldNames; + } + + private TestIndex CreateTestIndex(Directory luceneDirectory, string[] fieldNames) + { + var indexer = new TestIndex("TestIndex", luceneDirectory, fieldNames); + + using (indexer.ProcessNonAsync()) + { + //populate with some test data + indexer.IndexItem(new ValueSet("1", "content", new Dictionary + { + [fieldNames[0]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "n" + })); + indexer.IndexItem(new ValueSet("2", "content", new Dictionary + { + [fieldNames[1]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "y" + })); + indexer.IndexItem(new ValueSet("3", "content", new Dictionary + { + [fieldNames[2]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "y" + })); + } + + return indexer; + } + + private PublishedContentQuery CreatePublishedContentQuery(IIndex indexer) + { + var examineManager = new Mock(); + IIndex outarg = indexer; + examineManager.Setup(x => x.TryGetIndex("TestIndex", out outarg)).Returns(true); + + var contentCache = new Mock(); + contentCache.Setup(x => x.GetById(It.IsAny())).Returns((int intId) => Mock.Of(x => x.Id == intId)); + var snapshot = Mock.Of(x => x.Content == contentCache.Object); + var variationContext = new VariationContext(); + var variationContextAccessor = Mock.Of(x => x.VariationContext == variationContext); + + return new PublishedContentQuery(snapshot, variationContextAccessor, examineManager.Object); + } + + [TestCase("fr-fr", ExpectedResult = "1, 3", TestName = "Search Culture: fr-fr. Must return both fr-fr and invariant results")] + [TestCase("en-us", ExpectedResult = "1, 2", TestName = "Search Culture: en-us. Must return both en-us and invariant results")] + [TestCase("*", ExpectedResult = "1, 2, 3", TestName = "Search Culture: *. Must return all cultures and all invariant results")] + [TestCase(null, ExpectedResult = "1", TestName = "Search Culture: null. Must return only invariant results")] + public string Search(string culture) + { + using (var luceneDir = new RandomIdRAMDirectory()) + { + var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" }; + using (var indexer = CreateTestIndex(luceneDir, fieldNames)) + { + var pcq = CreatePublishedContentQuery(indexer); + + var results = pcq.Search("Products", culture, "TestIndex"); + + var ids = results.Select(x => x.Content.Id).ToArray(); + + return string.Join(", ", ids); + } + } + } + } +} diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 82624eced5..3a5405548b 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -82,8 +82,8 @@ namespace Umbraco.Tests.Web //setup a mock url provider which we'll use for testing var testUrlProvider = new Mock(); testUrlProvider - .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlProviderMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url")); + .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url")); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index b23b5bd6b7..26d85f60cf 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -1,5 +1,8 @@ using System; using System.Text; +using Examine.LuceneEngine; +using Lucene.Net.Analysis; +using Lucene.Net.Analysis.Standard; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -13,18 +16,19 @@ using Umbraco.Web; namespace Umbraco.Tests.Web { + [TestFixture] public class UmbracoHelperTests - { + { [TearDown] public void TearDown() { Current.Reset(); } - - + + // ------- Int32 conversion tests [Test] public static void Converting_Boxed_34_To_An_Int_Returns_34() diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index c27a2c5f53..4dae0dac08 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -41,12 +41,12 @@ module.exports = { assets: "./src/assets/**" } }, - root: "../Umbraco.Web.UI/Umbraco/", + root: "../Umbraco.Web.UI/", targets: { - js: "js/", - lib: "lib/", - views: "views/", - css: "assets/css/", - assets: "assets/" + js: "Umbraco/js/", + lib: "Umbraco/lib/", + views: "Umbraco/views/", + css: "Umbraco/assets/css/", + assets: "Umbraco/assets/" } }; diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js index 976708e62f..829db2aec7 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js @@ -23,7 +23,7 @@ module.exports = function(files, out) { // sort files in stream by path or any custom sort comparator task = task.pipe(babel()) .pipe(sort()) - .pipe(embedTemplates({ basePath: "./src/" })) + .pipe(embedTemplates({ basePath: "./src/", minimize:{ loose: true } })) .pipe(concat(out)) .pipe(wrap('(function(){\n%= body %\n})();')) .pipe(gulp.dest(config.root + config.targets.js)); diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less index f17e8cadb4..2c01fb771c 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less @@ -28,6 +28,11 @@ a:focus { color: @linkColorHover; text-decoration: underline; } +a[ng-click], +a[data-ng-click], +a[x-ng-click] { + cursor: pointer; +} // Images diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index c1c85a9688..3e8f053bc6 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1551,7 +1551,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -1566,7 +1566,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -1740,7 +1740,7 @@ "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", "dev": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", @@ -1750,7 +1750,7 @@ "buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", "dev": true }, "buffer-crc32": { @@ -4061,7 +4061,7 @@ }, "engine.io-client": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", "dev": true, "requires": { @@ -4482,7 +4482,7 @@ "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -5244,7 +5244,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", "dev": true }, "fs-extra": { @@ -9335,7 +9335,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", "dev": true }, "lpad-align": { @@ -9369,7 +9369,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "requires": { "pify": "^3.0.0" @@ -9818,9 +9818,9 @@ "dev": true }, "nouislider": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-12.1.0.tgz", - "integrity": "sha512-SAOabF6hBm8201c6LDbkVOVhgwY49+/ms72ZLUF2qkN5RCf7FfUvEh/hGZ7XcwZHU+I/grlicPmcSk1/rrMnOw==" + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.0.1.tgz", + "integrity": "sha512-YNLKuABWYxmC5WXJ9TUj3N7+iyL/xT3+jm1mgOMXoqBhAL0Pj9BMgyKmLgwRnrxNN+C/fe7sFmpQDDPsxbMT2w==" }, "npm": { "version": "6.4.1", @@ -12735,7 +12735,7 @@ "dependencies": { "minimist": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, @@ -14775,7 +14775,7 @@ }, "socket.io-parser": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", "dev": true, "requires": { @@ -14920,7 +14920,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "optional": true, @@ -15328,7 +15328,7 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, "requires": { "escape-string-regexp": "^1.0.2" @@ -15488,7 +15488,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15731,7 +15731,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true }, "to-fast-properties": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index ef1a9cc28d..35d808056c 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -35,7 +35,7 @@ "lazyload-js": "1.0.0", "moment": "2.22.2", "ng-file-upload": "12.2.13", - "nouislider": "12.1.0", + "nouislider": "14.0.1", "npm": "^6.4.1", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js index 91eb077ba3..8434a96ba5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js @@ -22,13 +22,16 @@ vm.search = search; vm.clickItem = clickItem; vm.clearSearch = clearSearch; - vm.handleKeyUp = handleKeyUp; + vm.handleKeyDown = handleKeyDown; vm.closeSearch = closeSearch; vm.focusSearch = focusSearch; //we need to capture the focus before this element is initialized. vm.focusBeforeOpening = focusService.getLastKnownFocus(); + vm.activeResult = null; + vm.activeResultGroup = null; + function onInit() { vm.searchQuery = ""; vm.searchResults = []; @@ -72,14 +75,66 @@ * Handles all keyboard events * @param {object} event */ - function handleKeyUp(event) { - - event.stopPropagation(); - event.preventDefault(); + function handleKeyDown(event) { // esc if(event.keyCode === 27) { + event.stopPropagation(); + event.preventDefault(); + closeSearch(); + return; + } + + // up/down (navigate search results) + if (vm.hasResults && (event.keyCode === 38 || event.keyCode === 40)) { + event.stopPropagation(); + event.preventDefault(); + + var allGroups = _.values(vm.searchResults); + var down = event.keyCode === 40; + if (vm.activeResultGroup === null) { + // it's the first time navigating, pick the appropriate group and result + // - first group and first result when navigating down + // - last group and last result when navigating up + vm.activeResultGroup = down ? _.first(allGroups) : _.last(allGroups); + vm.activeResult = down ? _.first(vm.activeResultGroup.results) : _.last(vm.activeResultGroup.results); + } + else if (down) { + // handle navigation down through the groups and results + if (vm.activeResult === _.last(vm.activeResultGroup.results)) { + if (vm.activeResultGroup === _.last(allGroups)) { + vm.activeResultGroup = _.first(allGroups); + } + else { + vm.activeResultGroup = allGroups[allGroups.indexOf(vm.activeResultGroup) + 1]; + } + vm.activeResult = _.first(vm.activeResultGroup.results); + } + else { + vm.activeResult = vm.activeResultGroup.results[vm.activeResultGroup.results.indexOf(vm.activeResult) + 1]; + } + } + else { + // handle navigation up through the groups and results + if (vm.activeResult === _.first(vm.activeResultGroup.results)) { + if (vm.activeResultGroup === _.first(allGroups)) { + vm.activeResultGroup = _.last(allGroups); + } + else { + vm.activeResultGroup = allGroups[allGroups.indexOf(vm.activeResultGroup) - 1]; + } + vm.activeResult = _.last(vm.activeResultGroup.results); + } + else { + vm.activeResult = vm.activeResultGroup.results[vm.activeResultGroup.results.indexOf(vm.activeResult) - 1]; + } + } + + $timeout(function () { + var resultElementLink = angular.element(".umb-search-item[active-result='true'] .umb-search-result__link"); + resultElementLink[0].focus(); + }); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index 14c9878839..b8ee797c82 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -42,7 +42,7 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se function calculateWidth() { $timeout(function () { //total width minus room for avatar, search, and help icon - var windowWidth = $(window).width() - 200; + var windowWidth = $(window).width() - 150; var sectionsWidth = 0; scope.totalSections = scope.sections.length; scope.maxSections = maxSections; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js index 6127153a16..f026a05c45 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js @@ -95,7 +95,8 @@ Use this directive to render an umbraco button. The directive can be used to gen size: "@?", alias: "@?", addEllipsis: "@?", - showCaret: "@?" + showCaret: "@?", + autoFocus: "@?" } }); @@ -115,6 +116,7 @@ Use this directive to render an umbraco button. The directive can be used to gen vm.blockElement = false; vm.style = null; vm.innerState = "init"; + vm.generalActions = vm.labelKey === "general_actions"; vm.buttonLabel = vm.label; // is this a primary button style (i.e. anything but an 'info' button)? diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js index 9390d64cdb..0ea5006c4a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js @@ -66,7 +66,7 @@ (function () { 'use strict'; - function ToggleDirective(localizationService, eventsService) { + function ToggleDirective(localizationService, eventsService, $timeout) { function link(scope, el, attr, ctrl) { @@ -75,7 +75,11 @@ function onInit() { setLabelText(); - eventsService.emit("toggleValue", { value: scope.checked }); + // must wait until the current digest cycle is finished before we emit this event on init, + // otherwise other property editors might not yet be ready to receive the event + $timeout(function () { + eventsService.emit("toggleValue", { value: scope.checked }); + }, 100); } function setLabelText() { 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 f7704ab870..58a22ac74e 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 @@ -8,6 +8,7 @@ var evts = []; var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode; + var watchingCulture = false; //setup scope vars $scope.defaultButton = null; @@ -26,26 +27,58 @@ $scope.allowOpen = true; $scope.app = null; + //initializes any watches + function startWatches(content) { + + //watch for changes to isNew & the content.id, set the page.isNew accordingly and load the breadcrumb if we can + $scope.$watchGroup(['isNew', 'content.id'], function (newVal, oldVal) { + + var contentId = newVal[1]; + $scope.page.isNew = Object.toBoolean(newVal[0]); + + //We fetch all ancestors of the node to generate the footer breadcrumb navigation + if (!$scope.page.isNew && contentId && content.parentId && content.parentId !== -1) { + loadBreadcrumb(); + if (!watchingCulture) { + $scope.$watch('culture', + function (value, oldValue) { + if (value !== oldValue) { + loadBreadcrumb(); + } + }); + } + } + }); + + } + + //this initializes the editor with the data which will be called more than once if the data is re-loaded function init() { - + var content = $scope.content; - + + if (content.id && content.isChildOfListView && content.trashed === false) { + $scope.page.listViewPath = ($routeParams.page) ? + "/content/content/edit/" + content.parentId + "?page=" + $routeParams.page : + "/content/content/edit/" + content.parentId; + } + // we need to check wether an app is present in the current data, if not we will present the default app. var isAppPresent = false; - + // on first init, we dont have any apps. but if we are re-initializing, we do, but ... if ($scope.app) { - + // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.) - _.forEach(content.apps, function(app) { + _.forEach(content.apps, function (app) { if (app === $scope.app) { isAppPresent = true; } }); - + // if we did reload our DocType, but still have the same app we will try to find it by the alias. if (isAppPresent === false) { - _.forEach(content.apps, function(app) { + _.forEach(content.apps, function (app) { if (app.alias === $scope.app.alias) { isAppPresent = true; app.active = true; @@ -53,7 +86,7 @@ } }); } - + } // if we still dont have a app, lets show the first one: @@ -61,31 +94,24 @@ content.apps[0].active = true; $scope.appChanged(content.apps[0]); } - - editorState.set(content); - - //We fetch all ancestors of the node to generate the footer breadcrumb navigation - if (!$scope.page.isNew) { - if (content.parentId && content.parentId !== -1) { - entityResource.getAncestors(content.id, "document", $scope.culture) - .then(function (anc) { - $scope.ancestors = anc; - }); - $scope.$watch('culture', - function (value, oldValue) { - entityResource.getAncestors(content.id, "document", value) - .then(function (anc) { - $scope.ancestors = anc; - }); - }); - } + // otherwise make sure the save options are up to date with the current content state + else { + createButtons($scope.content); } + editorState.set(content); + bindEvents(); resetVariantFlags(); } + function loadBreadcrumb() { + entityResource.getAncestors($scope.content.id, "document", $scope.culture) + .then(function (anc) { + $scope.ancestors = anc; + }); + } /** * This will reset isDirty flags if save is true. @@ -116,10 +142,10 @@ function isContentCultureVariant() { return $scope.content.variants.length > 1; } - + function reload() { $scope.page.loading = true; - loadContent().then(function() { + loadContent().then(function () { $scope.page.loading = false; }); } @@ -132,7 +158,7 @@ evts.push(eventsService.on("editors.documentType.saved", function (name, args) { // if this content item uses the updated doc type we need to reload the content item - if(args && args.documentType && $scope.content.documentType.id === args.documentType.id) { + if (args && args.documentType && $scope.content.documentType.id === args.documentType.id) { reload(); } })); @@ -150,12 +176,6 @@ $scope.content = data; - if (data.isChildOfListView && data.trashed === false) { - $scope.page.listViewPath = ($routeParams.page) ? - "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page : - "/content/content/edit/" + data.parentId; - } - init(); syncTreeNode($scope.content, $scope.content.path, true); @@ -217,7 +237,7 @@ $scope.page.showPreviewButton = true; } - + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ function syncTreeNode(content, path, initialLoad) { @@ -226,9 +246,13 @@ } if (!$scope.content.isChildOfListView) { - navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - $scope.page.menu.currentNode = syncArgs.node; - }); + navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }) + .then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; + }, function () { + //handle the rejection + console.log("A problem occurred syncing the tree! A path is probably incorrect.") + }); } else if (initialLoad === true) { @@ -326,7 +350,7 @@ $scope.contentForm.$dirty = false; for (var i = 0; i < $scope.content.variants.length; i++) { - if($scope.content.variants[i].isDirty){ + if ($scope.content.variants[i].isDirty) { $scope.contentForm.$dirty = true; return; } @@ -335,7 +359,7 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid var fieldsToRollback = checkValidility(); @@ -346,7 +370,8 @@ scope: $scope, content: $scope.content, action: args.action, - showNotifications: args.showNotifications + showNotifications: args.showNotifications, + softRedirect: true }).then(function (data) { //success init(); @@ -362,11 +387,6 @@ function (err) { syncTreeNode($scope.content, $scope.content.path); - //error - if (err) { - editorState.set($scope.content); - } - resetNestedFieldValiation(fieldsToRollback); return $q.reject(err); @@ -419,9 +439,9 @@ //need to show a notification else it's not clear there was an error. localizationService.localizeMany([ - "speechBubbles_validationFailedHeader", - "speechBubbles_validationFailedMessage" - ] + "speechBubbles_validationFailedHeader", + "speechBubbles_validationFailedMessage" + ] ).then(function (data) { notificationsService.error(data[0], data[1]); }); @@ -438,6 +458,7 @@ $scope.content = data; init(); + startWatches($scope.content); resetLastListPageNumber($scope.content); @@ -452,13 +473,14 @@ $scope.page.loading = true; loadContent().then(function () { + startWatches($scope.content); $scope.page.loading = false; }); } $scope.unpublish = function () { clearNotifications($scope.content); - if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { + if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { var dialog = { parentScope: $scope, view: "views/content/overlays/unpublish.html", @@ -492,6 +514,7 @@ overlayService.close(); } }; + overlayService.open(dialog); } }; @@ -508,7 +531,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_saveToPublish", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -516,14 +539,14 @@ saveMethod: contentResource.sendToPublish, action: "sendToPublish", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -532,7 +555,7 @@ return $q.when(err); }); }, - close: function() { + close: function () { overlayService.close(); } }; @@ -561,14 +584,13 @@ if (isContentCultureVariant()) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { - var dialog = { parentScope: $scope, view: "views/content/overlays/publish.html", variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_saveAndPublish", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -576,14 +598,14 @@ saveMethod: contentResource.publish, action: "publish", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -592,11 +614,10 @@ return $q.when(err); }); }, - close: function() { + close: function () { overlayService.close(); } }; - overlayService.open(dialog); } else { @@ -615,7 +636,7 @@ $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; - });; + }); } }; @@ -632,7 +653,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_save", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -640,14 +661,14 @@ saveMethod: $scope.saveMethod(), action: "save", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -656,7 +677,7 @@ return $q.when(err); }); }, - close: function(oldModel) { + close: function (oldModel) { overlayService.close(); } }; @@ -683,7 +704,7 @@ }; - $scope.schedule = function() { + $scope.schedule = function () { clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "schedule" })) { @@ -753,7 +774,7 @@ } }; - $scope.publishDescendants = function() { + $scope.publishDescendants = function () { clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) { @@ -881,13 +902,13 @@ * @param {any} app */ $scope.appChanged = function (app) { - + $scope.app = app; - + $scope.$broadcast("editors.apps.appChanged", { app: app }); - + createButtons($scope.content); - + }; /** @@ -905,11 +926,11 @@ $scope.infiniteModel.close($scope.infiniteModel); } }; - + /** * Call back when user click the back-icon */ - $scope.onBack = function() { + $scope.onBack = function () { if ($scope.infiniteModel && $scope.infiniteModel.close) { $scope.infiniteModel.close($scope.infiniteModel); } else { @@ -925,9 +946,7 @@ } //since we are not notifying and clearing server validation messages when they are received due to how the variant //switching works, we need to ensure they are cleared when this editor is destroyed - if (!$scope.page.isNew) { - serverValidationManager.clear(); - } + serverValidationManager.clear(); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index e58ff14e21..e2f5f71781 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -328,6 +328,8 @@ isInfoTab = true; loadAuditTrail(); loadRedirectUrls(); + setNodePublishStatus(); + formatDatesToLocal(); } else { isInfoTab = false; } @@ -344,6 +346,7 @@ loadAuditTrail(true); loadRedirectUrls(); setNodePublishStatus(); + formatDatesToLocal(); } updateCurrentUrls(); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js index 4999f7007a..75fa0469bb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js @@ -1,10 +1,12 @@ (function () { 'use strict'; - function EditorContentHeader() { + function EditorContentHeader(serverValidationManager) { function link(scope, el, attr, ctrl) { - + + var unsubscribe = []; + if (!scope.serverValidationNameField) { scope.serverValidationNameField = "Name"; } @@ -15,9 +17,44 @@ scope.vm = {}; scope.vm.dropdownOpen = false; scope.vm.currentVariant = ""; - + scope.vm.variantsWithError = []; + scope.vm.defaultVariant = null; + + scope.vm.errorsOnOtherVariants = false;// indicating wether to show that other variants, than the current, have errors. + + function checkErrorsOnOtherVariants() { + var check = false; + angular.forEach(scope.content.variants, function (variant) { + if (scope.openVariants.indexOf(variant.language.culture) === -1 && scope.variantHasError(variant.language.culture)) { + check = true; + } + }); + scope.vm.errorsOnOtherVariants = check; + } + + function onCultureValidation(valid, errors, allErrors, culture) { + var index = scope.vm.variantsWithError.indexOf(culture); + if(valid === true) { + if (index !== -1) { + scope.vm.variantsWithError.splice(index, 1); + } + } else { + if (index === -1) { + scope.vm.variantsWithError.push(culture); + } + } + checkErrorsOnOtherVariants(); + } + function onInit() { + // find default. + angular.forEach(scope.content.variants, function (variant) { + if (variant.language.isDefault) { + scope.vm.defaultVariant = variant; + } + }); + setCurrentVariant(); angular.forEach(scope.content.apps, (app) => { @@ -26,12 +63,22 @@ } }); + + angular.forEach(scope.content.variants, function (variant) { + unsubscribe.push(serverValidationManager.subscribe(null, variant.language.culture, null, onCultureValidation)); + }); + + unsubscribe.push(serverValidationManager.subscribe(null, null, null, onCultureValidation)); + + + } function setCurrentVariant() { angular.forEach(scope.content.variants, function (variant) { if (variant.active) { scope.vm.currentVariant = variant; + checkErrorsOnOtherVariants(); } }); } @@ -80,9 +127,24 @@ * @param {any} culture */ scope.variantIsOpen = function(culture) { - if(scope.openVariants.indexOf(culture) !== -1) { + return (scope.openVariants.indexOf(culture) !== -1); + } + + /** + * Check whether a variant has a error, used to display errors in variant switcher. + * @param {any} culture + */ + scope.variantHasError = function(culture) { + // if we are looking for the default language we also want to check for invariant. + if (culture === scope.vm.defaultVariant.language.culture) { + if(scope.vm.variantsWithError.indexOf("invariant") !== -1) { + return true; + } + } + if(scope.vm.variantsWithError.indexOf(culture) !== -1) { return true; } + return false; } onInit(); @@ -103,6 +165,12 @@ } }); } + + scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 77f2ffb54a..15e74bbd90 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -3,113 +3,113 @@ **/ angular.module('umbraco.directives') -.directive('onDragEnter', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragEnter); - }; - elm.on("dragenter", f); - scope.$on("$destroy", function(){ elm.off("dragenter", f);} ); - } - }; -}) - -.directive('onDragLeave', function () { - return function (scope, elm, attrs) { - var f = function (event) { - var rect = this.getBoundingClientRect(); - var getXY = function getCursorPosition(event) { - var x, y; - - if (typeof event.clientX === 'undefined') { - // try touch screen - x = event.pageX + document.documentElement.scrollLeft; - y = event.pageY + document.documentElement.scrollTop; - } else { - x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; - y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; - } - - return { x: x, y : y }; - }; - - var e = getXY(event.originalEvent); - - // Check the mouseEvent coordinates are outside of the rectangle - if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { - scope.$apply(attrs.onDragLeave); + .directive('onDragEnter', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnter); + }; + elm.on("dragenter", f); + scope.$on("$destroy", function () { elm.off("dragenter", f); }); } }; + }) - elm.on("dragleave", f); - scope.$on("$destroy", function(){ elm.off("dragleave", f);} ); - }; -}) + .directive('onDragLeave', function () { + return function (scope, elm, attrs) { + var f = function (event) { + var rect = this.getBoundingClientRect(); + var getXY = function getCursorPosition(event) { + var x, y; -.directive('onDragOver', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragOver); + if (typeof event.clientX === 'undefined') { + // try touch screen + x = event.pageX + document.documentElement.scrollLeft; + y = event.pageY + document.documentElement.scrollTop; + } else { + x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + + return { x: x, y: y }; + }; + + var e = getXY(event.originalEvent); + + // Check the mouseEvent coordinates are outside of the rectangle + if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { + scope.$apply(attrs.onDragLeave); + } }; - elm.on("dragover", f); - scope.$on("$destroy", function(){ elm.off("dragover", f);} ); - } - }; -}) -.directive('onDragStart', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragStart); - }; - elm.on("dragstart", f); - scope.$on("$destroy", function(){ elm.off("dragstart", f);} ); - } - }; -}) + elm.on("dragleave", f); + scope.$on("$destroy", function () { elm.off("dragleave", f); }); + }; + }) -.directive('onDragEnd', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragEnd); - }; - elm.on("dragend", f); - scope.$on("$destroy", function(){ elm.off("dragend", f);} ); - } - }; -}) + .directive('onDragOver', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragOver); + }; + elm.on("dragover", f); + scope.$on("$destroy", function () { elm.off("dragover", f); }); + } + }; + }) -.directive('onDrop', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDrop); - }; - elm.on("drop", f); - scope.$on("$destroy", function(){ elm.off("drop", f);} ); - } - }; -}) + .directive('onDragStart', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragStart); + }; + elm.on("dragstart", f); + scope.$on("$destroy", function () { elm.off("dragstart", f); }); + } + }; + }) -.directive('onOutsideClick', function ($timeout, angularHelper) { - return function (scope, element, attrs) { + .directive('onDragEnd', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnd); + }; + elm.on("dragend", f); + scope.$on("$destroy", function () { elm.off("dragend", f); }); + } + }; + }) - var eventBindings = []; + .directive('onDrop', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDrop); + }; + elm.on("drop", f); + scope.$on("$destroy", function () { elm.off("drop", f); }); + } + }; + }) - function oneTimeClick(event) { + .directive('onOutsideClick', function ($timeout, angularHelper) { + return function (scope, element, attrs) { + + var eventBindings = []; + + function oneTimeClick(event) { var el = event.target.nodeName; //ignore link and button clicks - var els = ["INPUT","A","BUTTON"]; - if(els.indexOf(el) >= 0){return;} + var els = ["INPUT", "A", "BUTTON"]; + if (els.indexOf(el) >= 0) { return; } // ignore clicks on new overlay var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour"); - if(parents.length > 0){ + if (parents.length > 0) { return; } @@ -132,70 +132,71 @@ angular.module('umbraco.directives') } //ignore clicks inside this element - if( $(element).has( $(event.target) ).length > 0 ){ + if ($(element).has($(event.target)).length > 0) { return; } + // please to not use angularHelper.safeApply here, it won't work scope.$apply(attrs.onOutsideClick); - } - - - $timeout(function(){ - - if ("bindClickOn" in attrs) { - - eventBindings.push(scope.$watch(function() { - return attrs.bindClickOn; - }, function(newValue) { - if (newValue === "true") { - $(document).on("click", oneTimeClick); - } else { - $(document).off("click", oneTimeClick); - } - })); - - } else { - $(document).on("click", oneTimeClick); } - scope.$on("$destroy", function() { - $(document).off("click", oneTimeClick); - // unbind watchers - for (var e in eventBindings) { - eventBindings[e](); + $timeout(function () { + + if ("bindClickOn" in attrs) { + + eventBindings.push(scope.$watch(function () { + return attrs.bindClickOn; + }, function (newValue) { + if (newValue === "true") { + $(document).on("click", oneTimeClick); + } else { + $(document).off("click", oneTimeClick); + } + })); + + } else { + $(document).on("click", oneTimeClick); } + scope.$on("$destroy", function () { + $(document).off("click", oneTimeClick); + + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + + }); + }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. + + }; + }) + + .directive('onRightClick', function ($parse) { + + document.oncontextmenu = function (e) { + if (e.target.hasAttribute('on-right-click')) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }; + + return function (scope, el, attrs) { + el.on('contextmenu', function (e) { + e.preventDefault(); + e.stopPropagation(); + var fn = $parse(attrs.onRightClick); + scope.$apply(function () { + fn(scope, { $event: e }); + }); + return false; }); - }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. + }; + }) - }; -}) - -.directive('onRightClick',function($parse){ - - document.oncontextmenu = function (e) { - if(e.target.hasAttribute('on-right-click')) { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - return function(scope,el,attrs){ - el.on('contextmenu',function(e){ - e.preventDefault(); - e.stopPropagation(); - var fn = $parse(attrs.onRightClick); - scope.$apply(function () { - fn(scope, { $event: e }); - }); - return false; - }); - }; -}) - -.directive('onDelayedMouseleave', function ($timeout, $parse) { + .directive('onDelayedMouseleave', function ($timeout, $parse) { return { restrict: 'A', @@ -204,20 +205,20 @@ angular.module('umbraco.directives') var active = false; var fn = $parse(attrs.onDelayedMouseleave); - var leave_f = function(event) { - var callback = function() { - fn(scope, {$event:event}); + var leave_f = function (event) { + var callback = function () { + fn(scope, { $event: event }); }; active = false; - $timeout(function(){ - if(active === false){ + $timeout(function () { + if (active === false) { scope.$apply(callback); } }, 650); }; - var enter_f = function(event, args){ + var enter_f = function (event, args) { active = true; }; @@ -226,7 +227,7 @@ angular.module('umbraco.directives') element.on("mouseenter", enter_f); //unsub events - scope.$on("$destroy", function(){ + scope.$on("$destroy", function () { element.off("mouseleave", leave_f); element.off("mouseenter", enter_f); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js index 6fcdd99001..eb3503f799 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js @@ -9,8 +9,10 @@ angular.module("umbraco.directives") } }; - $timeout(function() { - update(); - }); + if (attr.umbAutoFocus !== "false") { + $timeout(function() { + update(); + }); + } }; }); 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 dee3cfdab7..cd1f011018 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 @@ -4,7 +4,9 @@ angular.module("umbraco.directives") scope: { uniqueId: '=', value: '=', - configuration: "=" + configuration: "=", //this is the RTE configuration + datatypeKey: '@', + ignoreUserStartNodes: '@' }, templateUrl: 'views/components/grid/grid-rte.html', replace: true, @@ -35,6 +37,14 @@ angular.module("umbraco.directives") editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; } + //ensure the grid's global config is being passed up to the RTE, these 2 properties need to be in this format + //since below we are just passing up `scope` as the actual model and for 2 way binding to work with `value` that + //is the way it needs to be unless we start adding watchers. We'll just go with this for now but it's super ugly. + scope.config = { + ignoreUserStartNodes: scope.ignoreUserStartNodes === "true" + } + scope.dataTypeKey = scope.datatypeKey; //Yes - this casing is rediculous, but it's because the var starts with `data` so it can't be `data-type-id` :/ + //stores a reference to the editor var tinyMceEditor = null; 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 e65a3d238c..0135abd97c 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 @@ -286,11 +286,11 @@ Opens an overlay to show a custom YSOD.
$(document).on("keydown.overlay-" + overlayNumber, function(event) { - if (event.which === 27) { + if (event.which === 27) { numberOfOverlays = overlayHelper.getNumberOfOverlays(); - if (numberOfOverlays === overlayNumber) { + if (numberOfOverlays === overlayNumber && !scope.model.disableEscKey) { scope.$apply(function () { scope.closeOverLay(); }); @@ -311,9 +311,8 @@ Opens an overlay to show a custom YSOD.
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.trigger("click"); - event.preventDefault(); + if(clickableElements.indexOf(activeElementType) >= 0) { + // don't do anything, let the browser Enter key handle this } else if(activeElementType === "TEXTAREA" && !submitOnEnter) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbcontextdialog/umbcontextdialog.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbcontextdialog/umbcontextdialog.directive.js index 99a5dad58c..904a2ce8ca 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbcontextdialog/umbcontextdialog.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbcontextdialog/umbcontextdialog.directive.js @@ -1,16 +1,20 @@ (function() { 'use strict'; - function UmbContextDialog(navigationService, keyboardService) { + function UmbContextDialog(navigationService, keyboardService, localizationService, overlayService) { function link($scope) { - - $scope.outSideClick = function() { - navigationService.hideDialog(); - } - keyboardService.bind("esc", function() { - navigationService.hideDialog(); + $scope.dialog = { + confirmDiscardChanges: false + }; + + $scope.outSideClick = function() { + hide(); + }; + + keyboardService.bind("esc", function () { + hide(); }); //ensure to unregister from all events! @@ -18,6 +22,35 @@ keyboardService.unbind("esc"); }); + function hide() { + if ($scope.dialog.confirmDiscardChanges) { + localizationService.localizeMany(["prompt_unsavedChanges", "prompt_unsavedChangesWarning", "prompt_discardChanges", "prompt_stay"]).then( + function (values) { + var overlay = { + "view": "default", + "title": values[0], + "content": values[1], + "disableBackdropClick": true, + "disableEscKey": true, + "submitButtonLabel": values[2], + "closeButtonLabel": values[3], + submit: function () { + overlayService.close(); + navigationService.hideDialog(); + }, + close: function () { + overlayService.close(); + } + }; + + overlayService.open(overlay); + } + ); + } + else { + navigationService.hideDialog(); + } + } } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 7055d1a746..2bd93a4b27 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -38,7 +38,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use treeOptionsClick: [], treeNodeAltSelect: [] }; - + //this is the API exposed by this directive, for either hosting controllers or for other directives vm.callbacks = { treeNodeExpanded: function (f) { @@ -82,7 +82,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use // since it saves on data retreival and DOM processing. // TODO: This isn't used!? var lastSection = ""; - + /** Helper function to emit tree events */ function emitEvent(eventName, args) { if (registeredCallbacks[eventName] && angular.isArray(registeredCallbacks[eventName])) { @@ -92,10 +92,6 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } } - // TODO: This isn't used!? - function clearCache(section) { - treeService.clearCache({ section: section }); - } /** * Re-loads the tree with the updated parameters @@ -119,7 +115,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use $scope.cachekey = args.cacheKey; } } - + return loadTree(); } @@ -148,7 +144,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use if (!args.path) { throw "args.path cannot be null"; } - + if (angular.isString(args.path)) { args.path = args.path.replace('"', '').split(','); } @@ -172,8 +168,16 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use emitEvent("treeSynced", { node: data, activate: args.activate }); return $q.when({ node: data, activate: args.activate }); + }, function (data) { + return $q.reject(data); + }, function (data) { + //on notification + if (data.type === "treeNodeExpanded") { + //raise the event + emitEvent("treeNodeExpanded", { tree: $scope.tree, node: data.node, children: data.children }); + } }); - + } /** This will check the section tree loaded and return all actual root nodes based on a tree type (non group nodes, non section groups) */ @@ -201,7 +205,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node function loadActiveTree(treeAlias) { - + if (!$scope.tree) { throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null"; } @@ -229,11 +233,9 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } /** Method to load in the tree data */ - function loadTree() { - if (!$scope.loading && $scope.section) { - $scope.loading = true; - + if ($scope.section) { + //default args var args = { section: $scope.section, tree: $scope.treealias, cacheKey: $scope.cachekey, isDialog: $scope.isdialog ? $scope.isdialog : false }; @@ -244,20 +246,22 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use return treeService.getTree(args) .then(function (data) { - + //Only use the tree data, if we are still on the correct section + if(data.alias !== $scope.section){ + return $q.reject(); + } + //set the data once we have it $scope.tree = data; - $scope.loading = false; - //set the root as the current active tree $scope.activeTree = $scope.tree.root; emitEvent("treeLoaded", { tree: $scope.tree }); emitEvent("treeNodeExpanded", { tree: $scope.tree, node: $scope.tree.root, children: $scope.tree.root.children }); + return $q.when(data); }, function (reason) { - $scope.loading = false; notificationsService.error("Tree Error", reason); return $q.reject(reason); }); @@ -279,7 +283,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use if (forceReload || (node.hasChildren && node.children.length === 0)) { //get the children from the tree service return treeService.loadNodeChildren({ node: node, section: $scope.section, isDialog: $scope.isdialog }) - .then(function(data) { + .then(function (data) { //emit expanded event emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: data }); @@ -302,7 +306,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use // TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time // it would be better if we could cache the processing. The problem is that some of these things are dynamic. - + var css = []; if (node.cssClasses) { _.each(node.cssClasses, function (c) { @@ -322,7 +326,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use }; /* helper to force reloading children of a tree node */ - $scope.loadChildren = function(node, forceReload) { + $scope.loadChildren = function (node, forceReload) { return loadChildren(node, forceReload); }; @@ -359,7 +363,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use $scope.altSelect = function (n, ev) { emitEvent("treeNodeAltSelect", { element: $element, tree: $scope.tree, node: n, event: ev }); }; - + //call the onInit method, if the result is a promise then load the tree after that resolves (if it's not a promise this will just resolve automatically). //NOTE: The promise cannot be rejected, else the tree won't be loaded and we'll get exceptions if some API calls syncTree or similar. $q.when($scope.onInit(), function (args) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index b81e62a66b..9c28cbe367 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -12,7 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) { searchFromName: "@", showSearch: "@", section: "@", - ignoreUserStartNodes: "@", + datatypeKey: "@", hideSearchCallback: "=", searchCallback: "=" }, @@ -62,10 +62,10 @@ function treeSearchBox(localizationService, searchService, $q) { searchArgs["searchFrom"] = scope.searchFromId; } - //append ignoreUserStartNodes value if there is one - if (scope.ignoreUserStartNodes) { - searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes; - } + //append dataTypeId value if there is one + if (scope.datatypeKey) { + searchArgs["dataTypeKey"] = scope.datatypeKey; + } searcher(searchArgs).then(function (data) { scope.searchCallback(data); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js index a215bca645..cd1b1d8181 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js @@ -281,6 +281,10 @@ opts.callbacks.unshift(options.onLoad); } + if (opts.autoFocus === true) { + acee.focus(); + } + // EVENTS // unbind old change listener diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index da51528bd2..a51afd200c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -3,7 +3,7 @@ function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, dataTypeHelper, dataTypeResource, $filter, iconHelper, $q, $timeout, notificationsService, - localizationService, editorService, eventsService) { + localizationService, editorService, eventsService, overlayService) { function link(scope, el, attr, ctrl) { @@ -474,10 +474,16 @@ if (!property.inherited) { var oldPropertyModel = angular.copy(property); + if (oldPropertyModel.allowCultureVariant === undefined) { + // this is necessary for comparison when detecting changes to the property + oldPropertyModel.allowCultureVariant = scope.model.allowCultureVariant; + oldPropertyModel.alias = ""; + } + var propertyModel = angular.copy(property); var propertySettings = { title: "Property settings", - property: property, + property: propertyModel, contentType: scope.contentType, contentTypeName: scope.model.name, contentTypeAllowCultureVariant: scope.model.allowCultureVariant, @@ -487,7 +493,25 @@ property.inherited = false; property.dialogIsOpen = false; - + property.propertyState = "active"; + + // apply all property changes + property.label = propertyModel.label; + property.alias = propertyModel.alias; + property.description = propertyModel.description; + property.config = propertyModel.config; + property.editor = propertyModel.editor; + property.view = propertyModel.view; + property.dataTypeId = propertyModel.dataTypeId; + property.dataTypeIcon = propertyModel.dataTypeIcon; + property.dataTypeName = propertyModel.dataTypeName; + property.validation.mandatory = propertyModel.validation.mandatory; + property.validation.pattern = propertyModel.validation.pattern; + property.showOnMemberProfile = propertyModel.showOnMemberProfile; + property.memberCanEdit = propertyModel.memberCanEdit; + property.isSensitiveValue = propertyModel.isSensitiveValue; + property.allowCultureVariant = propertyModel.allowCultureVariant; + // update existing data types if(model.updateSameDataTypes) { updateSameDataTypes(property); @@ -508,43 +532,38 @@ }, close: function() { + if(_.isEqual(oldPropertyModel, propertyModel) === false) { + localizationService.localizeMany(["general_confirm", "contentTypeEditor_propertyHasChanges", "general_cancel", "general_ok"]).then(function (data) { + const overlay = { + title: data[0], + content: data[1], + closeButtonLabel: data[2], + submitButtonLabel: data[3], + submitButtonStyle: "danger", + close: function () { + overlayService.close(); + }, + submit: function () { + // close the confirmation + overlayService.close(); + // close the editor + editorService.close(); + } + }; - // reset all property changes - property.label = oldPropertyModel.label; - property.alias = oldPropertyModel.alias; - property.description = oldPropertyModel.description; - property.config = oldPropertyModel.config; - property.editor = oldPropertyModel.editor; - property.view = oldPropertyModel.view; - property.dataTypeId = oldPropertyModel.dataTypeId; - property.dataTypeIcon = oldPropertyModel.dataTypeIcon; - property.dataTypeName = oldPropertyModel.dataTypeName; - property.validation.mandatory = oldPropertyModel.validation.mandatory; - property.validation.pattern = oldPropertyModel.validation.pattern; - property.showOnMemberProfile = oldPropertyModel.showOnMemberProfile; - property.memberCanEdit = oldPropertyModel.memberCanEdit; - property.isSensitiveValue = oldPropertyModel.isSensitiveValue; - - // because we set state to active, to show a preview, we have to check if has been filled out - // label is required so if it is not filled we know it is a placeholder - if(oldPropertyModel.editor === undefined || oldPropertyModel.editor === null || oldPropertyModel.editor === "") { - property.propertyState = "init"; - } else { - property.propertyState = oldPropertyModel.propertyState; + overlayService.open(overlay); + }); + } + else { + // remove the editor + editorService.close(); } - - // remove the editor - editorService.close(); - } }; // open property settings editor editorService.open(propertySettings); - // set state to active to access the preview - property.propertyState = "active"; - // set property states property.dialogIsOpen = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 196a28c753..9c140d572e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -71,7 +71,7 @@ } // set published state for content if (c.metaData) { - c.hasChildren = c.metaData.HasChildren; + c.hasChildren = c.metaData.hasChildren; if(scope.entityType === "Document") { c.published = c.metaData.IsPublished; } @@ -79,7 +79,7 @@ // filter items if there is a filter and it's not advanced // ** ignores advanced filter at the moment - if (scope.entityTypeFilter && !scope.entityTypeFilter.filterAdvanced) { + if (scope.entityTypeFilter && scope.entityTypeFilter.filter && !scope.entityTypeFilter.filterAdvanced) { var a = scope.entityTypeFilter.filter.toLowerCase().replace(/\s/g, '').split(','); var found = a.indexOf(c.metaData.ContentTypeAlias.toLowerCase()) >= 0; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js index 4c1a8747d1..b49d47b979 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js @@ -96,13 +96,13 @@ Use this directive to generate a pagination. scope.pageNumber = parseInt(scope.pageNumber); } - scope.pagination = []; + let tempPagination = []; var i = 0; if (scope.totalPages <= 10) { for (i = 0; i < scope.totalPages; i++) { - scope.pagination.push({ + tempPagination.push({ val: (i + 1), isActive: scope.pageNumber === (i + 1) }); @@ -119,7 +119,7 @@ Use this directive to generate a pagination. start = Math.min(maxIndex, start); for (i = start; i < (10 + start) ; i++) { - scope.pagination.push({ + tempPagination.push({ val: (i + 1), isActive: scope.pageNumber === (i + 1) }); @@ -129,7 +129,7 @@ Use this directive to generate a pagination. if (start > 0) { localizationService.localize("general_first").then(function(value){ var firstLabel = value; - scope.pagination.unshift({ name: firstLabel, val: 1, isActive: false }, {val: "...",isActive: false}); + tempPagination.unshift({ name: firstLabel, val: 1, isActive: false }, {val: "...",isActive: false}); }); } @@ -137,11 +137,12 @@ Use this directive to generate a pagination. if (start < maxIndex) { localizationService.localize("general_last").then(function(value){ var lastLabel = value; - scope.pagination.push({ val: "...", isActive: false }, { name: lastLabel, val: scope.totalPages, isActive: false }); + tempPagination.push({ val: "...", isActive: false }, { name: lastLabel, val: scope.totalPages, isActive: false }); }); } } + scope.pagination = tempPagination; } scope.next = function () { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js index 2f2df7c12b..07e45ff0f7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js @@ -4,7 +4,7 @@ @restrict A @description -Use this directive make an element sticky and follow the page when scrolling. +Use this directive make an element sticky and follow the page when scrolling. `umb-sticky-bar--active` class is applied when the element is stuck

Markup example

@@ -12,140 +12,102 @@ Use this directive make an element sticky and follow the page when scrolling.
 
         
+ umb-sticky-bar>
-

CSS example

-
-    .my-sticky-bar {
-        padding: 15px 0;
-        background: #000000;
-        position: relative;
-        top: 0;
-    }
-
-    .my-sticky-bar.-umb-sticky-bar {
-        top: 100px;
-    }
-
- -@param {string} scrollableContainer Set the class (".element") or the id ("#element") of the scrollable container element. **/ (function () { 'use strict'; - function StickyBarDirective($rootScope) { + function StickyBarDirective() { + + /** + On initial load, the intersector fires if the grid editor is in the viewport + This flag is used to suppress the setClass behaviour on the initial load + **/ + var initial = true; + + /** + Toggle `umb-sticky-bar--active` class on the sticky-bar element + **/ + function setClass(addClass, current) { + if (!initial) { + current.classList.toggle('umb-sticky-bar--active', addClass); + } else { + initial = false; + } + } + + /** + Inserts two elements in the umbStickyBar parent element + These are used by the IntersectionObserve to calculate scroll position + **/ + function addSentinels(current) { + ['-top', '-bottom'].forEach(s => { + const sentinel = document.createElement('div'); + sentinel.classList.add('umb-sticky-sentinel', s); + current.parentElement.appendChild(sentinel); + }); + } + + /** + Calls into setClass when the footer sentinel enters/exits the bottom of the container + Container is the parent element of the umbStickyBar element + **/ + function observeFooter(current, container) { + const observer = new IntersectionObserver((records, observer) => { + let [target, rootBounds, intersected] = [records[0].boundingClientRect, records[0].rootBounds, records[0].intersectionRatio === 1]; + + if (target.bottom > rootBounds.top && intersected) { + setClass(true, current); + } + if (target.top < rootBounds.top && target.bottom < rootBounds.bottom) { + setClass(false, current); + } + }, { + threshold: [1], + root: container + }); + + observer.observe(current.parentElement.querySelector('.umb-sticky-sentinel.-bottom')); + } + + /** + Calls into setClass when the header sentinel enters/exits the top of the container + Container is the parent element of the umbStickyBar element + **/ + function observeHeader(current, container) { + const observer = new IntersectionObserver((records, observer) => { + let [target, rootBounds] = [records[0].boundingClientRect, records[0].rootBounds]; + + if (target.bottom < rootBounds.top) { + setClass(true, current); + } + + if (target.bottom >= rootBounds.top && target.bottom < rootBounds.bottom) { + setClass(false, current); + } + }, { + threshold: [0], + root: container + }); + + observer.observe(current.parentElement.querySelector('.umb-sticky-sentinel.-top')); + } function link(scope, el, attr, ctrl) { - var bar = $(el); - var scrollableContainer = null; - var clonedBar = null; - var cloneIsMade = false; + let current = el[0]; + let container = current.closest('[data-element="editor-container"]'); - function activate() { - - if (bar.parents(".umb-property").length > 1) { - bar.addClass("nested"); - return; - } - - if (attr.scrollableContainer) { - scrollableContainer = bar.closest(attr.scrollableContainer); - } else { - scrollableContainer = $(window); - } - - scrollableContainer.on('scroll.umbStickyBar', determineVisibility).trigger("scroll"); - $(window).on('resize.umbStickyBar', determineVisibility); - - scope.$on('$destroy', function () { - scrollableContainer.off('.umbStickyBar'); - $(window).off('.umbStickyBar'); - }); - - } - - function determineVisibility() { - - var barTop = bar[0].offsetTop; - var scrollTop = scrollableContainer.scrollTop(); - - if (scrollTop > barTop) { - - if (!cloneIsMade) { - - createClone(); - - clonedBar.css({ - 'visibility': 'visible' - }); - - } else { - - calculateSize(); - - } - - } else { - - if (cloneIsMade) { - - //remove cloned element (switched places with original on creation) - bar.remove(); - bar = clonedBar; - clonedBar = null; - - bar.removeClass('-umb-sticky-bar'); - bar.css({ - position: 'relative', - 'width': 'auto', - 'height': 'auto', - 'z-index': 'auto', - 'visibility': 'visible' - }); - - cloneIsMade = false; - - } - - } - - } - - function calculateSize() { - var width = bar.innerWidth(); - clonedBar.css({ - width: width + 10 // + 10 (5*2) because we need to add border to avoid seeing the shadow beneath. Look at the CSS. - }); - } - - function createClone() { - //switch place with cloned element, to keep binding intact - clonedBar = bar; - bar = clonedBar.clone(); - clonedBar.after(bar); - clonedBar.addClass('-umb-sticky-bar'); - clonedBar.css({ - 'position': 'fixed', - // if you change this z-index value, make sure the sticky editor sub headers do not - // clash with umb-dropdown (e.g. the content actions dropdown in content list view) - 'z-index': 99, - 'visibility': 'hidden' - }); - - cloneIsMade = true; - calculateSize(); - - } - - activate(); + addSentinels(current); + observeHeader(current, container); + observeFooter(current, container); } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/noPasswordManager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/noPasswordManager.directive.js index 190c504aa6..2c52506f42 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/noPasswordManager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/noPasswordManager.directive.js @@ -1,6 +1,6 @@ /** * @ngdoc directive -* @name umbraco.directives.directive:no-password-manager +* @name umbraco.directives.directive:noPasswordManager * @attribte * @function * @description diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js index 29920ebf00..a53fb75d93 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js @@ -165,6 +165,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location "title": labels.unsavedChangesTitle, "content": labels.unsavedChangesContent, "disableBackdropClick": true, + "disableEscKey": true, "submitButtonLabel": labels.stayButton, "closeButtonLabel": labels.discardChangesButton, submit: function() { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index 61f7f039d9..39d903d85e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -12,27 +12,50 @@ function valPropertyMsg(serverValidationManager) { return { - require: ['^^form', '^^valFormManager', '^^umbProperty'], + require: ['^^form', '^^valFormManager', '^^umbProperty', '?^^umbVariantContent'], replace: true, restrict: "E", template: "
{{errorMsg}}
", scope: {}, link: function (scope, element, attrs, ctrl) { + + var unsubscribe = []; + var watcher = null; + var hasError = false; + //create properties on our custom scope so we can use it in our template + scope.errorMsg = ""; + + //the property form controller api var formCtrl = ctrl[0]; //the valFormManager controller api var valFormManager = ctrl[1]; //the property controller api var umbPropCtrl = ctrl[2]; + //the variants controller api + var umbVariantCtrl = ctrl[3]; - scope.currentProperty = umbPropCtrl.property; + var currentProperty = umbPropCtrl.property; + scope.currentProperty = currentProperty; + var currentCulture = currentProperty.culture; - //if the property is invariant (no culture), then we will explicitly set it to the string 'invariant' - //since this matches the value that the server will return for an invariant property. - var currentCulture = scope.currentProperty.culture || "invariant"; + if (umbVariantCtrl) { + //if we are inside of an umbVariantContent directive - var watcher = null; + var currentVariant = umbVariantCtrl.editor.content; + + // Lets check if we have variants and we are on the default language then ... + if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) { + //This property is locked cause its a invariant property shown on a non-default language. + //Therefor do not validate this field. + return; + } + } + + // if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language. + currentCulture = currentCulture || "invariant"; + // Gets the error message to display function getErrorMsg() { @@ -138,13 +161,6 @@ function valPropertyMsg(serverValidationManager) { } - var hasError = false; - - //create properties on our custom scope so we can use it in our template - scope.errorMsg = ""; - - var unsubscribe = []; - //listen for form validation changes. //The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then // subscribing to this single watch. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js index eb522fe783..a0cc7e3033 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js @@ -7,7 +7,7 @@ **/ function valServer(serverValidationManager) { return { - require: ['ngModel', '?^^umbProperty'], + require: ['ngModel', '?^^umbProperty', '?^^umbVariantContent'], restrict: "A", scope: {}, link: function (scope, element, attr, ctrls) { @@ -18,9 +18,29 @@ function valServer(serverValidationManager) { //we cannot proceed, this validator will be disabled return; } + + // optional reference to the varaint-content-controller, needed to avoid validation when the field is invariant on non-default languages. + var umbVariantCtrl = ctrls.length > 2 ? ctrls[2] : null; var currentProperty = umbPropCtrl.property; var currentCulture = currentProperty.culture; + + if (umbVariantCtrl) { + //if we are inside of an umbVariantContent directive + + var currentVariant = umbVariantCtrl.editor.content; + + // Lets check if we have variants and we are on the default language then ... + if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) { + //This property is locked cause its a invariant property shown on a non-default language. + //Therefor do not validate this field. + return; + } + } + + // if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language. + currentCulture = currentCulture || "invariant"; + var watcher = null; var unsubscribe = []; diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/auth.resource.js new file mode 100644 index 0000000000..2953347f55 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/auth.resource.js @@ -0,0 +1,59 @@ +/** +* @ngdoc service +* @name umbraco.mocks.authMocks +* @description +* Mocks data retrival for the auth service +**/ +function authMocks($httpBackend, mocksUtils) { + + /** internal method to mock the current user to be returned */ + function getCurrentUser() { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var currentUser = { + "email":"warren@umbraco.com", + "locale":"en-US", + "emailHash":"da0673cb2c930ee247e8ba5ebe4355bf", + "userGroups":[ + "admin", + "sensitiveData" + ], + "remainingAuthSeconds":1178.2645038, + "startContentIds":[-1], + "startMediaIds":[-1], + "avatars":[ + "https://www.gravatar.com/avatar/da0673cb2c930ee247e8ba5ebe4355bf?d=404&s=30", + "https://www.gravatar.com/avatar/da0673cb2c930ee247e8ba5ebe4355bf?d=404&s=60", + "https://www.gravatar.com/avatar/da0673cb2c930ee247e8ba5ebe4355bf?d=404&s=90", + "https://www.gravatar.com/avatar/da0673cb2c930ee247e8ba5ebe4355bf?d=404&s=150", + "https://www.gravatar.com/avatar/da0673cb2c930ee247e8ba5ebe4355bf?d=404&s=300" + ], + "allowedSections":[ + "content", + "forms", + "media", + "member", + "packages", + "settings", + "users" + ], + "id":-1, + "name":"Warren Buckley" + }; + + return [200, currentUser, null]; + } + + return { + register: function () { + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Authentication/GetCurrentUser')) + .respond(getCurrentUser); + } + }; +} + +angular.module('umbraco.mocks').factory('authMocks', ['$httpBackend', 'mocksUtils', authMocks]); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.httpbackend.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.httpbackend.js index 92c2a67d23..21f2b020dd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.httpbackend.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.httpbackend.js @@ -1,10 +1,10 @@ var umbracoAppDev = angular.module('umbraco.httpbackend', ['umbraco', 'ngMockE2E', 'umbraco.mocks']); -function initBackEnd($httpBackend, contentMocks, mediaMocks, treeMocks, userMocks, contentTypeMocks, sectionMocks, entityMocks, dataTypeMocks, dashboardMocks, macroMocks, utilMocks, localizationMocks, prevaluesMocks) { +function initBackEnd($httpBackend, contentMocks, mediaMocks, treeMocks, userMocks, contentTypeMocks, sectionMocks, entityMocks, dataTypeMocks, dashboardMocks, macroMocks, utilMocks, localizationMocks, prevaluesMocks, authMocks) { console.log("httpBackend inited"); - + //Register mocked http responses contentMocks.register(); mediaMocks.register(); @@ -19,6 +19,7 @@ function initBackEnd($httpBackend, contentMocks, mediaMocks, treeMocks, userMock localizationMocks.register(); prevaluesMocks.register(); entityMocks.register(); + authMocks.register(); $httpBackend.whenGET(/^..\/config\//).passThrough(); $httpBackend.whenGET(/^views\//).passThrough(); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index d571de0e2d..d714ea4938 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -1,27 +1,27 @@ /** - * @ngdoc service - * @name umbraco.resources.contentResource - * @description Handles all transactions of content data - * from the angular application to the Umbraco database, using the Content WebApi controller - * - * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. - * - * @requires $q - * @requires $http - * @requires umbDataFormatter - * @requires umbRequestHelper - * - * ##usage - * To use, simply inject the contentResource into any controller or service that needs it, and make - * sure the umbraco.resources module is accesible - which it should be by default. - * - *
-  *    contentResource.getById(1234)
-  *          .then(function(data) {
-  *              $scope.content = data;
+ * @ngdoc service
+ * @name umbraco.resources.contentResource
+ * @description Handles all transactions of content data
+ * from the angular application to the Umbraco database, using the Content WebApi controller
+ *
+ * all methods returns a resource promise async, so all operations won't complete untill .then() is completed.
+ *
+ * @requires $q
+ * @requires $http
+ * @requires umbDataFormatter
+ * @requires umbRequestHelper
+ *
+ * ##usage
+ * To use, simply inject the contentResource into any controller or service that needs it, and make
+ * sure the umbraco.resources module is accesible - which it should be by default.
+ *
+ * 
+ *    contentResource.getById(1234)
+ *          .then(function(data) {
+ *              $scope.content = data;
   *          });
   * 
- **/ + **/ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { @@ -79,27 +79,27 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#sort - * @methodOf umbraco.resources.contentResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
-          * var ids = [123,34533,2334,23434];
-          * contentResource.sort({ parentId: 1244, sortedIds: ids })
-          *    .then(function() {
-          *        $scope.complete = true;
-          *    });
+         * @ngdoc method
+         * @name umbraco.resources.contentResource#sort
+         * @methodOf umbraco.resources.contentResource
+         *
+         * @description
+         * Sorts all children below a given parent node id, based on a collection of node-ids
+         *
+         * ##usage
+         * 
+         * var ids = [123,34533,2334,23434];
+         * contentResource.sort({ parentId: 1244, sortedIds: ids })
+         *    .then(function() {
+         *        $scope.complete = true;
+         *    });
           * 
- * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ sort: function (args) { if (!args) { throw "args cannot be null"; @@ -121,28 +121,28 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#move - * @methodOf umbraco.resources.contentResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-          * contentResource.move({ parentId: 1244, id: 123 })
-          *    .then(function() {
-          *        alert("node was moved");
-          *    }, function(err){
+         * @ngdoc method
+         * @name umbraco.resources.contentResource#move
+         * @methodOf umbraco.resources.contentResource
+         *
+         * @description
+         * Moves a node underneath a new parentId
+         *
+         * ##usage
+         * 
+         * contentResource.move({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("node was moved");
+         *    }, function(err){
           *      alert("node didnt move:" + err.data.Message);
-          *    });
+         *    });
           * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ move: function (args) { if (!args) { throw "args cannot be null"; @@ -164,29 +164,29 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#copy - * @methodOf umbraco.resources.contentResource - * - * @description - * Copies a node underneath a new parentId - * - * ##usage - *
-          * contentResource.copy({ parentId: 1244, id: 123 })
-          *    .then(function() {
-          *        alert("node was copied");
-          *    }, function(err){
+         * @ngdoc method
+         * @name umbraco.resources.contentResource#copy
+         * @methodOf umbraco.resources.contentResource
+         *
+         * @description
+         * Copies a node underneath a new parentId
+         *
+         * ##usage
+         * 
+         * contentResource.copy({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("node was copied");
+         *    }, function(err){
           *      alert("node wasnt copy:" + err.data.Message);
-          *    });
+         *    });
           * 
- * @param {Object} args arguments object - * @param {Int} args.id the ID of the node to copy - * @param {Int} args.parentId the ID of the parent node to copy to - * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api - * @returns {Promise} resourcePromise object. - * - */ + * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api + * @returns {Promise} resourcePromise object. + * + */ copy: function (args) { if (!args) { throw "args cannot be null"; @@ -205,26 +205,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method + * @ngdoc method * @name umbraco.resources.contentResource#unpublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
+         * @methodOf umbraco.resources.contentResource
+         *
+         * @description
+         * Unpublishes a content item with a given Id
+         *
+         * ##usage
+         * 
           * contentResource.unpublish(1234)
-          *    .then(function() {
-          *        alert("node was unpulished");
-          *    }, function(err){
+         *    .then(function() {
+         *        alert("node was unpulished");
+         *    }, function(err){
           *      alert("node wasnt unpublished:" + err.data.Message);
-          *    });
+         *    });
           * 
- * @param {Int} id the ID of the node to unpublish - * @returns {Promise} resourcePromise object. - * - */ + * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ unpublish: function (id, cultures) { if (!id) { throw "id cannot be null"; @@ -242,7 +242,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to publish content with id ' + id); }, /** - * @ngdoc method + * @ngdoc method * @name umbraco.resources.contentResource#getCultureAndDomains * @methodOf umbraco.resources.contentResource * @@ -281,23 +281,23 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** * @ngdoc method - * @name umbraco.resources.contentResource#emptyRecycleBin - * @methodOf umbraco.resources.contentResource - * - * @description - * Empties the content recycle bin - * - * ##usage - *
-          * contentResource.emptyRecycleBin()
-          *    .then(function() {
-          *        alert('its empty!');
-          *    });
+         * @name umbraco.resources.contentResource#emptyRecycleBin
+         * @methodOf umbraco.resources.contentResource
+         *
+         * @description
+         * Empties the content recycle bin
+         *
+         * ##usage
+         * 
+         * contentResource.emptyRecycleBin()
+         *    .then(function() {
+         *        alert('its empty!');
+         *    });
           * 
* - * @returns {Promise} resourcePromise object. - * - */ + * @returns {Promise} resourcePromise object. + * + */ emptyRecycleBin: function () { return umbRequestHelper.resourcePromise( $http.post( @@ -308,25 +308,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#deleteById - * @methodOf umbraco.resources.contentResource - * - * @description - * Deletes a content item with a given id - * - * ##usage - *
-          * contentResource.deleteById(1234)
-          *    .then(function() {
-          *        alert('its gone!');
-          *    });
+         * @ngdoc method
+         * @name umbraco.resources.contentResource#deleteById
+         * @methodOf umbraco.resources.contentResource
+         *
+         * @description
+         * Deletes a content item with a given id
+         *
+         * ##usage
+         * 
+         * contentResource.deleteById(1234)
+         *    .then(function() {
+         *        alert('its gone!');
+         *    });
           * 
* * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ + * @returns {Promise} resourcePromise object. + * + */ deleteById: function (id) { return umbRequestHelper.resourcePromise( $http.post( @@ -348,45 +348,34 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getById - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets a content item with a given id - * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
+         * @ngdoc method
+         * @name umbraco.resources.contentResource#getById
+         * @methodOf umbraco.resources.contentResource
+         *
+         * @description
+         * Gets a content item with a given id
+         *
+         * ##usage
+         * 
+         * contentResource.getById(1234)
+         *    .then(function(content) {
           *        var myDoc = content;
-          *        alert('its here!');
-          *    });
+         *        alert('its here!');
+         *    });
           * 
* * @param {Int} id id of content item to return - * @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id, options) { - var defaults = { - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - + * @param {Int} culture optional culture to retrieve the item in + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - [{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])), + { id: id })), 'Failed to retrieve data for content id ' + id) .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); @@ -430,26 +419,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getByIds - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets an array of content items, given a collection of ids - * - * ##usage - *
-          * contentResource.getByIds( [1234,2526,28262])
-          *    .then(function(contentArray) {
+         * @ngdoc method
+         * @name umbraco.resources.contentResource#getByIds
+         * @methodOf umbraco.resources.contentResource
+         *
+         * @description
+         * Gets an array of content items, given a collection of ids
+         *
+         * ##usage
+         * 
+         * contentResource.getByIds( [1234,2526,28262])
+         *    .then(function(contentArray) {
           *        var myDoc = contentArray;
-          *        alert('they are here!');
-          *    });
+         *        alert('they are here!');
+         *    });
           * 
* * @param {Array} ids ids of content items to return as an array - * @returns {Promise} resourcePromise object containing the content items array. - * - */ + * @returns {Promise} resourcePromise object containing the content items array. + * + */ getByIds: function (ids) { var idQuery = ""; @@ -475,37 +464,37 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. * - * @description - * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * - * - Parent Id must be provided so umbraco knows where to store the content + * - Parent Id must be provided so umbraco knows where to store the content * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold * - * The scaffold is used to build editors for content that has not yet been populated with data. + * The scaffold is used to build editors for content that has not yet been populated with data. * - * ##usage - *
-          * contentResource.getScaffold(1234, 'homepage')
-          *    .then(function(scaffold) {
-          *        var myDoc = scaffold;
+         * ##usage
+         * 
+         * contentResource.getScaffold(1234, 'homepage')
+         *    .then(function(scaffold) {
+         *        var myDoc = scaffold;
           *        myDoc.name = "My new document";
-          *
-          *        contentResource.publish(myDoc, true)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and published again");
-          *            });
-          *    });
+         *
+         *        contentResource.publish(myDoc, true)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and published again");
+         *            });
+         *    });
           * 
* - * @param {Int} parentId id of content item to return + * @param {Int} parentId id of content item to return * @param {String} alias contenttype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the content scaffold. - * - */ + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ getScaffold: function (parentId, alias) { return umbRequestHelper.resourcePromise( @@ -535,25 +524,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getNiceUrl - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a url, given a node ID - * - * ##usage - *
-          * contentResource.getNiceUrl(id)
-          *    .then(function(url) {
-          *        alert('its here!');
-          *    });
+         * @ngdoc method
+         * @name umbraco.resources.contentResource#getNiceUrl
+         * @methodOf umbraco.resources.contentResource
+         *
+         * @description
+         * Returns a url, given a node ID
+         *
+         * ##usage
+         * 
+         * contentResource.getNiceUrl(id)
+         *    .then(function(url) {
+         *        alert('its here!');
+         *    });
           * 
* - * @param {Int} id Id of node to return the public url to - * @returns {Promise} resourcePromise object containing the url. - * - */ + * @param {Int} id Id of node to return the public url to + * @returns {Promise} resourcePromise object containing the url. + * + */ getNiceUrl: function (id) { return umbRequestHelper.resourcePromise( $http.get( @@ -565,33 +554,33 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getChildren - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets children of a content item with a given id - * - * ##usage - *
-          * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
-          *    .then(function(contentArray) {
+         * @ngdoc method
+         * @name umbraco.resources.contentResource#getChildren
+         * @methodOf umbraco.resources.contentResource
+         *
+         * @description
+         * Gets children of a content item with a given id
+         *
+         * ##usage
+         * 
+         * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+         *    .then(function(contentArray) {
           *        var children = contentArray;
-          *        alert('they are here!');
-          *    });
+         *        alert('they are here!');
+         *    });
           * 
* - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` * @param {String} options.cultureName if provided, the results will be for this specific culture/variant - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ getChildren: function (parentId, options) { var defaults = { @@ -662,34 +651,34 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#save - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * @ngdoc method + * @name umbraco.resources.contentResource#save + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation * if the content item needs to have files attached, they must be provided as the files param and passed separately * * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *          content.name = "I want a new name!";
-          *          contentResource.save(content, false)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and saved again");
-          *            });
-          *    });
+         * ##usage
+         * 
+         * contentResource.getById(1234)
+         *    .then(function(content) {
+         *          content.name = "I want a new name!";
+         *          contentResource.save(content, false)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and saved again");
+         *            });
+         *    });
           * 
* - * @param {Object} content The content item object with changes applied + * @param {Object} content The content item object with changes applied * @param {Bool} isNew set to true to create a new item or to update an existing * @param {Array} files collection of files for the document * @param {Bool} showNotifications an option to disable/show notifications (default is true) - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ save: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", @@ -705,34 +694,34 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#publish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation * if the content item needs to have files attached, they must be provided as the files param and passed separately * * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *          content.name = "I want a new name, and be published!";
-          *          contentResource.publish(content, false)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and published again");
-          *            });
-          *    });
+         * ##usage
+         * 
+         * contentResource.getById(1234)
+         *    .then(function(content) {
+         *          content.name = "I want a new name, and be published!";
+         *          contentResource.publish(content, false)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and published again");
+         *            });
+         *    });
           * 
* - * @param {Object} content The content item object with changes applied + * @param {Object} content The content item object with changes applied * @param {Bool} isNew set to true to create a new item or to update an existing * @param {Array} files collection of files for the document * @param {Bool} showNotifications an option to disable/show notifications (default is true) - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ publish: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", @@ -754,31 +743,31 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource + * @ngdoc method + * @name umbraco.resources.contentResource#sendToPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item, and notifies any subscribers about a pending publication * - * @description - * Saves changes made to a content item, and notifies any subscribers about a pending publication - * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *          content.name = "I want a new name, and be published!";
-          *          contentResource.sendToPublish(content, false)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and notication send off");
-          *            });
-          *    });
+         * ##usage
+         * 
+         * contentResource.getById(1234)
+         *    .then(function(content) {
+         *          content.name = "I want a new name, and be published!";
+         *          contentResource.sendToPublish(content, false)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and notication send off");
+         *            });
+         *    });
           * 
* - * @param {Object} content The content item object with changes applied + * @param {Object} content The content item object with changes applied * @param {Bool} isNew set to true to create a new item or to update an existing * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ sendToPublish: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", @@ -808,25 +797,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
-          * contentResource.publishById(1234)
-          *    .then(function(content) {
-          *        alert("published");
-          *    });
+         * ##usage
+         * 
+         * contentResource.publishById(1234)
+         *    .then(function(content) {
+         *        alert("published");
+         *    });
           * 
* - * @param {Int} id The ID of the conten to publish - * @returns {Promise} resourcePromise object containing the published content item. - * - */ + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ publishById: function (id) { if (!id) { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index d5145727ac..a910629f4a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -2,10 +2,10 @@ * @ngdoc service * @name umbraco.resources.entityResource * @description Loads in basic data for all entities - * + * * ##What is an entity? * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most - * basic properties used to display the item in trees, lists and navigation. + * basic properties used to display the item in trees, lists and navigation. * * ##What is the difference between entity and content/media/etc...? * the entity only contains the basic node data, name, id and guid, whereas content @@ -15,7 +15,7 @@ * * ##Entity object types? * You need to specify the type of object you want returned. - * + * * The core object types are: * * - Document @@ -35,7 +35,7 @@ function entityResource($q, $http, umbRequestHelper) { //the factory object returned return { - + getSafeAlias: function (value, camelCase) { if (!value) { @@ -64,10 +64,10 @@ function entityResource($q, $http, umbRequestHelper) { * .then(function(pathArray) { * alert('its here!'); * }); - *
- * + *
+ * * @param {Int} id Id of node to return the public url to - * @param {string} type Object type name + * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the url. * */ @@ -100,8 +100,8 @@ function entityResource($q, $http, umbRequestHelper) { * .then(function(url) { * alert('its here!'); * }); - *
- * + *
+ * * @param {Int} id Id of node to return the public url to * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the url. @@ -135,17 +135,17 @@ function entityResource($q, $http, umbRequestHelper) { * //get media by id * entityResource.getEntityById(0, "Media") * .then(function(ent) { - * var myDoc = ent; + * var myDoc = ent; * alert('its here!'); * }); - *
- * + *
+ * * @param {Int} id id of entity to return - * @param {string} type Object type name + * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the entity. * */ - getById: function (id, type) { + getById: function (id, type) { if (id === -1 || id === "-1") { return null; @@ -160,6 +160,39 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve entity data for id ' + id); }, + + getUrlAndAnchors: function (id) { + + if (id === -1 || id === "-1") { + return null; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrlAndAnchors", + [{ id: id }])), + 'Failed to retrieve url and anchors data for id ' + id); + }, + + getAnchors: function (rteContent) { + + if (!rteContent || rteContent.length === 0) { + return []; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + 'GetAnchors'), + { + rteContent: rteContent + }), + 'Failed to anchors data for rte content ' + rteContent); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getByIds @@ -173,18 +206,18 @@ function entityResource($q, $http, umbRequestHelper) { * //Get templates for ids * entityResource.getEntitiesByIds( [1234,2526,28262], "Template") * .then(function(templateArray) { - * var myDoc = contentArray; + * var myDoc = contentArray; * alert('they are here!'); * }); - *
- * + *
+ * * @param {Array} ids ids of entities to return as an array - * @param {string} type type name + * @param {string} type type name * @returns {Promise} resourcePromise object containing the entity array. * */ getByIds: function (ids, type) { - + var query = "type=" + type; return umbRequestHelper.resourcePromise( @@ -212,14 +245,14 @@ function entityResource($q, $http, umbRequestHelper) { * //get content by xpath * entityResource.getByQuery("$current", -1, "Document") * .then(function(ent) { - * var myDoc = ent; + * var myDoc = ent; * alert('its here!'); * }); - *
- * + *
+ * * @param {string} query xpath to use in query * @param {Int} nodeContextId id id to start from - * @param {string} type Object type name + * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the entity. * */ @@ -247,12 +280,12 @@ function entityResource($q, $http, umbRequestHelper) { * //Only return media * entityResource.getAll("Media") * .then(function(ent) { - * var myDoc = ent; + * var myDoc = ent; * alert('its here!'); * }); - *
- * - * @param {string} type Object type name + *
+ * + * @param {string} type Object type name * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server * @returns {Promise} resourcePromise object containing the entity. * @@ -277,40 +310,36 @@ function entityResource($q, $http, umbRequestHelper) { * * @description * Gets ancestor entities for a given item - * - * + * + * * @param {string} type Object type name * @param {string} culture Culture * @returns {Promise} resourcePromise object containing the entity. * */ - getAncestors: function (id, type, culture, options) { - var defaults = { - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; + getAncestors: function (id, type, culture, options) { + if (!culture) { + culture = ""; } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - if (culture === undefined) culture = ""; + + var args = [ + { id: id }, + { type: type }, + { culture: culture} + ]; + if (options && options.dataTypeKey) { + args.push({ dataTypeKey: options.dataTypeKey }); + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [ - { id: id }, - { type: type }, - { culture: culture }, - { ignoreUserStartNodes: options.ignoreUserStartNodes } - ])), - + args)), 'Failed to retrieve ancestor data for id ' + id); }, - + /** * @ngdoc method * @name umbraco.resources.entityResource#getChildren @@ -318,20 +347,25 @@ function entityResource($q, $http, umbRequestHelper) { * * @description * Gets children entities for a given item - * + * * @param {Int} parentid id of content item to return children of - * @param {string} type Object type name + * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the entity. * */ - getChildren: function (id, type) { + getChildren: function (id, type, options) { + + var args = [{ id: id }, { type: type }]; + if (options && options.dataTypeKey) { + args.push({ dataTypeKey: options.dataTypeKey }); + } return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetChildren", - [{ id: id }, { type: type }])), + args)), 'Failed to retrieve child data for id ' + id); }, @@ -347,11 +381,11 @@ function entityResource($q, $http, umbRequestHelper) { *
           * entityResource.getPagedChildren(1234, "Content", {pageSize: 10, pageNumber: 2})
           *    .then(function(contentArray) {
-          *        var children = contentArray; 
+          *        var children = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * + *
+ * * @param {Int} parentid id of content item to return children of * @param {string} type Object type name * @param {Object} options optional options object @@ -370,7 +404,8 @@ function entityResource($q, $http, umbRequestHelper) { pageNumber: 100, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + dataTypeKey: null }; if (options === undefined) { options = {}; @@ -387,6 +422,7 @@ function entityResource($q, $http, umbRequestHelper) { options.orderDirection = "Descending"; } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( @@ -399,7 +435,8 @@ function entityResource($q, $http, umbRequestHelper) { pageSize: options.pageSize, orderBy: options.orderBy, orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) + filter: encodeURIComponent(options.filter), + dataTypeKey: options.dataTypeKey } )), 'Failed to retrieve child data for id ' + parentId); @@ -417,11 +454,11 @@ function entityResource($q, $http, umbRequestHelper) { *
           * entityResource.getPagedDescendants(1234, "Document", {pageSize: 10, pageNumber: 2})
           *    .then(function(contentArray) {
-          *        var children = contentArray; 
+          *        var children = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * + *
+ * * @param {Int} parentid id of content item to return descendants of * @param {string} type Object type name * @param {Object} options optional options object @@ -441,7 +478,7 @@ function entityResource($q, $http, umbRequestHelper) { filter: '', orderDirection: "Ascending", orderBy: "SortOrder", - ignoreUserStartNodes: false + dataTypeKey: null }; if (options === undefined) { options = {}; @@ -471,12 +508,13 @@ function entityResource($q, $http, umbRequestHelper) { orderBy: options.orderBy, orderDirection: options.orderDirection, filter: encodeURIComponent(options.filter), - ignoreUserStartNodes: options.ignoreUserStartNodes + dataTypeKey: options.dataTypeKey } )), 'Failed to retrieve child data for id ' + parentId); }, - + + /** * @ngdoc method * @name umbraco.resources.entityResource#search @@ -489,29 +527,26 @@ function entityResource($q, $http, umbRequestHelper) { *
          * entityResource.search("news", "Media")
          *    .then(function(mediaArray) {
-         *        var myDoc = mediaArray; 
+         *        var myDoc = mediaArray;
          *        alert('they are here!');
          *    });
-         * 
- * - * @param {String} Query search query - * @param {String} Type type of conten to search + *
+ * + * @param {String} Query search query + * @param {String} Type type of conten to search * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, options, canceler) { + search: function (query, type, searchFrom, canceler, dataTypeKey) { var args = [{ query: query }, { type: type }]; - - if(options !== undefined) { - if (options.searchFrom) { - args.push({ searchFrom: options.searchFrom }); - } - if (options.ignoreUserStartNodes) { - args.push({ ignoreUserStartNodes: options.ignoreUserStartNodes }); - } + if (searchFrom) { + args.push({ searchFrom: searchFrom }); + } + + if (dataTypeKey) { + args.push({ dataTypeKey: dataTypeKey }); } - var httpConfig = {}; if (canceler) { @@ -527,7 +562,7 @@ function entityResource($q, $http, umbRequestHelper) { httpConfig), 'Failed to retrieve entity data for query ' + query); }, - + /** * @ngdoc method @@ -541,12 +576,12 @@ function entityResource($q, $http, umbRequestHelper) { *
          * entityResource.searchAll("bob")
          *    .then(function(array) {
-         *        var myDoc = array; 
+         *        var myDoc = array;
          *        alert('they are here!');
          *    });
-         * 
- * - * @param {String} Query search query + *
+ * + * @param {String} Query search query * @returns {Promise} resourcePromise object containing the entity array. * */ @@ -566,7 +601,9 @@ function entityResource($q, $http, umbRequestHelper) { httpConfig), 'Failed to retrieve entity data for query ' + query); } - + + + }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js index 75c23e7adf..b52021ed38 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js @@ -1,39 +1,39 @@ /** - * @ngdoc service - * @name umbraco.resources.logViewerResource - * @description Retrives Umbraco log items (by default from JSON files on disk) - * - * - **/ - function logViewerResource($q, $http, umbRequestHelper) { + * @ngdoc service + * @name umbraco.resources.logViewerResource + * @description Retrives Umbraco log items (by default from JSON files on disk) + * + * + **/ +function logViewerResource($q, $http, umbRequestHelper) { //the factory object returned return { - getNumberOfErrors: function () { + getNumberOfErrors: function (startDate, endDate) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "logViewerApiBaseUrl", - "GetNumberOfErrors")), + "GetNumberOfErrors")+ '?startDate='+startDate+ '&endDate='+ endDate ), 'Failed to retrieve number of errors in logs'); }, - getLogLevelCounts: function () { + getLogLevelCounts: function (startDate, endDate) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "logViewerApiBaseUrl", - "GetLogLevelCounts")), + "GetLogLevelCounts")+ '?startDate='+startDate+ '&endDate='+ endDate ), 'Failed to retrieve log level counts'); }, - getMessageTemplates: function () { + getMessageTemplates: function (startDate, endDate) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "logViewerApiBaseUrl", - "GetMessageTemplates")), + "GetMessageTemplates")+ '?startDate='+startDate+ '&endDate='+ endDate ), 'Failed to retrieve log templates'); }, @@ -93,12 +93,12 @@ 'Failed to retrieve common log messages'); }, - canViewLogs: function () { + canViewLogs: function (startDate, endDate) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "logViewerApiBaseUrl", - "GetCanViewLogs")), + "GetCanViewLogs") + '?startDate='+startDate+ '&endDate='+ endDate ), 'Failed to retrieve state if logs can be viewed'); } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 462184c9f2..ca7700c188 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -1,16 +1,16 @@ /** - * @ngdoc service - * @name umbraco.resources.mediaResource - * @description Loads in data for media - **/ + * @ngdoc service + * @name umbraco.resources.mediaResource + * @description Loads in data for media + **/ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { /** internal method process the saving of data and post processing the result */ function saveMediaItem(content, action, files) { return umbRequestHelper.postSaveContent({ restApiUrl: umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "PostSave"), + "mediaApiBaseUrl", + "PostSave"), content: content, action: action, files: files, @@ -24,35 +24,35 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { getRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for media recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#sort - * @methodOf umbraco.resources.mediaResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
-          * var ids = [123,34533,2334,23434];
-          * mediaResource.sort({ sortedIds: ids })
-          *    .then(function() {
-          *        $scope.complete = true;
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#sort + * @methodOf umbraco.resources.mediaResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
+         * var ids = [123,34533,2334,23434];
+         * mediaResource.sort({ sortedIds: ids })
+         *    .then(function() {
+         *        $scope.complete = true;
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ sort: function (args) { if (!args) { throw "args cannot be null"; @@ -65,37 +65,37 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort media'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#move - * @methodOf umbraco.resources.mediaResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-          * mediaResource.move({ parentId: 1244, id: 123 })
-          *    .then(function() {
-          *        alert("node was moved");
-          *    }, function(err){
-          *      alert("node didnt move:" + err.data.Message);
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#move + * @methodOf umbraco.resources.mediaResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+         * mediaResource.move({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("node was moved");
+         *    }, function(err){
+         *      alert("node didnt move:" + err.data.Message);
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ move: function (args) { if (!args) { throw "args cannot be null"; @@ -108,121 +108,121 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id }, {responseType: 'text'}), - { - error: function(data){ - var errorMsg = 'Failed to move media'; - if (data.id !== undefined && data.parentId !== undefined) { - if (data.id === data.parentId) { - errorMsg = 'Media can\'t be moved into itself'; - } - } - else if (data.notifications !== undefined) { - if (data.notifications.length > 0) { - if (data.notifications[0].header.length > 0) { - errorMsg = data.notifications[0].header; - } - if (data.notifications[0].message.length > 0) { - errorMsg = errorMsg + ": " + data.notifications[0].message; - } + { + error: function(data){ + var errorMsg = 'Failed to move media'; + if (data.id !== undefined && data.parentId !== undefined) { + if (data.id === data.parentId) { + errorMsg = 'Media can\'t be moved into itself'; + } + } + else if (data.notifications !== undefined) { + if (data.notifications.length > 0) { + if (data.notifications[0].header.length > 0) { + errorMsg = data.notifications[0].header; + } + if (data.notifications[0].message.length > 0) { + errorMsg = errorMsg + ": " + data.notifications[0].message; } } + } - return { - errorMsg: errorMsg - }; - } - }); + return { + errorMsg: errorMsg + }; + } + }); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets a media item with a given id - * - * ##usage - *
-          * mediaResource.getById(1234)
-          *    .then(function(media) {
-          *        var myMedia = media;
-          *        alert('its here!');
-          *    });
-          * 
- * - * @param {Int} id id of media item to return - * @returns {Promise} resourcePromise object containing the media item. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a media item with a given id + * + * ##usage + *
+         * mediaResource.getById(1234)
+         *    .then(function(media) {
+         *        var myMedia = media;
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Int} id id of media item to return + * @returns {Promise} resourcePromise object containing the media item. + * + */ getById: function (id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for media id ' + id); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for media id ' + id); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#deleteById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Deletes a media item with a given id - * - * ##usage - *
-          * mediaResource.deleteById(1234)
-          *    .then(function() {
-          *        alert('its gone!');
-          *    });
-          * 
- * - * @param {Int} id id of media item to delete - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#deleteById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Deletes a media item with a given id + * + * ##usage + *
+         * mediaResource.deleteById(1234)
+         *    .then(function() {
+         *        alert('its gone!');
+         *    });
+         * 
+ * + * @param {Int} id id of media item to delete + * @returns {Promise} resourcePromise object. + * + */ deleteById: function (id) { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getByIds - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets an array of media items, given a collection of ids - * - * ##usage - *
-          * mediaResource.getByIds( [1234,2526,28262])
-          *    .then(function(mediaArray) {
-          *        var myDoc = contentArray;
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Array} ids ids of media items to return as an array - * @returns {Promise} resourcePromise object containing the media items array. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getByIds + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets an array of media items, given a collection of ids + * + * ##usage + *
+         * mediaResource.getByIds( [1234,2526,28262])
+         *    .then(function(mediaArray) {
+         *        var myDoc = contentArray;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {Array} ids ids of media items to return as an array + * @returns {Promise} resourcePromise object containing the media items array. + * + */ getByIds: function (ids) { var idQuery = ""; @@ -231,96 +231,96 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { }); return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for media ids ' + ids); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getScaffold - * @methodOf umbraco.resources.mediaResource - * - * @description - * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * - * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * - * The scaffold is used to build editors for media that has not yet been populated with data. - * - * ##usage - *
-          * mediaResource.getScaffold(1234, 'folder')
-          *    .then(function(scaffold) {
-          *        var myDoc = scaffold;
-          *        myDoc.name = "My new media item";
-          *
-          *        mediaResource.save(myDoc, true)
-          *            .then(function(media){
-          *                alert("Retrieved, updated and saved again");
-          *            });
-          *    });
-          * 
- * - * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the media scaffold. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getScaffold + * @methodOf umbraco.resources.mediaResource + * + * @description + * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. + * + * - Parent Id must be provided so umbraco knows where to store the media + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * + * The scaffold is used to build editors for media that has not yet been populated with data. + * + * ##usage + *
+         * mediaResource.getScaffold(1234, 'folder')
+         *    .then(function(scaffold) {
+         *        var myDoc = scaffold;
+         *        myDoc.name = "My new media item";
+         *
+         *        mediaResource.save(myDoc, true)
+         *            .then(function(media){
+         *                alert("Retrieved, updated and saved again");
+         *            });
+         *    });
+         * 
+ * + * @param {Int} parentId id of media item to return + * @param {String} alias mediatype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the media scaffold. + * + */ getScaffold: function (parentId, alias) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty media item type ' + alias); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); }, rootMedia: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRootMedia")), + 'Failed to retrieve data for root media'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildren - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets children of a media item with a given id - * - * ##usage - *
-          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
-          *    .then(function(contentArray) {
-          *        var children = contentArray;
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildren + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets children of a media item with a given id + * + * ##usage + *
+         * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+         *    .then(function(contentArray) {
+         *        var children = contentArray;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ getChildren: function (parentId, options) { var defaults = { @@ -329,8 +329,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { filter: '', orderDirection: "Ascending", orderBy: "SortOrder", - orderBySystemField: true, - ignoreUserStartNodes: false + orderBySystemField: true }; if (options === undefined) { options = {}; @@ -362,112 +361,111 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { ignoreUserStartNodes: options.ignoreUserStartNodes }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter } - ])), - 'Failed to retrieve children for media item ' + parentId); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ])), + 'Failed to retrieve children for media item ' + parentId); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#save - * @methodOf umbraco.resources.mediaResource - * - * @description - * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-          * mediaResource.getById(1234)
-          *    .then(function(media) {
-          *          media.name = "I want a new name!";
-          *          mediaResource.save(media, false)
-          *            .then(function(media){
-          *                alert("Retrieved, updated and saved again");
-          *            });
-          *    });
-          * 
- * - * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#save + * @methodOf umbraco.resources.mediaResource + * + * @description + * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+         * mediaResource.getById(1234)
+         *    .then(function(media) {
+         *          media.name = "I want a new name!";
+         *          mediaResource.save(media, false)
+         *            .then(function(media){
+         *                alert("Retrieved, updated and saved again");
+         *            });
+         *    });
+         * 
+ * + * @param {Object} media The media item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ save: function (media, isNew, files) { return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#addFolder - * @methodOf umbraco.resources.mediaResource - * - * @description - * Shorthand for adding a media item of the type "Folder" under a given parent ID - * - * ##usage - *
-          * mediaResource.addFolder("My gallery", 1234)
-          *    .then(function(folder) {
-          *        alert('New folder');
-          *    });
-          * 
- * - * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#addFolder + * @methodOf umbraco.resources.mediaResource + * + * @description + * Shorthand for adding a media item of the type "Folder" under a given parent ID + * + * ##usage + *
+         * mediaResource.addFolder("My gallery", 1234)
+         *    .then(function(folder) {
+         *        alert('New folder');
+         *    });
+         * 
+ * + * @param {string} name Name of the folder to create + * @param {int} parentId Id of the media item to create the folder underneath + * @returns {Promise} resourcePromise object. + * + */ addFolder: function (name, parentId) { return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), - 'Failed to add folder'); + $http.post(umbRequestHelper + .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), + { + name: name, + parentId: parentId + }), + 'Failed to add folder'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildFolders - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves all media children with types used as folders. - * Uses the convention of looking for media items with mediaTypes ending in - * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * * NOTE: This will return a page of max 500 folders, if more is required it needs to be paged * and then folders are in the .items property of the returned promise data - * - * ##usage - *
-          * mediaResource.getChildFolders(1234)
+         *
+         * ##usage
+         * 
+         * mediaResource.getChildFolders(1234)
           *    .then(function(page) {
-          *        alert('folders');
-          *    });
-          * 
- * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ + * alert('folders'); + * }); + *
+ * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ getChildFolders: function (parentId) { if (!parentId) { parentId = -1; @@ -475,69 +473,69 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { //NOTE: This will return a max of 500 folders, if more is required it needs to be paged return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - { + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + { id: parentId, pageNumber: 1, pageSize: 500 - })), + })), 'Failed to retrieve child folders for media item ' + parentId); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#emptyRecycleBin - * @methodOf umbraco.resources.mediaResource - * - * @description - * Empties the media recycle bin - * - * ##usage - *
-          * mediaResource.emptyRecycleBin()
-          *    .then(function() {
-          *        alert('its empty!');
-          *    });
-          * 
- * - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#emptyRecycleBin + * @methodOf umbraco.resources.mediaResource + * + * @description + * Empties the media recycle bin + * + * ##usage + *
+         * mediaResource.emptyRecycleBin()
+         *    .then(function() {
+         *        alert('its empty!');
+         *    });
+         * 
+ * + * @returns {Promise} resourcePromise object. + * + */ emptyRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#search - * @methodOf umbraco.resources.mediaResource - * - * @description - * Paginated search for media items starting on the supplied nodeId - * - * ##usage - *
-          * mediaResource.search("my search", 1, 100, -1)
-          *    .then(function(searchResult) {
-          *        alert('it's here!');
-          *    });
-          * 
- * - * @param {string} query The search query - * @param {int} pageNumber The page number - * @param {int} pageSize The number of media items on a page - * @param {int} searchFrom NodeId to search from (-1 for root) - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#search + * @methodOf umbraco.resources.mediaResource + * + * @description + * Paginated search for media items starting on the supplied nodeId + * + * ##usage + *
+         * mediaResource.search("my search", 1, 100, -1)
+         *    .then(function(searchResult) {
+         *        alert('it's here!');
+         *    });
+         * 
+ * + * @param {string} query The search query + * @param {int} pageNumber The page number + * @param {int} pageSize The number of media items on a page + * @param {int} searchFrom NodeId to search from (-1 for root) + * @returns {Promise} resourcePromise object. + * + */ search: function (query, pageNumber, pageSize, searchFrom) { var args = [ @@ -548,12 +546,12 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { ]; return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "Search", - args)), - 'Failed to retrieve media items for search: ' + query); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "Search", + args)), + 'Failed to retrieve media items for search: ' + query); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 1efcf84dcb..732f682082 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -5,7 +5,7 @@ * @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by * all editors to share logic and reduce the amount of replicated code among editors. **/ -function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, navigationService, localizationService, serverValidationManager, formHelper) { +function contentEditingHelper(fileManager, $q, $location, $routeParams, editorState, notificationsService, navigationService, localizationService, serverValidationManager, formHelper) { function isValidIdentifier(id) { @@ -34,9 +34,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica return { + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + /** Used by the content editor and mini content editor to perform saving operations */ - // TODO: Make this a more helpful/reusable method for other form operations! we can simplify this form most forms - // = this is already done in the formhelper service contentEditorPerformSave: function (args) { if (!angular.isObject(args)) { throw "args must be an object"; @@ -53,18 +54,19 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica if (args.showNotifications === undefined) { args.showNotifications = true; } - - var redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; - var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true; + if (args.softRedirect === undefined) { + //when true, the url will change but it won't actually re-route + //this is merely here for compatibility, if only the content/media/members used this service we'd prob be ok but tons of editors + //use this service unfortunately and probably packages too. + args.softRedirect = false; + } var self = this; //we will use the default one for content if not specified var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback; - if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, action: args.action })) { - - args.scope.busy = true; + if (formHelper.submitForm({ scope: args.scope, action: args.action })) { return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles(), args.showNotifications) .then(function (data) { @@ -74,26 +76,30 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica self.handleSuccessfulSave({ scope: args.scope, savedContent: data, - redirectOnSuccess: redirectOnSuccess, + softRedirect: args.softRedirect, rebindCallback: function () { rebindCallback.apply(self, [args.content, data]); } }); - args.scope.busy = false; + //update editor state to what is current + editorState.set(args.content); + return $q.resolve(data); }, function (err) { self.handleSaveError({ showNotifications: args.showNotifications, - redirectOnFailure: redirectOnFailure, + softRedirect: args.softRedirect, err: err, rebindCallback: function () { rebindCallback.apply(self, [args.content, err.data]); } }); - args.scope.busy = false; + //update editor state to what is current + editorState.set(args.content); + return $q.reject(err); }); } @@ -197,6 +203,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica letter: ch, labelKey: "buttons_schedulePublish", handler: args.methods.schedulePublish, + hotKey: "alt+shift+s", + hotKeyWhenHidden: true, alias: "schedulePublish", addEllipsis: "true" }; @@ -207,6 +215,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica letter: ch, labelKey: "buttons_publishDescendants", handler: args.methods.publishDescendants, + hotKey: "alt+shift+p", + hotKeyWhenHidden: true, alias: "publishDescendant", addEllipsis: "true" }; @@ -261,7 +271,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // if publishing is allowed also allow schedule publish // we add this manually becuase it doesn't have a permission so it wont // get picked up by the loop through permissions - if( _.contains(args.content.allowedActions, "U")) { + if (_.contains(args.content.allowedActions, "U")) { buttons.subButtons.push(createButtonDefinition("SCHEDULE")); buttons.subButtons.push(createButtonDefinition("PUBLISH_DESCENDANTS")); } @@ -270,7 +280,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // so long as it's already published and if the user has access to publish // and the user has access to unpublish (may have been removed via Event) if (!args.create) { - var hasPublishedVariant = args.content.variants.filter(function(variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0; + var hasPublishedVariant = args.content.variants.filter(function (variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0; if (hasPublishedVariant && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) { buttons.subButtons.push(createButtonDefinition("Z")); } @@ -440,7 +450,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var shouldIgnore = function (propName) { return _.some([ "variants", - + "tabs", "properties", "apps", @@ -570,15 +580,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * A function to handle what happens when we have validation issues from the server side * */ + + //TODO: Too many editors use this method for saving when this entire service should only be used for content/media/members, + // there is formHelper.handleError for other editors which should be used! + handleSaveError: function (args) { if (!args.err) { throw "args.err cannot be null"; } - if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) { - throw "args.redirectOnFailure must be set to true or false"; - } - + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error @@ -596,16 +607,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } } - if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. Then we need - // to re-bind any server validation errors after the digest takes place. + if (!this.redirectToCreatedContent(args.err.data.id) || args.softRedirect) { + // If we are not redirecting it's because this is not newly created content, else in some cases we are + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). + // In this case we need to detect what properties have changed and re-bind them with the server data. if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { args.rebindCallback(); } - //notify all validators (don't clear the server validations though since we need to maintain their state because of + // In this case notify all validators (don't clear the server validations though since we need to maintain their state because of // how the variant switcher works in content). server validation state is always cleared when an editor first loads // and in theory when an editor is destroyed. serverValidationManager.notify(); @@ -629,6 +640,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect * when we're creating new content. */ + + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + handleSuccessfulSave: function (args) { if (!args) { @@ -638,14 +653,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica throw "args.savedContent cannot be null"; } - // the default behaviour is to redirect on success. This adds option to prevent when false - args.redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; + if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id) || args.softRedirect) { - if (!args.redirectOnSuccess || !this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { + // If we are not redirecting it's because this is not newly created content, else in some cases we are + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. - //call the callback + // In this case we need to detect what properties have changed and re-bind them with the server data. if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { args.rebindCallback(); } @@ -663,7 +676,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * We need to decide if we need to redirect to edito mode or if we will remain in create mode. * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID */ - redirectToCreatedContent: function (id, modelState) { + redirectToCreatedContent: function (id) { //only continue if we are currently in create mode and not in infinite mode and if the resulting ID is valid if ($routeParams.create && (isValidIdentifier(id))) { @@ -675,7 +688,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //clear the query strings navigationService.clearSearch(["cculture"]); - + //change to new path $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this @@ -695,6 +708,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * For some editors like scripts or entites that have names as ids, these names can change and we need to redirect * to their new paths, this is helper method to do that. */ + + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + redirectToRenamedContent: function (id) { //clear the query strings navigationService.clearSearch(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index a97773f77e..3979eb2fb7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -261,7 +261,7 @@ When building a custom infinite editor view you can use the same components as a */ unbindKeyboardShortcuts(); - // set flag so we know when the editor is open in "infinie mode" + // set flag so we know when the editor is open in "infinite mode" editor.infiniteMode = true; editors.push(editor); @@ -368,6 +368,28 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#contentTypePicker + * @methodOf umbraco.services.editorService + * + * @description + * Opens a content type picker in infinite editing, the submit callback returns an array of selected items + * + * @param {Object} editor rendering options + * @param {Boolean} editor.multiPicker Pick one or multiple items + * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object + * @param {Function} editor.close Callback function when the close button is clicked. + * + * @returns {Object} editor object + */ + function contentTypePicker(editor) { + editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; + editor.size = "small"; + editor.section = "settings"; + editor.treeAlias = "documentTypes"; + open(editor); + } /** * @ngdoc method * @name umbraco.services.editorService#copy @@ -881,6 +903,7 @@ When building a custom infinite editor view you can use the same components as a mediaEditor: mediaEditor, contentEditor: contentEditor, contentPicker: contentPicker, + contentTypePicker: contentTypePicker, copy: copy, move: move, embed: embed, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js index 5e42af9c5e..97a9ac5c4b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -5,13 +5,15 @@ * * @description * Tracks the parent object for complex editors by exposing it as - * an object reference via editorState.current.entity + * an object reference via editorState.current.getCurrent(). + * The state is cleared on each successful route. * * it is possible to modify this object, so should be used with care */ -angular.module('umbraco.services').factory("editorState", function() { +angular.module('umbraco.services').factory("editorState", function ($rootScope) { var current = null; + var state = { /** @@ -40,7 +42,7 @@ angular.module('umbraco.services').factory("editorState", function() { * Since the editorstate entity is read-only, you cannot set it to null * only through the reset() method */ - reset: function() { + reset: function () { current = null; }, @@ -59,9 +61,10 @@ angular.module('umbraco.services').factory("editorState", function() { * editorState.current can not be overwritten, you should only read values from it * since modifying individual properties should be handled by the property editors */ - getCurrent: function() { + getCurrent: function () { return current; } + }; // TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing. @@ -76,5 +79,13 @@ angular.module('umbraco.services').factory("editorState", function() { } }); + //execute on each successful route (this is only bound once per application since a service is a singleton) + $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { + + //reset the editorState on each successful route chage + state.reset(); + + }); + return state; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 6d319ad90a..8fe6761c94 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -8,11 +8,12 @@ * that need to attach files. * When a route changes successfully, we ensure that the collection is cleared. */ -function fileManager() { +function fileManager($rootScope) { var fileCollection = []; - return { + + var mgr = { /** * @ngdoc function * @name umbraco.services.fileManager#addFiles @@ -24,7 +25,7 @@ function fileManager() { * for the files collection that effectively clears the files for the specified editor. */ setFiles: function (args) { - + //propertyAlias, files if (!angular.isString(args.propertyAlias)) { throw "args.propertyAlias must be a non empty string"; @@ -52,7 +53,7 @@ function fileManager() { fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, metaData: metaData }); } }, - + /** * @ngdoc function * @name umbraco.services.fileManager#getFiles @@ -62,10 +63,10 @@ function fileManager() { * @description * Returns all of the files attached to the file manager */ - getFiles: function() { + getFiles: function () { return fileCollection; }, - + /** * @ngdoc function * @name umbraco.services.fileManager#clearFiles @@ -78,7 +79,17 @@ function fileManager() { clearFiles: function () { fileCollection = []; } -}; + }; + + //execute on each successful route (this is only bound once per application since a service is a singleton) + $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { + //reset the file manager on each route change, the file collection is only relavent + // when working in an editor and submitting data to the server. + //This ensures that memory remains clear of any files and that the editors don't have to manually clear the files. + mgr.clearFiles(); + }); + + return mgr; } angular.module('umbraco.services').factory('fileManager', fileManager); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index b6bcafbddf..0555318bae 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -40,7 +40,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService else { currentForm = args.formCtrl; } - + //the first thing any form must do is broadcast the formSubmitting event args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); @@ -53,10 +53,10 @@ function formHelper(angularHelper, serverValidationManager, notificationsService //reset the server validations serverValidationManager.reset(); - + return true; }, - + /** * @ngdoc function * @name umbraco.services.formHelper#submitForm @@ -75,21 +75,21 @@ function formHelper(angularHelper, serverValidationManager, notificationsService if (!args.scope) { throw "args.scope cannot be null"; } - + args.scope.$broadcast("formSubmitted", { scope: args.scope }); }, showNotifications: function (args) { - if (!args || !args.notifications) { - return false; - } - if (angular.isArray(args.notifications)) { - for (var i = 0; i < args.notifications.length; i++) { - notificationsService.showNotification(args.notifications[i]); + if (!args || !args.notifications) { + return false; } - return true; - } - return false; + if (angular.isArray(args.notifications)) { + for (var i = 0; i < args.notifications.length; i++) { + notificationsService.showNotification(args.notifications[i]); + } + return true; + } + return false; }, /** @@ -104,7 +104,12 @@ function formHelper(angularHelper, serverValidationManager, notificationsService * * @param {object} err The error object returned from the http promise */ - handleError: function (err) { + handleError: function (err) { + + //TODO: Potentially add in the logic to showNotifications like the contentEditingHelper.handleSaveError does so that + // non content editors can just use this method instead of contentEditingHelper.handleSaveError which they should not use + // and they won't need to manually do it. + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error @@ -116,7 +121,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService this.handleServerValidation(err.data.ModelState); //execute all server validation events and subscribers - serverValidationManager.notifyAndClearAllSubscriptions(); + serverValidationManager.notifyAndClearAllSubscriptions(); } } else { @@ -124,7 +129,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService // TODO: All YSOD handling should be done with an interceptor overlayService.ysod(err); } - + }, /** @@ -184,8 +189,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService serverValidationManager.addPropertyError(propertyAlias, culture, "", modelState[e][0]); } - } - else { + } else { //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: // Groups[0].Properties[2].Alias diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index ea2ad73263..6081cbd9ad 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -29,35 +29,48 @@ angular.module('umbraco.services') var resourceFileLoadStatus = "none"; var resourceLoadingPromise = []; - function _lookup(value, tokens, dictionary) { + // array to hold the localized resource string entries + var innerDictionary = []; + + function _lookup(alias, tokens, dictionary, fallbackValue) { //strip the key identifier if its there - if (value && value[0] === "@") { - value = value.substring(1); + if (alias && alias[0] === "@") { + alias = alias.substring(1); } + var underscoreIndex = alias.indexOf("_"); //if no area specified, add general_ - if (value && value.indexOf("_") < 0) { - value = "general_" + value; + if (alias && underscoreIndex < 0) { + alias = "general_" + alias; + underscoreIndex = alias.indexOf("_"); } - var entry = dictionary[value]; - if (entry) { - return service.tokenReplace(entry, tokens); + var areaAlias = alias.substring(0, underscoreIndex); + var valueAlias = alias.substring(underscoreIndex + 1); + + var areaEntry = dictionary[areaAlias]; + if (areaEntry) { + var valueEntry = areaEntry[valueAlias]; + if (valueEntry) { + return service.tokenReplace(valueEntry, tokens); + } } - return "[" + value + "]"; + + if (fallbackValue) return fallbackValue; + + return "[" + alias + "]"; } var service = { - // array to hold the localized resource string entries - dictionary: [], + // loads the language resource file from the server initLocalizedResources: function () { var deferred = $q.defer(); if (resourceFileLoadStatus === "loaded") { - deferred.resolve(service.dictionary); + deferred.resolve(innerDictionary); return deferred.promise; } @@ -77,7 +90,7 @@ angular.module('umbraco.services') $http({ method: "GET", url: url, cache: false }) .then(function (response) { resourceFileLoadStatus = "loaded"; - service.dictionary = response.data; + innerDictionary = response.data; eventsService.emit("localizationService.updated", response.data); @@ -159,11 +172,14 @@ angular.module('umbraco.services') * @param {Array} tokens if specified this array will be sent as parameter values * This replaces %0% and %1% etc in the dictionary key value with the passed in strings * + * @param {String} fallbackValue if specified this string will be returned if no matching + * entry was found in the dictionary + * * @returns {String} localized resource string */ - localize: function (value, tokens) { + localize: function (value, tokens, fallbackValue) { return service.initLocalizedResources().then(function (dic) { - return _lookup(value, tokens, dic); + return _lookup(value, tokens, dic, fallbackValue); }); }, @@ -244,8 +260,8 @@ angular.module('umbraco.services') //Build a concat string by looping over the array of resolved promises/translations var returnValue = ""; - for(var i = 0; i < localizedValues.length; i++){ - returnValue += localizedValues[i]; + for(var j = 0; j < localizedValues.length; j++){ + returnValue += localizedValues[j]; } return returnValue; @@ -292,11 +308,11 @@ angular.module('umbraco.services') return $q.all(promises).then(function(localizedValues){ //Replace {0} and {1} etc in message with the localized values - for(var i = 0; i < localizedValues.length; i++){ - var token = "%" + i + "%"; + for(var j = 0; j < localizedValues.length; j++){ + var token = "%" + j + "%"; var regex = new RegExp(token, "g"); - message = message.replace(regex, localizedValues[i]); + message = message.replace(regex, localizedValues[j]); } return message; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 16c5b38a79..9350af1c47 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -3,7 +3,7 @@ * @name umbraco.services.mediaHelper * @description A helper object used for dealing with media items **/ -function mediaHelper(umbRequestHelper) { +function mediaHelper(umbRequestHelper, $log) { //container of fileresolvers var _mediaFileResolvers = {}; @@ -13,11 +13,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getImagePropertyValue * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the file path associated with the media property if there is one - * + * * @param {object} options Options object * @param {object} options.mediaModel The media object to retrieve the image path from * @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image @@ -80,11 +80,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getImagePropertyValue * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the actual image path associated with the image property if there is one - * + * * @param {object} options Options object * @param {object} options.imageModel The media object to retrieve the image path from */ @@ -104,11 +104,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getThumbnail * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * formats the display model used to display the content to the model used to save the content - * + * * @param {object} options Options object * @param {object} options.imageModel The media object to retrieve the image path from */ @@ -133,48 +133,44 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#resolveFileFromEntity * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Gets the media file url for a media entity returned with the entityResource - * + * * @param {object} mediaEntity A media Entity returned from the entityResource * @param {boolean} thumbnail Whether to return the thumbnail url or normal url */ resolveFileFromEntity: function (mediaEntity, thumbnail) { - if (!angular.isObject(mediaEntity.metaData)) { - throw "Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"; + if (!angular.isObject(mediaEntity.metaData) || !mediaEntity.metaData.MediaPath) { + //don't throw since this image legitimately might not contain a media path, but output a warning + $log.warn("Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"); + return null; } - var values = _.values(mediaEntity.metaData); - for (var i = 0; i < values.length; i++) { - var val = values[i]; - if (angular.isObject(val) && val.PropertyEditorAlias) { - for (var resolver in _mediaFileResolvers) { - if (val.PropertyEditorAlias === resolver) { - //we need to format a property variable that coincides with how the property would be structured - // if it came from the mediaResource just to keep things slightly easier for the file resolvers. - var property = { value: val.Value }; - - return _mediaFileResolvers[resolver](property, mediaEntity, thumbnail); - } - } + if (thumbnail) { + if (this.detectIfImageByExtension(mediaEntity.metaData.MediaPath)) { + return this.getThumbnailFromPath(mediaEntity.metaData.MediaPath); + } + else { + return null; } } - - return ""; + else { + return mediaEntity.metaData.MediaPath; + } }, /** * @ngdoc function * @name umbraco.services.mediaHelper#resolveFile * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Gets the media file url for a media object returned with the mediaResource - * + * * @param {object} mediaEntity A media Entity returned from the entityResource * @param {boolean} thumbnail Whether to return the thumbnail url or normal url */ @@ -246,11 +242,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#scaleToMaxSize * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Finds the corrct max width and max height, given maximum dimensions and keeping aspect ratios - * + * * @param {number} maxSize Maximum width & height * @param {number} width Current width * @param {number} height Current height @@ -289,11 +285,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getThumbnailFromPath * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the path to the thumbnail version of a given media library image path - * + * * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg */ getThumbnailFromPath: function (imagePath) { @@ -316,11 +312,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#detectIfImageByExtension * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns true/false, indicating if the given path has an allowed image extension - * + * * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg */ detectIfImageByExtension: function (imagePath) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js index 57c86cba90..7856714ccd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js @@ -71,7 +71,9 @@ function umbracoMenuActions(treeService, $location, navigationService, appState, if (treeRoot && treeRoot.root) { var treeNode = treeService.getDescendantNode(treeRoot.root, args.entity.id, args.treeAlias); if (treeNode) { - treeService.loadNodeChildren({ node: treeNode, section: args.section }); + treeService.loadNodeChildren({ node: treeNode, section: args.section }).then(function () { + navigationService.hideMenu(); + }); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index ba8334d307..a36e1a7633 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -13,7 +13,7 @@ * Section navigation and search, and maintain their state for the entire application lifetime * */ -function navigationService($routeParams, $location, $q, $timeout, $injector, eventsService, umbModelMapper, treeService, appState) { +function navigationService($routeParams, $location, $q, $injector, eventsService, umbModelMapper, treeService, appState) { //the promise that will be resolved when the navigation is ready var navReadyPromise = $q.defer(); @@ -31,7 +31,8 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve //A list of query strings defined that when changed will not cause a reload of the route var nonRoutingQueryStrings = ["mculture", "cculture", "lq"]; var retainedQueryStrings = ["mculture"]; - + //A list of trees that don't cause a route when creating new items (TODO: eventually all trees should do this!) + var nonRoutingTreesOnCreate = ["content", "contentblueprints"]; function setMode(mode) { switch (mode) { @@ -115,16 +116,17 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve } var service = { - + /** * @ngdoc method * @name umbraco.services.navigationService#isRouteChangingNavigation * @methodOf umbraco.services.navigationService * * @description - * Detects if the route param differences will cause a navigation change or if the route param differences are + * Detects if the route param differences will cause a navigation/route change or if the route param differences are * only tracking state changes. - * This is used for routing operations where reloadOnSearch is false and when detecting form dirty changes when navigating to a different page. + * This is used for routing operations where "reloadOnSearch: false" or "reloadOnUrl: false", when detecting form dirty changes when navigating to a different page, + * and when we are creating new entities and moving from a route with the ?create=true parameter to an ID based parameter once it's created. * @param {object} currUrlParams Either a string path or a dictionary of route parameters * @param {object} nextUrlParams Either a string path or a dictionary of route parameters */ @@ -138,6 +140,14 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve nextUrlParams = pathToRouteParts(nextUrlParams); } + //first check if this is a ?create=true url being redirected to it's true url + if (currUrlParams.create === "true" && currUrlParams.id && currUrlParams.section && currUrlParams.tree && currUrlParams.method === "edit" && + !nextUrlParams.create && nextUrlParams.id && nextUrlParams.section === currUrlParams.section && nextUrlParams.tree === currUrlParams.tree && nextUrlParams.method === currUrlParams.method && + nonRoutingTreesOnCreate.indexOf(nextUrlParams.tree.toLowerCase()) >= 0) { + //this means we're coming from a path like /content/content/edit/1234?create=true to the created path like /content/content/edit/9999 + return false; + } + var allowRoute = true; //The only time that we want to not route is if only any of the nonRoutingQueryStrings have changed/added. @@ -339,7 +349,7 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve reloadSection: function(sectionAlias) { return navReadyPromise.promise.then(function () { - mainTreeApi.clearCache({ section: sectionAlias }); + treeService.clearCache({ section: sectionAlias }); return mainTreeApi.load(sectionAlias); }); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index e853e07092..2165c1b7cb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -77,4 +77,5 @@ angular.module("umbraco.services").factory("overlayService", overlayService); + })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index a2010d20f2..fef286ec7e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -2,7 +2,7 @@ * @ngdoc service * @name umbraco.services.searchService * - * + * * @description * Service for handling the main application search, can currently search content, media and members * @@ -15,10 +15,10 @@ * angular.forEach(results, function(result){ * //returns: * {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" } - * }) - * var result = - * }) - * + * }) + * var result = + * }) + * */ angular.module('umbraco.services') .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { @@ -42,11 +42,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom - } - - return entityResource.search(args.term, "Member", options).then(function (data) { + return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMemberResult(item); }); @@ -71,12 +67,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom, - ignoreUserStartNodes: args.ignoreUserStartNodes - } - - return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { + return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeKey).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureContentResult(item); }); @@ -101,12 +92,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom, - ignoreUserStartNodes: args.ignoreUserStartNodes - } - - return entityResource.search(args.term, "Media", options).then(function (data) { + return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeKey).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMediaResult(item); }); @@ -136,7 +122,7 @@ angular.module('umbraco.services') _.each(data, function (resultByType) { //we need to format the search result data to include things like the subtitle, urls, etc... - // this is done with registered angular services as part of the SearchableTreeAttribute, if that + // this is done with registered angular services as part of the SearchableTreeAttribute, if that // is not found, than we format with the default formatter var formatterMethod = searchResultFormatter.configureDefaultResult; //check if a custom formatter is specified... @@ -157,7 +143,7 @@ angular.module('umbraco.services') _.each(resultByType.results, function (item) { formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); }); - + }); return data; @@ -171,4 +157,4 @@ angular.module('umbraco.services') var currentSection = sectionAlias; } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js index 43022d0e86..b9bfa51122 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js @@ -13,12 +13,15 @@ function serverValidationManager($timeout) { var callbacks = []; /** calls the callback specified with the errors specified, used internally */ - function executeCallback(self, errorsForCallback, callback) { + function executeCallback(self, errorsForCallback, callback, culture) { callback.apply(self, [ - false, //pass in a value indicating it is invalid - errorsForCallback, //pass in the errors for this item - self.items]); //pass in all errors in total + false, // pass in a value indicating it is invalid + errorsForCallback, // pass in the errors for this item + self.items, // pass in all errors in total + culture // pass the culture that we are listing for. + ] + ); } function getFieldErrors(self, fieldName) { @@ -28,7 +31,7 @@ function serverValidationManager($timeout) { //find errors for this field name return _.filter(self.items, function (item) { - return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); }); } @@ -39,27 +42,50 @@ function serverValidationManager($timeout) { if (fieldName && !angular.isString(fieldName)) { throw "fieldName must be a string"; } + + if (!culture) { + culture = "invariant"; + } //find all errors for this property - return _.filter(self.items, function (item) { + return _.filter(self.items, function (item) { return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); } + + function getCultureErrors(self, culture) { + + if (!culture) { + culture = "invariant"; + } + + //find all errors for this property + return _.filter(self.items, function (item) { + return (item.culture === culture); + }); + } function notifyCallbacks(self) { for (var cb in callbacks) { - if (callbacks[cb].propertyAlias === null) { + if (callbacks[cb].propertyAlias === null && callbacks[cb].fieldName !== null) { //its a field error callback var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName); if (fieldErrors.length > 0) { - executeCallback(self, fieldErrors, callbacks[cb].callback); + executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture); } } - else { + else if (callbacks[cb].propertyAlias != null) { //its a property error var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].fieldName); if (propErrors.length > 0) { - executeCallback(self, propErrors, callbacks[cb].callback); + executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture); + } + } + else { + //its a culture error + var cultureErrors = getCultureErrors(self, callbacks[cb].culture); + if (cultureErrors.length > 0) { + executeCallback(self, cultureErrors, callbacks[cb].callback, callbacks[cb].culture); } } } @@ -130,11 +156,14 @@ function serverValidationManager($timeout) { } var id = String.CreateGuid(); - + if (!culture) { + culture = "invariant"; + } + if (propertyAlias === null) { callbacks.push({ propertyAlias: null, - culture: null, + culture: culture, fieldName: fieldName, callback: callback, id: id @@ -142,12 +171,11 @@ function serverValidationManager($timeout) { } else if (propertyAlias !== undefined) { //normalize culture to null - if (!culture) { - culture = null; - } + callbacks.push({ propertyAlias: propertyAlias, - culture: culture, fieldName: fieldName, + culture: culture, + fieldName: fieldName, callback: callback, id: id }); @@ -173,21 +201,20 @@ function serverValidationManager($timeout) { */ unsubscribe: function (propertyAlias, culture, fieldName) { + //normalize culture to null + if (!culture) { + culture = "invariant"; + } + if (propertyAlias === null) { //remove all callbacks for the content field callbacks = _.reject(callbacks, function (item) { - return item.propertyAlias === null && item.culture === null && item.fieldName === fieldName; + return item.propertyAlias === null && item.culture === culture && item.fieldName === fieldName; }); } else if (propertyAlias !== undefined) { - - //normalize culture to null - if (!culture) { - culture = null; - } - //remove all callbacks for the content property callbacks = _.reject(callbacks, function (item) { return item.propertyAlias === propertyAlias && item.culture === culture && @@ -213,7 +240,7 @@ function serverValidationManager($timeout) { //normalize culture to null if (!culture) { - culture = null; + culture = "invariant"; } var found = _.filter(callbacks, function (item) { @@ -235,7 +262,24 @@ function serverValidationManager($timeout) { getFieldCallbacks: function (fieldName) { var found = _.filter(callbacks, function (item) { //returns any callback that have been registered directly against the field - return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); + }); + return found; + }, + + /** + * @ngdoc function + * @name getCultureCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the culture. + */ + getCultureCallbacks: function (culture) { + var found = _.filter(callbacks, function (item) { + //returns any callback that have been registered directly/ONLY against the culture + return (item.culture === culture && item.propertyAlias === null && item.fieldName === null); }); return found; }, @@ -258,7 +302,7 @@ function serverValidationManager($timeout) { if (!this.hasFieldError(fieldName)) { this.items.push({ propertyAlias: null, - culture: null, + culture: "invariant", fieldName: fieldName, errorMsg: errorMsg }); @@ -270,7 +314,7 @@ function serverValidationManager($timeout) { var cbs = this.getFieldCallbacks(fieldName); //call each callback for this error for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback); + executeCallback(this, errorsForCallback, cbs[cb].callback, null); } }, @@ -288,9 +332,9 @@ function serverValidationManager($timeout) { return; } - //normalize culture to null + //normalize culture to "invariant" if (!culture) { - culture = null; + culture = "invariant"; } //only add the item if it doesn't exist @@ -309,9 +353,16 @@ function serverValidationManager($timeout) { var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName); //call each callback for this error for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback); + executeCallback(this, errorsForCallback, cbs[cb].callback, culture); } - }, + + //execute culture specific callbacks here too when a propery error is added + var cultureCbs = this.getCultureCallbacks(culture); + //call each callback for this error + for (var cb in cultureCbs) { + executeCallback(this, errorsForCallback, cultureCbs[cb].callback, culture); + } + }, /** * @ngdoc function @@ -330,7 +381,7 @@ function serverValidationManager($timeout) { //normalize culture to null if (!culture) { - culture = null; + culture = "invariant"; } //remove the item @@ -384,7 +435,7 @@ function serverValidationManager($timeout) { //normalize culture to null if (!culture) { - culture = null; + culture = "invariant"; } var err = _.find(this.items, function (item) { @@ -406,7 +457,7 @@ function serverValidationManager($timeout) { getFieldError: function (fieldName) { var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); }); return err; }, @@ -424,7 +475,7 @@ function serverValidationManager($timeout) { //normalize culture to null if (!culture) { - culture = null; + culture = "invariant"; } var err = _.find(this.items, function (item) { @@ -446,11 +497,33 @@ function serverValidationManager($timeout) { hasFieldError: function (fieldName) { var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); }); return err ? true : false; }, + + /** + * @ngdoc function + * @name hasCultureError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if the given culture has an error + */ + hasCultureError: function (culture) { + + //normalize culture to null + if (!culture) { + culture = "invariant"; + } + + var err = _.find(this.items, function (item) { + return item.culture === culture; + }); + return err ? true : false; + }, /** The array of error messages */ items: [] }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 61f96fed28..e61bd38bc0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -6,7 +6,7 @@ * @description * A service containing all logic for all of the Umbraco TinyMCE plugins */ -function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService, editorService, editorState) { +function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource) { //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce @@ -360,7 +360,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s icon: "code", tooltip: "View Source Code", onclick: function(){ - callback(); + if (callback) { + angularHelper.safeApply($rootScope, function() { + callback(); + }); + } } }); @@ -418,23 +422,12 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s insertMediaInEditor: function (editor, img) { if (img) { - var hasUdi = img.udi ? true : false; - var data = { alt: img.altText || "", src: (img.url) ? img.url : "nothing.jpg", - id: '__mcenew' + id: '__mcenew', + 'data-udi': img.udi }; - - if (hasUdi) { - data["data-udi"] = img.udi; - } else { - //Considering these fixed because UDI will now be used and thus - // we have no need for rel http://issues.umbraco.org/issue/U4-6228, http://issues.umbraco.org/issue/U4-6595 - //TODO: Kill rel attribute - data["rel"] = img.id; - data["data-id"] = img.id; - } editor.selection.setContent(editor.dom.createHTML('img', data)); @@ -902,38 +895,6 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }, - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#getAnchorNames - * @methodOf umbraco.services.tinyMceService - * - * @description - * From the given string, generates a string array where each item is the id attribute value from a named anchor - * 'some string with a named anchor' returns ['anchor'] - * - * @param {string} input the string to parse - */ - getAnchorNames: function (input) { - var anchors = []; - if (!input) { - return anchors; - } - - var anchorPattern = //gi; - var matches = input.match(anchorPattern); - - - if (matches) { - anchors = matches.map(function (v) { - return v.substring(v.indexOf('"') + 1, v.lastIndexOf('\\')); - }); - } - - return anchors.filter(function(val, i, self) { - return self.indexOf(val) === i; - }); - }, - insertLinkInEditor: function (editor, target, anchorElm) { var href = target.url; @@ -986,7 +947,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } } - if (!href) { + if (!href && !target.anchor) { editor.execCommand('unlink'); return; } @@ -1000,6 +961,10 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s return; } + if (!href) { + href = ""; + } + // Is email and not //user@domain.com and protocol (e.g. mailto:, sip:) is not specified if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf(':') === -1) { // assume it's a mailto link @@ -1149,47 +1114,42 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s let self = this; - function getIgnoreUserStartNodes(args) { - var ignoreUserStartNodes = false; - // Most property editors have a "config" property with ignoreUserStartNodes on then - if (args.model.config) { - ignoreUserStartNodes = Object.toBoolean(args.model.config.ignoreUserStartNodes); - } - // EXCEPT for the grid's TinyMCE editor, that one wants to be special and the config is called "configuration" instead - else if (args.model.configuration) { - ignoreUserStartNodes = Object.toBoolean(args.model.configuration.ignoreUserStartNodes); - } - return ignoreUserStartNodes; - } - //create link picker self.createLinkPicker(args.editor, function (currentTarget, anchorElement) { - var linkPicker = { - currentTarget: currentTarget, - anchors: editorState.current ? self.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], - ignoreUserStartNodes: getIgnoreUserStartNodes(args), - submit: function (model) { - self.insertLinkInEditor(args.editor, model.target, anchorElement); - editorService.close(); - }, - close: function () { - editorService.close(); - } - }; - editorService.linkPicker(linkPicker); + + + entityResource.getAnchors(args.model.value).then(function (anchorValues) { + var linkPicker = { + currentTarget: currentTarget, + dataTypeKey: args.model.dataTypeKey, + ignoreUserStartNodes: args.model.config.ignoreUserStartNodes, + anchors: anchorValues, + submit: function (model) { + self.insertLinkInEditor(args.editor, model.target, anchorElement); + editorService.close(); + }, + close: function () { + editorService.close(); + } + }; + editorService.linkPicker(linkPicker); + }); + }); //Create the insert media plugin self.createMediaPicker(args.editor, function (currentTarget, userData) { - var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - var startNodeIsVirtual = userData.startMediaIds.length !== 1; - - var ignoreUserStartNodes = getIgnoreUserStartNodes(args); - if (ignoreUserStartNodes) { - ignoreUserStartNodes = true; - startNodeId = -1; - startNodeIsVirtual = true; + var startNodeId, startNodeIsVirtual; + if (!args.model.config.startNodeId) { + if (args.model.config.ignoreUserStartNodes === true) { + startNodeId = -1; + startNodeIsVirtual = true; + } + else { + startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + startNodeIsVirtual = userData.startMediaIds.length !== 1; + } } var mediaPicker = { @@ -1199,7 +1159,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s disableFolderSelect: true, startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, + dataTypeKey: args.model.dataTypeKey, submit: function (model) { self.insertMediaInEditor(args.editor, model.selection[0]); editorService.close(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index d61d1c3ba1..3e60b09ad9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -41,7 +41,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return { /** Internal method to return the tree cache */ - _getTreeCache: function() { + _getTreeCache: function () { return treeCache; }, @@ -97,7 +97,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } //create a method outside of the loop to return the parent - otherwise jshint blows up - var funcParent = function() { + var funcParent = function () { return parentNode; }; @@ -168,7 +168,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * * @param {String} treeAlias The tree alias to check */ - getTreePackageFolder: function(treeAlias) { + getTreePackageFolder: function (treeAlias) { //we determine this based on the server variables if (Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.trees && @@ -220,7 +220,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var self = this; this.clearCache({ cacheKey: args.cacheKey, - filter: function(cc) { + filter: function (cc) { //get the new parent node from the tree cache var parent = self.getDescendantNode(cc.root, args.childrenOf); if (parent) { @@ -288,7 +288,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @param {object} args.node The tree node * @param {object} args.section The current section */ - loadNodeChildren: function(args) { + loadNodeChildren: function (args) { if (!args) { throw "No args object defined for loadNodeChildren"; } @@ -303,7 +303,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS args.node.loading = true; return this.getChildren(args) - .then(function(data) { + .then(function (data) { //set state to done and expand (only if there actually are children!) args.node.loading = false; @@ -320,10 +320,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return $q.when(data); - }, function(reason) { + }, function (reason) { //in case of error, emit event - eventsService.emit("treeService.treeNodeLoadError", {error: reason } ); + eventsService.emit("treeService.treeNodeLoadError", { error: reason }); //stop show the loading indicator args.node.loading = false; @@ -346,7 +346,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * Removes a given node from the tree * @param {object} treeNode the node to remove */ - removeNode: function(treeNode) { + removeNode: function (treeNode) { if (!angular.isFunction(treeNode.parent)) { return; } @@ -359,7 +359,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS parent.children.splice(parent.children.indexOf(treeNode), 1); parent.hasChildren = parent.children.length !== 0; - + //Notify that the node has been removed eventsService.emit("treeService.removeNode", { node: treeNode }); }, @@ -374,7 +374,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * Removes all child nodes from a given tree node * @param {object} treeNode the node to remove children from */ - removeChildNodes : function(treeNode) { + removeChildNodes: function (treeNode) { treeNode.expanded = false; treeNode.children = []; treeNode.hasChildren = false; @@ -413,7 +413,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @param {int} id id of descendant node * @param {string} treeAlias - optional tree alias, if fetching descendant node from a child of a listview document */ - getDescendantNode: function(treeNode, id, treeAlias) { + getDescendantNode: function (treeNode, id, treeAlias) { //validate if it is a section container since we'll need a treeAlias if it is one if (treeNode.isContainer === true && !treeAlias) { @@ -432,7 +432,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var root = getTreeRoot(tn.children[c]); //only return if we found the root in this child, otherwise continue. - if(root){ + if (root) { return root; } } @@ -531,7 +531,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node * @param {object} treeNode to retrive tree alias from */ - getTreeAlias : function(treeNode) { + getTreeAlias: function (treeNode) { var root = this.getTreeRoot(treeNode); if (root) { return root.metaData["treeAlias"]; @@ -570,7 +570,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var self = this; return treeResource.loadApplication(args) - .then(function(data) { + .then(function (data) { //this will be called once the tree app data has loaded var result = { name: data.name, @@ -624,7 +624,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } return treeResource.loadMenu(args.treeNode) - .then(function(data) { + .then(function (data) { //need to convert the icons to new ones for (var i = 0; i < data.length; i++) { data[i].cssclass = iconHelper.convertFromLegacyIcon(data[i].cssclass); @@ -677,7 +677,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * Re-loads the single node from the server * @param {object} node Tree node to reload */ - reloadNode: function(node) { + reloadNode: function (node) { if (!node) { throw "node cannot be null"; } @@ -691,10 +691,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //set the node to loading node.loading = true; - return this.getChildren({ node: node.parent(), section: node.section }).then(function(data) { + return this.getChildren({ node: node.parent(), section: node.section }).then(function (data) { //ok, now that we have the children, find the node we're reloading - var found = _.find(data, function(item) { + var found = _.find(data, function (item) { return item.id === node.id; }); if (found) { @@ -720,7 +720,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS else { return $q.reject(); } - }, function() { + }, function () { return $q.reject(); }); }, @@ -735,7 +735,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * This will return the current node's path by walking up the tree * @param {object} node Tree node to retrieve path for */ - getPath: function(node) { + getPath: function (node) { if (!node) { throw "node cannot be null"; } @@ -760,7 +760,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return reversePath.reverse(); }, - syncTree: function(args) { + syncTree: function (args) { if (!args) { throw "No args object defined for syncTree"; @@ -800,6 +800,8 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } } + var deferred = $q.defer(); + //now that we have the first id to lookup, we can start the process var self = this; @@ -831,6 +833,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS else { //couldn't find it in the return self.loadNodeChildren({ node: node, section: node.section }).then(function (children) { + + //send back some progress to allow the caller to deal with expanded nodes + deferred.notify({ type: "treeNodeExpanded", node: node, children: children }) + //ok, got the children, let's find it var found = self.getChildNode(node, args.path[currPathIndex]); if (found) { @@ -858,8 +864,16 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS }; //start - return doSync(); + var wrappedPromise = doSync(); + //then wrap it + wrappedPromise.then(function (args) { + deferred.resolve(args); + }, function (args) { + deferred.reject(args); + }); + + return deferred.promise; } }; diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js index 83b3c920d7..654bbb1d03 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js @@ -9,8 +9,8 @@ * */ function MainController($scope, $location, appState, treeService, notificationsService, - userService, historyService, updateChecker, assetsService, eventsService, - tmhDynamicLocale, localStorageService, editorService, overlayService, focusService) { + userService, historyService, updateChecker, navigationService, eventsService, + tmhDynamicLocale, localStorageService, editorService, overlayService) { //the null is important because we do an explicit bool check on this in the view $scope.authenticated = null; @@ -105,6 +105,13 @@ function MainController($scope, $location, appState, treeService, notificationsS //if the user has changed we need to redirect to the root so they don't try to continue editing the //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true) if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) { + + var section = appState.getSectionState("currentSection"); + if (section) { + //if there's a section already assigned, reload it so the tree is cleared + navigationService.reloadSection(section); + } + $location.path("/").search(""); historyService.removeAll(); treeService.clearCache(); diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index c426d0d955..e4c94f3c66 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -140,8 +140,9 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //// TODO: remove this it's not a thing //$scope.selectedId = navigationService.currentId; + var isInit = false; var evts = []; - + //Listen for global state changes evts.push(eventsService.on("appState.globalState.changed", function (e, args) { if (args.key === "showNavigation") { @@ -236,8 +237,10 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar })); //when the application is ready and the user is authorized, setup the data + //this will occur anytime a new user logs in! evts.push(eventsService.on("app.ready", function (evt, data) { - init(); + $scope.authenticated = true; + ensureInit(); })); // event for infinite editors @@ -305,9 +308,14 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar /** * Called when the app is ready and sets up the navigation (should only be called once) */ - function init() { + function ensureInit() { - $scope.authenticated = true; + //only run once ever! + if (isInit) { + return; + } + + isInit = true; var navInit = false; diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index eaa2fe7b31..e169d78b36 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -1,6 +1,6 @@ /** Executed when the application starts, binds to events and set global state */ -app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService', 'tourService', 'dashboardResource', - function (userService, $q, $log, $rootScope, $route, $location, urlHelper, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService, tourService, dashboardResource) { +app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', + function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService) { //This sets the default jquery ajax headers to include our csrf token, we // need to user the beforeSend method because our token changes per user/login so @@ -91,13 +91,6 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH $rootScope.locationTitle = "Umbraco - " + $location.$$host; } - //reset the editorState on each successful route chage - editorState.reset(); - - //reset the file manager on each route change, the file collection is only relavent - // when working in an editor and submitting data to the server. - //This ensures that memory remains clear of any files and that the editors don't have to manually clear the files. - fileManager.clearFiles(); }); /** When the route change is rejected - based on checkAuth - we'll prevent the rejected route from executing including @@ -122,7 +115,7 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH }); //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. - //This is the case when a route uses reloadOnSearch: false which is the case for many or our routes so that we are able to maintain + //This is the case when a route uses "reloadOnSearch: false" or "reloadOnUrl: false" which is the case for many or our routes so that we are able to maintain //global state query strings without force re-loading views. //We can then detect if it's a location change that should force a route or not programatically. $rootScope.$on('$routeUpdate', function (event, next) { diff --git a/src/Umbraco.Web.UI.Client/src/less/accessibility/sr-only.less b/src/Umbraco.Web.UI.Client/src/less/accessibility/sr-only.less new file mode 100644 index 0000000000..21ca5e6718 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/accessibility/sr-only.less @@ -0,0 +1,24 @@ + +// sr-only - based on the boot strap naming conventions used to remove an element from the view, whilst retaining accessibily for screen readers. More info available at https://getbootstrap.com/docs/4.0/utilities/screenreaders/ +// -------------------------------------------------- +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; + + &--focusable:active, + &--focusable:focus, + &--hoverable:hover { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/application/animations.less b/src/Umbraco.Web.UI.Client/src/less/application/animations.less index 91e2213775..e081c4919a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/animations.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/animations.less @@ -40,10 +40,6 @@ // TREE ANIMATION -.umb-tree-item.ng-animate { - display: none; -} - .umb-tree-item--deleted.ng-leave { animation: leave 600ms cubic-bezier(0.445, 0.050, 0.550, 0.950); display: block; diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 1e48500bb0..bd1cdd5b4f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -80,6 +80,9 @@ @import "forms/umb-validation-label.less"; +// Umbraco Accessibility +@import "accessibility/sr-only.less"; + // Umbraco Components @import "components/application/umb-app-header.less"; @import "components/application/umb-app-content.less"; @@ -162,6 +165,7 @@ @import "components/umb-textarea.less"; @import "components/umb-dropdown.less"; @import "components/umb-range-slider.less"; +@import "components/umb-number.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index f21c7f3106..91a6c29a17 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -67,6 +67,21 @@ border-color: rgba(0,0,0,0.09); } +// Button Reset - remove the default browser styles from the button element +// -------------------------------------------------- + +.btn-reset { + padding: 0; + margin: 0; + border: none; + background: none; + color: currentColor; + font-family: @baseFontFamily; + font-size: @baseFontSize; + line-height: @baseLineHeight; + cursor: pointer; +} + // Button Sizes // -------------------------------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less index d4b21e66f0..bd1b8ab07a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less @@ -16,27 +16,26 @@ margin-right: -10px; } -.umb-app-header__action a { +.umb-app-header__button { padding-left: 10px; padding-right: 10px; text-decoration: none; display: flex; align-items: center; height: @appHeaderHeight; -} - -.umb-app-header__action a { outline: none; + &:focus { .tabbing-active & { .umb-app-header__action-icon::after { content: ''; position: absolute; z-index:10000; - top: -7px; - left: -7px; + top: 50%; + left: 50%; width: 36px; height: 35px; + transform: translate(-50%, -50%); border-radius: 3px; box-shadow: 0 0 2px @pinkLight, inset 0 0 2px 1px @pinkLight; } @@ -51,7 +50,7 @@ font-size: 22px; } -.umb-app-header__action a:hover .umb-app-header__action-icon, -.umb-app-header__action a:focus .umb-app-header__action-icon { +.umb-app-header__button:hover .umb-app-header__action-icon, +.umb-app-header__button:focus .umb-app-header__action-icon { opacity: 1; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less index 1d9aaabd44..18e331756e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less @@ -2,7 +2,7 @@ height: 100%; width: 100%; position: fixed; - z-index: @zindexOverlayBackdrop; + z-index: @zindexUmbOverlay; top: 0; left: 0; pointer-events: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less index a8fc9c7f8e..70e4f3d372 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less @@ -4,7 +4,7 @@ */ .umb-search { position: fixed; - z-index: @zindexUmbOverlay; + z-index: @zindexSearchBox; width: 660px; max-width: 90%; transform: translate(-50%, 0); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less index 6f23677a1c..a621370d02 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less @@ -21,23 +21,23 @@ background-color: @inputBorder; position: relative; transition: background-color 120ms; - - .umb-toggle:hover &, + + .umb-toggle:hover &, .umb-toggle:focus & { border-color: @inputBorderFocus; } - + .umb-toggle.umb-toggle--checked & { border-color: @ui-btn; background-color: @ui-btn; - + &:hover { background-color: @ui-btn-hover; } } - - .umb-toggle.umb-toggle--checked:focus & { - border-color: black; + + .tabbing-active .umb-toggle:focus & { + box-shadow: 0 0 0 2px highlight; } } @@ -54,12 +54,12 @@ background-color: @white; border-radius: 8px; transition: transform 120ms ease-in-out, background-color 120ms; - + .umb-toggle.umb-toggle--checked & { transform: translateX(20px); background-color: white; } - + } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index 21d459c598..52dd7ea678 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -174,6 +174,8 @@ a.umb-editor-header__close-split-view:hover { max-width: 50%; white-space: nowrap; + user-select: none; + span { text-overflow: ellipsis; overflow: hidden; @@ -189,6 +191,25 @@ a.umb-variant-switcher__toggle { color: @ui-action-discreet-type-hover; } } + + &.--error { + &::before { + content: '!'; + position: absolute; + top: -8px; + right: -10px; + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 10px; + text-align: center; + font-weight: bold; + background-color: @errorBackground; + color: @errorText; + } + } } .umb-variant-switcher__expand { @@ -205,20 +226,16 @@ a.umb-variant-switcher__toggle { align-items: center; border-bottom: 1px solid @gray-9; position: relative; - &:hover .umb-variant-switcher__name-wrapper { - - } } .umb-variant-switcher__item:last-child { border-bottom: none; } -.umb-variant-switcher_item--current { +.umb-variant-switcher__item.--current { color: @ui-light-active-type; } -.umb-variant-switcher_item--current .umb-variant-switcher__name-wrapper { - //background-color: @gray-10; +.umb-variant-switcher__item.--current .umb-variant-switcher__name-wrapper { border-left: 4px solid @ui-active; } @@ -227,7 +244,7 @@ a.umb-variant-switcher__toggle { outline: none; } -.umb-variant-switcher_item--not-allowed:not(.umb-variant-switcher_item--current) .umb-variant-switcher__name-wrapper:hover { +.umb-variant-switcher__item.--not-allowed:not(.--current) .umb-variant-switcher__name-wrapper:hover { //background-color: @white !important; cursor: default; } @@ -237,6 +254,29 @@ a.umb-variant-switcher__toggle { cursor: pointer; } +.umb-variant-switcher__item.--error { + .umb-variant-switcher__name { + color: @red; + &::after { + content: '!'; + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 5px; + top: -6px; + width: 14px; + height: 14px; + border-radius: 7px; + font-size: 8px; + text-align: center; + font-weight: bold; + background-color: @errorBackground; + color: @errorText; + } + } +} + .umb-variant-switcher__name-wrapper { font-size: 14px; flex: 1; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 78cccac57a..6cf3598638 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -32,15 +32,32 @@ border-radius: 3px; } -.umb-editor-sub-header.-umb-sticky-bar { - box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); +[umb-sticky-bar] { transition: box-shadow 240ms; - margin-top: 0; + margin-top: 0; margin-bottom: 0; - top: calc(@appHeaderHeight + @editorHeaderHeight); + position:sticky; + z-index: 99; - .umb-editor--infinityMode & { - top: calc(@editorHeaderHeight); + &.umb-sticky-bar--active { + box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); + } +} + +.umb-sticky-sentinel { + position: absolute; + left: 0; + width: 100%; + pointer-events: none; + + &.-top { + top:0px; + height:1px; + } + + &.-bottom { + bottom:50px; + height:10px; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index f5050fad85..c8f0195ea5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -12,6 +12,7 @@ display: flex; flex-wrap: nowrap; flex-direction: column; + height: 100%; } .umb-overlay .umb-overlay-header { @@ -23,8 +24,6 @@ padding: 20px 30px 0; } - - .umb-overlay__section-header { width: 100%; margin-top:30px; @@ -113,10 +112,6 @@ padding: 30px 30px 0; } -.umb-overlay.umb-overlay-center .umb-overlay__form { - -} - .umb-overlay.umb-overlay-center .umb-overlay-drawer { border: none; background: transparent; 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 index 0dbc4c381c..9947c793c2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less @@ -1,5 +1,5 @@ .umb-prevalues-multivalues { - width: 400px; + width: 425px; max-width: 100%; .umb-overlay & { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less index 14544ded10..0c231830de 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less @@ -27,6 +27,7 @@ } .umb-action-link { + position: relative; white-space: nowrap; font-size: 15px; color: @black; @@ -34,6 +35,7 @@ text-decoration: none; cursor: pointer; display: flex; + width: 100%; align-items: center; body.touch & { @@ -92,6 +94,7 @@ font-size: 14px; color: @black; margin-left: 10px; + text-align: left; } small { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less index b372d41eb4..8f0b55f9ed 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less @@ -8,12 +8,28 @@ user-select: none; } - &:hover ins { + &:hover .umb-tree-item__arrow { visibility: visible; cursor: pointer } } +.umb-tree-item__arrow { + position: relative; + margin-left: -16px; + width: 16px; + height: 16px; + visibility: hidden; + text-decoration: none; + font-size: 12px; + line-height: 12px; + transition: color 120ms; + + &:hover { + color: @ui-option-type-hover; + } +} + .umb-tree-item > .umb-tree-item__inner { &:hover .umb-tree-item__label { @@ -95,7 +111,7 @@ a, .umb-tree-icon, - ins { + .umb-tree-item__arrow { color: @ui-active-type !important; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 202c9400da..5c54232200 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -15,20 +15,6 @@ text-decoration: none; } - ins { - margin: -4px 0 0 -16px; - width: 16px; - height: 16px; - visibility: hidden; - text-decoration: none; - font-size: 12px; - transition: color 120ms; - - &:hover { - color: @ui-option-type-hover; - } - } - i.noSpr { display: inline-block; margin-top: 1px; @@ -62,7 +48,7 @@ } body.touch .umb-tree { - ins { + .umb-tree-item__arrow { font-size: 14px; visibility: visible; padding: 7px; @@ -104,7 +90,13 @@ body.touch .umb-tree { } > .umb-options { - visibility: visible; + position: relative; + width: auto; + height: auto; + margin: 0 10px 0 auto; + padding: 9px 5px; + overflow: visible; + clip: auto; } .umb-tree-icon { @@ -178,7 +170,7 @@ body.touch .umb-tree { } .umb-options { - visibility: hidden; + position: relative; display: flex; flex: 0 0 auto; justify-content: flex-end; @@ -192,6 +184,20 @@ body.touch .umb-tree { background: @btnBackgroundHighlight; } + // NOTE - We're having to repeat ourselves here due to an .sr-only class appearing in umbraco/lib/font-awesome/css/font-awesome.min.css + &.sr-only--hoverable:hover, + &.sr-only--focusable:focus { + position: relative; + display: flex; + flex: 0 0 auto; + justify-content: flex-end; + padding: 9px 5px; + text-align: center; + margin: 0 10px 0 auto; + cursor: pointer; + border-radius: 3px; + } + i { height: 5px !important; width: 5px !important; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less index 1f0808a592..f972633bf4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less @@ -102,6 +102,7 @@ border-radius: 40px; box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); font-size: 18px; + cursor: pointer; } .umb_confirm-action__overlay-action:hover { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index d8a5ebf9d8..deb573920f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -6,7 +6,7 @@ flex-wrap: wrap; align-items: center; position: relative; - padding: 0; + padding: 0 !important; margin: 0; min-height: 22px; line-height: 22px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less index f7aa0e4558..7b8845542e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less @@ -20,6 +20,15 @@ .umb-logviewer__sidebar { flex: 0 0 @sidebarwidth; + + .flatpickr-input { + background-color: @white; + border: 0; + width: 100%; + text-align: center; + font-size: larger; + padding-top: 20px; + } } @media (max-width: 768px) { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less index 6af0641d8c..30a32b8123 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less @@ -1,9 +1,10 @@ .umb-multiple-textbox{ &__confirm{ position: relative; + display: inline-block; &-action{ - margin: 0; + margin: -2px 0 0 0; padding: 2px; background: transparent; border: 0 none; @@ -11,6 +12,10 @@ } } +.umb-multiple-textbox .icon-wrapper { + width: 50px; +} + .umb-multiple-textbox .textbox-wrapper { align-items: center; margin-bottom: 15px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index f1a6300481..59c90972d2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -56,13 +56,20 @@ -webkit-user-select: none; -o-user-select: none; user-select: none; + + &:hover { + .umb-nested-content__heading .umb-nested-content__item-name { + padding-right: 60px; + } + } + } .umb-nested-content__heading { line-height: 20px; position: relative; margin-top:1px; - padding: 15px 20px; + padding: 15px 5px; color:@ui-option-type; border-radius: 3px 3px 0 0; @@ -71,16 +78,21 @@ } i { - display: inline; - margin-right: 10px; + position: absolute; + margin-top: -1px; } .umb-nested-content__item-name { - display: inline; + display: block; max-height: 20px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + padding-left: 5px; + + &.--has-icon { + padding-left: 30px; + } } } @@ -89,9 +101,10 @@ opacity: 0; transition: opacity 120ms ease-in-out; position: absolute; - right: 8px; - top: 4px; + right: 0; + top: 3px; padding: 5px; + background-color: @white; } .umb-nested-content__item--active > .umb-nested-content__header-bar { @@ -100,6 +113,9 @@ &:hover { color:@ui-option-type; } + .umb-nested-content__item-name { + padding-right: 60px; + } } .umb-nested-content__icons { background-color: @ui-active; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-number.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-number.less new file mode 100644 index 0000000000..c0e3dee8e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-number.less @@ -0,0 +1,3 @@ +.umb-number { + .umb-property-editor--limit-width(); +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less index 3bc431e01f..7c327bfb88 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less @@ -63,12 +63,14 @@ .umb-user-card__goToUser { &:hover, &:focus { text-decoration: none; + .umb-user-card__name { text-decoration: underline; color: @ui-option-type-hover; } + .umb-avatar { - border: 1px solid @ui-option-type-hover; + box-shadow: 0px 1px 3px rgba(0, 0, 0, .5); } } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-group-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-group-preview.less index f39096b565..47a2af9231 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-group-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-group-preview.less @@ -34,9 +34,13 @@ margin-top: 2px; } -.umb-user-group-preview__permission { +.umb-user-group-preview__permissions { font-size: 13px; color: @gray-3; + + .umb-user-group-preview__permission:not(:last-child):after { + content: ', '; + } } .umb-user-group-preview__actions { @@ -61,4 +65,4 @@ .umb-user-group-preview__action--red:hover { color: @red; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-table.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-table.less index 0c61a5d113..31b126e77c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-table.less @@ -3,16 +3,21 @@ .umb-user-table-col-avatar { flex: 0 0 32px; padding: 15px 0; - + + > a { + overflow: visible; + } + .umb-checkmark { margin-left:5px; } } - + .umb-table-cell a { + &:hover, &:focus { .umb-avatar { - border: 1px solid @ui-option-type-hover; + box-shadow: 0px 1px 3px rgba(0, 0, 0, .5); } } } diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 966fc1fb11..cfbb8b78ab 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -809,6 +809,7 @@ legend + .control-group { // Labels on own row .form-horizontal .control-label { + float:none; width: 100%; } .form-horizontal .controls { diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index 726b3fa5ed..0b6d1b7a60 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -221,3 +221,12 @@ pre { border: 0; } } + +/* Styling for content/media sort order dialog */ +.sort-order { + td.tree-icon { + font-size:20px; + width:20px; + padding-right:0; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 52a573d8c4..84de751b12 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -113,6 +113,10 @@ bottom: 0px; padding: 20px; margin: 0; + + .btn.umb-outline { + position: relative + } } /*we will always make sure to wrap iframe dialogs in proper padding*/ diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 435b64ad34..92351d09ca 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -5,6 +5,7 @@ // -------------------------------------------------- .umb-property-editor { width: 100%; + position:relative; } .umb-property-editor-tiny { diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index ccf52acc53..9f537a7931 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -5,10 +5,6 @@ position: relative; .umb-property-editor--limit-width(); - - .-loading { - position: absolute; - } } .umb-rte .mce-tinymce { @@ -76,3 +72,7 @@ line-height: 20px; } } + +.mce-fullscreen { + position:absolute; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index 5a1de02617..ef6c5f5046 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -57,16 +57,17 @@ ul.sections>li.current>a::after { opacity: 1; transform: translateY(0px); } -ul.sections>li.current>a .section__name, -ul.sections>li>a:hover .section__name, -ul.sections>li>a:focus .section__name { - opacity: 1; +ul.sections > li.current > a .section__name, +ul.sections > li > a:hover .section__name { + opacity: 1; -webkit-font-smoothing: subpixel-antialiased; } -ul.sections>li>a:focus .section__name { +ul.sections > li > a:focus .section__name { .tabbing-active & { - box-shadow: 0 0 2px @pinkLight, inset 0 0 2px 1px @pinkLight; + + border: 1px solid; + border-color: @gray-9; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less index 69bbeef0af..b9c8b909e8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less @@ -57,3 +57,12 @@ .mb5 { margin-bottom: @spacing-extra-large; } .mb6 { margin-bottom: @spacing-extra-extra-large; } .mb7 { margin-bottom: @spacing-extra-extra-extra-large; } + +.ml0 { margin-left: @spacing-none; } +.ml1 { margin-left: @spacing-extra-small; } +.ml2 { margin-left: @spacing-small; } +.ml3 { margin-left: @spacing-medium; } +.ml4 { margin-left: @spacing-large; } +.ml5 { margin-left: @spacing-extra-large; } +.ml6 { margin-left: @spacing-extra-extra-large; } +.ml7 { margin-left: @spacing-extra-extra-extra-large; } diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index cc9dced7df..a1dc0ba187 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -426,7 +426,7 @@ @zindexFixedNavbar: 1030; @zindexModalBackdrop: 1040; @zindexModal: 1050; - +@zindexSearchBox: 8000; @zindexUmbOverlay: 7500; @zindexOverlayBackdrop: 2000; diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index 7d6584d2f1..1316642e93 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -113,7 +113,10 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.exitPreview = function () { var culture = $location.search().culture || getParameterByName("culture"); - var relativeUrl = "/" + $scope.pageId +'?culture='+ culture; + var relativeUrl = "/" + $scope.pageId; + if (culture) { + relativeUrl += '?culture=' + culture; + } window.top.location.href = "../preview/end?redir=" + encodeURIComponent(relativeUrl); }; diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 001888f3ca..556a4d6aef 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -39,10 +39,14 @@ app.config(function ($routeProvider) { $route.current.params.section = "content"; } + var found = _.find(user.allowedSections, function (s) { + return s.localeCompare($route.current.params.section, undefined, { sensitivity: 'accent' }) === 0; + }) + // U4-5430, Benjamin Howarth // We need to change the current route params if the user only has access to a single section // To do this we need to grab the current user's allowed sections, then reject the promise with the correct path. - if (user.allowedSections.indexOf($route.current.params.section) > -1) { + if (found) { //this will resolve successfully so the route will continue return $q.when(true); } else { @@ -119,7 +123,7 @@ app.config(function ($routeProvider) { sectionService.getSectionsForUser().then(function(sections) { //find the one we're requesting var found = _.find(sections, function(s) { - return s.alias === $routeParams.section; + return s.alias.localeCompare($routeParams.section, undefined, { sensitivity: 'accent' }) === 0; }) if (found && found.routePath) { //there's a custom route path so redirect @@ -224,6 +228,7 @@ app.config(function ($routeProvider) { }, reloadOnSearch: false, + reloadOnUrl: false, resolve: canRoute(true) }) .otherwise({ redirectTo: '/login' }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index 029f765985..5da14fc6d3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -1,6 +1,6 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", - function ($scope, eventsService, entityResource, contentResource, mediaResource, mediaHelper, udiParser, userService, localizationService, tinyMceService, editorService) { + function ($scope, eventsService, entityResource, mediaResource, mediaHelper, udiParser, userService, localizationService, editorService) { var vm = this; var dialogOptions = $scope.model; @@ -20,19 +20,18 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.model.title = value; }); } - + $scope.customTreeParams = dialogOptions.dataTypeKey ? "dataTypeKey=" + dialogOptions.dataTypeKey : ""; $scope.dialogTreeApi = {}; $scope.model.target = {}; $scope.searchInfo = { searchFromId: null, searchFromName: null, showSearch: false, + dataTypeKey: dialogOptions.dataTypeKey, results: [], - selectedSearchResults: [], - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes + selectedSearchResults: [] }; - $scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : ""; $scope.showTarget = $scope.model.hideTarget !== true; // this ensures that we only sync the tree once and only when it's ready @@ -60,10 +59,10 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", if (dialogOptions.currentTarget) { // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target $scope.model.target = angular.copy(dialogOptions.currentTarget); - //if we have a node ID, we fetch the current node to build the form data + // if we have a node ID, we fetch the current node to build the form data if ($scope.model.target.id || $scope.model.target.udi) { - //will be either a udi or an int + // will be either a udi or an int var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; if ($scope.model.target.udi) { @@ -88,15 +87,11 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", oneTimeTreeSync.sync(); }); - // get the content properties to build the anchor name list - - var options = {}; - options.ignoreUserStartNodes = dialogOptions.ignoreUserStartNodes; - - contentResource.getById(id, options).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.model.target.url = resp.urls[0].text; + entityResource.getUrlAndAnchors(id).then(function (resp) { + $scope.anchorValues = resp.anchorValues; + $scope.model.target.url = resp.url; }); + } } else if ($scope.model.target.url.length) { // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs @@ -143,13 +138,11 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", if (args.node.id < 0) { $scope.model.target.url = "/"; - } else { - var options = {}; - options.ignoreUserStartNodes = dialogOptions.ignoreUserStartNodes; - - contentResource.getById(args.node.id, options).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.model.target.url = resp.urls[0].text; + } + else { + entityResource.getUrlAndAnchors(args.node.id).then(function (resp) { + $scope.anchorValues = resp.anchorValues; + $scope.model.target.url = resp.url; }); } @@ -158,6 +151,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", } } + function nodeExpandedHandler(args) { // open mini list view for list views if (args.node.metaData.isContainer) { @@ -167,17 +161,21 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.switchToMediaPicker = function () { userService.getCurrentUser().then(function (userData) { - var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - var startNodeIsVirtual = userData.startMediaIds.length !== 1; - if (dialogOptions.ignoreUserStartNodes) { + + var startNodeId, startNodeIsVirtual; + if (dialogOptions.ignoreUserStartNodes === true) { startNodeId = -1; startNodeIsVirtual = true; } + else { + startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + startNodeIsVirtual = userData.startMediaIds.length !== 1; + } var mediaPicker = { startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + dataTypeKey: dialogOptions.dataTypeKey, submit: function (model) { var media = model.selection[0]; @@ -185,7 +183,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.model.target.udi = media.udi; $scope.model.target.isMedia = true; $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); + $scope.model.target.url = media.image; editorService.close(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html index b7f63cfe22..704b61e333 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html @@ -25,7 +25,7 @@ ng-model="model.target.url" ng-disabled="model.target.id || model.target.udi" /> - + - + - + - +
Link to page
- +
- - +
- - - +
- + enablecheckboxes="true" + customtreeparams="{{customTreeParams}}">
- - - + - +
Link to media
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index f37e1156a8..5efc25fa10 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Editors.MediaPickerController", - function ($scope, mediaResource, entityResource, mediaHelper, mediaTypeHelper, eventsService, userService, treeService, localStorageService, localizationService, editorService) { + function ($scope, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) { if (!$scope.model.title) { localizationService.localizeMany(["defaultdialogs_selectMedia", "general_includeFromsubFolders"]) @@ -20,13 +20,13 @@ angular.module("umbraco") $scope.showDetails = dialogOptions.showDetails; $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - $scope.ignoreUserStartNodes = Object.toBoolean(dialogOptions.ignoreUserStartNodes); $scope.cropSize = dialogOptions.cropSize; $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); $scope.lockedFolder = true; $scope.allowMediaEdit = dialogOptions.allowMediaEdit ? dialogOptions.allowMediaEdit : false; - var userStartNodes = []; + var userStartNodes = []; + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); if ($scope.onlyImages) { @@ -47,17 +47,21 @@ angular.module("umbraco") $scope.acceptedMediatypes = []; mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { + .then(function (types) { $scope.acceptedMediatypes = types; }); + var dataTypeKey = null; + if($scope.model && $scope.model.dataTypeKey) { + dataTypeKey = $scope.model.dataTypeKey; + } $scope.searchOptions = { pageNumber: 1, pageSize: 100, totalItems: 0, totalPages: 0, - filter: "", - ignoreUserStartNodes: $scope.model.ignoreUserStartNodes + filter: '', + dataTypeKey: dataTypeKey }; //preload selected item @@ -67,12 +71,12 @@ angular.module("umbraco") } function onInit() { - userService.getCurrentUser().then(function(userData) { + userService.getCurrentUser().then(function (userData) { userStartNodes = userData.startMediaIds; if ($scope.startNodeId !== -1) { entityResource.getById($scope.startNodeId, "media") - .then(function(ent) { + .then(function (ent) { $scope.startNodeId = ent.id; run(); }); @@ -96,7 +100,7 @@ angular.module("umbraco") //media object so we need to look it up var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; var altText = $scope.target.altText; - mediaResource.getById(id) + entityResource.getById(id, "Media") .then(function (node) { $scope.target = node; if (ensureWithinStartNode(node)) { @@ -106,28 +110,28 @@ angular.module("umbraco") $scope.openDetailsDialog(); } }, - gotoStartNode); + gotoStartNode); } } - $scope.upload = function(v) { + $scope.upload = function (v) { angular.element(".umb-file-dropzone .file-select").trigger("click"); }; - $scope.dragLeave = function(el, event) { + $scope.dragLeave = function (el, event) { $scope.activeDrag = false; }; - $scope.dragEnter = function(el, event) { + $scope.dragEnter = function (el, event) { $scope.activeDrag = true; }; - $scope.submitFolder = function() { + $scope.submitFolder = function () { if ($scope.model.newFolderName) { $scope.model.creatingFolder = true; mediaResource .addFolder($scope.model.newFolderName, $scope.currentFolder.id) - .then(function(data) { + .then(function (data) { //we've added a new folder so lets clear the tree cache for that specific item treeService.clearCache({ cacheKey: "__media", //this is the main media tree cache key @@ -143,7 +147,7 @@ angular.module("umbraco") } }; - $scope.enterSubmitFolder = function(event) { + $scope.enterSubmitFolder = function (event) { if (event.keyCode === 13) { $scope.submitFolder(); event.stopPropagation(); @@ -159,19 +163,17 @@ angular.module("umbraco") folder = { id: -1, name: "Media", icon: "icon-folder" }; } - var options = {}; if (folder.id > 0) { - options.ignoreUserStartNodes = $scope.model.ignoreUserStartNodes; - entityResource.getAncestors(folder.id, "media", options) - .then(function(anc) { + entityResource.getAncestors(folder.id, "media", null, { dataTypeKey: dataTypeKey }) + .then(function (anc) { $scope.path = _.filter(anc, - function(f) { + function (f) { return f.path.indexOf($scope.startNodeId) !== -1; }); }); mediaTypeHelper.getAllowedImagetypes(folder.id) - .then(function(types) { + .then(function (types) { $scope.acceptedMediatypes = types; }); } else { @@ -179,26 +181,13 @@ angular.module("umbraco") } $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; - $scope.currentFolder = folder; localStorageService.set("umbLastOpenedMediaNodeId", folder.id); - options.ignoreUserStartNodes = $scope.ignoreUserStartNodes; - return getChildren(folder.id, options); + return getChildren(folder.id); }; - function hasFolderAccess(node) { - var nodePath = node.path ? node.path.split(',') : [node.id]; - - for (var i = 0; i < nodePath.length; i++) { - if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) - return true; - } - - return false; - } - - $scope.clickHandler = function(image, event, index) { + $scope.clickHandler = function (image, event, index) { if (image.isFolder) { if ($scope.disableFolderSelect) { $scope.gotoFolder(image); @@ -226,7 +215,7 @@ angular.module("umbraco") } }; - $scope.clickItemName = function(item) { + $scope.clickItemName = function (item) { if (item.isFolder) { $scope.gotoFolder(item); } @@ -258,8 +247,8 @@ angular.module("umbraco") images.length = 0; } - $scope.onUploadComplete = function(files) { - $scope.gotoFolder($scope.currentFolder).then(function() { + $scope.onUploadComplete = function (files) { + $scope.gotoFolder($scope.currentFolder).then(function () { if (files.length === 1 && $scope.model.selection.length === 0) { var image = $scope.images[$scope.images.length - 1]; $scope.target = image; @@ -269,7 +258,7 @@ angular.module("umbraco") }); }; - $scope.onFilesQueue = function() { + $scope.onFilesQueue = function () { $scope.activeDrag = false; }; @@ -287,16 +276,27 @@ angular.module("umbraco") } } + function hasFolderAccess(node) { + var nodePath = node.path ? node.path.split(',') : [node.id]; + + for (var i = 0; i < nodePath.length; i++) { + if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) + return true; + } + + return false; + } + function gotoStartNode(err) { $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); } - $scope.openDetailsDialog = function() { + $scope.openDetailsDialog = function () { $scope.mediaPickerDetailsOverlay = {}; $scope.mediaPickerDetailsOverlay.show = true; - $scope.mediaPickerDetailsOverlay.submit = function(model) { + $scope.mediaPickerDetailsOverlay.submit = function (model) { $scope.model.selection.push($scope.target); $scope.model.submit($scope.model); @@ -304,42 +304,43 @@ angular.module("umbraco") $scope.mediaPickerDetailsOverlay = null; }; - $scope.mediaPickerDetailsOverlay.close = function(oldModel) { + $scope.mediaPickerDetailsOverlay.close = function (oldModel) { $scope.mediaPickerDetailsOverlay.show = false; $scope.mediaPickerDetailsOverlay = null; }; }; - var debounceSearchMedia = _.debounce(function() { - $scope.$apply(function() { - if ($scope.searchOptions.filter) { - searchMedia(); - } else { - // reset pagination - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: "", - ignoreUserStartNodes: $scope.model.ignoreUserStartNodes - }; - getChildren($scope.currentFolder.id); - } - }); - }, 500); + var debounceSearchMedia = _.debounce(function () { + $scope.$apply(function () { + if ($scope.searchOptions.filter) { + searchMedia(); + } else { + + // reset pagination + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + dataTypeKey: dataTypeKey + }; + getChildren($scope.currentFolder.id); + } + }); + }, 500); - $scope.changeSearch = function() { + $scope.changeSearch = function () { $scope.loading = true; debounceSearchMedia(); }; - $scope.toggle = function() { + $scope.toggle = function () { // Make sure to activate the changeSearch function everytime the toggle is clicked $scope.changeSearch(); } - $scope.changePagination = function(pageNumber) { + $scope.changePagination = function (pageNumber) { $scope.loading = true; $scope.searchOptions.pageNumber = pageNumber; searchMedia(); @@ -348,9 +349,9 @@ angular.module("umbraco") function searchMedia() { $scope.loading = true; entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions) - .then(function(data) { + .then(function (data) { // update image data to work with image grid - angular.forEach(data.items, function(mediaItem) { + angular.forEach(data.items, function (mediaItem) { setMediaMetaData(mediaItem); }); // update images @@ -373,29 +374,47 @@ angular.module("umbraco") mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); // set properties to match a media object - if (mediaItem.metaData && - mediaItem.metaData.umbracoWidth && - mediaItem.metaData.umbracoHeight) { - - mediaItem.properties = [ - { - alias: "umbracoWidth", - value: mediaItem.metaData.umbracoWidth.Value - }, - { - alias: "umbracoHeight", - value: mediaItem.metaData.umbracoHeight.Value - } - ]; + if (mediaItem.metaData) { + mediaItem.properties = []; + if (mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) { + mediaItem.properties.push( + { + alias: "umbracoWidth", + editor: mediaItem.metaData.umbracoWidth.PropertyEditorAlias, + value: mediaItem.metaData.umbracoWidth.Value + }, + { + alias: "umbracoHeight", + editor: mediaItem.metaData.umbracoHeight.PropertyEditorAlias, + value: mediaItem.metaData.umbracoHeight.Value + } + ); + } + if (mediaItem.metaData.umbracoFile) { + // this is required for resolving files through the mediahelper + mediaItem.properties.push( + { + alias: "umbracoFile", + editor: mediaItem.metaData.umbracoFile.PropertyEditorAlias, + value: mediaItem.metaData.umbracoFile.Value + } + ); + } } } - function getChildren(id, options) { + function getChildren(id) { $scope.loading = true; - return mediaResource.getChildren(id, options) - .then(function(data) { + return entityResource.getChildren(id, "Media", $scope.searchOptions) + .then(function (data) { + for (var i = 0; i < data.length; i++) { + if (data[i].metaData.MediaPath !== null) { + data[i].thumbnail = mediaHelper.resolveFileFromEntity(data[i], true); + data[i].image = mediaHelper.resolveFileFromEntity(data[i], false); + } + } $scope.searchOptions.filter = ""; - $scope.images = data.items ? data.items : []; + $scope.images = data ? data : []; // set already selected images to selected preSelectImages(); $scope.loading = false; @@ -425,15 +444,15 @@ angular.module("umbraco") } } - $scope.editMediaItem = function(item) { + $scope.editMediaItem = function (item) { var mediaEditor = { id: item.id, - submit: function(model) { + submit: function (model) { editorService.close() // update the media picker item in the picker so it matched the saved media item // the media picker is using media entities so we get the // entity so we easily can format it for use in the media grid - if(model && model.mediaNode) { + if (model && model.mediaNode) { entityResource.getById(model.mediaNode.id, "media") .then(function (mediaEntity) { angular.extend(item, mediaEntity); @@ -442,7 +461,7 @@ angular.module("umbraco") }); } }, - close: function(model) { + close: function (model) { setUpdatedMediaNodes(item); editorService.close(); } @@ -452,19 +471,19 @@ angular.module("umbraco") function setUpdatedMediaNodes(item) { // add udi to list of updated media items so we easily can update them in other editors - if($scope.model.updatedMediaNodes.indexOf(item.udi) === -1) { + if ($scope.model.updatedMediaNodes.indexOf(item.udi) === -1) { $scope.model.updatedMediaNodes.push(item.udi); } } - $scope.submit = function() { - if($scope.model.submit) { + $scope.submit = function () { + if ($scope.model.submit) { $scope.model.submit($scope.model); } }; - $scope.close = function() { - if($scope.model.close) { + $scope.close = function () { + if ($scope.model.close) { $scope.model.close($scope.model); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index 93d7936326..fab6ba4069 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -91,6 +91,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 247f694470..31430c81cb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -28,15 +28,16 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", vm.treeAlias = $scope.model.treeAlias; vm.multiPicker = $scope.model.multiPicker; vm.hideHeader = (typeof $scope.model.hideHeader) === "boolean" ? $scope.model.hideHeader : true; + vm.dataTypeKey = $scope.model.dataTypeKey; vm.searchInfo = { searchFromId: $scope.model.startNodeId, searchFromName: null, showSearch: false, + dataTypeKey: vm.dataTypeKey, results: [], selectedSearchResults: [] } vm.startNodeId = $scope.model.startNodeId; - vm.ignoreUserStartNodes = $scope.model.ignoreUserStartNodes; //Used for toggling an empty-state message //Some trees can have no items (dictionary & forms email templates) vm.hasItems = true; @@ -60,6 +61,8 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", vm.submit = submit; vm.close = close; + var currentNode = $scope.model.currentNode; + function initDialogTree() { vm.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler); // TODO: Also deal with unexpanding!! @@ -93,6 +96,14 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", }); } } + if (vm.treeAlias === "documentTypes") { + vm.entityType = "DocumentType"; + if (!$scope.model.title) { + localizationService.localize("defaultdialogs_selectContentType").then(function(value){ + $scope.model.title = value; + }); + } + } else if (vm.treeAlias === "member" || vm.section === "member") { vm.entityType = "Member"; if (!$scope.model.title) { @@ -161,6 +172,12 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", } } } + + vm.filter = { + filterAdvanced: $scope.model.filterAdvanced, + filterExclude: $scope.model.filterExclude, + filter: $scope.model.filter + }; } /** @@ -172,12 +189,13 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", if (vm.startNodeId) { queryParams["startNodeId"] = $scope.model.startNodeId; } - if (vm.ignoreUserStartNodes) { - queryParams["ignoreUserStartNodes"] = $scope.model.ignoreUserStartNodes; - } if (vm.selectedLanguage && vm.selectedLanguage.id) { queryParams["culture"] = vm.selectedLanguage.culture; } + if (vm.dataTypeKey) { + queryParams["dataTypeKey"] = vm.dataTypeKey; + } + var queryString = $.param(queryParams); //create the query string from the params object if (!queryString) { @@ -260,6 +278,12 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", vm.hasItems = args.tree.root.children.length > 0; tree = args.tree; + + var nodeHasPath = currentNode && currentNode.path; + var startNodeNotDefined = !vm.startNodeId; + if (startNodeNotDefined && nodeHasPath) { + vm.dialogTreeApi.syncTree({ path: currentNode.path, activate: true }); + } } //wires up selection diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html index acd838f7bf..78c75f6f8d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html @@ -36,22 +36,22 @@ search-from-id="{{vm.searchInfo.searchFromId}}" search-from-name="{{vm.searchInfo.searchFromName}}" show-search="{{vm.searchInfo.showSearch}}" - ignore-user-start-nodes="{{vm.ignoreUserStartNodes}}" + datatype-key="{{vm.searchInfo.dataTypeKey}}" section="{{vm.section}}">
- + - + {{ vm.emptyStateMessage }} - +
-
- + - - + entity-type-filter="vm.filter"> @@ -93,7 +93,7 @@ shortcut="esc" action="vm.close()"> - + - + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html index a14e930b8a..93c3a9b50d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html @@ -1,3 +1,4 @@ +
@@ -10,25 +11,28 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html index 92da12c423..a6d42d923b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html @@ -6,10 +6,14 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index baf9af916c..e7513617b7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -148,10 +148,9 @@
-
-
{{vm.errorMsg}}
+
+
-
@@ -169,12 +168,11 @@
- +
Forgotten password? diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index 829582329f..737253feb2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -12,7 +12,7 @@  
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html index 56d9eae16c..35bf725e0a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html @@ -1,5 +1,5 @@ - diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html index 054681d7f1..6ae256f4ee 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html @@ -16,14 +16,16 @@ add-ellipsis={{defaultButton.addEllipsis}}> - - + {{subButton.labelKey}} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html index 95c628376b..8163b2807b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html @@ -1,4 +1,4 @@ -
+
@@ -15,7 +15,7 @@
- -
@@ -74,7 +75,8 @@ type="button" disabled="model.disableSubmitButton" action="submitForm(model)" - state="model.submitButtonState"> + state="model.submitButtonState" + auto-focus="true">
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html index 2005666292..46fb11c310 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html @@ -1,5 +1,5 @@ - diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html index e8d9839d45..d777b3db78 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html @@ -1,16 +1,20 @@
  • -   + ng-click="load(node)">  + + Expand child items for {{node.name}} + {{node.name}} - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html index c2559ef31a..0141ff264f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html @@ -8,9 +8,9 @@ {{tree.name}} - +
  • - +
    - Akk {{itemLabel}}s are added + All {{itemLabel}}s are added
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html index 32fc0a687a..00ca425d7a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html @@ -5,10 +5,10 @@
    {{ name }}
    {{ description }}
    -
    +
    Permissions: - {{ permission.name }}, + {{ permission.name }}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html index 817a4f2a94..fae431a11a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html @@ -7,7 +7,7 @@ -
  • {{ name }}
    -
    +
    Sections: - {{ section.name }}, + {{ section.name }} All sections
    -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js index f101450705..d04c707b25 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js @@ -7,86 +7,103 @@ * The controller for the content creation dialog */ function contentCreateController($scope, - $routeParams, - contentTypeResource, - iconHelper, - $location, - navigationService, - blueprintConfig) { - - var mainCulture = $routeParams.mculture ? $routeParams.mculture : null; + $routeParams, + contentTypeResource, + iconHelper, + $location, + navigationService, + blueprintConfig, + authResource, + contentResource) { - function initialize() { - $scope.allowedTypes = null; - contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { - $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); - }); + var mainCulture = $routeParams.mculture ? $routeParams.mculture : null; - $scope.selectContentType = true; - $scope.selectBlueprint = false; - $scope.allowBlank = blueprintConfig.allowBlank; - } + function initialize() { + $scope.allowedTypes = null; + contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { + $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); + }); - function close() { - navigationService.hideMenu(); - } + if ($scope.currentNode.id > -1) { + authResource.getCurrentUser().then(function(currentUser) { + if (currentUser.allowedSections.indexOf("settings") > -1) { + $scope.hasSettingsAccess = true; + contentResource.getById($scope.currentNode.id).then(function(data) { + $scope.contentTypeId = data.contentTypeId; + }); + } + }); + } - function createBlank(docType) { - $location - .path("/content/content/edit/" + $scope.currentNode.id) - .search("doctype", docType.alias) - .search("create", "true") - /* when we create a new node we want to make sure it uses the same - language as what is selected in the tree */ - .search("cculture", mainCulture); - close(); - } - - function createOrSelectBlueprintIfAny(docType) { - // map the blueprints into a collection that's sortable in the view - var blueprints = _.map(_.pairs(docType.blueprints || {}), function (pair) { - return { - id: pair[0], - name: pair[1] - }; - }); - $scope.docType = docType; - if (blueprints.length) { - if (blueprintConfig.skipSelect) { - createFromBlueprint(blueprints[0].id); - } else { - $scope.selectContentType = false; - $scope.selectBlueprint = true; - $scope.selectableBlueprints = blueprints; - } - } else { - createBlank(docType); + $scope.selectContentType = true; + $scope.selectBlueprint = false; + $scope.allowBlank = blueprintConfig.allowBlank; } - } - function createFromBlueprint(blueprintId) { - $location - .path("/content/content/edit/" + $scope.currentNode.id) - .search("doctype", $scope.docType.alias) - .search("create", "true") - .search("blueprintId", blueprintId); - close(); - } + function close() { + navigationService.hideMenu(); + } - $scope.closeDialog = function(showMenu) { - navigationService.hideDialog(showMenu); - }; + function createBlank(docType) { + $location + .path("/content/content/edit/" + $scope.currentNode.id) + .search("doctype", docType.alias) + .search("create", "true") + /* when we create a new node we want to make sure it uses the same + language as what is selected in the tree */ + .search("cculture", mainCulture); + close(); + } - $scope.createBlank = createBlank; - $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny; - $scope.createFromBlueprint = createFromBlueprint; + function createOrSelectBlueprintIfAny(docType) { + // map the blueprints into a collection that's sortable in the view + var blueprints = _.map(_.pairs(docType.blueprints || {}), function (pair) { + return { + id: pair[0], + name: pair[1] + }; + }); + $scope.docType = docType; + if (blueprints.length) { + if (blueprintConfig.skipSelect) { + createFromBlueprint(blueprints[0].id); + } else { + $scope.selectContentType = false; + $scope.selectBlueprint = true; + $scope.selectableBlueprints = blueprints; + } + } else { + createBlank(docType); + } + } - // the current node changes behind the scenes when the context menu is clicked without closing - // the default menu first, so we must watch the current node and re-initialize accordingly - var unbindModelWatcher = $scope.$watch("currentNode", initialize); - $scope.$on('$destroy', function () { - unbindModelWatcher(); - }); + function createFromBlueprint(blueprintId) { + $location + .path("/content/content/edit/" + $scope.currentNode.id) + .search("doctype", $scope.docType.alias) + .search("create", "true") + .search("blueprintId", blueprintId); + close(); + } + + $scope.close = function() { + close(); + } + + $scope.closeDialog = function (showMenu) { + navigationService.hideDialog(showMenu); + }; + + $scope.createBlank = createBlank; + $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny; + $scope.createFromBlueprint = createFromBlueprint; + + // the current node changes behind the scenes when the context menu is clicked without closing + // the default menu first, so we must watch the current node and re-initialize accordingly + var unbindModelWatcher = $scope.$watch("currentNode", initialize); + $scope.$on('$destroy', function () { + unbindModelWatcher(); + }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js index c0002220e3..9a2c845d49 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js @@ -36,7 +36,6 @@ function(err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index cc55fbbf4d..5cbf25ba7d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the content editor */ -function ContentEditController($scope, $rootScope, $routeParams, contentResource) { +function ContentEditController($scope, $routeParams, contentResource) { var infiniteMode = $scope.model && $scope.model.infiniteMode; @@ -24,13 +24,17 @@ function ContentEditController($scope, $rootScope, $routeParams, contentResource $scope.page = $routeParams.page; $scope.isNew = infiniteMode ? $scope.model.create : $routeParams.create; //load the default culture selected in the main tree if any - $scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + $scope.culture = $routeParams.cculture ? $routeParams.cculture : ($routeParams.mculture === "true"); //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. //This is so we can listen to changes on the cculture parameter since that will not cause a route change - // and then we can pass in the updated culture to the editor + //and then we can pass in the updated culture to the editor. + //This will also execute when we are redirecting from creating an item to a newly created item since that + //will not cause a route change and so we can update the isNew and contentId flags accordingly. $scope.$on('$routeUpdate', function (event, next) { $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture; + $scope.isNew = next.params.create === "true"; + $scope.contentId = infiniteMode ? $scope.model.id : $routeParams.id; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index 8d80f308ab..fcd0294849 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -102,6 +102,7 @@ }; }); navigationService.syncTree({ tree: "content", path: $scope.currentNode.path, forceReload: true }); + $scope.dialog.confirmDiscardChanges = true; }, function (error) { vm.error = error; vm.buttonState = "error"; @@ -117,6 +118,7 @@ function toggle(group) { group.selected = !group.selected; + $scope.dialog.confirmDiscardChanges = true; } function pickGroup() { @@ -137,6 +139,7 @@ }); editorService.close(); navigationService.allowHideDialog(true); + $scope.dialog.confirmDiscardChanges = true; }, close: function() { editorService.close(); @@ -147,6 +150,7 @@ function removeGroup(group) { vm.groups = _.reject(vm.groups, function(g) { return g.id === group.id }); + $scope.dialog.confirmDiscardChanges = true; } function pickMember() { @@ -186,6 +190,7 @@ $q.all(promises).then(function() { vm.loading = false; }); + $scope.dialog.confirmDiscardChanges = true; } }, close: function () { @@ -219,6 +224,7 @@ } editorService.close(); navigationService.allowHideDialog(true); + $scope.dialog.confirmDiscardChanges = true; }, close: function () { editorService.close(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js index a8f87ce2c9..0e40fc2e6c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function ContentRightsController($scope, $timeout, contentResource, localizationService, angularHelper, navigationService) { + function ContentRightsController($scope, $timeout, contentResource, localizationService, angularHelper, navigationService, overlayService) { var vm = this; var currentForm; @@ -11,7 +11,6 @@ vm.removedUserGroups = []; vm.viewState = "manageGroups"; vm.labels = {}; - vm.showNotification = false; vm.setViewSate = setViewSate; vm.editPermissions = editPermissions; @@ -20,7 +19,6 @@ vm.removePermissions = removePermissions; vm.cancelManagePermissions = cancelManagePermissions; vm.closeDialog = closeDialog; - vm.stay = stay; vm.discardChanges = discardChanges; function onInit() { @@ -95,6 +93,7 @@ function setPermissions(group) { assignGroupPermissions(group); setViewSate("manageGroups"); + $scope.dialog.confirmDiscardChanges = true; } /** @@ -164,14 +163,31 @@ }); } - function stay() { - vm.showNotification = false; - } - function closeDialog() { // check if form has been changed. If it has show discard changes notification if (currentForm && currentForm.$dirty) { - vm.showNotification = true; + localizationService.localizeMany(["prompt_unsavedChanges", "prompt_unsavedChangesWarning", "prompt_discardChanges", "prompt_stay"]).then( + function(values) { + var overlay = { + "view": "default", + "title": values[0], + "content": values[1], + "disableBackdropClick": true, + "disableEscKey": true, + "submitButtonLabel": values[2], + "closeButtonLabel": values[3], + submit: function () { + overlayService.close(); + navigationService.hideDialog(); + }, + close: function () { + overlayService.close(); + } + }; + + overlayService.open(overlay); + } + ); } else { navigationService.hideDialog(); } @@ -187,4 +203,4 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RightsController", ContentRightsController); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/create.html b/src/Umbraco.Web.UI.Client/src/views/content/create.html index 94299f6a54..059b5eaf25 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -1,19 +1,25 @@
    - + \ No newline at end of file +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/content/rights.html b/src/Umbraco.Web.UI.Client/src/views/content/rights.html index 292db6f105..f430aad342 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/rights.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/rights.html @@ -62,24 +62,6 @@
    -
    -
    -

    -

    - - - - -
    -
    - diff --git a/src/Umbraco.Web.UI.Client/src/views/content/sort.html b/src/Umbraco.Web.UI.Client/src/views/content/sort.html index 789f7fe6b5..471f3de956 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/sort.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/sort.html @@ -1,6 +1,6 @@
    - diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index ead73beab8..6282ecb0b6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -174,7 +174,6 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic //NOTE: in the case of data type values we are setting the orig/new props // to be the same thing since that only really matters for content/media. contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js index 596f848abe..c05638f344 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js @@ -91,7 +91,6 @@ function DictionaryEditController($scope, $routeParams, $location, dictionaryRes function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 4085e84ccd..2241f852b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -7,36 +7,36 @@
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index 62cf9d3e88..5ceb5f01f2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -56,7 +56,7 @@ function onInit() { // get init values from model when in infinite mode - if(infiniteMode) { + if (infiniteMode) { documentTypeId = $scope.model.id; create = $scope.model.create; noTemplate = $scope.model.notemplate; @@ -89,8 +89,7 @@ "name": vm.labels.design, "alias": "design", "icon": "icon-document-dashed-line", - "view": "views/documenttypes/views/design/design.html", - "active": true + "view": "views/documenttypes/views/design/design.html" }, { "name": vm.labels.listview, @@ -291,6 +290,28 @@ }); vm.page.navigation = buttons; + initializeActiveNavigationPanel(); + } + + function initializeActiveNavigationPanel() { + // Initialise first loaded panel based on page route paramater + // i.e. ?view=design|listview|permissions + var initialViewSetFromRouteParams = false; + var view = $routeParams.view; + if (view) { + var viewPath = "views/documenttypes/views/" + view + "/" + view + ".html"; + for (var i = 0; i < vm.page.navigation.length; i++) { + if (vm.page.navigation[i].view === viewPath) { + vm.page.navigation[i].active = true; + initialViewSetFromRouteParams = true; + break; + } + } + } + + if (initialViewSetFromRouteParams === false) { + vm.page.navigation[0].active = true; + } } /* ---------- SAVE ---------- */ @@ -317,10 +338,6 @@ saveMethod: contentTypeResource.save, scope: $scope, content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, // we need to rebind... the IDs that have been created! rebindCallback: function (origContentType, savedContentType) { vm.contentType.id = savedContentType.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html index 15a9c84367..2e5f8572bf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html @@ -39,7 +39,7 @@ - {{ language.name }} + {{ language.name }} {{ language.culture }} diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js index b64ad36245..00fcbc0cea 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js @@ -10,11 +10,12 @@ vm.numberOfErrors = 0; vm.commonLogMessages = []; vm.commonLogMessagesCount = 10; + vm.dateRangeLabel = ""; // ChartJS Options - for count/overview of log distribution - vm.logTypeLabels = ["Info", "Debug", "Warning", "Error", "Critical"]; + vm.logTypeLabels = ["Debug", "Info", "Warning", "Error", "Fatal"]; vm.logTypeData = [0, 0, 0, 0, 0]; - vm.logTypeColors = [ '#dcdcdc', '#97bbcd', '#46bfbd', '#fdb45c', '#f7464a']; + vm.logTypeColors = ['#eaddd5', '#2bc37c', '#3544b1', '#ff9412', '#d42054']; vm.chartOptions = { legend: { display: true, @@ -22,20 +23,43 @@ } }; + let querystring = $location.search(); + if (querystring.startDate) { + vm.startDate = querystring.startDate; + vm.dateRangeLabel = getDateRangeLabel("Selected Time Period"); + } else { + vm.startDate = new Date(Date.now()); + vm.startDate.setDate(vm.startDate.getDate() - 1); + vm.startDate = vm.startDate.toIsoDateString(); + vm.dateRangeLabel = getDateRangeLabel("Today"); + } + + if (querystring.endDate) { + vm.endDate = querystring.endDate; + + if (querystring.endDate === querystring.startDate) { + vm.dateRangeLabel = getDateRangeLabel("Selected Date"); + } + } else { + vm.endDate = new Date(Date.now()).toIsoDateString(); + } + + vm.period = [vm.startDate, vm.endDate]; + //functions vm.searchLogQuery = searchLogQuery; vm.findMessageTemplate = findMessageTemplate; + vm.searchErrors = searchErrors; function preFlightCheck(){ vm.loading = true; - //Do our pre-flight check (to see if we can view logs) //IE the log file is NOT too big such as 1GB & crash the site - logViewerResource.canViewLogs().then(function(result){ + logViewerResource.canViewLogs(vm.startDate, vm.endDate).then(function (result) { vm.loading = false; vm.canLoadLogs = result; - if(result){ + if (result) { //Can view logs - so initalise init(); } @@ -48,76 +72,122 @@ vm.loading = true; var savedSearches = logViewerResource.getSavedSearches().then(function (data) { - vm.searches = data; - }, - // fallback to some defaults if error from API response - function () { - vm.searches = [ - { - "name": "Find all logs where the Level is NOT Verbose and NOT Debug", - "query": "Not(@Level='Verbose') and Not(@Level='Debug')" + vm.searches = data; + }, + // fallback to some defaults if error from API response + function () { + vm.searches = [ + { + "name": "Find all logs where the Level is NOT Verbose and NOT Debug", + "query": "Not(@Level='Verbose') and Not(@Level='Debug')" }, - { - "name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)", - "query": "Has(@Exception)" + { + "name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)", + "query": "Has(@Exception)" }, - { - "name": "Find all logs that have the property 'Duration'", - "query": "Has(Duration)" + { + "name": "Find all logs that have the property 'Duration'", + "query": "Has(Duration)" }, - { - "name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms", - "query": "Has(Duration) and Duration > 1000" + { + "name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms", + "query": "Has(Duration) and Duration > 1000" }, - { - "name": "Find all logs that are from the namespace 'Umbraco.Core'", - "query": "StartsWith(SourceContext, 'Umbraco.Core')" + { + "name": "Find all logs that are from the namespace 'Umbraco.Core'", + "query": "StartsWith(SourceContext, 'Umbraco.Core')" }, - { - "name": "Find all logs that use a specific log message template", - "query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'" + { + "name": "Find all logs that use a specific log message template", + "query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'" } ] - }); + }); - var numOfErrors = logViewerResource.getNumberOfErrors().then(function (data) { + var numOfErrors = logViewerResource.getNumberOfErrors(vm.startDate, vm.endDate).then(function (data) { vm.numberOfErrors = data; }); - var logCounts = logViewerResource.getLogLevelCounts().then(function (data) { + var logCounts = logViewerResource.getLogLevelCounts(vm.startDate, vm.endDate).then(function (data) { vm.logTypeData = []; - vm.logTypeData.push(data.Information); - vm.logTypeData.push(data.Debug); - vm.logTypeData.push(data.Warning); - vm.logTypeData.push(data.Error); - vm.logTypeData.push(data.Fatal); + + for (let [key, value] of Object.entries(data)) { + const index = vm.logTypeLabels.findIndex(x => key.startsWith(x)); + if (index > -1) { + vm.logTypeData[index] = value; + } + } }); - var commonMsgs = logViewerResource.getMessageTemplates().then(function(data){ + var commonMsgs = logViewerResource.getMessageTemplates(vm.startDate, vm.endDate).then(function (data) { vm.commonLogMessages = data; }); - //Set loading indicatior to false when these 3 queries complete - $q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function(data) { + //Set loading indicator to false when these 3 queries complete + $q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function () { vm.loading = false; }); $timeout(function () { - navigationService.syncTree({ tree: "logViewer", path: "-1" }); + navigationService.syncTree({ + tree: "logViewer", + path: "-1" + }); }); } - function searchLogQuery(logQuery){ - $location.path("/settings/logViewer/search").search({lq: logQuery}); + function searchLogQuery(logQuery) { + $location.path("/settings/logViewer/search").search({ + lq: logQuery, + startDate: vm.startDate, + endDate: vm.endDate + }); } - function findMessageTemplate(template){ + function findMessageTemplate(template) { var logQuery = "@MessageTemplate='" + template.MessageTemplate + "'"; searchLogQuery(logQuery); } + function getDateRangeLabel(suffix) { + return "Log Overview for " + suffix; + } + + function searchErrors(){ + var logQuery = "@Level='Fatal' or @Level='Error' or Has(@Exception)"; + searchLogQuery(logQuery); + } + preFlightCheck(); + ///////////////////// + + vm.config = { + enableTime: false, + dateFormat: "Y-m-d", + time_24hr: false, + mode: "range", + maxDate: "today", + conjunction: " to " + }; + + vm.dateRangeChange = function (selectedDates, dateStr, instance) { + + if (selectedDates.length > 0) { + + // Update view by re-requesting route with updated querystring. + // By doing this we make sure the URL matches the selected time period, aiding sharing the link. + // Also resolves a minor layout issue where the " to " conjunction between the selected dates + // is collapsed to a comma. + const startDate = selectedDates[0].toIsoDateString(); + const endDate = selectedDates[selectedDates.length - 1].toIsoDateString(); // Take the last date as end + $location.path("/settings/logViewer/overview").search({ + startDate: startDate, + endDate: endDate + }); + } + + } } angular.module("umbraco").controller("Umbraco.Editors.LogViewer.OverviewController", LogViewerOverviewController); diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html index a46853f97e..9e6936ad47 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html @@ -3,7 +3,7 @@ -
    - - - -

    Today's log file is too large to be viewed and would cause performance problems.

    -

    If you need to view the log files, try opening them manually

    -
    -
    -
    - -
    +
    - - - - - - - - - - - - -
    - All Logs -
    - {{search.name}} -
    -
    -
    +
    + + + +

    Today's log file is too large to be viewed and would cause performance problems.

    +

    If you need to view the log files, try opening them manually

    +
    +
    +
    +
    + + + + + + + + + + + + +
    + All Logs +
    + {{search.name}} +
    +
    +
    - - - - - Total Unique Message types: {{ vm.commonLogMessages.length }} - - + + + + + Total Unique Message types: {{ vm.commonLogMessages.length }} +
    + - -
    {{ template.MessageTemplate }} @@ -61,38 +61,53 @@ {{ template.Count }}
    - Show More -
    -
    + + + Show More + + +
    -
    - - - - - {{ vm.numberOfErrors }} - - - + - - + - - - - + + + +
    + + + + + {{ vm.numberOfErrors }} + + + + + + + + + + + + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js index 74395ffb25..fb627855b6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js @@ -11,26 +11,28 @@ vm.showBackButton = true; vm.page = {}; + // this array is also used to map the logTypeColor param onto the log items + // in setLogTypeColors() vm.logLevels = [ { name: 'Verbose', - logTypeColor: 'gray' + logTypeColor: '' }, { name: 'Debug', - logTypeColor: 'secondary' + logTypeColor: 'gray' }, { name: 'Information', - logTypeColor: 'primary' + logTypeColor: 'success' }, { name: 'Warning', - logTypeColor: 'warning' + logTypeColor: 'primary' }, { name: 'Error', - logTypeColor: 'danger' + logTypeColor: 'warning' }, { name: 'Fatal', @@ -96,6 +98,14 @@ vm.logOptions.filterExpression = querystring.lq; } + if(querystring.startDate){ + vm.logOptions.startDate = querystring.startDate; + } + + if(querystring.endDate){ + vm.logOptions.endDate = querystring.endDate; + } + vm.loading = true; logViewerResource.getSavedSearches().then(function (data) { @@ -110,7 +120,7 @@ "query": "Not(@Level='Verbose') and Not(@Level='Debug')" }, { - "name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)", + "name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)", "query": "Has(@Exception)" }, { @@ -165,25 +175,8 @@ } function setLogTypeColor(logItems) { - angular.forEach(logItems, function (log) { - switch (log.Level) { - case "Information": - log.logTypeColor = "primary"; - break; - case "Debug": - log.logTypeColor = "secondary"; - break; - case "Warning": - log.logTypeColor = "warning"; - break; - case "Fatal": - case "Error": - log.logTypeColor = "danger"; - break; - default: - log.logTypeColor = "gray"; - } - }); + logItems.forEach(logItem => + logItem.logTypeColor = vm.logLevels.find(x => x.name === logItem.Level).logTypeColor); } function getFilterName(array) { @@ -270,7 +263,11 @@ submitButtonLabel: "Save Search", disableSubmitButton: true, view: "logviewersearch", - query: vm.logOptions.filterExpression, + query: { + filterExpression: vm.logOptions.filterExpression, + startDate: vm.logOptions.startDate, + endDate: vm.logOptions.endDate + }, submit: function (model) { //Resource call with two params (name & query) //API that opens the JSON and adds it to the bottom diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html index 5981b41598..7a7ea7699c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html @@ -107,15 +107,15 @@ Total Items: {{ vm.logItems.totalItems }} - +
    - - - + + @@ -124,7 +124,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js index e91d8ae366..79d837e516 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js @@ -35,7 +35,6 @@ function MacrosEditController($scope, $q, $routeParams, macroResource, editorSta vm.page.saveButtonState = "success"; }, function (error) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: error }); diff --git a/src/Umbraco.Web.UI.Client/src/views/media/create.html b/src/Umbraco.Web.UI.Client/src/views/media/create.html index 13c12f3c9a..57c8f48eb4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/create.html @@ -4,20 +4,26 @@
    Create under {{currentNode.name}}
    -

    - -

    +
    +

    +
    +

    + + + +
    +
    + Timestamp   LevelMachineLevelMachine Message
    {{ log.Timestamp | date:'medium' }} {{ log.Level }} {{ log.Properties.MachineName.Value }}{{ log.RenderedMessage }}{{ log.RenderedMessage }}