diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 0761168d80..6681c97b49 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,7 +22,7 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - + @@ -52,7 +52,6 @@ - diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt deleted file mode 100644 index a81af8c365..0000000000 --- a/build/NuSpecs/tools/Dashboard.config.install.xdt +++ /dev/null @@ -1,95 +0,0 @@ - - -
- - - - views/dashboard/settings/settingsdashboardintro.html - - -
- -
- - forms - - - - views/dashboard/forms/formsdashboardintro.html - - -
- -
- - developer - -
- -
- - - views/dashboard/developer/developerdashboardvideos.html - - - -
- -
- - - views/dashboard/developer/examinemanagement.html - - -
- -
- - - - - views/dashboard/media/mediafolderbrowser.html - - -
- -
- - - - views/dashboard/members/membersdashboardvideos.html - - -
- -
- -
- -
- - -
- -
- - content - - - - views/dashboard/developer/redirecturls.html - - -
- -
- - developer - - - - views/dashboard/developer/healthcheck.html - - -
- diff --git a/src/Umbraco.Core/Components/CompositionExtensions.cs b/src/Umbraco.Core/Components/CompositionExtensions.cs index 2aef865994..4e3e4816e5 100644 --- a/src/Umbraco.Core/Components/CompositionExtensions.cs +++ b/src/Umbraco.Core/Components/CompositionExtensions.cs @@ -10,6 +10,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Core._Legacy.PackageActions; +using Umbraco.Core.Logging.Viewer; namespace Umbraco.Core.Components { @@ -297,6 +298,37 @@ namespace Umbraco.Core.Components public static void SetMediaFileSystem(this Composition composition, Func filesystemFactory) => composition.RegisterUniqueFor(_ => filesystemFactory()); + /// + /// Sets the log viewer. + /// + /// The type of the log viewer. + /// The composition. + public static void SetLogViewer(this Composition composition) + where T : ILogViewer + { + composition.RegisterUnique(); + } + + /// + /// Sets the log viewer. + /// + /// The composition. + /// A function creating a log viewer. + public static void SetLogViewer(this Composition composition, Func factory) + { + composition.RegisterUnique(factory); + } + + /// + /// Sets the log viewer. + /// + /// A composition. + /// A log viewer. + public static void SetLogViewer(this Composition composition, ILogViewer viewer) + { + composition.RegisterUnique(_ => viewer); + } + #endregion } } diff --git a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs index f8ecc11d98..88eb61de76 100644 --- a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs @@ -114,7 +114,7 @@ namespace Umbraco.Core.Composing return list; } - protected virtual int DefaultWeight { get; set; } = 10; + public virtual int DefaultWeight { get; set; } = 100; protected virtual int GetWeight(Type type) { diff --git a/src/Umbraco.Core/ConfigsExtensions.cs b/src/Umbraco.Core/ConfigsExtensions.cs index 0fcea5f430..6fdf7ea3b9 100644 --- a/src/Umbraco.Core/ConfigsExtensions.cs +++ b/src/Umbraco.Core/ConfigsExtensions.cs @@ -2,7 +2,6 @@ using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Dashboard; using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Configuration.UmbracoSettings; @@ -22,9 +21,6 @@ namespace Umbraco.Core public static IUmbracoSettingsSection Settings(this Configs configs) => configs.GetConfig(); - public static IDashboardSection Dashboards(this Configs configs) - => configs.GetConfig(); - public static IHealthChecks HealthChecks(this Configs configs) => configs.GetConfig(); @@ -40,7 +36,6 @@ namespace Umbraco.Core configs.Add(() => new GlobalSettings()); configs.Add("umbracoConfiguration/settings"); - configs.Add("umbracoConfiguration/dashBoard"); configs.Add("umbracoConfiguration/HealthChecks"); configs.Add(() => new CoreDebug()); diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs deleted file mode 100644 index 01538c8e0b..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class AccessElement : RawXmlConfigurationElement, IAccess - { - public AccessElement() - { } - - public AccessElement(XElement rawXml) - : base(rawXml) - { } - - public IEnumerable Rules - { - get - { - var result = new List(); - if (RawXml == null) return result; - - result.AddRange(RawXml.Elements("deny").Select(x => new AccessRule {Type = AccessRuleType.Deny, Value = x.Value })); - result.AddRange(RawXml.Elements("grant").Select(x => new AccessRule { Type = AccessRuleType.Grant, Value = x.Value })); - result.AddRange(RawXml.Elements("grantBySection").Select(x => new AccessRule { Type = AccessRuleType.GrantBySection, Value = x.Value })); - return result; - } - } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs deleted file mode 100644 index 31cc3eaec8..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class AreaCollection : ConfigurationElementCollection, IEnumerable - { - protected override ConfigurationElement CreateNewElement() - { - return new AreaElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((AreaElement) element).Value; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IArea; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs b/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs deleted file mode 100644 index 20393f89d8..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class AreaElement : InnerTextConfigurationElement, IArea - { - string IArea.AreaName - { - get { return Value; } - } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs b/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs deleted file mode 100644 index 92e51c9b73..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class AreasElement : ConfigurationElement - { - [ConfigurationCollection(typeof(SectionCollection), AddItemName = "area")] - [ConfigurationProperty("", IsDefaultCollection = true)] - public AreaCollection AreaCollection - { - get { return (AreaCollection)base[""]; } - set { base[""] = value; } - } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs deleted file mode 100644 index b8aa40da7f..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class ControlCollection : ConfigurationElementCollection, IEnumerable - { - internal void Add(ControlElement c) - { - BaseAdd(c); - } - - protected override ConfigurationElement CreateNewElement() - { - return new ControlElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((ControlElement)element).ControlPath; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IDashboardControl; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs b/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs deleted file mode 100644 index 20dac7460e..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Configuration; -using System.Linq; -using System.Xml.Linq; - -namespace Umbraco.Core.Configuration.Dashboard -{ - - internal class ControlElement : RawXmlConfigurationElement, IDashboardControl - { - public string PanelCaption - { - get - { - var panelCaption = RawXml.Attribute("panelCaption"); - return panelCaption == null ? "" : panelCaption.Value; - } - } - - public AccessElement Access - { - get - { - var access = RawXml.Element("access"); - return access == null ? new AccessElement() : new AccessElement(access); - } - } - - public string ControlPath - { - get - { - //we need to return the first (and only) text element of the children (wtf... who designed this configuration ! :P ) - var txt = RawXml.Nodes().OfType().FirstOrDefault(); - if (txt == null) - { - throw new ConfigurationErrorsException("The control element must contain a text node indicating the control path"); - } - return txt.Value.Trim(); - } - } - - IAccess IDashboardControl.AccessRights => Access; - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/DashboardSection.cs b/src/Umbraco.Core/Configuration/Dashboard/DashboardSection.cs deleted file mode 100644 index 12bf0522e0..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/DashboardSection.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class DashboardSection : ConfigurationSection, IDashboardSection - { - [ConfigurationCollection(typeof(SectionCollection), AddItemName = "section")] - [ConfigurationProperty("", IsDefaultCollection = true)] - public SectionCollection SectionCollection - { - get { return (SectionCollection)base[""]; } - set { base[""] = value; } - } - - IEnumerable IDashboardSection.Sections - { - get { return SectionCollection; } - } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs b/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs deleted file mode 100644 index 8ac1b18cca..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IAccess - { - IEnumerable Rules { get; } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IArea.cs b/src/Umbraco.Core/Configuration/Dashboard/IArea.cs deleted file mode 100644 index 25401db408..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/IArea.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IArea - { - string AreaName { get; } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs b/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs deleted file mode 100644 index cdf05af1ec..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IDashboardControl - { - string PanelCaption { get; } - - string ControlPath { get; } - - IAccess AccessRights { get; } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs b/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs deleted file mode 100644 index 32dfc6653d..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IDashboardSection - { - IEnumerable Sections { get; } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs b/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs deleted file mode 100644 index 914b226265..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IDashboardTab - { - string Caption { get; } - - IEnumerable Controls { get; } - - IAccess AccessRights { get; } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/ISection.cs b/src/Umbraco.Core/Configuration/Dashboard/ISection.cs deleted file mode 100644 index 1005c0750d..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/ISection.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface ISection - { - string Alias { get; } - - IEnumerable Areas { get; } - - IEnumerable Tabs { get; } - - IAccess AccessRights { get; } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs deleted file mode 100644 index 5717bd28c3..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class SectionCollection : ConfigurationElementCollection, IEnumerable - { - internal void Add(SectionElement c) - { - BaseAdd(c); - } - - protected override ConfigurationElement CreateNewElement() - { - return new SectionElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((SectionElement)element).Alias; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as ISection; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs b/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs deleted file mode 100644 index 09049c13db..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using System.Configuration; -using System.Linq; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class SectionElement : ConfigurationElement, ISection - { - [ConfigurationProperty("alias", IsRequired = true)] - public string Alias - { - get { return (string) this["alias"]; } - } - - [ConfigurationProperty("areas", IsRequired = true)] - public AreasElement Areas - { - get { return (AreasElement)this["areas"]; } - } - - [ConfigurationProperty("access")] - public AccessElement Access - { - get { return (AccessElement)this["access"]; } - } - - [ConfigurationCollection(typeof(SectionCollection), AddItemName = "tab")] - [ConfigurationProperty("", IsDefaultCollection = true)] - public TabCollection TabCollection - { - get { return (TabCollection)base[""]; } - set { base[""] = value; } - } - - IEnumerable ISection.Tabs - { - get { return TabCollection; } - } - - IEnumerable ISection.Areas - { - get { return Areas.AreaCollection.Cast().Select(x => x.Value); } - } - - IAccess ISection.AccessRights - { - get { return Access; } - } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs deleted file mode 100644 index 1b77ffd3fb..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class TabCollection : ConfigurationElementCollection, IEnumerable - { - internal void Add(TabElement c) - { - BaseAdd(c); - } - - protected override ConfigurationElement CreateNewElement() - { - return new TabElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((TabElement)element).Caption; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IDashboardTab; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs b/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs deleted file mode 100644 index b92394596e..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class TabElement : ConfigurationElement, IDashboardTab - { - [ConfigurationProperty("caption", IsRequired = true)] - public string Caption - { - get { return (string)this["caption"]; } - } - - [ConfigurationProperty("access")] - public AccessElement Access - { - get { return (AccessElement)this["access"]; } - } - - [ConfigurationCollection(typeof(ControlCollection), AddItemName = "control")] - [ConfigurationProperty("", IsDefaultCollection = true)] - public ControlCollection ControlCollection - { - get { return (ControlCollection)base[""]; } - set { base[""] = value; } - } - - IEnumerable IDashboardTab.Controls - { - get { return ControlCollection; } - } - - IAccess IDashboardTab.AccessRights - { - get { return Access; } - } - } -} diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index b9f20fb449..8b637fe90e 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -118,12 +118,7 @@ namespace Umbraco.Core /// RadioButton list. /// public const string RadioButtonList = "Umbraco.RadioButtonList"; - - /// - /// Related Links. - /// - public const string RelatedLinks = "Umbraco.RelatedLinks"; - + /// /// Slider. /// diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessRule.cs b/src/Umbraco.Core/Dashboards/AccessRule.cs similarity index 72% rename from src/Umbraco.Core/Configuration/Dashboard/AccessRule.cs rename to src/Umbraco.Core/Dashboards/AccessRule.cs index fe6840ff64..4f725e32f0 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessRule.cs +++ b/src/Umbraco.Core/Dashboards/AccessRule.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Core.Configuration.Dashboard +namespace Umbraco.Core.Dashboards { /// /// Implements . /// - internal class AccessRule : IAccessRule + public class AccessRule : IAccessRule { /// public AccessRuleType Type { get; set; } diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessRuleType.cs b/src/Umbraco.Core/Dashboards/AccessRuleType.cs similarity index 93% rename from src/Umbraco.Core/Configuration/Dashboard/AccessRuleType.cs rename to src/Umbraco.Core/Dashboards/AccessRuleType.cs index cb9ce983fe..efed361f6c 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessRuleType.cs +++ b/src/Umbraco.Core/Dashboards/AccessRuleType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Configuration.Dashboard +namespace Umbraco.Core.Dashboards { /// /// Defines dashboard access rules type. diff --git a/src/Umbraco.Core/Dashboards/DashboardSlim.cs b/src/Umbraco.Core/Dashboards/DashboardSlim.cs new file mode 100644 index 0000000000..a2869a90a2 --- /dev/null +++ b/src/Umbraco.Core/Dashboards/DashboardSlim.cs @@ -0,0 +1,12 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Core.Dashboards +{ + [DataContract(IsReference = true)] + public class DashboardSlim : IDashboardSlim + { + public string Alias { get; set; } + + public string View { get; set; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IAccessRule.cs b/src/Umbraco.Core/Dashboards/IAccessRule.cs similarity index 80% rename from src/Umbraco.Core/Configuration/Dashboard/IAccessRule.cs rename to src/Umbraco.Core/Dashboards/IAccessRule.cs index 8b51b1b73a..e3ac583379 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IAccessRule.cs +++ b/src/Umbraco.Core/Dashboards/IAccessRule.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Core.Configuration.Dashboard +using Newtonsoft.Json; +using Umbraco.Core.Manifest; + +namespace Umbraco.Core.Dashboards { /// /// Represents an access rule. diff --git a/src/Umbraco.Core/Dashboards/IDashboard.cs b/src/Umbraco.Core/Dashboards/IDashboard.cs new file mode 100644 index 0000000000..c990e2ba61 --- /dev/null +++ b/src/Umbraco.Core/Dashboards/IDashboard.cs @@ -0,0 +1,38 @@ +using System.Runtime.Serialization; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Dashboards +{ + /// + /// Represents a dashboard. + /// + public interface IDashboard : IDashboardSlim, IDiscoverable + { + /// + /// Gets the aliases of sections/applications where this dashboard appears. + /// + /// + /// This field is *not* needed by the UI and therefore we want to exclude + /// it from serialization, but it is deserialized as part of the manifest, + /// therefore we cannot plainly ignore it. + /// So, it has to remain a data member, plus we use our special + /// JsonDontSerialize attribute (see attribute for more details). + /// + [DataMember(Name = "sections")] + string[] Sections { get; } + + + /// + /// Gets the access rule determining the visibility of the dashboard. + /// + /// + /// This field is *not* needed by the UI and therefore we want to exclude + /// it from serialization, but it is deserialized as part of the manifest, + /// therefore we cannot plainly ignore it. + /// So, it has to remain a data member, plus we use our special + /// JsonDontSerialize attribute (see attribute for more details). + /// + [DataMember(Name = "access")] + IAccessRule[] AccessRules { get; } + } +} diff --git a/src/Umbraco.Core/Dashboards/IDashboardSlim.cs b/src/Umbraco.Core/Dashboards/IDashboardSlim.cs new file mode 100644 index 0000000000..655f56dfd9 --- /dev/null +++ b/src/Umbraco.Core/Dashboards/IDashboardSlim.cs @@ -0,0 +1,22 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Core.Dashboards +{ + /// + /// Represents a dashboard with only minimal data. + /// + public interface IDashboardSlim + { + /// + /// Gets the alias of the dashboard. + /// + [DataMember(Name = "alias")] + string Alias { get; } + + /// + /// Gets the view used to render the dashboard. + /// + [DataMember(Name = "view")] + string View { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs b/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs index f3d1b5137f..5ddcb01078 100644 --- a/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs +++ b/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Logging.Viewer { public void Compose(Composition composition) { - composition.RegisterUnique(_ => new JsonLogViewer()); + composition.SetLogViewer(_ => new JsonLogViewer()); } } } diff --git a/src/Umbraco.Core/Manifest/DashboardAccessRuleConverter.cs b/src/Umbraco.Core/Manifest/DashboardAccessRuleConverter.cs index c627728a32..67c5a5824e 100644 --- a/src/Umbraco.Core/Manifest/DashboardAccessRuleConverter.cs +++ b/src/Umbraco.Core/Manifest/DashboardAccessRuleConverter.cs @@ -1,7 +1,7 @@ using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core.Configuration.Dashboard; +using Umbraco.Core.Dashboards; using Umbraco.Core.Serialization; namespace Umbraco.Core.Manifest diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 0667f11aab..af66bfc544 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -1,12 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; -using System.Text.RegularExpressions; using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Models.ContentEditing; -using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Manifest { @@ -30,6 +24,7 @@ namespace Umbraco.Core.Manifest /// /// Represents a content app definition, parsed from a manifest. /// + /// Is used to create an actual . [DataContract(Name = "appdef", Namespace = "")] public class ManifestContentAppDefinition { diff --git a/src/Umbraco.Core/Manifest/ManifestDashboardDefinition.cs b/src/Umbraco.Core/Manifest/ManifestDashboard.cs similarity index 79% rename from src/Umbraco.Core/Manifest/ManifestDashboardDefinition.cs rename to src/Umbraco.Core/Manifest/ManifestDashboard.cs index 83f047b264..e790655221 100644 --- a/src/Umbraco.Core/Manifest/ManifestDashboardDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestDashboard.cs @@ -1,23 +1,20 @@ using System; using System.ComponentModel; using Newtonsoft.Json; -using Umbraco.Core.Configuration.Dashboard; +using Umbraco.Core.Dashboards; using Umbraco.Core.IO; namespace Umbraco.Core.Manifest { - public class ManifestDashboardDefinition + public class ManifestDashboard : IDashboard { private string _view; - [JsonProperty("name", Required = Required.Always)] - public string Name { get; set; } - [JsonProperty("alias", Required = Required.Always)] public string Alias { get; set; } [JsonProperty("weight", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [DefaultValue(100)] + [DefaultValue(100)] // must be equal to DashboardCollectionBuilder.DefaultWeight public int Weight { get; set; } [JsonProperty("view", Required = Required.Always)] diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 01bc0b1983..6ca3b916ea 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -1,184 +1,182 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using Newtonsoft.Json; -using Umbraco.Core.Cache; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.ContentEditing; -using Umbraco.Core.Models.Trees; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Manifest -{ - /// - /// Parses the Main.js file and replaces all tokens accordingly. - /// - public class ManifestParser - { - private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); - - private readonly IAppPolicyCache _cache; - private readonly ILogger _logger; - private readonly ManifestValueValidatorCollection _validators; - - private string _path; - - /// - /// Initializes a new instance of the class. - /// - public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ILogger logger) - : this(appCaches, validators, "~/App_Plugins", logger) - { } - - /// - /// Initializes a new instance of the class. - /// - private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, string path, ILogger logger) - { - if (appCaches == null) throw new ArgumentNullException(nameof(appCaches)); - _cache = appCaches.RuntimeCache; - _validators = validators ?? throw new ArgumentNullException(nameof(validators)); - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); - Path = path; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public string Path - { - get => _path; - set => _path = value.StartsWith("~/") ? IOHelper.MapPath(value) : value; - } - - /// - /// Gets all manifests, merged into a single manifest object. - /// - /// - public PackageManifest Manifest - => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => - { - var manifests = GetManifests(); - return MergeManifests(manifests); - }, new TimeSpan(0, 4, 0)); - - /// - /// Gets all manifests. - /// - private IEnumerable GetManifests() - { - var manifests = new List(); - - foreach (var path in GetManifestFiles()) - { - try - { - var text = File.ReadAllText(path); - text = TrimPreamble(text); - if (string.IsNullOrWhiteSpace(text)) - continue; - var manifest = ParseManifest(text); - manifests.Add(manifest); - } - catch (Exception e) - { - _logger.Error(e, "Failed to parse manifest at '{Path}', ignoring.", path); - } - } - - return manifests; - } - - /// - /// Merges all manifests into one. - /// - private static PackageManifest MergeManifests(IEnumerable manifests) - { - var scripts = new HashSet(); - var stylesheets = new HashSet(); - var propertyEditors = new List(); - var parameterEditors = new List(); - var gridEditors = new List(); - var contentApps = new List(); - var dashboards = new List(); - var sections = new List(); - - foreach (var manifest in manifests) - { - if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script); - if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet); - if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors); - if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors); - if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); - if (manifest.ContentApps != null) contentApps.AddRange(manifest.ContentApps); - if (manifest.Dashboards != null) dashboards.AddRange(manifest.Dashboards); - if (manifest.Sections != null) sections.AddRange(manifest.Sections.DistinctBy(x => x.Alias.ToLowerInvariant())); - } - - return new PackageManifest - { - Scripts = scripts.ToArray(), - Stylesheets = stylesheets.ToArray(), - PropertyEditors = propertyEditors.ToArray(), - ParameterEditors = parameterEditors.ToArray(), - GridEditors = gridEditors.ToArray(), - ContentApps = contentApps.ToArray(), - Dashboards = dashboards.ToArray(), - Sections = sections.ToArray() - }; - } - - // gets all manifest files (recursively) - private IEnumerable GetManifestFiles() - { - if (Directory.Exists(_path) == false) - return new string[0]; - return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories); - } - - private static string TrimPreamble(string text) - { - // strangely StartsWith(preamble) would always return true - if (text.Substring(0, 1) == Utf8Preamble) - text = text.Remove(0, Utf8Preamble.Length); - - return text; - } - - /// - /// Parses a manifest. - /// - internal PackageManifest ParseManifest(string text) - { - if (string.IsNullOrWhiteSpace(text)) - throw new ArgumentNullOrEmptyException(nameof(text)); - - var manifest = JsonConvert.DeserializeObject(text, - new DataEditorConverter(_logger), - new ValueValidatorConverter(_validators), - new DashboardAccessRuleConverter()); - - // scripts and stylesheets are raw string, must process here - for (var i = 0; i < manifest.Scripts.Length; i++) - manifest.Scripts[i] = IOHelper.ResolveVirtualUrl(manifest.Scripts[i]); - for (var i = 0; i < manifest.Stylesheets.Length; i++) - manifest.Stylesheets[i] = IOHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); - - // add property editors that are also parameter editors, to the parameter editors list - // (the manifest format is kinda legacy) - var ppEditors = manifest.PropertyEditors.Where(x => (x.Type & EditorType.MacroParameter) > 0).ToList(); - if (ppEditors.Count > 0) - manifest.ParameterEditors = manifest.ParameterEditors.Union(ppEditors).ToArray(); - - return manifest; - } - - // purely for tests - internal IEnumerable ParseGridEditors(string text) - { - return JsonConvert.DeserializeObject>(text); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Manifest +{ + /// + /// Parses the Main.js file and replaces all tokens accordingly. + /// + public class ManifestParser + { + private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); + + private readonly IAppPolicyCache _cache; + private readonly ILogger _logger; + private readonly ManifestValueValidatorCollection _validators; + + private string _path; + + /// + /// Initializes a new instance of the class. + /// + public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ILogger logger) + : this(appCaches, validators, "~/App_Plugins", logger) + { } + + /// + /// Initializes a new instance of the class. + /// + private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, string path, ILogger logger) + { + if (appCaches == null) throw new ArgumentNullException(nameof(appCaches)); + _cache = appCaches.RuntimeCache; + _validators = validators ?? throw new ArgumentNullException(nameof(validators)); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); + Path = path; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public string Path + { + get => _path; + set => _path = value.StartsWith("~/") ? IOHelper.MapPath(value) : value; + } + + /// + /// Gets all manifests, merged into a single manifest object. + /// + /// + public PackageManifest Manifest + => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => + { + var manifests = GetManifests(); + return MergeManifests(manifests); + }, new TimeSpan(0, 4, 0)); + + /// + /// Gets all manifests. + /// + private IEnumerable GetManifests() + { + var manifests = new List(); + + foreach (var path in GetManifestFiles()) + { + try + { + var text = File.ReadAllText(path); + text = TrimPreamble(text); + if (string.IsNullOrWhiteSpace(text)) + continue; + var manifest = ParseManifest(text); + manifests.Add(manifest); + } + catch (Exception e) + { + _logger.Error(e, "Failed to parse manifest at '{Path}', ignoring.", path); + } + } + + return manifests; + } + + /// + /// Merges all manifests into one. + /// + private static PackageManifest MergeManifests(IEnumerable manifests) + { + var scripts = new HashSet(); + var stylesheets = new HashSet(); + var propertyEditors = new List(); + var parameterEditors = new List(); + var gridEditors = new List(); + var contentApps = new List(); + var dashboards = new List(); + var sections = new List(); + + foreach (var manifest in manifests) + { + if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script); + if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet); + if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors); + if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors); + if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); + if (manifest.ContentApps != null) contentApps.AddRange(manifest.ContentApps); + if (manifest.Dashboards != null) dashboards.AddRange(manifest.Dashboards); + if (manifest.Sections != null) sections.AddRange(manifest.Sections.DistinctBy(x => x.Alias.ToLowerInvariant())); + } + + return new PackageManifest + { + Scripts = scripts.ToArray(), + Stylesheets = stylesheets.ToArray(), + PropertyEditors = propertyEditors.ToArray(), + ParameterEditors = parameterEditors.ToArray(), + GridEditors = gridEditors.ToArray(), + ContentApps = contentApps.ToArray(), + Dashboards = dashboards.ToArray(), + Sections = sections.ToArray() + }; + } + + // gets all manifest files (recursively) + private IEnumerable GetManifestFiles() + { + if (Directory.Exists(_path) == false) + return new string[0]; + return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories); + } + + private static string TrimPreamble(string text) + { + // strangely StartsWith(preamble) would always return true + if (text.Substring(0, 1) == Utf8Preamble) + text = text.Remove(0, Utf8Preamble.Length); + + return text; + } + + /// + /// Parses a manifest. + /// + internal PackageManifest ParseManifest(string text) + { + if (string.IsNullOrWhiteSpace(text)) + throw new ArgumentNullOrEmptyException(nameof(text)); + + var manifest = JsonConvert.DeserializeObject(text, + new DataEditorConverter(_logger), + new ValueValidatorConverter(_validators), + new DashboardAccessRuleConverter()); + + // scripts and stylesheets are raw string, must process here + for (var i = 0; i < manifest.Scripts.Length; i++) + manifest.Scripts[i] = IOHelper.ResolveVirtualUrl(manifest.Scripts[i]); + for (var i = 0; i < manifest.Stylesheets.Length; i++) + manifest.Stylesheets[i] = IOHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); + + // add property editors that are also parameter editors, to the parameter editors list + // (the manifest format is kinda legacy) + var ppEditors = manifest.PropertyEditors.Where(x => (x.Type & EditorType.MacroParameter) > 0).ToList(); + if (ppEditors.Count > 0) + manifest.ParameterEditors = manifest.ParameterEditors.Union(ppEditors).ToArray(); + + return manifest; + } + + // purely for tests + internal IEnumerable ParseGridEditors(string text) + { + return JsonConvert.DeserializeObject>(text); + } + } +} diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index b29a28ab06..c272449509 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -49,7 +49,7 @@ namespace Umbraco.Core.Manifest /// Gets or sets the dashboards listed in the manifest. /// [JsonProperty("dashboards")] - public ManifestDashboardDefinition[] Dashboards { get; set; } = Array.Empty(); + public ManifestDashboard[] Dashboards { get; set; } = Array.Empty(); /// /// Gets or sets the sections listed in the manifest. diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 82ca5960ec..7e9df321c3 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -135,7 +135,7 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Related Links", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Multi URL Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); } private void CreateLockData() @@ -301,7 +301,7 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1048, EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1049, EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext", Configuration = "{\"multiPicker\":1}" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1050, EditorAlias = Constants.PropertyEditors.Aliases.RelatedLinks, DbType = "Ntext" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1050, EditorAlias = Constants.PropertyEditors.Aliases.MultiUrlPicker, DbType = "Ntext" }); } private void CreateRelationTypeData() diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 833955ee6a..9bf58c8d2f 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -124,6 +124,8 @@ namespace Umbraco.Core.Migrations.Upgrade 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}"); //FINAL 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 new file mode 100644 index 0000000000..ed1c08f0f8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs @@ -0,0 +1,139 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class ConvertRelatedLinksToMultiUrlPicker : MigrationBase + { + public ConvertRelatedLinksToMultiUrlPicker(IMigrationContext context) : base(context) + { } + + public override void Migrate() + { + var sqlDataTypes = Sql() + .Select() + .From() + .Where(x => x.EditorAlias == "Umbraco.RelatedLinks" + || x.EditorAlias == "Umbraco.RelatedLinks2"); + + var dataTypes = Database.Fetch(sqlDataTypes); + var dataTypeIds = dataTypes.Select(x => x.NodeId).ToList(); + + if (dataTypeIds.Count == 0) return; + + foreach (var dataType in dataTypes) + { + dataType.EditorAlias = Constants.PropertyEditors.Aliases.MultiUrlPicker; + Database.Update(dataType); + } + + var sqlPropertyTpes = Sql() + .Select() + .From() + .Where(x => dataTypeIds.Contains(x.DataTypeId)); + + var propertyTypeIds = Database.Fetch(sqlPropertyTpes).Select(x => x.Id).ToList(); + + if (propertyTypeIds.Count == 0) return; + + var sqlPropertyData = Sql() + .Select() + .From() + .Where(x => propertyTypeIds.Contains(x.PropertyTypeId)); + + 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(); + if (string.IsNullOrWhiteSpace(value)) + continue; + + var relatedLinks = JsonConvert.DeserializeObject>(value); + var links = new List(); + foreach (var relatedLink in relatedLinks) + { + GuidUdi udi = null; + if (relatedLink.IsInternal) + { + var linkIsUdi = GuidUdi.TryParse(relatedLink.Link, out udi); + if (linkIsUdi == false) + { + // oh no.. probably an integer, yikes! + if (int.TryParse(relatedLink.Link, out var intId)) + { + var sqlNodeData = Sql() + .Select() + .Where(x => x.NodeId == intId); + + var node = Database.Fetch(sqlNodeData).FirstOrDefault(); + if (node != null) + // Note: RelatedLinks did not allow for picking media items, + // so if there's a value this will be a content item - hence + // the hardcoded "document" here + udi = new GuidUdi("document", node.UniqueId); + } + } + } + + var link = new LinkDto + { + 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 + Url = relatedLink.IsInternal == false ? relatedLink.Link : null + }; + + links.Add(link); + } + + var json = JsonConvert.SerializeObject(links); + + // Update existing data + property.TextValue = json; + Database.Update(property); + } + + + } + } + + internal class RelatedLink + { + public int? Id { get; internal set; } + internal bool IsDeleted { get; set; } + [JsonProperty("caption")] + public string Caption { get; set; } + [JsonProperty("link")] + public string Link { get; set; } + [JsonProperty("newWindow")] + public bool NewWindow { get; set; } + [JsonProperty("isInternal")] + public bool IsInternal { get; set; } + } + + [DataContract] + internal class LinkDto + { + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "target")] + public string Target { get; set; } + + [DataMember(Name = "udi")] + public GuidUdi Udi { get; set; } + + [DataMember(Name = "url")] + public string Url { get; set; } + } +} 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 ee439088be..064ffc7228 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 @@ -16,7 +16,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 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(Constants.PropertyEditors.Aliases.RelatedLinks + "2", Constants.PropertyEditors.Aliases.RelatedLinks); RenameDataType("Umbraco.TextboxMultiple", Constants.PropertyEditors.Aliases.TextArea, false); RenameDataType("Umbraco.Textbox", Constants.PropertyEditors.Aliases.TextBox, false); } 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 new file mode 100644 index 0000000000..2e37c79632 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdatePickerIntegerValuesToUdi.cs @@ -0,0 +1,108 @@ +using System; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class UpdatePickerIntegerValuesToUdi : MigrationBase + { + public UpdatePickerIntegerValuesToUdi(IMigrationContext context) : base(context) + { } + + public override void Migrate() + { + var sqlDataTypes = Sql() + .Select() + .From() + .Where(x => x.EditorAlias == Constants.PropertyEditors.Aliases.ContentPicker + || x.EditorAlias == Constants.PropertyEditors.Aliases.MediaPicker + || x.EditorAlias == Constants.PropertyEditors.Aliases.MultiNodeTreePicker); + + var dataTypes = Database.Fetch(sqlDataTypes).ToList(); + + foreach (var datatype in dataTypes.Where(x => !x.Configuration.IsNullOrWhiteSpace())) + { + switch (datatype.EditorAlias) + { + case Constants.PropertyEditors.Aliases.ContentPicker: + case Constants.PropertyEditors.Aliases.MediaPicker: + { + var config = JsonConvert.DeserializeObject(datatype.Configuration); + var startNodeId = config.Value("startNodeId"); + if (!startNodeId.IsNullOrWhiteSpace() && int.TryParse(startNodeId, out var intStartNode)) + { + var guid = intStartNode <= 0 + ? null + : Context.Database.ExecuteScalar( + Sql().Select(x => x.UniqueId).From().Where(x => x.NodeId == intStartNode)); + if (guid.HasValue) + { + var udi = new GuidUdi(datatype.EditorAlias == Constants.PropertyEditors.Aliases.MediaPicker + ? Constants.UdiEntityType.Media + : Constants.UdiEntityType.Document, guid.Value); + config["startNodeId"] = new JValue(udi.ToString()); + } + else + config.Remove("startNodeId"); + + datatype.Configuration = JsonConvert.SerializeObject(config); + Database.Update(datatype); + } + + break; + } + case Constants.PropertyEditors.Aliases.MultiNodeTreePicker: + { + var config = JsonConvert.DeserializeObject(datatype.Configuration); + var startNodeConfig = config.Value("startNode"); + if (startNodeConfig != null) + { + var startNodeId = startNodeConfig.Value("id"); + var objectType = startNodeConfig.Value("type"); + if (!objectType.IsNullOrWhiteSpace() + && !startNodeId.IsNullOrWhiteSpace() + && int.TryParse(startNodeId, out var intStartNode)) + { + var guid = intStartNode <= 0 + ? null + : Context.Database.ExecuteScalar( + Sql().Select(x => x.UniqueId).From().Where(x => x.NodeId == intStartNode)); + + string entityType = null; + switch (objectType.ToLowerInvariant()) + { + case "content": + entityType = Constants.UdiEntityType.Document; + break; + case "media": + entityType = Constants.UdiEntityType.Media; + break; + case "member": + entityType = Constants.UdiEntityType.Member; + break; + } + + if (entityType != null && guid.HasValue) + { + var udi = new GuidUdi(entityType, guid.Value); + startNodeConfig["id"] = new JValue(udi.ToString()); + } + else + startNodeConfig.Remove("id"); + + datatype.Configuration = JsonConvert.SerializeObject(config); + Database.Update(datatype); + } + } + + break; + } + } + } + + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 33efc48c95..d38c8eb721 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -156,10 +156,17 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets a value indicating whether the content is published. /// - /// + /// + /// A content is published when it has a published version. + /// When retrieving documents from cache in non-preview mode, IsPublished is always + /// true, as only published documents are returned. When retrieving in draft mode, IsPublished + /// can either be true (document has a published version) or false (document has no + /// published version). + /// It is therefore possible for both IsDraft and IsPublished to be true at the same + /// time, meaning that the content is the draft version, and a published version exists. + /// bool IsPublished(string culture = null); - #endregion #region Tree diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 42ff16bae5..8bf8cec244 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -114,7 +114,6 @@ namespace Umbraco.Core.Models.PublishedContent /// public virtual bool IsPublished(string culture = null) => _content.IsPublished(culture); - #endregion #region Tree diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 1a978650f0..fb54e4a4f4 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -211,26 +211,6 @@ - - - - - - - - - - - - - - - - - - - - @@ -331,6 +311,12 @@ + + + + + + @@ -373,7 +359,7 @@ - + @@ -393,6 +379,7 @@ + @@ -416,6 +403,7 @@ + diff --git a/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs index 860dc0ac4b..bc18b97d47 100644 --- a/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs @@ -145,7 +145,7 @@ namespace Umbraco.Tests.Benchmarks XmlPublishedContent.InitializeNode(null, _xml10.DocumentElement, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out createDate, out updateDate, out level, out isDraft, out isPublished, out publishedContentType, + out createDate, out updateDate, out level, out isDraft, out publishedContentType, out properties, GetPublishedContentType); } @@ -155,7 +155,7 @@ namespace Umbraco.Tests.Benchmarks XmlPublishedContent.InitializeNode(null, _xml100.DocumentElement, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out createDate, out updateDate, out level, out isDraft, out isPublished,out publishedContentType, + out createDate, out updateDate, out level, out isDraft, out publishedContentType, out properties, GetPublishedContentType); } @@ -165,7 +165,7 @@ namespace Umbraco.Tests.Benchmarks XmlPublishedContent.InitializeNode(null, _xml1000.DocumentElement, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out createDate, out updateDate, out level, out isDraft, out isPublished,out publishedContentType, + out createDate, out updateDate, out level, out isDraft,out publishedContentType, out properties, GetPublishedContentType); } @@ -175,7 +175,7 @@ namespace Umbraco.Tests.Benchmarks XmlPublishedContent.InitializeNode(null, _xml10000.DocumentElement, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out createDate, out updateDate, out level, out isDraft, out isPublished,out publishedContentType, + out createDate, out updateDate, out level, out isDraft,out publishedContentType, out properties, GetPublishedContentType); } diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs index 147a159d5f..e868e32b07 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs @@ -59,6 +59,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var umbracoSettings = Factory.GetInstance(); var globalSettings = Factory.GetInstance(); + var umbracoContextAccessor = Factory.GetInstance(); _xml = new XmlDocument(); _xml.LoadXml(GetXml()); @@ -66,9 +67,9 @@ namespace Umbraco.Tests.Cache.PublishedCache var appCache = new DictionaryAppCache(); var domainCache = new DomainCache(ServiceContext.DomainService, DefaultCultureAccessor); var publishedShapshot = new Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedSnapshot( - new PublishedContentCache(xmlStore, domainCache, appCache, globalSettings, new SiteDomainHelper(), ContentTypesCache, null, null), - new PublishedMediaCache(xmlStore, ServiceContext.MediaService, ServiceContext.UserService, appCache, ContentTypesCache, Factory.GetInstance()), - new PublishedMemberCache(null, appCache, Current.Services.MemberService, ContentTypesCache), + new PublishedContentCache(xmlStore, domainCache, appCache, globalSettings, new SiteDomainHelper(), umbracoContextAccessor, ContentTypesCache, null, null), + new PublishedMediaCache(xmlStore, ServiceContext.MediaService, ServiceContext.UserService, appCache, ContentTypesCache, Factory.GetInstance(), umbracoContextAccessor), + new PublishedMemberCache(null, appCache, Current.Services.MemberService, ContentTypesCache, umbracoContextAccessor), domainCache); var publishedSnapshotService = new Mock(); publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedShapshot); diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index bd4f1610cd..07a6a6ee82 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Tests.PublishedContent; +using Umbraco.Web; namespace Umbraco.Tests.Cache.PublishedCache { @@ -26,6 +27,7 @@ namespace Umbraco.Tests.Cache.PublishedCache { private Dictionary _mediaTypes; + private IUmbracoContextAccessor _umbracoContextAccessor; protected override void Compose() { base.Compose(); @@ -33,6 +35,8 @@ namespace Umbraco.Tests.Cache.PublishedCache Composition.WithCollectionBuilder() .Clear() .Append(); + + _umbracoContextAccessor = Current.UmbracoContextAccessor; } protected override void Initialize() @@ -75,7 +79,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var mChild2 = MakeNewMedia("Child2", mType, user, mRoot2.Id); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(new XmlStore((XmlDocument) null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var cache = new PublishedMediaCache(new XmlStore((XmlDocument) null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); var roots = cache.GetAtRoot(); Assert.AreEqual(2, roots.Count()); Assert.IsTrue(roots.Select(x => x.Id).ContainsAll(new[] {mRoot1.Id, mRoot2.Id})); @@ -93,7 +97,7 @@ namespace Umbraco.Tests.Cache.PublishedCache //var publishedMedia = PublishedMediaTests.GetNode(mRoot.Id, GetUmbracoContext("/test", 1234)); var umbracoContext = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), Current.Services.MediaService, Current.Services.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), Current.Services.MediaService, Current.Services.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); var publishedMedia = cache.GetById(mRoot.Id); Assert.IsNotNull(publishedMedia); @@ -204,7 +208,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var result = new SearchResult("1234", 1, () => fields.ToDictionary(x => x.Key, x => new List { x.Value })); - var store = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var store = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result)); DoAssert(doc, 1234, key, null, 0, "/media/test.jpg", "Image", 23, "Shannon", "Shannon", 0, 0, "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2); @@ -220,7 +224,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var xmlDoc = GetMediaXml(); ((XmlElement)xmlDoc.DocumentElement.FirstChild).SetAttribute("key", key.ToString()); var navigator = xmlDoc.SelectSingleNode("/root/Image").CreateNavigator(); - var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); var doc = cache.CreateFromCacheValues(cache.ConvertFromXPathNavigator(navigator, true)); DoAssert(doc, 2000, key, null, 2, "image1", "Image", 23, "Shannon", "Shannon", 33, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); @@ -319,7 +323,8 @@ namespace Umbraco.Tests.Cache.PublishedCache // no xpath null, // not from examine - false), + false, + _umbracoContextAccessor), //callback to get the children (dd, n) => children, // callback to get a property @@ -329,7 +334,8 @@ namespace Umbraco.Tests.Cache.PublishedCache // no xpath null, // not from examine - false); + false, + _umbracoContextAccessor); return dicDoc; } diff --git a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs index 4c262fbf82..8ea13fc920 100644 --- a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs @@ -453,7 +453,7 @@ namespace Umbraco.Tests.Composing public class Resolved1 : Resolved { } - [Weight(5)] // default is 10 + [Weight(50)] // default is 100 public class Resolved2 : Resolved { } diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 6b3ce3eb4e..add3424599 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -279,7 +279,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(40, types.Count()); + Assert.AreEqual(39, types.Count()); } /// diff --git a/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config b/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config deleted file mode 100644 index 4c86355a1b..0000000000 --- a/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config +++ /dev/null @@ -1,114 +0,0 @@ - - - -
- - settings - - - - views/dashboard/settings/settingsdashboardintro.html - - - views/dashboard/settings/settingsdashboardvideos.html - - - - dashboard/ExamineManagement.ascx - -
- -
- - developer - - - - views/dashboard/developer/developerdashboardintro.html - - - views/dashboard/developer/developerdashboardvideos.html - - -
- -
- - media - - - - views/dashboard/media/mediafolderbrowser.html - - - - - admin - - - views/dashboard/media/mediadashboardintro.html - - - views/dashboard/media/desktopmediauploader.html - - - views/dashboard/media/mediadashboardvideos.html - - -
- -
- - translator - hello - world - - - content - - - - admin - - - views/dashboard/default/startupdashboardintro.html - - - views/dashboard/default/startupdashboardkits.html - - editor - writer - - - - views/dashboard/default/startupdashboardvideos.html - - - - dashboard/latestEdits.ascx - - - - views/dashboard/changepassword.html - - -
- -
- - default - member - - - - views/dashboard/members/membersdashboardintro.html - - - members/membersearch.ascx - - - views/dashboard/members/membersdashboardvideos.html - - -
-
diff --git a/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs b/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs deleted file mode 100644 index 920de683b4..0000000000 --- a/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System.Configuration; -using System.IO; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core.Configuration.Dashboard; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Configurations.DashboardSettings -{ - [TestFixture] - public class DashboardSettingsTests - { - [SetUp] - public void Init() - { - var config = new FileInfo(TestHelper.MapPathForTest("~/Configurations/DashboardSettings/web.config")); - - var fileMap = new ExeConfigurationFileMap() { ExeConfigFilename = config.FullName }; - var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); - - SettingsSection = configuration.GetSection("umbracoConfiguration/dashBoard") as DashboardSection; - - Assert.IsNotNull(SettingsSection); - } - - protected IDashboardSection SettingsSection { get; private set; } - - [Test] - public void Test_Sections() - { - Assert.AreEqual(5, SettingsSection.Sections.Count()); - - Assert.AreEqual("StartupSettingsDashboardSection", SettingsSection.Sections.ElementAt(0).Alias); - Assert.AreEqual("StartupDeveloperDashboardSection", SettingsSection.Sections.ElementAt(1).Alias); - Assert.AreEqual("StartupMediaDashboardSection", SettingsSection.Sections.ElementAt(2).Alias); - Assert.AreEqual("StartupDashboardSection", SettingsSection.Sections.ElementAt(3).Alias); - Assert.AreEqual("StartupMemberDashboardSection", SettingsSection.Sections.ElementAt(4).Alias); - } - - [Test] - public void Test_Section_Area() - { - Assert.AreEqual("settings", SettingsSection.Sections.ElementAt(0).Areas.First()); - Assert.AreEqual("developer", SettingsSection.Sections.ElementAt(1).Areas.First()); - Assert.AreEqual("media", SettingsSection.Sections.ElementAt(2).Areas.First()); - Assert.AreEqual("content", SettingsSection.Sections.ElementAt(3).Areas.First()); - Assert.AreEqual("default", SettingsSection.Sections.ElementAt(4).Areas.First()); - Assert.AreEqual("member", SettingsSection.Sections.ElementAt(4).Areas.Last()); - } - - [Test] - public void Test_Section_Access() - { - - Assert.AreEqual(3, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.Count()); - - Assert.AreEqual("translator", SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(0).Value); - Assert.AreEqual(AccessRuleType.Deny, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(0).Type); - Assert.AreEqual("hello", SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(1).Value); - Assert.AreEqual(AccessRuleType.Grant, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(1).Type); - Assert.AreEqual("world", SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(2).Value); - Assert.AreEqual(AccessRuleType.GrantBySection, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(2).Type); - } - - [Test] - public void Test_Section_Tabs() - { - //Element 0 Alias "StartupSettingsDashboardSection" - Assert.AreEqual(2, SettingsSection.Sections.ElementAt(0).Tabs.Count()); - - //Element 1 Alias "StartupDeveloperDashboardSection" - Assert.AreEqual(1, SettingsSection.Sections.ElementAt(1).Tabs.Count()); - - //Element 2 Alias "StartupMediaDashboardSection" - Assert.AreEqual(2, SettingsSection.Sections.ElementAt(2).Tabs.Count()); - - //Element 3 Alias "StartupDashboardSection" - Assert.AreEqual(3, SettingsSection.Sections.ElementAt(3).Tabs.Count()); - - //Element 4 Alias "StartupMemberDashboardSection" - Assert.AreEqual(1, SettingsSection.Sections.ElementAt(4).Tabs.Count()); - - } - - [Test] - public void Test_Tab() - { - Assert.AreEqual("Get Started", SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Caption); - Assert.AreEqual(2, SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.Count()); - } - - [Test] - public void Test_Tab_Access() - { - Assert.AreEqual(1, SettingsSection.Sections.ElementAt(2).Tabs.ElementAt(1).AccessRights.Rules.Count()); - Assert.AreEqual(AccessRuleType.Grant, SettingsSection.Sections.ElementAt(2).Tabs.ElementAt(1).AccessRights.Rules.ElementAt(0).Type); - Assert.AreEqual("admin", SettingsSection.Sections.ElementAt(2).Tabs.ElementAt(1).AccessRights.Rules.ElementAt(0).Value); - } - - [Test] - public void Test_Control() - { - Assert.AreEqual("hello", SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(0).PanelCaption); - Assert.AreEqual("views/dashboard/settings/settingsdashboardintro.html", - SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(0).ControlPath); - - Assert.AreEqual("", SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(1).PanelCaption); - Assert.AreEqual("views/dashboard/settings/settingsdashboardvideos.html", - SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(1).ControlPath); - } - - [Test] - public void Test_Control_Access() - { - Assert.AreEqual(2, SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.Count()); - Assert.AreEqual(AccessRuleType.Deny, SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.ElementAt(0).Type); - Assert.AreEqual("editor", SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.ElementAt(0).Value); - Assert.AreEqual(AccessRuleType.Deny, SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.ElementAt(1).Type); - Assert.AreEqual("writer", SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.ElementAt(1).Value); - } - } -} diff --git a/src/Umbraco.Tests/Configurations/DashboardSettings/web.config b/src/Umbraco.Tests/Configurations/DashboardSettings/web.config deleted file mode 100644 index 8cf262cbff..0000000000 --- a/src/Umbraco.Tests/Configurations/DashboardSettings/web.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - -
- - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 051c660c66..6605bc4546 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -7,12 +7,12 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration.Dashboard; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.Services; +using Umbraco.Core.Dashboards; namespace Umbraco.Tests.Manifest { @@ -387,7 +387,6 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 { const string json = @"{'dashboards': [ { - 'name': 'First One', 'alias': 'something', 'view': '~/App_Plugins/MyPackage/Dashboards/one.html', 'sections': [ 'content' ], @@ -395,7 +394,6 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 }, { - 'name': 'Second-One', 'alias': 'something.else', 'weight': -1, 'view': '~/App_Plugins/MyPackage/Dashboards/two.html', @@ -406,10 +404,9 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 var manifest = _parser.ParseManifest(json); Assert.AreEqual(2, manifest.Dashboards.Length); - Assert.IsInstanceOf(manifest.Dashboards[0]); + Assert.IsInstanceOf(manifest.Dashboards[0]); var db0 = manifest.Dashboards[0]; Assert.AreEqual("something", db0.Alias); - Assert.AreEqual("First One", db0.Name); Assert.AreEqual(100, db0.Weight); Assert.AreEqual("/App_Plugins/MyPackage/Dashboards/one.html", db0.View); Assert.AreEqual(1, db0.Sections.Length); @@ -420,10 +417,9 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.AreEqual(AccessRuleType.Deny, db0.AccessRules[1].Type); Assert.AreEqual("foo", db0.AccessRules[1].Value); - Assert.IsInstanceOf(manifest.Dashboards[1]); + Assert.IsInstanceOf(manifest.Dashboards[1]); var db1 = manifest.Dashboards[1]; Assert.AreEqual("something.else", db1.Alias); - Assert.AreEqual("Second-One", db1.Name); Assert.AreEqual(-1, db1.Weight); Assert.AreEqual("/App_Plugins/MyPackage/Dashboards/two.html", db1.View); Assert.AreEqual(1, db1.Sections.Length); diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 91810d6de1..8f3b9a1df9 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -161,7 +161,7 @@ namespace Umbraco.Tests.Published 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 @@ -189,7 +189,8 @@ namespace Umbraco.Tests.Published {{ ""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 @@ -249,7 +250,7 @@ namespace Umbraco.Tests.Published class TestPublishedContent : PublishedContentBase { - public TestPublishedContent(PublishedContentType contentType, Guid key, IEnumerable properties) + public TestPublishedContent(PublishedContentType contentType, Guid key, IEnumerable properties, IUmbracoContextAccessor umbracoContextAccessor): base(umbracoContextAccessor) { ContentType = contentType; Key = key; diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index ef37a822c1..4f18320fe8 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -20,6 +20,7 @@ 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; using Umbraco.Web.Routing; @@ -29,12 +30,13 @@ namespace Umbraco.Tests.PublishedContent [TestFixture] public class NuCacheTests { - [Test] - public void StandaloneVariations() - { - // this test implements a full standalone NuCache (based upon a test IDataSource, does not - // use any local db files, does not rely on any database) - and tests variations + private IPublishedSnapshotService _snapshotService; + private IVariationContextAccessor _variationAccesor; + private ContentType _contentType; + private PropertyType _propertyType; + private void Init() + { Current.Reset(); Current.UnlockConfigs(); Current.Configs.Add(SettingsForTests.GenerateMockUmbracoSettings); @@ -46,20 +48,39 @@ namespace Umbraco.Tests.PublishedContent { ContentTypeId = 2, Node = new ContentNode(1, Guid.NewGuid(), 0, "-1,1", 0, -1, DateTime.Now, 0), - DraftData = new ContentData { Name="It Works2!", Published = false, TemplateId = 0, VersionId = 2, VersionDate = DateTime.Now, WriterId = 0, + DraftData = new ContentData + { + Name = "It Works2!", + Published = false, + TemplateId = 0, + VersionId = 2, + VersionDate = DateTime.Now, + WriterId = 0, Properties = new Dictionary { { "prop", new[] { new PropertyData { Culture = "", Segment = "", Value = "val2" }, new PropertyData { Culture = "fr-FR", Segment = "", Value = "val-fr2" }, - new PropertyData { Culture = "en-UK", Segment = "", Value = "val-uk2" } + new PropertyData { Culture = "en-UK", Segment = "", Value = "val-uk2" }, + new PropertyData { Culture = "dk-DA", Segment = "", Value = "val-da2" }, + new PropertyData { Culture = "de-DE", Segment = "", Value = "val-de2" } } } }, CultureInfos = new Dictionary { - { "fr-FR", new CultureVariation { Name = "name-fr2", Date = new DateTime(2018, 01, 03, 01, 00, 00) } }, - { "en-UK", new CultureVariation { Name = "name-uk2", Date = new DateTime(2018, 01, 04, 01, 00, 00) } } + // draft data = everything, and IsDraft indicates what's edited + { "fr-FR", new CultureVariation { Name = "name-fr2", IsDraft = true, Date = new DateTime(2018, 01, 03, 01, 00, 00) } }, + { "en-UK", new CultureVariation { Name = "name-uk2", IsDraft = true, Date = new DateTime(2018, 01, 04, 01, 00, 00) } }, + { "dk-DA", new CultureVariation { Name = "name-da2", IsDraft = true, Date = new DateTime(2018, 01, 05, 01, 00, 00) } }, + { "de-DE", new CultureVariation { Name = "name-de1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) } } } }, - PublishedData = new ContentData { Name="It Works1!", Published = true, TemplateId = 0, VersionId = 1, VersionDate = DateTime.Now, WriterId = 0, + PublishedData = new ContentData + { + Name = "It Works1!", + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, Properties = new Dictionary { { "prop", new[] { new PropertyData { Culture = "", Segment = "", Value = "val1" }, @@ -68,8 +89,10 @@ namespace Umbraco.Tests.PublishedContent } } }, CultureInfos = new Dictionary { - { "fr-FR", new CultureVariation { Name = "name-fr1", Date = new DateTime(2018, 01, 01, 01, 00, 00) } }, - { "en-UK", new CultureVariation { Name = "name-uk1", Date = new DateTime(2018, 01, 02, 01, 00, 00) } } + // published data = only what's actually published, and IsDraft has to be false + { "fr-FR", new CultureVariation { Name = "name-fr1", IsDraft = false, Date = new DateTime(2018, 01, 01, 01, 00, 00) } }, + { "en-UK", new CultureVariation { Name = "name-uk1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) } }, + { "de-DE", new CultureVariation { Name = "name-de1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) } } } } }; @@ -88,13 +111,13 @@ namespace Umbraco.Tests.PublishedContent dataType }; - var propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Culture }; - var contentType = new ContentType(-1) { Id = 2, Alias = "alias-ct", Variations = ContentVariation.Culture }; - contentType.AddPropertyType(propertyType); + _propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Culture }; + _contentType = new ContentType(-1) { Id = 2, Alias = "alias-ct", Variations = ContentVariation.Culture }; + _contentType.AddPropertyType(_propertyType); var contentTypes = new[] { - contentType + _contentType }; var contentTypeService = Mock.Of(); @@ -106,10 +129,10 @@ namespace Umbraco.Tests.PublishedContent // create a service context var serviceContext = ServiceContext.CreatePartial( - dataTypeService : dataTypeService, + dataTypeService: dataTypeService, memberTypeService: Mock.Of(), memberService: Mock.Of(), - contentTypeService : contentTypeService, + contentTypeService: contentTypeService, localizationService: Mock.Of() ); @@ -132,18 +155,19 @@ namespace Umbraco.Tests.PublishedContent dataTypeService); // create a variation accessor - var variationAccessor = new TestVariationContextAccessor(); + _variationAccesor = new TestVariationContextAccessor(); // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; - var snapshotService = new PublishedSnapshotService(options, + _snapshotService = new PublishedSnapshotService(options, null, runtime, serviceContext, contentTypeFactory, null, new TestPublishedSnapshotAccessor(), - variationAccessor, + _variationAccesor, + Mock.Of(), Mock.Of(), scopeProvider, Mock.Of(), @@ -155,12 +179,21 @@ namespace Umbraco.Tests.PublishedContent new SiteDomainHelper(), Mock.Of()); - // get a snapshot, get a published content - var snapshot = snapshotService.CreatePublishedSnapshot(previewToken: null); - var publishedContent = snapshot.Content.GetById(1); - // invariant is the current default - variationAccessor.VariationContext = new VariationContext(); + _variationAccesor.VariationContext = new VariationContext(); + } + + [Test] + public void StandaloneVariations() + { + // this test implements a full standalone NuCache (based upon a test IDataSource, does not + // use any local db files, does not rely on any database) - and tests variations + + Init(); + + // get a snapshot, get a published content + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + var publishedContent = snapshot.Content.GetById(1); Assert.IsNotNull(publishedContent); Assert.AreEqual("It Works1!", publishedContent.Name); @@ -181,31 +214,31 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual("name-uk2", draftContent.GetCulture("en-UK").Name); // now french is default - variationAccessor.VariationContext = new VariationContext("fr-FR"); + _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); // now uk is default - variationAccessor.VariationContext = new VariationContext("en-UK"); + _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); - // invariant needs to be retrieved explicitely, when it's not default + // invariant needs to be retrieved explicitly, when it's not default Assert.AreEqual("val1", publishedContent.Value("prop", culture: "")); // but, // if the content type / property type does not vary, then it's all invariant again // modify the content type and property type, notify the snapshot service - contentType.Variations = ContentVariation.Nothing; - propertyType.Variations = ContentVariation.Nothing; - snapshotService.Notify(new[] { new ContentTypeCacheRefresher.JsonPayload("IContentType", publishedContent.ContentType.Id, ContentTypeChangeTypes.RefreshMain) }); + _contentType.Variations = ContentVariation.Nothing; + _propertyType.Variations = ContentVariation.Nothing; + _snapshotService.Notify(new[] { new ContentTypeCacheRefresher.JsonPayload("IContentType", publishedContent.ContentType.Id, ContentTypeChangeTypes.RefreshMain) }); // get a new snapshot (nothing changed in the old one), get the published content again - var anotherSnapshot = snapshotService.CreatePublishedSnapshot(previewToken: null); + var anotherSnapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); var againContent = anotherSnapshot.Content.GetById(1); Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.Variations); @@ -215,5 +248,42 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual("It Works1!", againContent.Name); Assert.AreEqual("val1", againContent.Value("prop")); } + + [Test] + public void IsDraftIsPublished() + { + Init(); + + // get the published published content + var s = _snapshotService.CreatePublishedSnapshot(null); + var c1 = s.Content.GetById(1); + + // published content = nothing is draft here + Assert.IsFalse(c1.IsDraft("fr-FR")); + Assert.IsFalse(c1.IsDraft("en-UK")); + Assert.IsFalse(c1.IsDraft("dk-DA")); + Assert.IsFalse(c1.IsDraft("de-DE")); + + // and only those with published name, are published + Assert.IsTrue(c1.IsPublished("fr-FR")); + Assert.IsTrue(c1.IsPublished("en-UK")); + Assert.IsFalse(c1.IsDraft("dk-DA")); + Assert.IsTrue(c1.IsPublished("de-DE")); + + // get the draft published content + var c2 = s.Content.GetById(true, 1); + + // draft content = we have drafts + Assert.IsTrue(c2.IsDraft("fr-FR")); + Assert.IsTrue(c2.IsDraft("en-UK")); + Assert.IsTrue(c2.IsDraft("dk-DA")); + Assert.IsFalse(c2.IsDraft("de-DE")); // except for the one that does not + + // and only those with published name, are published + Assert.IsTrue(c2.IsPublished("fr-FR")); + Assert.IsTrue(c2.IsPublished("en-UK")); + Assert.IsFalse(c2.IsPublished("dk-DA")); + Assert.IsTrue(c2.IsPublished("de-DE")); + } } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index dfb51e83fb..bc1d8c2b70 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -69,7 +69,7 @@ namespace Umbraco.Tests.PublishedContent { var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, - Factory.GetInstance()); + Factory.GetInstance(), Factory.GetInstance()); var doc = cache.GetById(id); Assert.IsNotNull(doc); return doc; @@ -482,7 +482,7 @@ namespace Umbraco.Tests.PublishedContent "); var node = xml.DescendantsAndSelf("Image").Single(x => (int)x.Attribute("id") == nodeId); - var publishedMedia = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var publishedMedia = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); var nav = node.CreateNavigator(); @@ -502,7 +502,7 @@ namespace Umbraco.Tests.PublishedContent var errorXml = new XElement("error", string.Format("No media is maching '{0}'", 1234)); var nav = errorXml.CreateNavigator(); - var publishedMedia = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var publishedMedia = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); var converted = publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/"), 1234); Assert.IsNull(converted); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index d70b736bc2..31681067d6 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -90,6 +90,7 @@ namespace Umbraco.Tests.Scoping null, publishedSnapshotAccessor, Mock.Of(), + Mock.Of(), Logger, ScopeProvider, documentRepository, mediaRepository, memberRepository, diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 75eb01e0e3..f633c003ba 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -2215,7 +2215,7 @@ namespace Umbraco.Tests.Services Assert.That(sut.GetValue("contentPicker"), Is.EqualTo(Udi.Create(Constants.UdiEntityType.Document, new Guid("74ECA1D4-934E-436A-A7C7-36CC16D4095C")))); Assert.That(sut.GetValue("mediaPicker"), Is.EqualTo(Udi.Create(Constants.UdiEntityType.Media, new Guid("44CB39C8-01E5-45EB-9CF8-E70AAF2D1691")))); Assert.That(sut.GetValue("memberPicker"), Is.EqualTo(Udi.Create(Constants.UdiEntityType.Member, new Guid("9A50A448-59C0-4D42-8F93-4F1D55B0F47D")))); - Assert.That(sut.GetValue("relatedLinks"), Is.EqualTo("")); + Assert.That(sut.GetValue("multiUrlPicker"), Is.EqualTo("[{\"name\":\"https://test.com\",\"url\":\"https://test.com\"}]")); Assert.That(sut.GetValue("tags"), Is.EqualTo("this,is,tags")); } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index d0c0b93b48..3d9a36ca80 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; 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; @@ -62,6 +63,7 @@ namespace Umbraco.Tests.Services null, publishedSnapshotAccessor, Mock.Of(), + Mock.Of(), Logger, ScopeProvider, documentRepository, mediaRepository, memberRepository, diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs index faf4acf8a4..19a57d7775 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs @@ -132,7 +132,7 @@ namespace Umbraco.Tests.TestHelpers.Entities content.SetValue("contentPicker", Udi.Create(Constants.UdiEntityType.Document, new Guid("74ECA1D4-934E-436A-A7C7-36CC16D4095C")).ToString()); content.SetValue("mediaPicker", Udi.Create(Constants.UdiEntityType.Media, new Guid("44CB39C8-01E5-45EB-9CF8-E70AAF2D1691")).ToString()); content.SetValue("memberPicker", Udi.Create(Constants.UdiEntityType.Member, new Guid("9A50A448-59C0-4D42-8F93-4F1D55B0F47D")).ToString()); - content.SetValue("relatedLinks", ""); + content.SetValue("multiUrlPicker", "[{\"name\":\"https://test.com\",\"url\":\"https://test.com\"}]"); content.SetValue("tags", "this,is,tags"); return content; diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index 14b967b1c9..d7dcf8e79a 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -370,7 +370,7 @@ namespace Umbraco.Tests.TestHelpers.Entities contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ContentPicker, ValueStorageType.Integer) { Alias = "contentPicker", Name = "Content Picker", Mandatory = false, SortOrder = 16, DataTypeId = 1046 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.MediaPicker, ValueStorageType.Integer) { Alias = "mediaPicker", Name = "Media Picker", Mandatory = false, SortOrder = 17, DataTypeId = 1048 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.MemberPicker, ValueStorageType.Integer) { Alias = "memberPicker", Name = "Member Picker", Mandatory = false, SortOrder = 18, DataTypeId = 1047 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.RelatedLinks, ValueStorageType.Ntext) { Alias = "relatedLinks", Name = "Related Links", Mandatory = false, SortOrder = 21, DataTypeId = 1050 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.MultiUrlPicker, ValueStorageType.Nvarchar) { Alias = "multiUrlPicker", Name = "Multi URL Picker", Mandatory = false, SortOrder = 21, DataTypeId = 1050 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Tags, ValueStorageType.Ntext) { Alias = "tags", Name = "Tags", Mandatory = false, SortOrder = 22, DataTypeId = 1041 }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 35085ddd85..76b163a2df 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -264,6 +264,7 @@ namespace Umbraco.Tests.TestHelpers Factory.GetInstance(), ScopeProvider, cache, publishedSnapshotAccessor, variationContextAccessor, + Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), DefaultCultureAccessor, Logger, diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 61d0672d7f..e2afb61839 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -292,7 +292,6 @@ - @@ -498,14 +497,6 @@ Designer - - Designer - Always - - - Designer - Always - Designer Always diff --git a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs index cc83dcb1c9..ba19f41e74 100644 --- a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs @@ -29,30 +29,5 @@ namespace Umbraco.Tests.Web.Mvc var output = _htmlHelper.Wrap("div", "hello world", new {style = "color:red;", onclick = "void();"}); Assert.AreEqual("
hello world
", output.ToHtmlString()); } - - [Test] - public void GetRelatedLinkHtml_Simple() - { - var relatedLink = new Umbraco.Web.Models.RelatedLink { - Caption = "Link Caption", - NewWindow = true, - Link = "https://www.google.com/" - }; - var output = _htmlHelper.GetRelatedLinkHtml(relatedLink); - Assert.AreEqual("Link Caption", output.ToHtmlString()); - } - - [Test] - public void GetRelatedLinkHtml_HtmlAttributes() - { - var relatedLink = new Umbraco.Web.Models.RelatedLink - { - Caption = "Link Caption", - NewWindow = true, - Link = "https://www.google.com/" - }; - var output = _htmlHelper.GetRelatedLinkHtml(relatedLink, new { @class = "test-class"}); - Assert.AreEqual("Link Caption", output.ToHtmlString()); - } } } diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs index 133cbb2ee7..fee18cb382 100644 --- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs @@ -419,13 +419,15 @@ namespace Umbraco.Tests.Web.Mvc //var provider = new ScopeUnitOfWorkProvider(databaseFactory, new RepositoryFactory(Mock.Of())); var scopeProvider = TestObjects.GetScopeProvider(Mock.Of()); var factory = Mock.Of(); - _service = new PublishedSnapshotService(svcCtx, factory, scopeProvider, cache, + var umbracoContextAccessor = Mock.Of(); + _service = new PublishedSnapshotService(svcCtx, factory, scopeProvider, cache, null, null, - null, null, null, + umbracoContextAccessor, null, null, null, new TestDefaultCultureAccessor(), Current.Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper(), Factory.GetInstance(), - null, true, false); // no events + null, true, false + ); // no events var http = GetHttpContextFactory(url, routeData).HttpContext; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 8afb0a17e3..fe657ae470 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -13278,7 +13278,7 @@ }, "pretty-hrtime": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, @@ -15838,7 +15838,7 @@ "dependencies": { "lru-cache": { "version": "2.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "resolved": "http://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=", "dev": true } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html index 6453963670..c3b60e2f0a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html @@ -17,16 +17,15 @@
- +
- +
-

{{property.caption}}

-
+
- +
- +
@@ -34,5 +33,5 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html index a9551138e1..7e54e2ef8e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html @@ -12,11 +12,11 @@ - +
- + - +
- + @@ -47,28 +47,28 @@
- +
- +
{{model.selectedMacro.name}}
- +
  • - + - +
- + There are no parameters for this macro - +
@@ -83,6 +83,13 @@ label-key="general_close" action="close()"> + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js index 82432ba78d..d6f6f67c55 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js @@ -55,7 +55,11 @@ } function hasAnyData(variant) { - var result = variant.isDirty != null || (variant.name != null && variant.name.length > 0); + + if(variant.name == null || variant.name.length === 0) { + return false; + } + var result = variant.isDirty != null; if(result) return true; @@ -110,8 +114,15 @@ vm.hasPristineVariants = pristineVariantFilter(variant); } - if(vm.isNew && hasAnyData(variant)){ - variant.save = true; + if(hasAnyData(variant)){ + if(vm.isNew || variant.publishDate == null){ + variant.publish = true; + variant.save = true; + } + }else{ + variant.publish = false; + variant.save = false; + variant.canSave = false; } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html index b33b7ccbfc..364ed47361 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html @@ -18,6 +18,7 @@ type="checkbox" ng-model="variant.publish" ng-change="vm.changeSelection(variant)" + ng-disabled="(vm.isNew && variant.language.isMandatory) || !variant.canSave" style="margin-right: 8px;" val-server-field="{{variant.htmlId}}" />
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js index 8d21234aee..9d3a8a87e5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js @@ -32,7 +32,10 @@ } function hasAnyData(variant) { - var result = variant.isDirty != null || (variant.name != null && variant.name.length > 0); + if(variant.name == null || variant.name.length === 0) { + return false; + } + var result = variant.isDirty != null; if(result) return true; diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 045c56764a..f05a7b4eed 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -105,7 +105,7 @@ - 8.0.0-alpha.33 + 8.0.0-alpha.34 @@ -212,6 +212,8 @@ Dashboard.config Designer + + feedProxy.config @@ -308,6 +310,7 @@ Designer + Designer diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 2e63acd3c3..3dcbfcded2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2016,4 +2016,16 @@ To manage your website, simply open the Umbraco back office and start adding con Relation Type Relations + + Getting Started + Redirect URL Management + Content + Welcome + Examine Management + Published Status + Models Builder + Health Check + Getting Started + Install Umbraco Forms + > diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 86484b1393..8c8a405e29 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2049,4 +2049,16 @@ To manage your website, simply open the Umbraco back office and start adding con Relation Type Relations + + Getting Started + Redirect URL Management + Content + Welcome + Examine Management + Published Status + Models Builder + Health Check + Getting Started + Install Umbraco Forms + diff --git a/src/Umbraco.Web.UI/config/Dashboard.Release.config b/src/Umbraco.Web.UI/config/Dashboard.Release.config deleted file mode 100644 index fec6ab34ae..0000000000 --- a/src/Umbraco.Web.UI/config/Dashboard.Release.config +++ /dev/null @@ -1,107 +0,0 @@ - - - -
- - settings - - - - views/dashboard/settings/settingsdashboardintro.html - - - - - views/dashboard/settings/examinemanagement.html - - - - - views/dashboard/settings/publishedstatus.html - - -
- -
- - forms - - - - views/dashboard/forms/formsdashboardintro.html - - -
- -
- - media - - - - views/dashboard/media/mediafolderbrowser.html - - -
- -
- - translator - - - content - - - - admin - - - - views/dashboard/default/startupdashboardintro.html - - -
- -
- - member - - - - views/dashboard/members/membersdashboardvideos.html - - -
- -
- - settings - - - - /App_Plugins/ModelsBuilder/modelsbuilder.htm - - -
- -
- - settings - - - - views/dashboard/settings/healthcheck.html - - -
-
- - content - - - - views/dashboard/content/redirecturls.html - - -
-
diff --git a/src/Umbraco.Web.UI/config/Dashboard.config b/src/Umbraco.Web.UI/config/Dashboard.config deleted file mode 100644 index fec6ab34ae..0000000000 --- a/src/Umbraco.Web.UI/config/Dashboard.config +++ /dev/null @@ -1,107 +0,0 @@ - - - -
- - settings - - - - views/dashboard/settings/settingsdashboardintro.html - - - - - views/dashboard/settings/examinemanagement.html - - - - - views/dashboard/settings/publishedstatus.html - - -
- -
- - forms - - - - views/dashboard/forms/formsdashboardintro.html - - -
- -
- - media - - - - views/dashboard/media/mediafolderbrowser.html - - -
- -
- - translator - - - content - - - - admin - - - - views/dashboard/default/startupdashboardintro.html - - -
- -
- - member - - - - views/dashboard/members/membersdashboardvideos.html - - -
- -
- - settings - - - - /App_Plugins/ModelsBuilder/modelsbuilder.htm - - -
- -
- - settings - - - - views/dashboard/settings/healthcheck.html - - -
-
- - content - - - - views/dashboard/content/redirecturls.html - - -
-
diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 2371282a14..e7399f600b 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -14,7 +14,6 @@
-
@@ -28,7 +27,6 @@ - diff --git a/src/Umbraco.Web/CompositionExtensions.cs b/src/Umbraco.Web/CompositionExtensions.cs index 246127b312..783c991ab4 100644 --- a/src/Umbraco.Web/CompositionExtensions.cs +++ b/src/Umbraco.Web/CompositionExtensions.cs @@ -10,6 +10,7 @@ using Umbraco.Web.Routing; using Umbraco.Web.ContentApps; using Umbraco.Web.Tour; using Umbraco.Web.Trees; +using Umbraco.Web.Dashboards; // the namespace here is intentional - although defined in Umbraco.Web assembly, // this class should be visible when using Umbraco.Core.Components, alongside @@ -92,6 +93,13 @@ namespace Umbraco.Core.Components public static BackOfficeSectionCollectionBuilder Sections(this Composition composition) => composition.WithCollectionBuilder(); + /// + /// Gets the backoffice dashboards collection builder. + /// + /// The composition. + public static DashboardCollectionBuilder Dashboards(this Composition composition) + => composition.WithCollectionBuilder(); + #endregion #region Uniques diff --git a/src/Umbraco.Web/Dashboards/ContentDashboard.cs b/src/Umbraco.Web/Dashboards/ContentDashboard.cs new file mode 100644 index 0000000000..0cd96f738c --- /dev/null +++ b/src/Umbraco.Web/Dashboards/ContentDashboard.cs @@ -0,0 +1,29 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(10)] + public class ContentDashboard : IDashboard + { + public string Alias => "contentIntro"; + + public string[] Sections => new [] { "content" }; + + public string View => "views/dashboard/default/startupdashboardintro.html"; + + public IAccessRule[] AccessRules + { + get + { + var rules = new IAccessRule[] + { + new AccessRule {Type = AccessRuleType.Deny, Value = Constants.Security.TranslatorGroupAlias}, + new AccessRule {Type = AccessRuleType.Grant, Value = Constants.Security.AdminGroupAlias} + }; + return rules; + } + } + } +} diff --git a/src/Umbraco.Web/Dashboards/DashboardCollection.cs b/src/Umbraco.Web/Dashboards/DashboardCollection.cs new file mode 100644 index 0000000000..616a2cc8cc --- /dev/null +++ b/src/Umbraco.Web/Dashboards/DashboardCollection.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + public class DashboardCollection : BuilderCollectionBase + { + public DashboardCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Web/Dashboards/DashboardCollectionBuilder.cs b/src/Umbraco.Web/Dashboards/DashboardCollectionBuilder.cs new file mode 100644 index 0000000000..36a417e957 --- /dev/null +++ b/src/Umbraco.Web/Dashboards/DashboardCollectionBuilder.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; +using Umbraco.Core.Manifest; + +namespace Umbraco.Web.Dashboards +{ + public class DashboardCollectionBuilder : WeightedCollectionBuilderBase + { + protected override DashboardCollectionBuilder This => this; + + protected override IEnumerable CreateItems(IFactory factory) + { + // get the manifest parser just-in-time - injecting it in the ctor would mean that + // simply getting the builder in order to configure the collection, would require + // its dependencies too, and that can create cycles or other oddities + var manifestParser = factory.GetInstance(); + + var dashboardSections = Merge(base.CreateItems(factory), manifestParser.Manifest.Dashboards); + + return dashboardSections; + } + + private IEnumerable Merge(IEnumerable dashboardsFromCode, IReadOnlyList dashboardFromManifest) + { + return dashboardsFromCode.Concat(dashboardFromManifest) + .Where(x => !string.IsNullOrEmpty(x.Alias)) + .OrderBy(GetWeight); + } + + private int GetWeight(IDashboard dashboard) + { + switch (dashboard) + { + case ManifestDashboard manifestDashboardDefinition: + return manifestDashboardDefinition.Weight; + + default: + var weightAttribute = dashboard.GetType().GetCustomAttribute(false); + return weightAttribute?.Weight ?? DefaultWeight; + } + } + } +} diff --git a/src/Umbraco.Web/Dashboards/ExamineDashboard.cs b/src/Umbraco.Web/Dashboards/ExamineDashboard.cs new file mode 100644 index 0000000000..47cf97ca3b --- /dev/null +++ b/src/Umbraco.Web/Dashboards/ExamineDashboard.cs @@ -0,0 +1,20 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(20)] + public class ExamineDashboard : IDashboard + { + public string Alias => "settingsExamine"; + + public string[] Sections => new [] { "settings" }; + + public string View => "views/dashboard/settings/examinemanagement.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } + + +} diff --git a/src/Umbraco.Web/Dashboards/FormsDashboard.cs b/src/Umbraco.Web/Dashboards/FormsDashboard.cs new file mode 100644 index 0000000000..a3e1123369 --- /dev/null +++ b/src/Umbraco.Web/Dashboards/FormsDashboard.cs @@ -0,0 +1,18 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(10)] + public class FormsDashboard : IDashboard + { + public string Alias => "formsInstall"; + + public string[] Sections => new [] { "forms" }; + + public string View => "views/dashboard/forms/formsdashboardintro.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } +} diff --git a/src/Umbraco.Web/Dashboards/HealthCheckDashboard.cs b/src/Umbraco.Web/Dashboards/HealthCheckDashboard.cs new file mode 100644 index 0000000000..746dd04439 --- /dev/null +++ b/src/Umbraco.Web/Dashboards/HealthCheckDashboard.cs @@ -0,0 +1,20 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(50)] + public class HealthCheckDashboard : IDashboard + { + public string Alias => "settingsHealthCheck"; + + public string[] Sections => new [] { "settings" }; + + public string View => "views/dashboard/settings/healthcheck.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } + + +} diff --git a/src/Umbraco.Web/Dashboards/MediaDashboard.cs b/src/Umbraco.Web/Dashboards/MediaDashboard.cs new file mode 100644 index 0000000000..c97ae298f3 --- /dev/null +++ b/src/Umbraco.Web/Dashboards/MediaDashboard.cs @@ -0,0 +1,18 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(10)] + public class MediaDashboard : IDashboard + { + public string Alias => "mediaFolderBrowser"; + + public string[] Sections => new [] { "media" }; + + public string View => "views/dashboard/media/mediafolderbrowser.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } +} diff --git a/src/Umbraco.Web/Dashboards/MembersDashboard.cs b/src/Umbraco.Web/Dashboards/MembersDashboard.cs new file mode 100644 index 0000000000..473722dce1 --- /dev/null +++ b/src/Umbraco.Web/Dashboards/MembersDashboard.cs @@ -0,0 +1,18 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(10)] + public class MembersDashboard : IDashboard + { + public string Alias => "memberIntro"; + + public string[] Sections => new [] { "member" }; + + public string View => "views/dashboard/members/membersdashboardvideos.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } +} diff --git a/src/Umbraco.Web/Dashboards/ModelsBuilderDashboard.cs b/src/Umbraco.Web/Dashboards/ModelsBuilderDashboard.cs new file mode 100644 index 0000000000..44bc00cb6f --- /dev/null +++ b/src/Umbraco.Web/Dashboards/ModelsBuilderDashboard.cs @@ -0,0 +1,20 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(40)] + public class ModelsBuilderDashboard : IDashboard + { + public string Alias => "settingsModelsBuilder"; + + public string[] Sections => new [] { "settings" }; + + public string View => "/App_Plugins/ModelsBuilder/modelsbuilder.htm"; + + public IAccessRule[] AccessRules => Array.Empty(); + } + + +} diff --git a/src/Umbraco.Web/Dashboards/PublishedStatusDashboard.cs b/src/Umbraco.Web/Dashboards/PublishedStatusDashboard.cs new file mode 100644 index 0000000000..66faa20b55 --- /dev/null +++ b/src/Umbraco.Web/Dashboards/PublishedStatusDashboard.cs @@ -0,0 +1,20 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(30)] + public class PublishedStatusDashboard : IDashboard + { + public string Alias => "settingsPublishedStatus"; + + public string[] Sections => new [] { "settings" }; + + public string View => "views/dashboard/settings/publishedstatus.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } + + +} diff --git a/src/Umbraco.Web/Dashboards/RedirectUrlDashboard.cs b/src/Umbraco.Web/Dashboards/RedirectUrlDashboard.cs new file mode 100644 index 0000000000..f538cbc122 --- /dev/null +++ b/src/Umbraco.Web/Dashboards/RedirectUrlDashboard.cs @@ -0,0 +1,18 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(20)] + public class RedirectUrlDashboard : IDashboard + { + public string Alias => "contentRedirectManager"; + + public string[] Sections => new [] { "content" }; + + public string View => "views/dashboard/content/redirecturls.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } +} diff --git a/src/Umbraco.Web/Dashboards/SettingsDashboards.cs b/src/Umbraco.Web/Dashboards/SettingsDashboards.cs new file mode 100644 index 0000000000..5cd92e4c3f --- /dev/null +++ b/src/Umbraco.Web/Dashboards/SettingsDashboards.cs @@ -0,0 +1,18 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(10)] + public class SettingsDashboard : IDashboard + { + public string Alias => "settingsWelcome"; + + public string[] Sections => new [] { "settings" }; + + public string View => "views/dashboard/settings/settingsdashboardintro.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } +} diff --git a/src/Umbraco.Web/Editors/DashboardController.cs b/src/Umbraco.Web/Editors/DashboardController.cs index 44fe04ef92..91a966cef7 100644 --- a/src/Umbraco.Web/Editors/DashboardController.cs +++ b/src/Umbraco.Web/Editors/DashboardController.cs @@ -6,8 +6,8 @@ using Umbraco.Web.Mvc; using Newtonsoft.Json.Linq; using System.Threading.Tasks; using System.Net.Http; -using System.Web.Http; using System; +using System.Linq; using System.Net; using System.Text; using Umbraco.Core.Cache; @@ -16,6 +16,8 @@ using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Services; +using Umbraco.Core.Dashboards; +using Umbraco.Web.Services; namespace Umbraco.Web.Editors { @@ -25,9 +27,10 @@ namespace Umbraco.Web.Editors [AngularJsonOnlyConfiguration] [IsBackOffice] [WebApi.UmbracoAuthorize] + public class DashboardController : UmbracoApiController { - private readonly Dashboards _dashboards; + private readonly IDashboardService _dashboardService; /// /// Initializes a new instance of the with auto dependencies. @@ -38,10 +41,10 @@ namespace Umbraco.Web.Editors /// /// Initializes a new instance of the with all its dependencies. /// - public DashboardController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, Dashboards dashboards) + public DashboardController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, IDashboardService dashboardService) : base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState) { - _dashboards = dashboards; + _dashboardService = dashboardService; } //we have just one instance of HttpClient shared for the entire application @@ -199,11 +202,24 @@ namespace Umbraco.Web.Editors } + // return IDashboardSlim - we don't need sections nor access rules [ValidateAngularAntiForgeryToken] [OutgoingEditorModelEvent] - public IEnumerable> GetDashboard(string section) + public IEnumerable> GetDashboard(string section) { - return _dashboards.GetDashboards(section, Security.CurrentUser); + return _dashboardService.GetDashboards(section, Security.CurrentUser).Select(x => new Tab + { + Id = x.Id, + Alias = x.Alias, + Label = x.Label, + Expanded = x.Expanded, + IsActive = x.IsActive, + Properties = x.Properties.Select(y => new DashboardSlim + { + Alias = y.Alias, + View = y.View + }) + }); } } } diff --git a/src/Umbraco.Web/Editors/Dashboards.cs b/src/Umbraco.Web/Editors/Dashboards.cs deleted file mode 100644 index c837cbbf33..0000000000 --- a/src/Umbraco.Web/Editors/Dashboards.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration.Dashboard; -using Umbraco.Core.IO; -using Umbraco.Core.Manifest; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Services; - -namespace Umbraco.Web.Editors -{ - public class Dashboards - { - private readonly ISectionService _sectionService; - private readonly IDashboardSection _dashboardSection; - private readonly ManifestParser _manifestParser; - - public Dashboards(ISectionService sectionService, IDashboardSection dashboardSection, ManifestParser manifestParser) - { - _sectionService = sectionService ?? throw new ArgumentNullException(nameof(sectionService)); - _dashboardSection = dashboardSection; - _manifestParser = manifestParser; - } - - /// - /// Gets all dashboards, organized by section, for a user. - /// - public IDictionary>> GetDashboards(IUser currentUser) - { - return _sectionService.GetSections().ToDictionary(x => x.Alias, x => GetDashboards(x.Alias, currentUser)); - } - - /// - /// Returns dashboards for a specific section, for a user. - /// - public IEnumerable> GetDashboards(string section, IUser currentUser) - { - var tabId = 1; - var configDashboards = GetDashboardsFromConfig(ref tabId, section, currentUser); - var pluginDashboards = GetDashboardsFromPlugins(ref tabId, section, currentUser); - - // merge dashboards - // both collections contain tab.alias -> controls - var dashboards = configDashboards; - - // until now, it was fine to have duplicate tab.aliases in configDashboard - // so... the rule should be - just merge whatever we get, don't be clever - dashboards.AddRange(pluginDashboards); - - // re-sort by id - dashboards.Sort((tab1, tab2) => tab1.Id > tab2.Id ? 1 : 0); - - // re-assign ids (why?) - var i = 1; - foreach (var tab in dashboards) - { - tab.Id = i++; - tab.IsActive = tab.Id == 1; - } - - return configDashboards; - } - - // note: - // in dashboard.config we have 'sections' which define 'tabs' for 'areas' - // and 'areas' are the true UI sections - and each tab can have more than - // one control - // in a manifest, we directly have 'dashboards' which map to a unique - // control in a tab - - // gets all tabs & controls from the config file - private List> GetDashboardsFromConfig(ref int tabId, string section, IUser currentUser) - { - var tabs = new List>(); - - // disable packages section dashboard - if (section == "packages") return tabs; - - foreach (var dashboardSection in _dashboardSection.Sections.Where(x => x.Areas.InvariantContains(section))) - { - // validate access to this section - if (!DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _sectionService)) - continue; - - foreach (var tab in dashboardSection.Tabs) - { - // validate access to this tab - if (!DashboardSecurity.AuthorizeAccess(tab, currentUser, _sectionService)) - continue; - - var dashboardControls = new List(); - - foreach (var control in tab.Controls) - { - // validate access to this control - if (!DashboardSecurity.AuthorizeAccess(control, currentUser, _sectionService)) - continue; - - // create and add control - var dashboardControl = new DashboardControl - { - Caption = control.PanelCaption, - Path = IOHelper.FindFile(control.ControlPath.Trim()) - }; - - if (dashboardControl.Path.InvariantEndsWith(".ascx")) - throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported."); - - dashboardControls.Add(dashboardControl); - } - - // create and add tab - tabs.Add(new Tab - { - Id = tabId++, - Alias = tab.Caption.ToSafeAlias(), - Label = tab.Caption, - Properties = dashboardControls - }); - } - } - - return tabs; - } - - private List> GetDashboardsFromPlugins(ref int tabId, string section, IUser currentUser) - { - var tabs = new List>(); - - foreach (var dashboard in _manifestParser.Manifest.Dashboards.Where(x => x.Sections.InvariantContains(section)).OrderBy(x => x.Weight)) - { - // validate access - if (!DashboardSecurity.CheckUserAccessByRules(currentUser, _sectionService, dashboard.AccessRules)) - continue; - - var dashboardControl = new DashboardControl - { - Caption = "", - Path = IOHelper.FindFile(dashboard.View.Trim()) - }; - - if (dashboardControl.Path.InvariantEndsWith(".ascx")) - throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported."); - - tabs.Add(new Tab - { - Id = tabId++, - Alias = dashboard.Alias.ToSafeAlias(), - Label = dashboard.Name, - Properties = new[] { dashboardControl } - }); - } - - return tabs; - } - } -} diff --git a/src/Umbraco.Web/Editors/EditorModelEventManager.cs b/src/Umbraco.Web/Editors/EditorModelEventManager.cs index 2225f5c577..c3a7d128ec 100644 --- a/src/Umbraco.Web/Editors/EditorModelEventManager.cs +++ b/src/Umbraco.Web/Editors/EditorModelEventManager.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Web.Http.Filters; +using Umbraco.Core.Dashboards; using Umbraco.Core.Events; using Umbraco.Web.Models.ContentEditing; @@ -14,9 +15,9 @@ namespace Umbraco.Web.Editors public static event TypedEventHandler> SendingMediaModel; public static event TypedEventHandler> SendingMemberModel; public static event TypedEventHandler> SendingUserModel; - public static event TypedEventHandler>>> SendingDashboardModel; + public static event TypedEventHandler>>> SendingDashboardModel; - private static void OnSendingDashboardModel(HttpActionExecutedContext sender, EditorModelEventArgs>> e) + private static void OnSendingDashboardModel(HttpActionExecutedContext sender, EditorModelEventArgs>> e) { var handler = SendingDashboardModel; handler?.Invoke(sender, e); @@ -65,8 +66,8 @@ namespace Umbraco.Web.Editors if (e.Model is UserDisplay) OnSendingUserModel(sender, new EditorModelEventArgs(e)); - if (e.Model is IEnumerable>) - OnSendingDashboardModel(sender, new EditorModelEventArgs>>(e)); + if (e.Model is IEnumerable) + OnSendingDashboardModel(sender, new EditorModelEventArgs>>(e)); } } } diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index 74a06d7149..f6973fcbb9 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -22,15 +22,15 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class SectionController : UmbracoAuthorizedJsonController { - private readonly Dashboards _dashboards; + private readonly IDashboardService _dashboardService; private readonly ISectionService _sectionService; private readonly ITreeService _treeService; public SectionController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, - Dashboards dashboards, ISectionService sectionService, ITreeService treeService) + IDashboardService dashboardService, ISectionService sectionService, ITreeService treeService) : base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState) { - _dashboards = dashboards; + _dashboardService = dashboardService; _sectionService = sectionService; _treeService = treeService; } @@ -48,7 +48,7 @@ namespace Umbraco.Web.Editors ControllerContext = ControllerContext }; - var dashboards = _dashboards.GetDashboards(Security.CurrentUser); + var dashboards = _dashboardService.GetDashboards(Security.CurrentUser); //now we can add metadata for each section so that the UI knows if there's actually anything at all to render for //a dashboard for a given section, then the UI can deal with it accordingly (i.e. redirect to the first tree) diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index e18397dea2..c5d2a65cb9 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -830,39 +830,5 @@ namespace Umbraco.Web } #endregion - - - #region RelatedLink - - /// - /// Renders an anchor element for a RelatedLink instance. - /// Format: <a href="relatedLink.Link" target="_blank/_self">relatedLink.Caption</a> - /// - /// The HTML helper instance that this method extends. - /// The RelatedLink instance - /// An anchor element - public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink) - { - return htmlHelper.GetRelatedLinkHtml(relatedLink, null); - } - - /// - /// Renders an anchor element for a RelatedLink instance, accepting htmlAttributes. - /// Format: <a href="relatedLink.Link" target="_blank/_self" htmlAttributes>relatedLink.Caption</a> - /// - /// The HTML helper instance that this method extends. - /// The RelatedLink instance - /// An object that contains the HTML attributes to set for the element. - /// - public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink, object htmlAttributes) - { - var tagBuilder = new TagBuilder("a"); - tagBuilder.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); - tagBuilder.MergeAttribute("href", relatedLink.Link); - tagBuilder.MergeAttribute("target", relatedLink.NewWindow ? "_blank" : "_self"); - tagBuilder.InnerHtml = HttpUtility.HtmlEncode(relatedLink.Caption); - return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal)); - } - #endregion } } diff --git a/src/Umbraco.Web/Models/ContentEditing/DashboardControl.cs b/src/Umbraco.Web/Models/ContentEditing/DashboardControl.cs deleted file mode 100644 index aad6bf2d64..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/DashboardControl.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Web.Models.ContentEditing -{ - [DataContract(Name = "control", Namespace = "")] - public class DashboardControl - { - [DataMember(Name = "path")] - public string Path { get; set; } - - [DataMember(Name = "caption")] - public string Caption { get; set; } - } -} diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index dd6afbd97f..8a7565265f 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -15,7 +15,12 @@ namespace Umbraco.Web.Models [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] public abstract class PublishedContentBase : IPublishedContent { - private string _url; // FIXME: task - cannot cache urls, they depends on the current request + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + protected PublishedContentBase(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor; + } #region ContentType @@ -81,24 +86,21 @@ namespace Umbraco.Web.Models /// public virtual string GetUrl(string culture = null) // TODO: consider .GetCulture("fr-FR").Url { + var umbracoContext = _umbracoContextAccessor.UmbracoContext; switch (ItemType) { case PublishedItemType.Content: - // TODO: consider injecting an umbraco context accessor - if (UmbracoContext.Current == null) - throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext.Current is null."); - if (UmbracoContext.Current.UrlProvider == null) - throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext.Current.UrlProvider is null."); - return UmbracoContext.Current.UrlProvider.GetUrl(this, culture); + if (umbracoContext == null) + throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext is null."); + if (umbracoContext.UrlProvider == null) + throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext.UrlProvider is null."); + return umbracoContext.UrlProvider.GetUrl(this, culture); case PublishedItemType.Media: - if (_url != null) return _url; // assume it will not depend on current uri/culture - var prop = GetProperty(Constants.Conventions.Media.File); if (prop?.GetValue() == null) { - _url = string.Empty; - return _url; + return string.Empty; } var propType = ContentType.GetPropertyType(Constants.Conventions.Media.File); @@ -110,7 +112,7 @@ namespace Umbraco.Web.Models switch (propType.EditorAlias) { case Constants.PropertyEditors.Aliases.UploadField: - _url = prop.GetValue().ToString(); + return prop.GetValue().ToString(); break; case Constants.PropertyEditors.Aliases.ImageCropper: //get the url from the json format @@ -118,14 +120,12 @@ namespace Umbraco.Web.Models var stronglyTyped = prop.GetValue() as ImageCropperValue; if (stronglyTyped != null) { - _url = stronglyTyped.Src; - break; + return stronglyTyped.Src; } - _url = prop.GetValue()?.ToString(); - break; + return prop.GetValue()?.ToString(); } - return _url; + return string.Empty; default: throw new NotSupportedException(); @@ -144,6 +144,7 @@ namespace Umbraco.Web.Models /// public abstract bool IsDraft(string culture = null); + /// public abstract bool IsPublished(string culture = null); #endregion diff --git a/src/Umbraco.Web/Models/RelatedLink.cs b/src/Umbraco.Web/Models/RelatedLink.cs deleted file mode 100644 index 1e1d7636ad..0000000000 --- a/src/Umbraco.Web/Models/RelatedLink.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web.Models -{ - public class RelatedLink : RelatedLinkBase - { - public int? Id { get; internal set; } - internal bool IsDeleted { get; set; } - public IPublishedContent Content { get; set; } - } -} diff --git a/src/Umbraco.Web/Models/RelatedLinkBase.cs b/src/Umbraco.Web/Models/RelatedLinkBase.cs deleted file mode 100644 index c2077ce4a9..0000000000 --- a/src/Umbraco.Web/Models/RelatedLinkBase.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Newtonsoft.Json; - -namespace Umbraco.Web.Models -{ - public abstract class RelatedLinkBase - { - [JsonProperty("caption")] - public string Caption { get; set; } - [JsonProperty("link")] - public string Link { get; set; } - [JsonProperty("newWindow")] - public bool NewWindow { get; set; } - [JsonProperty("isInternal")] - public bool IsInternal { get; set; } - [JsonProperty("type")] - public RelatedLinkType Type { get; set; } - } -} diff --git a/src/Umbraco.Web/Models/RelatedLinkType.cs b/src/Umbraco.Web/Models/RelatedLinkType.cs deleted file mode 100644 index eec7817ab6..0000000000 --- a/src/Umbraco.Web/Models/RelatedLinkType.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Umbraco -// -// -// Defines the RelatedLinkType type. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace Umbraco.Web.Models -{ - /// - /// The related link type. - /// - public enum RelatedLinkType - { - /// - /// Internal link type - /// - Internal, - - /// - /// External link type - /// - External - } -} diff --git a/src/Umbraco.Web/Models/RelatedLinks.cs b/src/Umbraco.Web/Models/RelatedLinks.cs deleted file mode 100644 index 22cdcd11b6..0000000000 --- a/src/Umbraco.Web/Models/RelatedLinks.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; - -namespace Umbraco.Web.Models -{ - [TypeConverter(typeof(RelatedLinksTypeConverter))] - public class RelatedLinks : IEnumerable - { - private readonly string _propertyData; - - private readonly IEnumerable _relatedLinks; - - public RelatedLinks(IEnumerable relatedLinks, string propertyData) - { - _relatedLinks = relatedLinks; - _propertyData = propertyData; - } - - /// - /// Gets the property data. - /// - internal string PropertyData - { - get - { - return this._propertyData; - } - } - - public IEnumerator GetEnumerator() - { - return _relatedLinks.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - } -} diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs index 54bf5c4d15..1648b81623 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs @@ -7,7 +7,7 @@ using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors { - [DataEditor(Constants.PropertyEditors.Aliases.MultiUrlPicker, EditorType.PropertyValue|EditorType.MacroParameter, "Multi Url Picker", "multiurlpicker", ValueType = "JSON", Group = "pickers", Icon = "icon-link")] + [DataEditor(Constants.PropertyEditors.Aliases.MultiUrlPicker, EditorType.PropertyValue|EditorType.MacroParameter, "Multi Url Picker", "multiurlpicker", ValueType = ValueTypes.Json, Group = "pickers", Icon = "icon-link")] public class MultiUrlPickerPropertyEditor : DataEditor { private readonly IEntityService _entityService; diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksConfiguration.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksConfiguration.cs deleted file mode 100644 index 5db14c6842..0000000000 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinksConfiguration.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// Represents the configuration for the related links value editor. - /// - public class RelatedLinksConfiguration - { - [ConfigurationField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] - public int Maximum { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksConfigurationEditor.cs deleted file mode 100644 index 07ff359a82..0000000000 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinksConfigurationEditor.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// Represents the configuration editor for the related links value editor. - /// - public class RelatedLinksConfigurationEditor : ConfigurationEditor - { - public override IDictionary ToValueEditor(object configuration) - { - var d = base.ToValueEditor(configuration); - d["idType"] = "udi"; - return d; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs deleted file mode 100644 index b450fcc67f..0000000000 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - [DataEditor(Constants.PropertyEditors.Aliases.RelatedLinks, "Related links", "relatedlinks", ValueType = ValueTypes.Json, Icon = "icon-thumbnail-list", Group = "pickers")] - public class RelatedLinksPropertyEditor : DataEditor - { - public RelatedLinksPropertyEditor(ILogger logger) - : base(logger) - { } - - protected override IConfigurationEditor CreateConfigurationEditor() => new RelatedLinksConfigurationEditor(); - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs deleted file mode 100644 index 6c2a4331d0..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Core.Services; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter(typeof(JsonValueConverter))] //this shadows the JsonValueConverter - public class RelatedLinksLegacyValueConverter : PropertyValueConverterBase - { - private static readonly string[] MatchingEditors = { - Constants.PropertyEditors.Aliases.RelatedLinks - }; - - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ILogger _logger; - private readonly ServiceContext _services; - - public RelatedLinksLegacyValueConverter(IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, ILogger logger) - { - _umbracoContextAccessor = umbracoContextAccessor; - _services = services; - _logger = logger; - } - - public override bool IsConverter(PublishedPropertyType propertyType) - => MatchingEditors.Contains(propertyType.EditorAlias); - - public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (JArray); - - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) return null; - var sourceString = source.ToString(); - - if (sourceString.DetectIsJson()) - { - try - { - var obj = JsonConvert.DeserializeObject(sourceString); - //update the internal links if we have a context - if (UmbracoContext.Current != null) - { - var helper = new UmbracoHelper(_umbracoContextAccessor.UmbracoContext, _services); - foreach (var a in obj) - { - var type = a.Value("type"); - if (type.IsNullOrWhiteSpace() == false) - { - if (type == "internal") - { - switch (propertyType.EditorAlias) - { - case Constants.PropertyEditors.Aliases.RelatedLinks: - var strLinkId = a.Value("link"); - var udiAttempt = strLinkId.TryConvertTo(); - if (udiAttempt) - { - var content = helper.PublishedContent(udiAttempt.Result); - if (content == null) break; - a["link"] = helper.Url(content.Id); - } - break; - } - } - } - } - } - return obj; - } - catch (Exception ex) - { - _logger.Error(ex, "Could not parse the string '{Json}' to a json object", sourceString); - } - } - - //it's not json, just return the string - return sourceString; - } - - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object source, bool preview) - { - if (source == null) return null; - var sourceString = source.ToString(); - - if (sourceString.DetectIsJson()) - { - try - { - var obj = JsonConvert.DeserializeObject(sourceString); - - var d = new XmlDocument(); - var e = d.CreateElement("links"); - d.AppendChild(e); - - foreach (dynamic link in obj) - { - var ee = d.CreateElement("link"); - ee.SetAttribute("title", link.title); - ee.SetAttribute("link", link.link); - ee.SetAttribute("type", link.type); - ee.SetAttribute("newwindow", link.newWindow); - - e.AppendChild(ee); - } - - return d.CreateNavigator(); - } - catch (Exception ex) - { - _logger.Error(ex, "Could not parse the string '{Json}' to a json object", sourceString); - } - } - - //it's not json, just return the string - return sourceString; - } - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs deleted file mode 100644 index 983d122a83..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Web.Models; -using Umbraco.Web.PublishedCache; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - /// - /// The related links property value converter. - /// - [DefaultPropertyValueConverter(typeof(RelatedLinksLegacyValueConverter), typeof(JsonValueConverter))] - public class RelatedLinksValueConverter : PropertyValueConverterBase - { - private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ILogger _logger; - - public RelatedLinksValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger) - { - _publishedSnapshotAccessor = publishedSnapshotAccessor; - _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; - } - - /// - /// Checks if this converter can convert the property editor and registers if it can. - /// - /// - /// The property type. - /// - /// - /// The . - /// - public override bool IsConverter(PublishedPropertyType propertyType) - => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.RelatedLinks); - - public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (JArray); - - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) return null; - var sourceString = source.ToString(); - - var relatedLinksData = JsonConvert.DeserializeObject>(sourceString); - var relatedLinks = new List(); - - foreach (var linkData in relatedLinksData) - { - var relatedLink = new RelatedLink - { - Caption = linkData.Caption, - NewWindow = linkData.NewWindow, - IsInternal = linkData.IsInternal, - Type = linkData.Type, - Link = linkData.Link - }; - - int contentId; - if (int.TryParse(relatedLink.Link, out contentId)) - { - relatedLink.Id = contentId; - relatedLink = CreateLink(relatedLink); - } - else - { - var strLinkId = linkData.Link; - var udiAttempt = strLinkId.TryConvertTo(); - if (udiAttempt.Success && udiAttempt.Result != null) - { - var content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(udiAttempt.Result.Guid); - if (content != null) - { - relatedLink.Id = content.Id; - relatedLink = CreateLink(relatedLink); - relatedLink.Content = content; - } - } - } - - if (relatedLink.IsDeleted == false) - { - relatedLinks.Add(relatedLink); - } - else - { - _logger.Warn("Related Links value converter skipped a link as the node has been unpublished/deleted (Internal Link NodeId: {RelatedLinkNodeId}, Link Caption: '{RelatedLinkCaption}')", relatedLink.Link, relatedLink.Caption); - } - } - - return new RelatedLinks(relatedLinks, sourceString); - } - - private RelatedLink CreateLink(RelatedLink link) - { - var umbracoContext = _umbracoContextAccessor.UmbracoContext; - - if (link.IsInternal && link.Id != null) - { - if (umbracoContext == null) - return null; - - var urlProvider = umbracoContext.UrlProvider; - - link.Link = urlProvider.GetUrl((int)link.Id); - if (link.Link.Equals("#")) - { - link.IsDeleted = true; - link.Link = link.Id.ToString(); - } - else - { - link.IsDeleted = false; - } - } - - return link; - } - - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - if (inter == null) return null; - var sourceString = inter.ToString(); - - if (sourceString.DetectIsJson()) - { - try - { - var obj = JsonConvert.DeserializeObject(sourceString); - - var d = new XmlDocument(); - var e = d.CreateElement("links"); - d.AppendChild(e); - - foreach (dynamic link in obj) - { - var ee = d.CreateElement("link"); - ee.SetAttribute("title", link.title); - ee.SetAttribute("link", link.link); - ee.SetAttribute("type", link.type); - ee.SetAttribute("newwindow", link.newWindow); - - e.AppendChild(ee); - } - - return d.CreateNavigator(); - } - catch (Exception ex) - { - _logger.Error(ex, "Could not parse the string {Json} to a json object", sourceString); - } - } - - //it's not json, just return the string - return sourceString; - } - } -} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index cab6e7d759..74cf960170 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -243,7 +243,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var n = _snapshot.Get(contentId); if (n == null) return false; - return preview || n.Published != null; + return preview || n.PublishedModel != null; } public override IEnumerable GetAtRoot(bool preview) @@ -280,8 +280,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // both .Draft and .Published cannot be null at the same time return preview - ? node.Draft ?? GetPublishedContentAsDraft(node.Published) - : node.Published; + ? node.DraftModel ?? GetPublishedContentAsDraft(node.PublishedModel) + : node.PublishedModel; } // gets a published content as a previewing draft, if preview is true @@ -302,7 +302,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { return preview ? _snapshot.IsEmpty == false - : _snapshot.GetAtRoot().Any(x => x.Published != null); + : _snapshot.GetAtRoot().Any(x => x.PublishedModel != null); } #endregion diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index 647adaad91..5bab8aa265 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -34,10 +34,11 @@ namespace Umbraco.Web.PublishedCache.NuCache DateTime createDate, int creatorId, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor, - IVariationContextAccessor variationContextAccessor) + IVariationContextAccessor variationContextAccessor, + IUmbracoContextAccessor umbracoContextAccessor) : this(id, uid, level, path, sortOrder, parentContentId, createDate, creatorId) { - SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor, variationContextAccessor); + SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); } // 2-phases ctor, phase 1 @@ -59,7 +60,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // two-phase ctor, phase 2 - public void SetContentTypeAndData(PublishedContentType contentType, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor) + public void SetContentTypeAndData(PublishedContentType contentType, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor) { ContentType = contentType; @@ -67,13 +68,20 @@ namespace Umbraco.Web.PublishedCache.NuCache throw new ArgumentException("Both draftData and publishedData cannot be null at the same time."); if (draftData != null) - Draft = new PublishedContent(this, draftData, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); + { + DraftContent = new PublishedContent(this, draftData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); + DraftModel = DraftContent.CreateModel(); + } + if (publishedData != null) - Published = new PublishedContent(this, publishedData, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); + { + PublishedContent = new PublishedContent(this, publishedData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); + PublishedModel = PublishedContent.CreateModel(); + } } // clone parent - private ContentNode(ContentNode origin) + private ContentNode(ContentNode origin, IUmbracoContextAccessor umbracoContextAccessor) { // everything is the same, except for the child items // list which is a clone of the original list @@ -88,17 +96,20 @@ namespace Umbraco.Web.PublishedCache.NuCache CreateDate = origin.CreateDate; CreatorId = origin.CreatorId; - var originDraft = origin.Draft == null ? null : PublishedContent.UnwrapIPublishedContent(origin.Draft); - var originPublished = origin.Published == null ? null : PublishedContent.UnwrapIPublishedContent(origin.Published); + var originDraft = origin.DraftContent; + var originPublished = origin.PublishedContent; - Draft = originDraft == null ? null : new PublishedContent(this, originDraft).CreateModel(); - Published = originPublished == null ? null : new PublishedContent(this, originPublished).CreateModel(); + + DraftContent = new PublishedContent(this, originDraft, umbracoContextAccessor); + PublishedContent = new PublishedContent(this, originPublished, umbracoContextAccessor); + DraftModel = DraftContent?.CreateModel(); + PublishedModel = PublishedContent?.CreateModel(); ChildContentIds = new List(origin.ChildContentIds); // needs to be *another* list } // clone with new content type - public ContentNode(ContentNode origin, PublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor) + public ContentNode(ContentNode origin, PublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor) { Id = origin.Id; Uid = origin.Uid; @@ -110,11 +121,13 @@ namespace Umbraco.Web.PublishedCache.NuCache CreateDate = origin.CreateDate; CreatorId = origin.CreatorId; - var originDraft = origin.Draft == null ? null : PublishedContent.UnwrapIPublishedContent(origin.Draft); - var originPublished = origin.Published == null ? null : PublishedContent.UnwrapIPublishedContent(origin.Published); + var originDraft = origin.DraftContent; + var originPublished = origin.PublishedContent; - Draft = originDraft == null ? null : new PublishedContent(this, originDraft._contentData, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); - Published = originPublished == null ? null : new PublishedContent(this, originPublished._contentData, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); + DraftContent = originDraft == null ? null : new PublishedContent(this, originDraft.ContentData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); + DraftModel = DraftContent?.CreateModel(); + PublishedContent = originPublished == null ? null : new PublishedContent(this, originPublished.ContentData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); + PublishedModel = PublishedContent?.CreateModel(); ChildContentIds = origin.ChildContentIds; // can be the *same* list } @@ -133,32 +146,31 @@ namespace Umbraco.Web.PublishedCache.NuCache public readonly int CreatorId; // draft and published version (either can be null, but not both) - // are models not direct PublishedContent instances - public IPublishedContent Draft; - public IPublishedContent Published; + // are the direct PublishedContent instances + public PublishedContent DraftContent; + public PublishedContent PublishedContent; - public ContentNode CloneParent(IPublishedSnapshotAccessor publishedSnapshotAccessor) + // draft and published version (either can be null, but not both) + // are models not direct PublishedContent instances + public IPublishedContent DraftModel; + public IPublishedContent PublishedModel; + + public ContentNode CloneParent( + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IUmbracoContextAccessor umbracoContextAccessor) { - return new ContentNode(this); + return new ContentNode(this, umbracoContextAccessor); } public ContentNodeKit ToKit() { - var draft = Draft is PublishedContentModel draftModel - ? (PublishedContent) draftModel.Unwrap() - : (PublishedContent) Draft; - - var published = Published is PublishedContentModel publishedModel - ? (PublishedContent) publishedModel.Unwrap() - : (PublishedContent) Published; - return new ContentNodeKit { Node = this, ContentTypeId = ContentType.Id, - DraftData = draft?._contentData, - PublishedData = published?._contentData + DraftData = DraftContent?.ContentData, + PublishedData = PublishedContent?.ContentData }; } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs index 5a47b99382..739a6141be 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs @@ -17,9 +17,14 @@ namespace Umbraco.Web.PublishedCache.NuCache public static ContentNodeKit Null { get; } = new ContentNodeKit { ContentTypeId = -1 }; - public void Build(PublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, bool canBePublished) + public void Build( + PublishedContentType contentType, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IVariationContextAccessor variationContextAccessor, + bool canBePublished, + IUmbracoContextAccessor umbracoContextAccessor) { - Node.SetContentTypeAndData(contentType, DraftData, canBePublished ? PublishedData : null, publishedSnapshotAccessor, variationContextAccessor); + Node.SetContentTypeAndData(contentType, DraftData, canBePublished ? PublishedData : null, publishedSnapshotAccessor, variationContextAccessor,umbracoContextAccessor); } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 353ac83eda..b3996050a6 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -20,6 +20,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ConcurrentDictionary> _contentNodes; private readonly ConcurrentDictionary> _contentRootNodes; private readonly ConcurrentDictionary> _contentTypesById; @@ -44,10 +45,16 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Ctor - public ContentStore(IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, ILogger logger, BPlusTree localDb = null) + public ContentStore( + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IVariationContextAccessor variationContextAccessor, + IUmbracoContextAccessor umbracoContextAccessor, + ILogger logger, + BPlusTree localDb = null) { _publishedSnapshotAccessor = publishedSnapshotAccessor; _variationContextAccessor = variationContextAccessor; + _umbracoContextAccessor = umbracoContextAccessor; _logger = logger; _localDb = localDb; @@ -279,7 +286,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (node == null) continue; var contentTypeId = node.ContentType.Id; if (index.TryGetValue(contentTypeId, out PublishedContentType contentType) == false) continue; - SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType, _publishedSnapshotAccessor, _variationContextAccessor)); + SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor)); } } finally @@ -393,7 +400,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _contentNodes.TryGetValue(id, out LinkedNode link); if (link?.Value == null) continue; - var node = new ContentNode(link.Value, contentType, _publishedSnapshotAccessor, _variationContextAccessor); + var node = new ContentNode(link.Value, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor); SetValueLocked(_contentNodes, id, node); if (_localDb != null) RegisterChange(id, node.ToKit()); } @@ -419,7 +426,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var canBePublished = ParentPublishedLocked(kit); // and use - kit.Build(link.Value, _publishedSnapshotAccessor, _variationContextAccessor, canBePublished); + kit.Build(link.Value, _publishedSnapshotAccessor, _variationContextAccessor, canBePublished, _umbracoContextAccessor); return true; } @@ -631,7 +638,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var link = GetParentLink(content); var parent = link.Value; if (link.Gen < _liveGen) - parent = parent.CloneParent(_publishedSnapshotAccessor); + parent = parent.CloneParent(_publishedSnapshotAccessor, _umbracoContextAccessor); parent.ChildContentIds.Remove(content.Id); if (link.Gen < _liveGen) SetValueLocked(_contentNodes, parent.Id, parent); @@ -652,7 +659,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return true; var link = GetParentLink(kit.Node); var node = link?.Value; - return node?.Published != null; + return node?.PublishedModel != null; } private void AddToParentLocked(ContentNode content) @@ -670,7 +677,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var link = GetParentLink(content); var parent = link.Value; if (link.Gen < _liveGen) - parent = parent.CloneParent(_publishedSnapshotAccessor); + parent = parent.CloneParent(_publishedSnapshotAccessor, _umbracoContextAccessor); parent.ChildContentIds.Add(content.Id); if (link.Gen < _liveGen) SetValueLocked(_contentNodes, parent.Id, parent); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs index 28c7c38c36..7a22366165 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs @@ -34,14 +34,14 @@ namespace Umbraco.Web.PublishedCache.NuCache { // ignore preview, there's only draft for media var n = _snapshot.Get(contentId); - return n?.Published; + return n?.PublishedModel; } public override IPublishedContent GetById(bool preview, Guid contentId) { // ignore preview, there's only draft for media var n = _snapshot.Get(contentId); - return n?.Published; + return n?.PublishedModel; } public override bool HasById(bool preview, int contentId) @@ -73,7 +73,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var c = _snapshot.GetAtRoot(); // ignore preview, there's only draft for media - return c.Select(n => n.Published); + return c.Select(n => n.PublishedModel); } public override bool HasContent(bool preview) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs index ecf099f90b..f7ffe73109 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs @@ -17,18 +17,22 @@ namespace Umbraco.Web.PublishedCache.NuCache internal class MemberCache : IPublishedMemberCache, INavigableData { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + public readonly IVariationContextAccessor VariationContextAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IEntityXmlSerializer _entitySerializer; private readonly IAppCache _snapshotCache; private readonly IMemberService _memberService; private readonly PublishedContentTypeCache _contentTypeCache; private readonly bool _previewDefault; - public MemberCache(bool previewDefault, IAppCache snapshotCache, IMemberService memberService, PublishedContentTypeCache contentTypeCache, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IEntityXmlSerializer entitySerializer) + public MemberCache(bool previewDefault, IAppCache snapshotCache, IMemberService memberService, PublishedContentTypeCache contentTypeCache, + IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor, IEntityXmlSerializer entitySerializer) { _snapshotCache = snapshotCache; _publishedSnapshotAccessor = publishedSnapshotAccessor; VariationContextAccessor = variationContextAccessor; + _umbracoContextAccessor = umbracoContextAccessor; _entitySerializer = entitySerializer; _memberService = memberService; _previewDefault = previewDefault; @@ -64,14 +68,14 @@ namespace Umbraco.Web.PublishedCache.NuCache var member = _memberService.GetById(memberId); return member == null ? null - : PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, VariationContextAccessor); + : PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor); }); } private IPublishedContent /*IPublishedMember*/ GetById(IMember member, bool previewing) { return GetCacheItem(CacheKeys.MemberCacheMember("ById", _previewDefault, member.Id), () => - PublishedMember.Create(member, GetContentType(member.ContentTypeId), previewing, _publishedSnapshotAccessor, VariationContextAccessor)); + PublishedMember.Create(member, GetContentType(member.ContentTypeId), previewing, _publishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor)); } public IPublishedContent /*IPublishedMember*/ GetByProviderKey(object key) @@ -106,7 +110,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public IPublishedContent /*IPublishedMember*/ GetByMember(IMember member) { - return PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, VariationContextAccessor); + return PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor); } public IEnumerable GetAtRoot(bool preview) @@ -114,7 +118,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // because members are flat (not a tree) everything is at root // because we're loading everything... let's just not cache? var members = _memberService.GetAllMembers(); - return members.Select(m => PublishedMember.Create(m, GetContentType(m.ContentTypeId), preview, _publishedSnapshotAccessor, VariationContextAccessor)); + return members.Select(m => PublishedMember.Create(m, GetContentType(m.ContentTypeId), preview, _publishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor)); } public XPathNavigator CreateNavigator() diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 3dee81f1bb..aa19918ca9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Composing; using Umbraco.Web.Models; @@ -14,23 +13,27 @@ namespace Umbraco.Web.PublishedCache.NuCache internal class PublishedContent : PublishedContentBase { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ContentNode _contentNode; - // ReSharper disable once InconsistentNaming - internal readonly ContentData _contentData; // internal for ContentNode cloning - private readonly string _urlSegment; #region Constructors - public PublishedContent(ContentNode contentNode, ContentData contentData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor) + public PublishedContent( + ContentNode contentNode, + ContentData contentData, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IVariationContextAccessor variationContextAccessor, + IUmbracoContextAccessor umbracoContextAccessor) :base(umbracoContextAccessor) { _contentNode = contentNode; - _contentData = contentData; + ContentData = contentData; _publishedSnapshotAccessor = publishedSnapshotAccessor; + _umbracoContextAccessor = umbracoContextAccessor; VariationContextAccessor = variationContextAccessor; - _urlSegment = _contentData.Name.ToUrlSegment(); - IsPreviewing = _contentData.Published == false; + _urlSegment = ContentData.Name.ToUrlSegment(); + IsPreviewing = ContentData.Published == false; var properties = new List(); foreach (var propertyType in _contentNode.ContentType.PropertyTypes) @@ -66,12 +69,15 @@ namespace Umbraco.Web.PublishedCache.NuCache } // (see ContentNode.CloneParent) - public PublishedContent(ContentNode contentNode, PublishedContent origin) + public PublishedContent( + ContentNode contentNode, + PublishedContent origin, + IUmbracoContextAccessor umbracoContextAccessor) :base(umbracoContextAccessor) { _contentNode = contentNode; _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; VariationContextAccessor = origin.VariationContextAccessor; - _contentData = origin._contentData; + ContentData = origin.ContentData; _urlSegment = origin._urlSegment; IsPreviewing = origin.IsPreviewing; @@ -83,12 +89,14 @@ namespace Umbraco.Web.PublishedCache.NuCache } // clone for previewing as draft a published content that is published and has no draft - private PublishedContent(PublishedContent origin) + private PublishedContent( + PublishedContent origin, + IUmbracoContextAccessor umbracoContextAccessor) :base(umbracoContextAccessor) { _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; VariationContextAccessor = origin.VariationContextAccessor; _contentNode = origin._contentNode; - _contentData = origin._contentData; + ContentData = origin.ContentData; _urlSegment = origin._urlSegment; IsPreviewing = true; @@ -170,6 +178,8 @@ namespace Umbraco.Web.PublishedCache.NuCache #region PublishedContent + internal ContentData ContentData { get; } + /// public override int Id => _contentNode.Id; @@ -179,11 +189,11 @@ namespace Umbraco.Web.PublishedCache.NuCache get { if (!ContentType.VariesByCulture()) - return _contentData.Name; + return ContentData.Name; var culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; if (culture == "") - return _contentData.Name; + return ContentData.Name; return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos.Name : null; } @@ -215,7 +225,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override string Path => _contentNode.Path; /// - public override int? TemplateId => _contentData.TemplateId; + public override int? TemplateId => ContentData.TemplateId; /// public override int CreatorId => _contentNode.CreatorId; @@ -227,13 +237,13 @@ namespace Umbraco.Web.PublishedCache.NuCache public override DateTime CreateDate => _contentNode.CreateDate; /// - public override int WriterId => _contentData.WriterId; + public override int WriterId => ContentData.WriterId; /// - public override string WriterName => GetProfileNameById(_contentData.WriterId); + public override string WriterName => GetProfileNameById(ContentData.WriterId); /// - public override DateTime UpdateDate => _contentData.VersionDate; + public override DateTime UpdateDate => ContentData.VersionDate; private IReadOnlyDictionary _cultureInfos; @@ -263,9 +273,9 @@ namespace Umbraco.Web.PublishedCache.NuCache if (_cultureInfos != null) return _cultureInfos; - if (_contentData.CultureInfos == null) + if (ContentData.CultureInfos == null) throw new Exception("oops: _contentDate.CultureInfos is null."); - return _cultureInfos = _contentData.CultureInfos + return _cultureInfos = ContentData.CultureInfos .ToDictionary(x => x.Key, x => new PublishedCultureInfo(x.Key, x.Value.Name, x.Value.Date), StringComparer.OrdinalIgnoreCase); } } @@ -277,7 +287,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override bool IsDraft(string culture = null) { // if this is the 'published' published content, nothing can be draft - if (_contentData.Published) + if (ContentData.Published) return false; // not the 'published' published content, and does not vary = must be draft @@ -290,24 +300,32 @@ namespace Umbraco.Web.PublishedCache.NuCache // not the 'published' published content, and varies // = depends on the culture - return _contentData.CultureInfos.TryGetValue(culture, out var cvar) && cvar.IsDraft; + return ContentData.CultureInfos.TryGetValue(culture, out var cvar) && cvar.IsDraft; } + /// public override bool IsPublished(string culture = null) { + // whether we are the 'draft' or 'published' content, need to determine whether + // there is a 'published' version for the specified culture (or at all, for + // invariant content items) + + // if there is no 'published' published content, no culture can be published + var hasPublished = _contentNode.PublishedContent != null; + if (!hasPublished) + return false; + + // if there is a 'published' published content, and does not vary = published if (!ContentType.VariesByCulture()) - { - return _contentData.Published; - } + return true; // handle context culture if (culture == null) - { culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - } - //If the current culture is not a draft, it must be the published version - return _contentData.CultureInfos.TryGetValue(culture, out var cvar) && !cvar.IsDraft; + // there is a 'published' published content, and varies + // = depends on the culture + return _contentNode.PublishedContent.ContentData.CultureInfos.ContainsKey(culture); } #endregion @@ -450,8 +468,8 @@ namespace Umbraco.Web.PublishedCache.NuCache return this; var cache = GetAppropriateCache(); - if (cache == null) return new PublishedContent(this).CreateModel(); - return (IPublishedContent)cache.Get(AsPreviewingCacheKey, () => new PublishedContent(this).CreateModel()); + if (cache == null) return new PublishedContent(this, _umbracoContextAccessor).CreateModel(); + return (IPublishedContent)cache.Get(AsPreviewingCacheKey, () => new PublishedContent(this, _umbracoContextAccessor).CreateModel()); } // used by Navigable.Source,... diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs index 47c8d738f1..db50fc3fe6 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs @@ -15,13 +15,26 @@ namespace Umbraco.Web.PublishedCache.NuCache { private readonly IMember _member; - private PublishedMember(IMember member, ContentNode contentNode, ContentData contentData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor) - : base(contentNode, contentData, publishedSnapshotAccessor, variationContextAccessor) + private PublishedMember( + IMember member, + ContentNode contentNode, + ContentData contentData, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IVariationContextAccessor variationContextAccessor, + IUmbracoContextAccessor umbracoContextAccessor + ) + : base(contentNode, contentData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor) { _member = member; } - public static IPublishedContent Create(IMember member, PublishedContentType contentType, bool previewing, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor) + public static IPublishedContent Create( + IMember member, + PublishedContentType contentType, + bool previewing, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IVariationContextAccessor variationContextAccessor, + IUmbracoContextAccessor umbracoContextAccessor) { var d = new ContentData { @@ -37,7 +50,7 @@ namespace Umbraco.Web.PublishedCache.NuCache member.Level, member.Path, member.SortOrder, member.ParentId, member.CreateDate, member.CreatorId); - return new PublishedMember(member, n, d, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); + return new PublishedMember(member, n, d, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor).CreateModel(); } private static Dictionary GetPropertyValues(PublishedContentType contentType, IMember member) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index e1cd1ec764..e23e84aabd 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -32,6 +32,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { private readonly ServiceContext _serviceContext; private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IScopeProvider _scopeProvider; private readonly IDataSource _dataSource; private readonly ILogger _logger; @@ -80,7 +81,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public PublishedSnapshotService(Options options, IMainDom mainDom, IRuntimeState runtime, ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IdkMap idkMap, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, - ILogger logger, IScopeProvider scopeProvider, + IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IScopeProvider scopeProvider, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IDefaultCultureAccessor defaultCultureAccessor, IDataSource dataSource, IGlobalSettings globalSettings, ISiteDomainHelper siteDomainHelper, @@ -92,6 +93,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _serviceContext = serviceContext; _publishedContentTypeFactory = publishedContentTypeFactory; + _umbracoContextAccessor = umbracoContextAccessor; _dataSource = dataSource; _logger = logger; _scopeProvider = scopeProvider; @@ -148,13 +150,13 @@ namespace Umbraco.Web.PublishedCache.NuCache // stores are created with a db so they can write to it, but they do not read from it, // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to // figure out whether it can read the databases or it should populate them from sql - _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localContentDb); - _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localMediaDb); + _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _umbracoContextAccessor, logger, _localContentDb); + _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _umbracoContextAccessor, logger, _localMediaDb); } else { - _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger); - _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger); + _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _umbracoContextAccessor, logger); + _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _umbracoContextAccessor, logger); } _domainStore = new SnapDictionary(); @@ -1015,7 +1017,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainHelper, _globalSettings, _serviceContext.LocalizationService), MediaCache = new MediaCache(previewDefault, mediaSnap, snapshotCache, elementsCache), - MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, memberTypeCache, PublishedSnapshotAccessor, VariationContextAccessor, _entitySerializer), + MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, memberTypeCache, PublishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor, _entitySerializer), DomainCache = domainCache, SnapshotCache = snapshotCache, ElementsCache = elementsCache diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 906cc7a4ae..419c279d46 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -19,7 +19,11 @@ namespace Umbraco.Web.PublishedCache private readonly IPublishedProperty[] _properties; private readonly PublishedContentType _publishedMemberType; - public PublishedMember(IMember member, PublishedContentType publishedMemberType) + public PublishedMember( + IMember member, + PublishedContentType publishedMemberType, + IUmbracoContextAccessor umbracoContextAccessor) + :base(umbracoContextAccessor) { _member = member ?? throw new ArgumentNullException(nameof(member)); _membershipUser = member; diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs index 4e206f73d7..74043a9519 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs @@ -38,7 +38,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache IAppCache appCache, PublishedContentTypeCache contentTypeCache, XPathNavigator nav, - bool fromExamine) + bool fromExamine, + IUmbracoContextAccessor umbracoContextAccessor) + :base(umbracoContextAccessor) { if (valueDictionary == null) throw new ArgumentNullException(nameof(valueDictionary)); if (getParent == null) throw new ArgumentNullException(nameof(getParent)); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index d8c7c41ea1..6fd9f1da2b 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -17,6 +17,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { private readonly IAppCache _appCache; private readonly IGlobalSettings _globalSettings; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly RoutesCache _routesCache; private readonly IDomainCache _domainCache; private readonly DomainHelper _domainHelper; @@ -33,6 +34,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache 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) @@ -40,6 +42,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { _appCache = appCache; _globalSettings = globalSettings; + _umbracoContextAccessor = umbracoContextAccessor; _routesCache = routesCache; // may be null for unit-testing _contentTypeCache = contentTypeCache; _domainCache = domainCache; @@ -315,13 +318,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing) { - return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache); + return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache,_umbracoContextAccessor); } private IEnumerable ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing) { return xmlNodes.Cast() - .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache)); + .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache, _umbracoContextAccessor)); } #endregion diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index a3aad608d5..7f9a21d24b 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -41,11 +41,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private readonly XmlStore _xmlStore; private readonly PublishedContentTypeCache _contentTypeCache; private readonly IEntityXmlSerializer _entitySerializer; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; // must be specified by the ctor private readonly IAppCache _appCache; - public PublishedMediaCache(XmlStore xmlStore, IMediaService mediaService, IUserService userService, IAppCache appCache, PublishedContentTypeCache contentTypeCache, IEntityXmlSerializer entitySerializer) + public PublishedMediaCache(XmlStore xmlStore, IMediaService mediaService, IUserService userService, + IAppCache appCache, PublishedContentTypeCache contentTypeCache, IEntityXmlSerializer entitySerializer, + IUmbracoContextAccessor umbracoContextAccessor) : base(false) { _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); @@ -55,6 +58,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _xmlStore = xmlStore; _contentTypeCache = contentTypeCache; _entitySerializer = entitySerializer; + _umbracoContextAccessor = umbracoContextAccessor; } /// @@ -666,7 +670,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _appCache, _contentTypeCache, cacheValues.XPath, // though, outside of tests, that should be null - cacheValues.FromExamine + cacheValues.FromExamine, + _umbracoContextAccessor ); return content.CreateModel(); } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMemberCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMemberCache.cs index 816eb3c545..db6d85fb2d 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMemberCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMemberCache.cs @@ -16,13 +16,16 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache 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) + public PublishedMemberCache(XmlStore xmlStore, IAppCache requestCache, IMemberService memberService, + PublishedContentTypeCache contentTypeCache, IUmbracoContextAccessor umbracoContextAccessor) { _requestCache = requestCache; _memberService = memberService; _xmlStore = xmlStore; _contentTypeCache = contentTypeCache; + _umbracoContextAccessor = umbracoContextAccessor; } public IPublishedContent GetByProviderKey(object key) @@ -39,7 +42,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var result = _memberService.GetByProviderKey(key); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type).CreateModel(); + return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); }); } @@ -57,7 +60,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var result = _memberService.GetById(memberId); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type).CreateModel(); + return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); }); } @@ -75,7 +78,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var result = _memberService.GetByUsername(username); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type).CreateModel(); + return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); }); } @@ -93,14 +96,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var result = _memberService.GetByEmail(email); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type).CreateModel(); + return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); }); } public IPublishedContent GetByMember(IMember member) { var type = _contentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId); - return new PublishedMember(member, type).CreateModel(); + return new PublishedMember(member, type, _umbracoContextAccessor).CreateModel(); } public XPathNavigator CreateNavigator() diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs index c4ce05c9a9..d96bfd8a0a 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs @@ -35,6 +35,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private readonly IDefaultCultureAccessor _defaultCultureAccessor; private readonly ISiteDomainHelper _siteDomainHelper; private readonly IEntityXmlSerializer _entitySerializer; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; #region Constructors @@ -44,6 +45,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache IScopeProvider scopeProvider, IAppCache requestCache, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, + IUmbracoContextAccessor umbracoContextAccessor, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IDefaultCultureAccessor defaultCultureAccessor, ILogger logger, @@ -52,12 +54,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache IEntityXmlSerializer entitySerializer, MainDom mainDom, bool testing = false, bool enableRepositoryEvents = true) - : this(serviceContext, publishedContentTypeFactory, scopeProvider, requestCache, - publishedSnapshotAccessor, variationContextAccessor, + : this(serviceContext, publishedContentTypeFactory, scopeProvider, requestCache, + publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor, documentRepository, mediaRepository, memberRepository, defaultCultureAccessor, logger, globalSettings, siteDomainHelper, entitySerializer, null, mainDom, testing, enableRepositoryEvents) - { } + { + _umbracoContextAccessor = umbracoContextAccessor; + } // used in some tests internal PublishedSnapshotService(ServiceContext serviceContext, @@ -65,6 +69,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache IScopeProvider scopeProvider, IAppCache requestCache, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, + IUmbracoContextAccessor umbracoContextAccessor, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IDefaultCultureAccessor defaultCultureAccessor, ILogger logger, @@ -92,6 +97,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _defaultCultureAccessor = defaultCultureAccessor; _requestCache = requestCache; + _umbracoContextAccessor = umbracoContextAccessor; _globalSettings = globalSettings; _siteDomainHelper = siteDomainHelper; _entitySerializer = entitySerializer; @@ -138,9 +144,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var domainCache = new DomainCache(_domainService, _defaultCultureAccessor); return new PublishedSnapshot( - new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _siteDomainHelper, _contentTypeCache, _routesCache, previewToken), - new PublishedMediaCache(_xmlStore, _mediaService, _userService, _requestCache, _contentTypeCache, _entitySerializer), - new PublishedMemberCache(_xmlStore, _requestCache, _memberService, _contentTypeCache), + new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _siteDomainHelper,_umbracoContextAccessor, _contentTypeCache, _routesCache, previewToken), + new PublishedMediaCache(_xmlStore, _mediaService, _userService, _requestCache, _contentTypeCache, _entitySerializer, _umbracoContextAccessor), + new PublishedMemberCache(_xmlStore, _requestCache, _memberService, _contentTypeCache, _umbracoContextAccessor), domainCache); } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index b1037759dc..9a4c1f239c 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -20,21 +20,29 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache [XmlType(Namespace = "http://umbraco.org/webservices/")] internal class XmlPublishedContent : PublishedContentBase { - private XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, IAppCache appCache, PublishedContentTypeCache contentTypeCache) + private XmlPublishedContent( + XmlNode xmlNode, + bool isPreviewing, + IAppCache appCache, + PublishedContentTypeCache contentTypeCache, + IUmbracoContextAccessor umbracoContextAccessor) + :base(umbracoContextAccessor) { _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(); + private readonly object _initializeLock = new object(); private bool _nodeInitialized; private bool _parentInitialized; @@ -63,7 +71,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private int _sortOrder; private int _level; private bool _isDraft; - private bool _isPublished; + public override IEnumerable Children { @@ -232,7 +240,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache public override bool IsPublished(string culture = null) { EnsureNodeInitialized(); - return _isPublished; + return true; // Intentionally not implemented, because the XmlPublishedContent should not support this. } public override IEnumerable Properties @@ -259,7 +267,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (parent == null) return; if (parent.Attributes?.GetNamedItem("isDoc") != null) - _parent = Get(parent, _isPreviewing, _appCache, _contentTypeCache); + _parent = Get(parent, _isPreviewing, _appCache, _contentTypeCache, _umbracoContextAccessor); _parentInitialized = true; } @@ -287,7 +295,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache InitializeNode(this, _xmlNode, _isPreviewing, out _id, out _key, out _template, out _sortOrder, out _name, out _writerName, out _urlName, out _creatorName, out _creatorId, out _writerId, out _docTypeAlias, out _docTypeId, out _path, - out _createDate, out _updateDate, out _level, out _isDraft, out _isPublished, out _contentType, out _properties, + out _createDate, out _updateDate, out _level, out _isDraft, out _contentType, out _properties, _contentTypeCache.Get); _nodeInitialized = true; @@ -297,7 +305,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache internal static void InitializeNode(XmlPublishedContent node, XmlNode xmlNode, bool isPreviewing, 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 bool isPublished, + out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft, out PublishedContentType contentType, out Dictionary properties, Func getPublishedContentType) { @@ -309,7 +317,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache name = writerName = urlName = creatorName = docTypeAlias = path = null; createDate = updateDate = default(DateTime); isDraft = false; - isPublished = false; contentType = null; properties = null; @@ -417,7 +424,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var iterator = nav.Select(expr); _children = iterator.Cast() - .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing, _appCache, _contentTypeCache)) + .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing, _appCache, _contentTypeCache, _umbracoContextAccessor)) .OrderBy(x => x.SortOrder) .ToList(); @@ -431,11 +438,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// A value indicating whether we are previewing or not. /// A cache. /// A content type cache. + /// A umbraco context accessor /// The IPublishedContent corresponding to the Xml cache node. /// Maintains a per-request cache of IPublishedContent items in order to make /// 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) + public static IPublishedContent Get(XmlNode node, bool isPreviewing, IAppCache appCache, + PublishedContentTypeCache contentTypeCache, IUmbracoContextAccessor umbracoContextAccessor) { // only 1 per request @@ -443,7 +452,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache 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)).CreateModel()); + return (IPublishedContent) appCache.Get(key, () => (new XmlPublishedContent(node, isPreviewing, appCache, contentTypeCache, umbracoContextAccessor)).CreateModel()); } public static void ClearRequest() diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index e1bf60f558..5d47d7b6a5 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -228,7 +228,7 @@ namespace Umbraco.Web /// /// Culture is case-insensitive. public static bool HasCulture(this IPublishedContent content, string culture) - => content.Cultures.ContainsKey(culture); + => content.Cultures.ContainsKey(culture ?? string.Empty); /// /// Filters a sequence of to return invariant items, and items that are published for the specified culture. diff --git a/src/Umbraco.Web/RelatedLinksTypeConverter.cs b/src/Umbraco.Web/RelatedLinksTypeConverter.cs deleted file mode 100644 index 647959b920..0000000000 --- a/src/Umbraco.Web/RelatedLinksTypeConverter.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using System.Linq; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Composing; -using Umbraco.Web.Models; - -namespace Umbraco.Web -{ - public class RelatedLinksTypeConverter : TypeConverter - { - private readonly UmbracoHelper _umbracoHelper; - - public RelatedLinksTypeConverter(UmbracoHelper umbracoHelper) - { - _umbracoHelper = umbracoHelper; - } - - public RelatedLinksTypeConverter() - { - - } - - private static readonly Type[] ConvertableTypes = new[] - { - typeof(JArray) - }; - - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - return ConvertableTypes.Any(x => TypeHelper.IsTypeAssignableFrom(x, destinationType)) - || base.CanConvertFrom(context, destinationType); - } - - public override object ConvertTo( - ITypeDescriptorContext context, - CultureInfo culture, - object value, - Type destinationType) - { - var relatedLinks = value as RelatedLinks; - if (relatedLinks == null) - return null; - - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - // Conversion to JArray taken from old value converter - - var obj = JsonConvert.DeserializeObject(relatedLinks.PropertyData); - - var umbracoHelper = GetUmbracoHelper(); - - //update the internal links if we have a context - if (umbracoHelper != null) - { - foreach (var a in obj) - { - var type = a.Value("type"); - if (type.IsNullOrWhiteSpace() == false) - { - if (type == "internal") - { - var linkId = a.Value("link"); - var link = umbracoHelper.Url(linkId); - a["link"] = link; - } - } - } - } - return obj; - - } - - return base.ConvertTo(context, culture, value, destinationType); - } - - private UmbracoHelper GetUmbracoHelper() - { - if (_umbracoHelper != null) - return _umbracoHelper; - - if (UmbracoContext.Current == null) - { - Current.Logger.Warn("Cannot create an UmbracoHelper the UmbracoContext is null"); - return null; - } - - //DO NOT assign to _umbracoHelper variable, this is a singleton class and we cannot assign this based on an UmbracoHelper which is request based - return new UmbracoHelper(UmbracoContext.Current, Current.Services); - } - } -} diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs b/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs index de30590e92..b8a7fee2f7 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs @@ -6,6 +6,7 @@ using Microsoft.AspNet.SignalR; using Umbraco.Core; using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; using Umbraco.Core.Dictionary; using Umbraco.Core.Events; using Umbraco.Core.Models.PublishedContent; @@ -17,6 +18,7 @@ using Umbraco.Web.Actions; using Umbraco.Web.Cache; using Umbraco.Web.Composing.Composers; using Umbraco.Web.ContentApps; +using Umbraco.Web.Dashboards; using Umbraco.Web.Dictionary; using Umbraco.Web.Editors; using Umbraco.Web.Features; @@ -95,14 +97,13 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(factory => ExamineManager.Instance); // configure the container for web composition.ConfigureForWeb(); - - composition.RegisterUnique(); - composition .ComposeUmbracoControllers(GetType().Assembly) .SetDefaultRenderMvcController(); // default controller for template views @@ -202,6 +203,10 @@ namespace Umbraco.Web.Runtime .Append() .Append(); + // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards + composition.WithCollectionBuilder() + .Add(composition.TypeLoader.GetTypes()); + // register back office trees foreach (var treeControllerType in umbracoApiControllerTypes .Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))) diff --git a/src/Umbraco.Web/Editors/DashboardSecurity.cs b/src/Umbraco.Web/Services/DashboardService.cs similarity index 61% rename from src/Umbraco.Web/Editors/DashboardSecurity.cs rename to src/Umbraco.Web/Services/DashboardService.cs index 2e3fc4123b..71969df475 100644 --- a/src/Umbraco.Web/Editors/DashboardSecurity.cs +++ b/src/Umbraco.Web/Services/DashboardService.cs @@ -1,65 +1,71 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Configuration.Dashboard; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; -using Umbraco.Web.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Web.Dashboards; +using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.Services { - /// + /// /// A utility class for determine dashboard security /// - internal class DashboardSecurity + internal class DashboardService : IDashboardService { // TODO: Unit test all this!!! :/ + + private readonly ISectionService _sectionService; + private readonly DashboardCollection _dashboardCollection; + private readonly ILocalizedTextService _localizedText; - public static bool AuthorizeAccess(ISection dashboardSection, IUser user, ISectionService sectionService) + public DashboardService(ISectionService sectionService, DashboardCollection dashboardCollection, ILocalizedTextService localizedText) { - return CheckUserAccessByRules(user, sectionService, dashboardSection.AccessRights.Rules); + _sectionService = sectionService ?? throw new ArgumentNullException(nameof(sectionService)); + _dashboardCollection = dashboardCollection ?? throw new ArgumentNullException(nameof(dashboardCollection)); + _localizedText = localizedText ?? throw new ArgumentNullException(nameof(localizedText)); } - public static bool AuthorizeAccess(IDashboardTab dashboardTab, IUser user, ISectionService sectionService) - { - return CheckUserAccessByRules(user, sectionService, dashboardTab.AccessRights.Rules); - } - public static bool AuthorizeAccess(IDashboardControl dashboardControl, IUser user, ISectionService sectionService) + /// + public IEnumerable> GetDashboards(string section, IUser currentUser) { - return CheckUserAccessByRules(user, sectionService, dashboardControl.AccessRights.Rules); - } + var tabs = new List>(); + var tabId = 0; - private static (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable rules) - { - IAccessRule[] denyRules = null, grantRules = null, grantBySectionRules = null; - - var groupedRules = rules.GroupBy(x => x.Type); - foreach (var group in groupedRules) + foreach (var dashboard in _dashboardCollection.Where(x => x.Sections.InvariantContains(section))) { - var a = group.ToArray(); - switch (group.Key) + // validate access + if (!CheckUserAccessByRules(currentUser, _sectionService, dashboard.AccessRules)) + continue; + + if (dashboard.View.InvariantEndsWith(".ascx")) + throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported."); + + var dashboards = new List {dashboard}; + tabs.Add(new Tab { - case AccessRuleType.Deny: - denyRules = a; - break; - case AccessRuleType.Grant: - grantRules = a; - break; - case AccessRuleType.GrantBySection: - grantBySectionRules = a; - break; - default: - throw new Exception("panic"); - } + Id = tabId++, + Label = _localizedText.Localize("dashboardTabs", dashboard.Alias), + Alias = dashboard.Alias, + Properties = dashboards + }); } - return (denyRules ?? Array.Empty(), grantRules ?? Array.Empty(), grantBySectionRules ?? Array.Empty()); + return tabs; } - public static bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IEnumerable rules) + /// + public IDictionary>> GetDashboards(IUser currentUser) + { + return _sectionService.GetSections().ToDictionary(x => x.Alias, x => GetDashboards(x.Alias, currentUser)); + } + + private bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IEnumerable rules) { if (user.Id == Constants.Security.SuperUserId) return true; @@ -99,7 +105,7 @@ namespace Umbraco.Web.Editors } if (!hasAccess || denyRules.Length == 0) - return false; + return true; // check if this item has any deny arguments, if so check if the user is in one of the denied user groups, if so they will // be denied to see it no matter what @@ -111,5 +117,32 @@ namespace Umbraco.Web.Editors return hasAccess; } + + private static (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable rules) + { + IAccessRule[] denyRules = null, grantRules = null, grantBySectionRules = null; + + var groupedRules = rules.GroupBy(x => x.Type); + foreach (var group in groupedRules) + { + var a = group.ToArray(); + switch (group.Key) + { + case AccessRuleType.Deny: + denyRules = a; + break; + case AccessRuleType.Grant: + grantRules = a; + break; + case AccessRuleType.GrantBySection: + grantBySectionRules = a; + break; + default: + throw new NotSupportedException($"The '{group.Key.ToString()}'-AccessRuleType is not supported."); + } + } + + return (denyRules ?? Array.Empty(), grantRules ?? Array.Empty(), grantBySectionRules ?? Array.Empty()); + } } } diff --git a/src/Umbraco.Web/Services/IDashboardService.cs b/src/Umbraco.Web/Services/IDashboardService.cs new file mode 100644 index 0000000000..11ff2728cf --- /dev/null +++ b/src/Umbraco.Web/Services/IDashboardService.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Umbraco.Core.Dashboards; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Services +{ + public interface IDashboardService + { + /// + /// Gets dashboard for a specific section/application + /// For a specific backoffice user + /// + /// + /// + /// + IEnumerable> GetDashboards(string section, IUser currentUser); + + /// + /// Gets all dashboards, organized by section, for a user. + /// + /// + /// + IDictionary>> GetDashboards(IUser currentUser); + + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 47c3c24de4..2d30c27c27 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -122,6 +122,18 @@ + + + + + + + + + + + + @@ -170,6 +182,8 @@ + + @@ -254,7 +268,6 @@ - @@ -406,10 +419,6 @@ - - - - @@ -455,9 +464,6 @@ - - - @@ -473,7 +479,6 @@ - @@ -537,7 +542,6 @@ - @@ -715,7 +719,6 @@ - @@ -836,7 +839,6 @@ - @@ -862,7 +864,6 @@ - diff --git a/src/Umbraco.Web/UrlHelperExtensions.cs b/src/Umbraco.Web/UrlHelperExtensions.cs index 1397ffc818..249ce76193 100644 --- a/src/Umbraco.Web/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/UrlHelperExtensions.cs @@ -21,17 +21,6 @@ namespace Umbraco.Web /// public static class UrlHelperExtensions { - /// - /// Returns the base path (not including the 'action') of the MVC controller "ExamineManagementController" - /// - /// - /// - public static string GetExamineManagementServicePath(this UrlHelper url) - { - // TODO: Possibly remove this method, I think it's unused... - var result = url.GetUmbracoApiService("GetIndexerDetails"); - return result.TrimEnd("GetIndexerDetails").EnsureEndsWith('/'); - } /// /// Return the Url for a Web Api service @@ -47,19 +36,6 @@ namespace Umbraco.Web return url.GetUmbracoApiService(actionName, typeof(T), routeVals); } - /// - /// Return the Base Url (not including the action) for a Web Api service - /// - /// - /// - /// - /// - public static string GetUmbracoApiServiceBaseUrl(this UrlHelper url, string actionName) - where T : UmbracoApiController - { - return url.GetUmbracoApiService(actionName).TrimEnd(actionName); - } - public static string GetUmbracoApiServiceBaseUrl(this UrlHelper url, Expression> methodSelector) where T : UmbracoApiController { @@ -112,19 +88,6 @@ namespace Umbraco.Web return url.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area, routeVals); } - /// - /// Return the Url for a Web Api service - /// - /// - /// - /// - /// - /// - public static string GetUmbracoApiService(this UrlHelper url, string actionName, string controllerName, RouteValueDictionary routeVals = null) - { - return url.GetUmbracoApiService(actionName, controllerName, "", routeVals); - } - /// /// Return the Url for a Web Api service /// diff --git a/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs b/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs index 19683f7f86..912edc1397 100644 --- a/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs +++ b/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Net.Http.Formatting; using System.Web.Http.Controllers; -using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace Umbraco.Web.WebApi @@ -25,7 +24,7 @@ namespace Umbraco.Web.WebApi { SerializerSettings = { - ContractResolver = new CamelCasePropertyNamesContractResolver() + ContractResolver = new CamelCasePropertyNamesContractResolver() } }; controllerSettings.Formatters.Add(jsonFormatter); diff --git a/src/umbraco.sln b/src/umbraco.sln index 0bdcb53d99..c02a5ba85c 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -70,7 +70,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{E3F9F378 ProjectSection(SolutionItems) = preProject ..\build\NuSpecs\tools\applications.config.install.xdt = ..\build\NuSpecs\tools\applications.config.install.xdt ..\build\NuSpecs\tools\ClientDependency.config.install.xdt = ..\build\NuSpecs\tools\ClientDependency.config.install.xdt - ..\build\NuSpecs\tools\Dashboard.config.install.xdt = ..\build\NuSpecs\tools\Dashboard.config.install.xdt ..\build\NuSpecs\tools\install.core.ps1 = ..\build\NuSpecs\tools\install.core.ps1 ..\build\NuSpecs\tools\install.ps1 = ..\build\NuSpecs\tools\install.ps1 ..\build\NuSpecs\tools\Readme.txt = ..\build\NuSpecs\tools\Readme.txt