From e962a48138815da4f58ac88eb972ca3f32d91b7c Mon Sep 17 00:00:00 2001 From: Arnold Visser Date: Tue, 6 Sep 2016 10:53:09 +0200 Subject: [PATCH 001/105] Dutch Languagefile Update This updates the Dutch language file with new/missing translations and fixes some wrong translations used by the grid. --- src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 2955ce716a..ce89979502 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -189,38 +189,38 @@ Welkom - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes + Blijf op deze pagina + Negeer wijzigingen + Wijzigingen niet opgeslagen + Weet je zeker dat deze pagina wilt verlaten? - er zijn onopgeslagen wijzigingen Done - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items + %0% item verwijderd + %0% items verwijderd + Item %0% van de %1% verwijderd + Items %0% van de %1% verwijderd - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items + %0% item gepubliceerd + %0% items gepubliceerd + Item %0% van de %1% gepubliceerd + Items %0% van de %1% gepubliceerd - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items + %0% item gedepubliceerd + %0% items gedepubliceerd + Item %0% van de %1% gedepubliceerd + Items %0% van de %1% gedepubliceerd - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items + %0% item verplaatst + %0% items verplaatst + item %0% van de %1% verplaatst + items %0% van de %1% verplaatst - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items + %0% item gekopieerd + %0% items gekopieerd + item %0% van de %1% gekopieerd + item %0% van de %1% gekopieerd Naam @@ -431,7 +431,7 @@ Toon pagina bij versturen Formaat Sorteren - Submit + Verstuur Type Type om te zoeken... Omhoog @@ -447,8 +447,8 @@ Breedte Ja Map - Reorder - I am done reordering + Herschikken + Klaar met herschikken Zoekresultaten @@ -846,15 +846,15 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Item toevoegen - Choose a layout - Een rij aan de lay-out toevoegen - teken onderaan en voeg je eerste item toe]]> + Kies de indeling + Kies een indeling voor deze pagina om content toe te kunnen voegen + Plaats een (extra) content blok]]> Drop content - Settings applied - - This content is not allowed here - This content is allowed here + Instellingen toegepast + Deze content is hier niet toegestaan + Deze content is hier toegestaan + Klik om een item te embedden Klik om een afbeelding in te voegen Afbeelding ondertitel... From a5af5ba1a957f05990710268a2ecdc459fd84590 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Jan 2017 18:54:50 +1100 Subject: [PATCH 002/105] Adds new property to PropertyEditor's to mark them as deprecated so they don't show up in the data type list unless they are already selected --- .../UmbracoSettings/ContentElement.cs | 11 +++++++++++ .../UmbracoSettings/IContentSection.cs | 6 ++++++ src/Umbraco.Core/PropertyEditors/PropertyEditor.cs | 4 ++++ .../PropertyEditors/PropertyEditorAttribute.cs | 6 ++++++ .../Mapping/AvailablePropertyEditorsResolver.cs | 14 ++++++++++++++ .../Models/Mapping/DataTypeModelMapper.cs | 3 ++- .../PropertyEditors/ContentPickerPropertyEditor.cs | 2 +- .../PropertyEditors/FolderBrowserPropertyEditor.cs | 2 +- .../PropertyEditors/MediaPickerPropertyEditor.cs | 3 ++- .../MemberGroupPickerPropertyEditor.cs | 2 +- .../PropertyEditors/MemberPickerPropertyEditor.cs | 2 +- .../MultiNodeTreePickerPropertyEditor.cs | 2 +- .../MultipleMediaPickerPropertyEditor.cs | 2 +- .../PropertyEditors/UserPickerPropertyEditor.cs | 2 +- 14 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index aed7f61a06..05189acc3e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -159,6 +159,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return GetOptionalTextElement("defaultDocumentTypeProperty", "Textstring"); } } + [ConfigurationProperty("showDeprecatedPropertyEditors")] + internal InnerTextConfigurationElement ShowDeprecatedPropertyEditors + { + get { return GetOptionalTextElement("showDeprecatedPropertyEditors", false); } + } + [ConfigurationProperty("EnableInheritedDocumentTypes")] internal InnerTextConfigurationElement EnableInheritedDocumentTypes { @@ -306,6 +312,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return DefaultDocumentTypeProperty; } } + bool IContentSection.ShowDeprecatedPropertyEditors + { + get { return ShowDeprecatedPropertyEditors; } + } + bool IContentSection.EnableInheritedDocumentTypes { get { return EnableInheritedDocumentTypes; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index 3d5e4435b6..a388126948 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -60,6 +60,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string DefaultDocumentTypeProperty { get; } + /// + /// The default for this is false but if you would like deprecated property editors displayed + /// in the data type editor you can enable this + /// + bool ShowDeprecatedPropertyEditors { get; } + bool EnableInheritedDocumentTypes { get; } bool EnableInheritedMediaTypes { get; } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs index 6cecc9dff5..c2498ecc7a 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs @@ -38,6 +38,7 @@ namespace Umbraco.Core.PropertyEditors IsParameterEditor = _attribute.IsParameterEditor; Icon = _attribute.Icon; Group = _attribute.Group; + IsDeprecated = _attribute.IsDeprecated; } } @@ -90,6 +91,9 @@ namespace Umbraco.Core.PropertyEditors get { return CreateValueEditor(); } } + [JsonIgnore] + public bool IsDeprecated { get; internal set; } + [JsonIgnore] IValueEditor IParameterEditor.ValueEditor { diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs index d120753185..41e4ccc74e 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs @@ -60,6 +60,12 @@ namespace Umbraco.Core.PropertyEditors public string ValueType { get; set; } public bool IsParameterEditor { get; set; } + /// + /// If set to true, this property editor will not show up in the DataType's drop down list + /// if there is not already one of them chosen for a DataType + /// + public bool IsDeprecated { get; set; } + /// /// If this is is true than the editor will be displayed full width without a label /// diff --git a/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs b/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs index 624c641d3b..2b9047c284 100644 --- a/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; @@ -10,9 +11,22 @@ namespace Umbraco.Web.Models.Mapping { internal class AvailablePropertyEditorsResolver : ValueResolver> { + private readonly IContentSection _contentSection; + + public AvailablePropertyEditorsResolver(IContentSection contentSection) + { + _contentSection = contentSection; + } + protected override IEnumerable ResolveCore(IDataTypeDefinition source) { return PropertyEditorResolver.Current.PropertyEditors + .Where(x => + { + if (_contentSection.ShowDeprecatedPropertyEditors) + return true; + return source.PropertyEditorAlias == x.Alias || x.IsDeprecated == false; + }) .OrderBy(x => x.Name) .Select(Mapper.Map); } diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index ac2faa82dc..d7dfdbf9d0 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using AutoMapper; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.Mapping; using Umbraco.Core.PropertyEditors; @@ -61,7 +62,7 @@ namespace Umbraco.Web.Models.Mapping }); config.CreateMap() - .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing(new AvailablePropertyEditorsResolver())) + .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing(new AvailablePropertyEditorsResolver(UmbracoConfig.For.UmbracoSettings().Content))) .ForMember(display => display.PreValues, expression => expression.ResolveUsing( new PreValueDisplayResolver(lazyDataTypeService))) .ForMember(display => display.SelectedEditor, expression => expression.MapFrom( diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index 4d58060164..6a709845ff 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.ContentPickerAlias, "Content Picker", PropertyEditorValueTypes.Integer, "contentpicker", IsParameterEditor = true, Group = "Pickers")] + [PropertyEditor(Constants.PropertyEditors.ContentPickerAlias, "[Legacy] Content Picker", PropertyEditorValueTypes.Integer, "contentpicker", IsParameterEditor = true, Group = "Pickers", IsDeprecated = true)] public class ContentPickerPropertyEditor : PropertyEditor { diff --git a/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs index bfdbe368d1..5c89b868b8 100644 --- a/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs @@ -9,7 +9,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { [Obsolete("This is no longer used by default, use the ListViewPropertyEditor instead")] - [PropertyEditor(Constants.PropertyEditors.FolderBrowserAlias, "(Obsolete) Folder Browser", "folderbrowser", HideLabel=true, Icon="icon-folder", Group="media")] + [PropertyEditor(Constants.PropertyEditors.FolderBrowserAlias, "(Obsolete) Folder Browser", "folderbrowser", HideLabel=true, Icon="icon-folder", Group="media", IsDeprecated = true)] public class FolderBrowserPropertyEditor : PropertyEditor { diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index 7e966d31ad..718334f60b 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -9,7 +9,8 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MediaPickerAlias, "Legacy Media Picker", PropertyEditorValueTypes.Integer, "mediapicker", Group="media", Icon="icon-picture")] + + [PropertyEditor(Constants.PropertyEditors.MediaPickerAlias, "Legacy Media Picker", PropertyEditorValueTypes.Integer, "mediapicker", Group="media", Icon="icon-picture", IsDeprecated = true)] public class MediaPickerPropertyEditor : PropertyEditor { public MediaPickerPropertyEditor() diff --git a/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs index 433199c536..d7051ba8da 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs @@ -8,7 +8,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MemberGroupPickerAlias, "Member Group Picker", "membergrouppicker", Group="People", Icon="icon-users")] + [PropertyEditor(Constants.PropertyEditors.MemberGroupPickerAlias, "Member Group Picker", "membergrouppicker", Group="People", Icon="icon-users", IsDeprecated = true)] public class MemberGroupPickerPropertyEditor : PropertyEditor { } diff --git a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs index 7dadbfe24c..90e7562f3a 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs @@ -8,7 +8,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MemberPickerAlias, "Member Picker", PropertyEditorValueTypes.Integer, "memberpicker", Group = "People", Icon = "icon-user")] + [PropertyEditor(Constants.PropertyEditors.MemberPickerAlias, "Member Picker", PropertyEditorValueTypes.Integer, "memberpicker", Group = "People", Icon = "icon-user", IsDeprecated = true)] public class MemberPickerPropertyEditor : PropertyEditor { } diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index b3d3f02448..1bdf0d45d9 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -5,7 +5,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "Multinode Treepicker", "contentpicker", Group="pickers", Icon="icon-page-add")] + [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "Multinode Treepicker", "contentpicker", Group="pickers", Icon="icon-page-add", IsDeprecated = true)] public class MultiNodeTreePickerPropertyEditor : PropertyEditor { public MultiNodeTreePickerPropertyEditor() diff --git a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs index 37587d1c54..18a1e564d6 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs @@ -3,7 +3,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "Media Picker", "mediapicker", Group = "media", Icon = "icon-pictures-alt-2")] + [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "Media Picker", "mediapicker", Group = "media", Icon = "icon-pictures-alt-2", IsDeprecated = true)] public class MultipleMediaPickerPropertyEditor : MediaPickerPropertyEditor { public MultipleMediaPickerPropertyEditor() diff --git a/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs index e4a070cb29..f824f25294 100644 --- a/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs @@ -6,7 +6,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.UserPickerAlias, "User picker", PropertyEditorValueTypes.Integer, "entitypicker", Group="People", Icon="icon-user")] + [PropertyEditor(Constants.PropertyEditors.UserPickerAlias, "User picker", PropertyEditorValueTypes.Integer, "entitypicker", Group="People", Icon="icon-user", IsDeprecated = true)] public class UserPickerPropertyEditor : PropertyEditor { private IDictionary _defaultPreValues; From 371c4ae1a321131157a472821a051ac9604e9092 Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 28 Jan 2017 00:22:12 +1100 Subject: [PATCH 003/105] Starts creating new UDI picker property editors --- src/Umbraco.Core/Constants-PropertyEditors.cs | 26 +++++++++++++---- .../mediapicker/mediapicker.controller.js | 3 ++ .../config/umbracoSettings.config | 3 +- .../ContentPickerPropertyEditor.cs | 23 ++++++++++++--- .../MediaPickerPropertyEditor.cs | 29 ++++++++++++++----- .../MemberGroupPickerPropertyEditor.cs | 2 +- .../MemberPickerPropertyEditor.cs | 11 +++++-- .../MultiNodeTreePickerPropertyEditor.cs | 16 +++++++--- .../MultipleMediaPickerPropertyEditor.cs | 14 +++++++-- .../UserPickerPropertyEditor.cs | 2 +- 10 files changed, 100 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 80f118b58e..2712fffdfa 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -42,10 +42,14 @@ namespace Umbraco.Core [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string ContentPicker = "158AA029-24ED-4948-939E-C3DA209E5FBA"; + + [Obsolete("This is an obsoleted content picker, use ContentPicker2Alias instead")] + public const string ContentPickerAlias = "Umbraco.ContentPickerAlias"; + /// /// Alias for the Content Picker datatype. /// - public const string ContentPickerAlias = "Umbraco.ContentPickerAlias"; + public const string ContentPicker2Alias = "Umbraco.ContentPicker2"; /// /// Guid for the Date datatype. @@ -192,39 +196,51 @@ namespace Umbraco.Core [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string MediaPicker = "EAD69342-F06D-4253-83AC-28000225583B"; + [Obsolete("This is an obsoleted picker, use MediaPicker2Alias instead")] + public const string MediaPickerAlias = "Umbraco.MediaPicker"; + /// /// Alias for the Media Picker datatype. /// - public const string MediaPickerAlias = "Umbraco.MediaPicker"; + public const string MediaPicker2Alias = "Umbraco.MediaPicker2"; + [Obsolete("This is an obsoleted picker, use MultipleMediaPicker2Alias instead")] public const string MultipleMediaPickerAlias = "Umbraco.MultipleMediaPicker"; + public const string MultipleMediaPicker2Alias = "Umbraco.MultipleMediaPicker2"; + /// /// Guid for the Member Picker datatype. /// [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string MemberPicker = "39F533E4-0551-4505-A64B-E0425C5CE775"; + [Obsolete("This is an obsoleted picker, use MemberPicker2Alias instead")] + public const string MemberPickerAlias = "Umbraco.MemberPicker"; + /// /// Alias for the Member Picker datatype. /// - public const string MemberPickerAlias = "Umbraco.MemberPicker"; + public const string MemberPicker2Alias = "Umbraco.MemberPicker2"; /// /// Alias for the Member Group Picker datatype. /// public const string MemberGroupPickerAlias = "Umbraco.MemberGroupPicker"; - + /// /// Guid for the Multi-Node Tree Picker datatype /// [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string MultiNodeTreePicker = "7E062C13-7C41-4AD9-B389-41D88AEEF87C"; + [Obsolete("This is an obsoleted picker, use MultiNodeTreePicker2Alias instead")] + public const string MultiNodeTreePickerAlias = "Umbraco.MultiNodeTreePicker"; + /// /// Alias for the Multi-Node Tree Picker datatype /// - public const string MultiNodeTreePickerAlias = "Umbraco.MultiNodeTreePicker"; + public const string MultiNodeTreePicker2Alias = "Umbraco.MultiNodeTreePicker2"; /// /// Guid for the Multiple Textstring datatype. diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index bbf61a32d3..6945d995cd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -80,6 +80,9 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl } $scope.images.push(media); + + //TODO: Determine if we are storing UDI vs INT + $scope.ids.push(media.id); }); diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 9eb3f135e9..cc71b26b70 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -104,7 +104,8 @@ Textstring - + + true diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index f2c62693d6..7ccd5e2495 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -1,14 +1,29 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.ContentPickerAlias, "[Legacy] Content Picker", PropertyEditorValueTypes.Integer, "contentpicker", IsParameterEditor = true, Group = "Pickers", IsDeprecated = true)] - public class ContentPickerPropertyEditor : PropertyEditor + + /// + /// Legacy content property editor that stores Integer Ids + /// + [Obsolete("This editor is obsolete, use ContentPickerPropertyEditor2 instead which stores UDI")] + [PropertyEditor(Constants.PropertyEditors.ContentPickerAlias, "(Obsolete) Content Picker", PropertyEditorValueTypes.Integer, "contentpicker", IsParameterEditor = true, Group = "Pickers", IsDeprecated = true)] + public class ContentPickerPropertyEditor : ContentPickerPropertyEditor2 + { + + } + + /// + /// Content property editor that stores UDI + /// + [PropertyEditor(Constants.PropertyEditors.ContentPicker2Alias, "Content Picker", PropertyEditorValueTypes.String, "contentpicker", IsParameterEditor = true, Group = "Pickers")] + public class ContentPickerPropertyEditor2 : PropertyEditor { - public ContentPickerPropertyEditor() + public ContentPickerPropertyEditor2() { _internalPreValues = new Dictionary { diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index 718334f60b..f776d74829 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -9,11 +9,22 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - - [PropertyEditor(Constants.PropertyEditors.MediaPickerAlias, "Legacy Media Picker", PropertyEditorValueTypes.Integer, "mediapicker", Group="media", Icon="icon-picture", IsDeprecated = true)] - public class MediaPickerPropertyEditor : PropertyEditor + /// + /// Legacy media property editor that stores Integer Ids + /// + [Obsolete("This editor is obsolete, use ContentPickerPropertyEditor2 instead which stores UDI")] + [PropertyEditor(Constants.PropertyEditors.MediaPickerAlias, "(Obsolete) Media Picker", PropertyEditorValueTypes.Integer, "mediapicker", Group = "media", Icon = "icon-picture", IsDeprecated = true)] + public class MediaPickerPropertyEditor : MediaPickerPropertyEditor2 { - public MediaPickerPropertyEditor() + } + + /// + /// Media picker property editors that stores UDI + /// + [PropertyEditor(Constants.PropertyEditors.MediaPicker2Alias, "Media Picker", PropertyEditorValueTypes.String, "mediapicker", Group = "media", Icon = "icon-picture")] + public class MediaPickerPropertyEditor2 : PropertyEditor + { + public MediaPickerPropertyEditor2() { InternalPreValues = new Dictionary { @@ -26,12 +37,9 @@ namespace Umbraco.Web.PropertyEditors protected override PropertyValueEditor CreateValueEditor() { - //TODO: Need to add some validation to the ValueEditor to ensure that any media chosen actually exists! - return base.CreateValueEditor(); + return new SingleMediaPickerValueEditor(); } - - public override IDictionary DefaultPreValues { get { return InternalPreValues; } @@ -43,6 +51,11 @@ namespace Umbraco.Web.PropertyEditors return new SingleMediaPickerPreValueEditor(); } + internal class SingleMediaPickerValueEditor : PropertyValueEditor + { + override + } + internal class SingleMediaPickerPreValueEditor : PreValueEditor { [PreValueField("startNodeId", "Start node", "mediapicker")] diff --git a/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs index d7051ba8da..433199c536 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs @@ -8,7 +8,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MemberGroupPickerAlias, "Member Group Picker", "membergrouppicker", Group="People", Icon="icon-users", IsDeprecated = true)] + [PropertyEditor(Constants.PropertyEditors.MemberGroupPickerAlias, "Member Group Picker", "membergrouppicker", Group="People", Icon="icon-users")] public class MemberGroupPickerPropertyEditor : PropertyEditor { } diff --git a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs index 90e7562f3a..7314121e70 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs @@ -8,8 +8,15 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MemberPickerAlias, "Member Picker", PropertyEditorValueTypes.Integer, "memberpicker", Group = "People", Icon = "icon-user", IsDeprecated = true)] - public class MemberPickerPropertyEditor : PropertyEditor + + [Obsolete("This editor is obsolete, use MemberPickerPropertyEditor2 instead which stores UDI")] + [PropertyEditor(Constants.PropertyEditors.MemberPickerAlias, "(Obsolete) Member Picker", PropertyEditorValueTypes.Integer, "memberpicker", Group = "People", Icon = "icon-user", IsDeprecated = true)] + public class MemberPickerPropertyEditor : MemberPickerPropertyEditor2 + { + } + + [PropertyEditor(Constants.PropertyEditors.MemberPicker2Alias, "Member Picker", PropertyEditorValueTypes.String, "memberpicker", Group = "People", Icon = "icon-user")] + public class MemberPickerPropertyEditor2 : PropertyEditor { } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index d6b9beee7a..73cb002a16 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -1,14 +1,22 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "Multinode Treepicker", "contentpicker", Group="pickers", Icon="icon-page-add", IsDeprecated = true)] - public class MultiNodeTreePickerPropertyEditor : PropertyEditor + [Obsolete("This editor is obsolete, use MultiNodeTreePickerPropertyEditor2 instead which stores UDI")] + [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "(Obsolete) Multinode Treepicker", "contentpicker", Group = "pickers", Icon = "icon-page-add", IsDeprecated = true)] + public class MultiNodeTreePickerPropertyEditor : MultiNodeTreePickerPropertyEditor2 { - public MultiNodeTreePickerPropertyEditor() + + } + + [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePicker2Alias, "Multinode Treepicker", PropertyEditorValueTypes.Text, "contentpicker", Group="pickers", Icon="icon-page-add")] + public class MultiNodeTreePickerPropertyEditor2 : PropertyEditor + { + public MultiNodeTreePickerPropertyEditor2() { _internalPreValues = new Dictionary { diff --git a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs index 18a1e564d6..cd002a103b 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs @@ -1,12 +1,20 @@ -using Umbraco.Core; +using System; +using Umbraco.Core; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "Media Picker", "mediapicker", Group = "media", Icon = "icon-pictures-alt-2", IsDeprecated = true)] + [Obsolete("This editor is obsolete, use MultipleMediaPickerPropertyEditor2 instead which stores UDI")] + [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "(Obsolete) Media Picker", "mediapicker", Group = "media", Icon = "icon-pictures-alt-2", IsDeprecated = true)] public class MultipleMediaPickerPropertyEditor : MediaPickerPropertyEditor { - public MultipleMediaPickerPropertyEditor() + + } + + [PropertyEditor(Constants.PropertyEditors.MultipleMediaPicker2Alias, "Media Picker", PropertyEditorValueTypes.Text, "mediapicker", Group = "media", Icon = "icon-pictures-alt-2", IsDeprecated = true)] + public class MultipleMediaPickerPropertyEditor2 : MediaPickerPropertyEditor2 + { + public MultipleMediaPickerPropertyEditor2() { //clear the pre-values so it defaults to a multiple picker. InternalPreValues.Clear(); diff --git a/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs index f824f25294..e4a070cb29 100644 --- a/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs @@ -6,7 +6,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.UserPickerAlias, "User picker", PropertyEditorValueTypes.Integer, "entitypicker", Group="People", Icon="icon-user", IsDeprecated = true)] + [PropertyEditor(Constants.PropertyEditors.UserPickerAlias, "User picker", PropertyEditorValueTypes.Integer, "entitypicker", Group="People", Icon="icon-user")] public class UserPickerPropertyEditor : PropertyEditor { private IDictionary _defaultPreValues; From 9ef53536bf8f8a0dfa711eb902fd2ae987242616 Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 28 Jan 2017 01:14:24 +1100 Subject: [PATCH 004/105] Updates the base editor models to include a readonly UDI, updates all model mappers to map the UDI, --- .../UmbracoUdiTypeAttribute.cs | 15 ++++++ src/Umbraco.Core/Models/UmbracoObjectTypes.cs | 18 ++++++- .../Models/UmbracoObjectTypesExtensions.cs | 36 +++++++++++++ src/Umbraco.Core/Udi.cs | 8 +-- ...s-DeployEntityType.cs => UdiEntityType.cs} | 5 +- src/Umbraco.Core/UdiGetterExtensions.cs | 50 +++++++++---------- src/Umbraco.Core/Umbraco.Core.csproj | 3 +- src/Umbraco.Tests/UdiTests.cs | 44 ++++++++-------- .../Models/ContentEditing/EntityBasic.cs | 7 ++- .../Models/Mapping/ContentModelMapper.cs | 3 ++ .../Models/Mapping/ContentTypeModelMapper.cs | 9 ++-- .../ContentTypeModelMapperExtensions.cs | 1 + .../Models/Mapping/ContentTypeUdiResolver.cs | 21 ++++++++ .../Models/Mapping/DataTypeModelMapper.cs | 3 ++ .../Models/Mapping/EntityModelMapper.cs | 7 +++ .../Models/Mapping/MacroModelMapper.cs | 1 + .../Models/Mapping/MediaModelMapper.cs | 3 ++ .../Models/Mapping/MemberModelMapper.cs | 6 ++- .../Models/Mapping/OwnerResolver.cs | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 20 files changed, 182 insertions(+), 60 deletions(-) create mode 100644 src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs rename src/Umbraco.Core/{Constants-DeployEntityType.cs => UdiEntityType.cs} (98%) create mode 100644 src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs new file mode 100644 index 0000000000..0bb9de6c86 --- /dev/null +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Umbraco.Core.CodeAnnotations +{ + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] + internal class UmbracoUdiTypeAttribute : Attribute + { + public string UdiType { get; private set; } + + public UmbracoUdiTypeAttribute(string udiType) + { + UdiType = udiType; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 02dc44c4ce..162c66e174 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Models /// /// Root /// - [UmbracoObjectType(Constants.ObjectTypes.SystemRoot)] + [UmbracoObjectType(Constants.ObjectTypes.SystemRoot)] [FriendlyName("Root")] ROOT, @@ -35,6 +35,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Document, typeof(IContent))] [FriendlyName("Document")] + [UmbracoUdiType(Constants.UdiEntityType.Document)] Document, /// @@ -42,6 +43,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Media, typeof(IMedia))] [FriendlyName("Media")] + [UmbracoUdiType(Constants.UdiEntityType.Media)] Media, /// @@ -49,6 +51,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.MemberType, typeof(IMemberType))] [FriendlyName("Member Type")] + [UmbracoUdiType(Constants.UdiEntityType.MemberType)] MemberType, /// @@ -56,6 +59,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Template, typeof(ITemplate))] [FriendlyName("Template")] + [UmbracoUdiType(Constants.UdiEntityType.Template)] Template, /// @@ -63,6 +67,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.MemberGroup)] [FriendlyName("Member Group")] + [UmbracoUdiType(Constants.UdiEntityType.MemberGroup)] MemberGroup, //TODO: What is a 'Content Item' supposed to be??? @@ -80,6 +85,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.MediaType, typeof(IMediaType))] [FriendlyName("Media Type")] + [UmbracoUdiType(Constants.UdiEntityType.MediaType)] MediaType, /// @@ -87,13 +93,14 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.DocumentType, typeof(IContentType))] [FriendlyName("Document Type")] + [UmbracoUdiType(Constants.UdiEntityType.DocumentType)] DocumentType, /// /// Recycle Bin /// [UmbracoObjectType(Constants.ObjectTypes.ContentRecycleBin)] - [FriendlyName("Recycle Bin")] + [FriendlyName("Recycle Bin")] RecycleBin, /// @@ -101,6 +108,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Stylesheet)] [FriendlyName("Stylesheet")] + [UmbracoUdiType(Constants.UdiEntityType.Stylesheet)] Stylesheet, /// @@ -108,6 +116,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Member, typeof(IMember))] [FriendlyName("Member")] + [UmbracoUdiType(Constants.UdiEntityType.Member)] Member, /// @@ -115,6 +124,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.DataType, typeof(IDataTypeDefinition))] [FriendlyName("Data Type")] + [UmbracoUdiType(Constants.UdiEntityType.DataType)] DataType, /// @@ -122,6 +132,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.DocumentTypeContainer)] [FriendlyName("Document Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.DocumentTypeContainer)] DocumentTypeContainer, /// @@ -129,6 +140,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.MediaTypeContainer)] [FriendlyName("Media Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.MediaTypeContainer)] MediaTypeContainer, /// @@ -136,6 +148,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.DataTypeContainer)] [FriendlyName("Data Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.DataTypeContainer)] DataTypeContainer, /// @@ -143,6 +156,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.RelationType)] [FriendlyName("Relation Type")] + [UmbracoUdiType(Constants.UdiEntityType.RelationType)] RelationType } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs b/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs index 34ee29b96f..5f92e6425e 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs @@ -12,6 +12,7 @@ namespace Umbraco.Core.Models { //MUST be concurrent to avoid thread collisions! private static readonly ConcurrentDictionary UmbracoObjectTypeCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary UmbracoObjectTypeUdiCache = new ConcurrentDictionary(); /// /// Get an UmbracoObjectTypes value from it's name @@ -43,6 +44,21 @@ namespace Umbraco.Core.Models return umbracoObjectType; } + public static string GetUdiType(Guid guid) + { + var umbracoObjectType = Constants.UdiEntityType.Unknown; + + foreach (var name in Enum.GetNames(typeof(UmbracoObjectTypes))) + { + var objType = GetUmbracoObjectType(name); + if (objType.GetGuid() == guid) + { + umbracoObjectType = GetUdiType(objType); + } + } + return umbracoObjectType; + } + /// /// Extension method for the UmbracoObjectTypes enum to return the enum GUID /// @@ -68,6 +84,26 @@ namespace Umbraco.Core.Models }); } + public static string GetUdiType(this UmbracoObjectTypes umbracoObjectType) + { + return UmbracoObjectTypeUdiCache.GetOrAdd(umbracoObjectType, types => + { + var type = typeof(UmbracoObjectTypes); + var memInfo = type.GetMember(umbracoObjectType.ToString()); + var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoUdiTypeAttribute), + false); + + if (attributes.Length == 0) + return Constants.UdiEntityType.Unknown; + + var attribute = ((UmbracoUdiTypeAttribute)attributes[0]); + if (attribute == null) + return Constants.UdiEntityType.Unknown; + + return attribute.UdiType; + }); + } + /// /// Extension method for the UmbracoObjectTypes enum to return the enum name /// diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index a7298c6f89..ae43bbf270 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -40,12 +40,12 @@ namespace Umbraco.Core static Udi() { // for tests etc. - UdiTypes[Constants.DeployEntityType.AnyGuid] = UdiType.GuidUdi; - UdiTypes[Constants.DeployEntityType.AnyString] = UdiType.StringUdi; + UdiTypes[Constants.UdiEntityType.AnyGuid] = UdiType.GuidUdi; + UdiTypes[Constants.UdiEntityType.AnyString] = UdiType.StringUdi; // we don't have connectors for these... - UdiTypes[Constants.DeployEntityType.Member] = UdiType.GuidUdi; - UdiTypes[Constants.DeployEntityType.MemberGroup] = UdiType.GuidUdi; + UdiTypes[Constants.UdiEntityType.Member] = UdiType.GuidUdi; + UdiTypes[Constants.UdiEntityType.MemberGroup] = UdiType.GuidUdi; // fixme - or inject from...? // there is no way we can get the "registered" service connectors, as registration diff --git a/src/Umbraco.Core/Constants-DeployEntityType.cs b/src/Umbraco.Core/UdiEntityType.cs similarity index 98% rename from src/Umbraco.Core/Constants-DeployEntityType.cs rename to src/Umbraco.Core/UdiEntityType.cs index f622661dff..4e43cb06c5 100644 --- a/src/Umbraco.Core/Constants-DeployEntityType.cs +++ b/src/Umbraco.Core/UdiEntityType.cs @@ -13,11 +13,14 @@ namespace Umbraco.Core /// /// Well-known entity types are those that Deploy already knows about, /// but entity types are strings and so can be extended beyond what is defined here. - public static class DeployEntityType + public static class UdiEntityType { + public const string Unknown = "unknown"; + // guid entity types public const string AnyGuid = "any-guid"; // that one is for tests + public const string Document = "document"; public const string Media = "media"; diff --git a/src/Umbraco.Core/UdiGetterExtensions.cs b/src/Umbraco.Core/UdiGetterExtensions.cs index 1bd018a754..8acaab6512 100644 --- a/src/Umbraco.Core/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/UdiGetterExtensions.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this ITemplate entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.Template, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed(); } /// @@ -28,7 +28,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IContentType entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.DocumentType, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.DocumentType, entity.Key).EnsureClosed(); } /// @@ -39,7 +39,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMediaType entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.MediaType, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.MediaType, entity.Key).EnsureClosed(); } /// @@ -50,7 +50,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMemberType entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.MemberType, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.MemberType, entity.Key).EnsureClosed(); } /// @@ -61,7 +61,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMemberGroup entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.MemberGroup, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed(); } /// @@ -74,9 +74,9 @@ namespace Umbraco.Core if (entity == null) throw new ArgumentNullException("entity"); string type; - if (entity is IContentType) type = Constants.DeployEntityType.DocumentType; - else if (entity is IMediaType) type = Constants.DeployEntityType.MediaType; - else if (entity is IMemberType) type = Constants.DeployEntityType.MemberType; + if (entity is IContentType) type = Constants.UdiEntityType.DocumentType; + else if (entity is IMediaType) type = Constants.UdiEntityType.MediaType; + else if (entity is IMemberType) type = Constants.UdiEntityType.MemberType; else throw new NotSupportedException(string.Format("Composition type {0} is not supported.", entity.GetType().FullName)); return new GuidUdi(type, entity.Key).EnsureClosed(); } @@ -89,7 +89,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IDataTypeDefinition entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.DataType, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.DataType, entity.Key).EnsureClosed(); } /// @@ -103,11 +103,11 @@ namespace Umbraco.Core string entityType; if (entity.ContainedObjectType == Constants.ObjectTypes.DataTypeGuid) - entityType = Constants.DeployEntityType.DataTypeContainer; + entityType = Constants.UdiEntityType.DataTypeContainer; else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentTypeGuid) - entityType = Constants.DeployEntityType.DocumentTypeContainer; + entityType = Constants.UdiEntityType.DocumentTypeContainer; else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaTypeGuid) - entityType = Constants.DeployEntityType.MediaTypeContainer; + entityType = Constants.UdiEntityType.MediaTypeContainer; else throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", entity.ContainedObjectType)); return new GuidUdi(entityType, entity.Key).EnsureClosed(); @@ -121,7 +121,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMedia entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.Media, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed(); } /// @@ -132,7 +132,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IContent entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.Document, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.Document, entity.Key).EnsureClosed(); } /// @@ -143,7 +143,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMember entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.Member, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed(); } /// @@ -154,7 +154,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this Stylesheet entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.DeployEntityType.Stylesheet, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.UdiEntityType.Stylesheet, entity.Path.TrimStart('/')).EnsureClosed(); } /// @@ -165,7 +165,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this Script entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.DeployEntityType.Script, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.UdiEntityType.Script, entity.Path.TrimStart('/')).EnsureClosed(); } /// @@ -176,7 +176,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IDictionaryItem entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.DictionaryItem, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.DictionaryItem, entity.Key).EnsureClosed(); } /// @@ -187,7 +187,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IMacro entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.Macro, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.Macro, entity.Key).EnsureClosed(); } /// @@ -198,7 +198,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this IPartialView entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.DeployEntityType.PartialView, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.UdiEntityType.PartialView, entity.Path.TrimStart('/')).EnsureClosed(); } /// @@ -209,7 +209,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this IXsltFile entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.DeployEntityType.Xslt, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.UdiEntityType.Xslt, entity.Path.TrimStart('/')).EnsureClosed(); } /// @@ -222,9 +222,9 @@ namespace Umbraco.Core if (entity == null) throw new ArgumentNullException("entity"); string type; - if (entity is IContent) type = Constants.DeployEntityType.Document; - else if (entity is IMedia) type = Constants.DeployEntityType.Media; - else if (entity is IMember) type = Constants.DeployEntityType.Member; + if (entity is IContent) type = Constants.UdiEntityType.Document; + else if (entity is IMedia) type = Constants.UdiEntityType.Media; + else if (entity is IMember) type = Constants.UdiEntityType.Member; else throw new NotSupportedException(string.Format("ContentBase type {0} is not supported.", entity.GetType().FullName)); return new GuidUdi(type, entity.Key).EnsureClosed(); } @@ -237,7 +237,7 @@ namespace Umbraco.Core public static GuidUdi GetUdi(this IRelationType entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.DeployEntityType.RelationType, entity.Key).EnsureClosed(); + return new GuidUdi(Constants.UdiEntityType.RelationType, entity.Key).EnsureClosed(); } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3a53f273f4..05854890f7 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -164,6 +164,7 @@ + @@ -290,7 +291,6 @@ - @@ -1406,6 +1406,7 @@ + diff --git a/src/Umbraco.Tests/UdiTests.cs b/src/Umbraco.Tests/UdiTests.cs index 36242bab13..9b803d5fa3 100644 --- a/src/Umbraco.Tests/UdiTests.cs +++ b/src/Umbraco.Tests/UdiTests.cs @@ -13,41 +13,41 @@ namespace Umbraco.Tests [Test] public void StringEntityCtorTest() { - var udi = new StringUdi(Constants.DeployEntityType.AnyString, "test-id"); - Assert.AreEqual(Constants.DeployEntityType.AnyString, udi.EntityType); + var udi = new StringUdi(Constants.UdiEntityType.AnyString, "test-id"); + Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType); Assert.AreEqual("test-id", udi.Id); - Assert.AreEqual("umb://" + Constants.DeployEntityType.AnyString + "/test-id", udi.ToString()); + Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/test-id", udi.ToString()); } [Test] public void StringEntityParseTest() { - var udi = Udi.Parse("umb://" + Constants.DeployEntityType.AnyString + "/test-id"); - Assert.AreEqual(Constants.DeployEntityType.AnyString, udi.EntityType); + var udi = Udi.Parse("umb://" + Constants.UdiEntityType.AnyString + "/test-id"); + Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType); Assert.IsInstanceOf(udi); var stringEntityId = udi as StringUdi; Assert.IsNotNull(stringEntityId); Assert.AreEqual("test-id", stringEntityId.Id); - Assert.AreEqual("umb://" + Constants.DeployEntityType.AnyString + "/test-id", udi.ToString()); + Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/test-id", udi.ToString()); } [Test] public void GuidEntityCtorTest() { var guid = Guid.NewGuid(); - var udi = new GuidUdi(Constants.DeployEntityType.AnyGuid, guid); - Assert.AreEqual(Constants.DeployEntityType.AnyGuid, udi.EntityType); + var udi = new GuidUdi(Constants.UdiEntityType.AnyGuid, guid); + Assert.AreEqual(Constants.UdiEntityType.AnyGuid, udi.EntityType); Assert.AreEqual(guid, udi.Guid); - Assert.AreEqual("umb://" + Constants.DeployEntityType.AnyGuid + "/" + guid.ToString("N"), udi.ToString()); + Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyGuid + "/" + guid.ToString("N"), udi.ToString()); } [Test] public void GuidEntityParseTest() { var guid = Guid.NewGuid(); - var s = "umb://" + Constants.DeployEntityType.AnyGuid + "/" + guid.ToString("N"); + var s = "umb://" + Constants.UdiEntityType.AnyGuid + "/" + guid.ToString("N"); var udi = Udi.Parse(s); - Assert.AreEqual(Constants.DeployEntityType.AnyGuid, udi.EntityType); + Assert.AreEqual(Constants.UdiEntityType.AnyGuid, udi.EntityType); Assert.IsInstanceOf(udi); var gudi = udi as GuidUdi; Assert.IsNotNull(gudi); @@ -82,9 +82,9 @@ namespace Umbraco.Tests var guid1 = Guid.NewGuid(); var entities = new[] { - new GuidUdi(Constants.DeployEntityType.AnyGuid, guid1), - new GuidUdi(Constants.DeployEntityType.AnyGuid, guid1), - new GuidUdi(Constants.DeployEntityType.AnyGuid, guid1), + new GuidUdi(Constants.UdiEntityType.AnyGuid, guid1), + new GuidUdi(Constants.UdiEntityType.AnyGuid, guid1), + new GuidUdi(Constants.UdiEntityType.AnyGuid, guid1), }; Assert.AreEqual(1, entities.Distinct().Count()); } @@ -93,12 +93,12 @@ namespace Umbraco.Tests public void CreateTest() { var guid = Guid.NewGuid(); - var udi = Udi.Create(Constants.DeployEntityType.AnyGuid, guid); - Assert.AreEqual(Constants.DeployEntityType.AnyGuid, udi.EntityType); + var udi = Udi.Create(Constants.UdiEntityType.AnyGuid, guid); + Assert.AreEqual(Constants.UdiEntityType.AnyGuid, udi.EntityType); Assert.AreEqual(guid, ((GuidUdi)udi).Guid); - Assert.Throws(() => Udi.Create(Constants.DeployEntityType.AnyString, guid)); - Assert.Throws(() => Udi.Create(Constants.DeployEntityType.AnyGuid, "foo")); + Assert.Throws(() => Udi.Create(Constants.UdiEntityType.AnyString, guid)); + Assert.Throws(() => Udi.Create(Constants.UdiEntityType.AnyGuid, "foo")); Assert.Throws(() => Udi.Create("barf", "foo")); } @@ -106,13 +106,13 @@ namespace Umbraco.Tests public void RangeTest() { // can parse open string udi - var stringUdiString = "umb://" + Constants.DeployEntityType.AnyString; + var stringUdiString = "umb://" + Constants.UdiEntityType.AnyString; Udi stringUdi; Assert.IsTrue(Udi.TryParse(stringUdiString, out stringUdi)); Assert.AreEqual(string.Empty, ((StringUdi)stringUdi).Id); // can parse open guid udi - var guidUdiString = "umb://" + Constants.DeployEntityType.AnyGuid; + var guidUdiString = "umb://" + Constants.UdiEntityType.AnyGuid; Udi guidUdi; Assert.IsTrue(Udi.TryParse(guidUdiString, out guidUdi)); Assert.AreEqual(Guid.Empty, ((GuidUdi)guidUdi).Guid); @@ -134,12 +134,12 @@ namespace Umbraco.Tests var guid = Guid.NewGuid(); - var udi = new GuidUdi(Constants.DeployEntityType.AnyGuid, guid); + var udi = new GuidUdi(Constants.UdiEntityType.AnyGuid, guid); var json = JsonConvert.SerializeObject(udi, settings); Assert.AreEqual(string.Format("\"umb://any-guid/{0:N}\"", guid), json); var dudi = JsonConvert.DeserializeObject(json, settings); - Assert.AreEqual(Constants.DeployEntityType.AnyGuid, dudi.EntityType); + Assert.AreEqual(Constants.UdiEntityType.AnyGuid, dudi.EntityType); Assert.AreEqual(guid, ((GuidUdi)dudi).Guid); var range = new UdiRange(udi, Constants.DeploySelector.ChildrenOfThis); diff --git a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs index 40d884d653..b17b876e76 100644 --- a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; +using Umbraco.Core; using Umbraco.Core.Models.Validation; namespace Umbraco.Web.Models.ContentEditing @@ -25,7 +26,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "id", IsRequired = true)] [Required] public object Id { get; set; } - + + [DataMember(Name = "udi")] + [ReadOnly(true)] + public Udi Udi { get; set; } + [DataMember(Name = "icon")] public string Icon { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 27f34a12e6..afed4bfb20 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -28,6 +28,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IContent TO ContentItemDisplay config.CreateMap() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Document, content.Key))) .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(display => display.Updater, expression => expression.ResolveUsing(new CreatorResolver())) .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) @@ -58,6 +59,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IContent TO ContentItemBasic config.CreateMap>() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Document, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.Updater, expression => expression.ResolveUsing(new CreatorResolver())) .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) @@ -68,6 +70,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IContent TO ContentItemDto config.CreateMap>() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Document, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion)) .ForMember(dto => dto.Updater, expression => expression.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs index bf0ec1f457..de5b5a14fd 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs @@ -161,9 +161,12 @@ namespace Umbraco.Web.Models.Mapping }); - config.CreateMap(); - config.CreateMap(); - config.CreateMap(); + config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MemberType, content.Key))); + config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MediaType, content.Key))); + config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DocumentType, content.Key))); config.CreateMap() diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs index 52f2dbad4b..cd42c87a56 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs @@ -126,6 +126,7 @@ namespace Umbraco.Web.Models.Mapping where TPropertyTypeDisplay : PropertyTypeDisplay, new() { return mapping + .ForMember(x => x.Udi, expression => expression.ResolveUsing(new ContentTypeUdiResolver())) .ForMember(display => display.Notifications, expression => expression.Ignore()) .ForMember(display => display.Errors, expression => expression.Ignore()) .ForMember(display => display.AllowAsRoot, expression => expression.MapFrom(type => type.AllowedAsRoot)) diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs new file mode 100644 index 0000000000..142ff43a99 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Resolves a UDI for a content type based on it's type + /// + internal class ContentTypeUdiResolver : ValueResolver + { + protected override Udi ResolveCore(IContentTypeComposition source) + { + return Udi.Create( + source.GetType() == typeof(IMemberType) + ? Constants.UdiEntityType.MemberType + : source.GetType() == typeof(IMediaType) + ? Constants.UdiEntityType.MediaType : Constants.UdiEntityType.DocumentType, source.Key); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index d7dfdbf9d0..e78aeaf6a3 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -35,6 +35,7 @@ namespace Umbraco.Web.Models.Mapping }; config.CreateMap() + .ForMember(x => x.Udi, expression => expression.Ignore()) .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) .ForMember(x => x.IsSystemDataType, expression => expression.Ignore()) .ForMember(x => x.Id, expression => expression.Ignore()) @@ -45,6 +46,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.AdditionalData, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DataType, content.Key))) .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) .ForMember(x => x.Icon, expression => expression.Ignore()) .ForMember(x => x.Alias, expression => expression.Ignore()) @@ -62,6 +64,7 @@ namespace Umbraco.Web.Models.Mapping }); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DataType, content.Key))) .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing(new AvailablePropertyEditorsResolver(UmbracoConfig.For.UmbracoSettings().Content))) .ForMember(display => display.PreValues, expression => expression.ResolveUsing( new PreValueDisplayResolver(lazyDataTypeService))) diff --git a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs index 5610a70008..361836e529 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs @@ -17,11 +17,13 @@ namespace Umbraco.Web.Models.Mapping public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(x => Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(x.NodeObjectTypeId), x.Key))) .ForMember(basic => basic.Icon, expression => expression.MapFrom(entity => entity.ContentTypeIcon)) .ForMember(dto => dto.Trashed, expression => expression.Ignore()) .ForMember(x => x.Alias, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.Ignore()) .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-box")) .ForMember(basic => basic.Path, expression => expression.UseValue("")) .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) @@ -29,6 +31,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.AdditionalData, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.Ignore()) .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-tab")) .ForMember(basic => basic.Path, expression => expression.UseValue("")) .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) @@ -38,6 +41,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.AdditionalData, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.Ignore()) .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-user")) .ForMember(basic => basic.Path, expression => expression.UseValue("")) .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) @@ -46,6 +50,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.AdditionalData, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(x => Udi.Create(Constants.UdiEntityType.Template, x.Key))) .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-layout")) .ForMember(basic => basic.Path, expression => expression.MapFrom(template => template.Path)) .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) @@ -70,6 +75,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.SortOrder, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.ResolveUsing(new ContentTypeUdiResolver())) .ForMember(basic => basic.Path, expression => expression.MapFrom(x => x.Path)) .ForMember(basic => basic.ParentId, expression => expression.MapFrom(x => x.ParentId)) .ForMember(dto => dto.Trashed, expression => expression.Ignore()) @@ -77,6 +83,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() //default to document icon + .ForMember(x => x.Udi, expression => expression.Ignore()) .ForMember(x => x.Icon, expression => expression.Ignore()) .ForMember(x => x.Id, expression => expression.MapFrom(result => result.Id)) .ForMember(x => x.Name, expression => expression.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs index 54ebca6e68..3dc86e61c9 100644 --- a/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs @@ -20,6 +20,7 @@ namespace Umbraco.Web.Models.Mapping { //FROM IMacro TO EntityBasic config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Macro, content.Key))) .ForMember(entityBasic => entityBasic.Icon, expression => expression.UseValue("icon-settings-alt")) .ForMember(dto => dto.ParentId, expression => expression.UseValue(-1)) .ForMember(dto => dto.Path, expression => expression.ResolveUsing(macro => "-1," + macro.Id)) diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 4bcdf7a158..eddb4a582e 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -25,6 +25,7 @@ namespace Umbraco.Web.Models.Mapping { //FROM IMedia TO MediaItemDisplay config.CreateMap() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) @@ -45,6 +46,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IMedia TO ContentItemBasic config.CreateMap>() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) .ForMember(dto => dto.Trashed, expression => expression.MapFrom(content => content.Trashed)) @@ -56,6 +58,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IMedia TO ContentItemDto config.CreateMap>() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.Published, expression => expression.Ignore()) .ForMember(dto => dto.Updater, expression => expression.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 0536efd307..3982302906 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -61,6 +61,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IMember TO MediaItemDisplay config.CreateMap() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) @@ -84,6 +85,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IMember TO MemberBasic config.CreateMap() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) .ForMember(dto => dto.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) @@ -96,9 +98,10 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); //FROM MembershipUser TO MemberBasic - config.CreateMap() + config.CreateMap() //we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity .ForMember(member => member.Id, expression => expression.MapFrom(user => int.MaxValue)) + .ForMember(display => display.Udi, expression => expression.Ignore()) .ForMember(member => member.CreateDate, expression => expression.MapFrom(user => user.CreationDate)) .ForMember(member => member.UpdateDate, expression => expression.MapFrom(user => user.LastActivityDate)) .ForMember(member => member.Key, expression => expression.MapFrom(user => user.ProviderUserKey.TryConvertTo().Result.ToString("N"))) @@ -121,6 +124,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IMember TO ContentItemDto config.CreateMap>() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.Published, expression => expression.Ignore()) .ForMember(dto => dto.Updater, expression => expression.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs index 5f3697c3d5..067d495591 100644 --- a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs @@ -1,5 +1,6 @@ using AutoMapper; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e4bb3c9e88..48cb6a455b 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -342,6 +342,7 @@ + From a31b03b3e2797ddba29d7e252a11e8a163edc9e0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Jan 2017 15:59:42 +1100 Subject: [PATCH 005/105] Lazily loads all known UdiType's based on reading through the known constants --- src/Umbraco.Core/Udi.cs | 84 ++++++++++++++++++------------- src/Umbraco.Core/UdiEntityType.cs | 36 ++++++++++++- 2 files changed, 84 insertions(+), 36 deletions(-) diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index ae43bbf270..6267af67dc 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Umbraco.Core.Deploy; namespace Umbraco.Core @@ -12,7 +13,7 @@ namespace Umbraco.Core /// An Udi can be fully qualified or "closed" eg umb://document/{guid} or "open" eg umb://document. public abstract class Udi : IComparable { - private static readonly Dictionary UdiTypes = new Dictionary(); + private static readonly Lazy> UdiTypes; private static readonly ConcurrentDictionary RootUdis = new ConcurrentDictionary(); internal readonly Uri UriValue; // internal for UdiRange @@ -39,32 +40,47 @@ namespace Umbraco.Core static Udi() { - // for tests etc. - UdiTypes[Constants.UdiEntityType.AnyGuid] = UdiType.GuidUdi; - UdiTypes[Constants.UdiEntityType.AnyString] = UdiType.StringUdi; - - // we don't have connectors for these... - UdiTypes[Constants.UdiEntityType.Member] = UdiType.GuidUdi; - UdiTypes[Constants.UdiEntityType.MemberGroup] = UdiType.GuidUdi; - - // fixme - or inject from...? - // there is no way we can get the "registered" service connectors, as registration - // happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we - // just pick every service connectors - just making sure that not two of them - // would register the same entity type, with different udi types (would not make - // much sense anyways). - var connectors = PluginManager.Current.ResolveTypes(); - foreach (var connector in connectors) + UdiTypes = new Lazy>(() => { - var attrs = connector.GetCustomAttributes(false); - foreach (var attr in attrs) + var result = new Dictionary(); + + // known types: + foreach (var fi in typeof(Constants.UdiEntityType).GetFields(BindingFlags.Public | BindingFlags.Static)) { - UdiType udiType; - if (UdiTypes.TryGetValue(attr.EntityType, out udiType) && udiType != attr.UdiType) - throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType)); - UdiTypes[attr.EntityType] = attr.UdiType; + // IsLiteral determines if its value is written at + // compile time and not changeable + // IsInitOnly determine if the field can be set + // in the body of the constructor + // for C# a field which is readonly keyword would have both true + // but a const field would have only IsLiteral equal to true + if (fi.IsLiteral && fi.IsInitOnly == false) + { + var udiType = fi.GetCustomAttribute(); + result[fi.GetValue(null).ToString()] = udiType.UdiType; + } } - } + + // Scan for unknown UDI types + // there is no way we can get the "registered" service connectors, as registration + // happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we + // just pick every service connectors - just making sure that not two of them + // would register the same entity type, with different udi types (would not make + // much sense anyways). + var connectors = PluginManager.Current.ResolveTypes(); + foreach (var connector in connectors) + { + var attrs = connector.GetCustomAttributes(false); + foreach (var attr in attrs) + { + UdiType udiType; + if (result.TryGetValue(attr.EntityType, out udiType) && udiType != attr.UdiType) + throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType)); + result[attr.EntityType] = attr.UdiType; + } + } + + return result; + }); } /// @@ -105,8 +121,8 @@ namespace Umbraco.Core udi = null; Uri uri; - if (!Uri.IsWellFormedUriString(s, UriKind.Absolute) - || !Uri.TryCreate(s, UriKind.Absolute, out uri)) + if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false + || Uri.TryCreate(s, UriKind.Absolute, out uri) == false) { if (tryParse) return false; throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); @@ -114,7 +130,7 @@ namespace Umbraco.Core var entityType = uri.Host; UdiType udiType; - if (!UdiTypes.TryGetValue(entityType, out udiType)) + if (UdiTypes.Value.TryGetValue(entityType, out udiType) == false) { if (tryParse) return false; throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType)); @@ -128,7 +144,7 @@ namespace Umbraco.Core return true; } Guid guid; - if (!Guid.TryParse(path, out guid)) + if (Guid.TryParse(path, out guid) == false) { if (tryParse) return false; throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); @@ -150,7 +166,7 @@ namespace Umbraco.Core return RootUdis.GetOrAdd(entityType, x => { UdiType udiType; - if (!UdiTypes.TryGetValue(x, out udiType)) + if (UdiTypes.Value.TryGetValue(x, out udiType) == false) throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType)); return udiType == UdiType.StringUdi ? (Udi)new StringUdi(entityType, string.Empty) @@ -177,7 +193,7 @@ namespace Umbraco.Core public static Udi Create(string entityType, string id) { UdiType udiType; - if (!UdiTypes.TryGetValue(entityType, out udiType)) + if (UdiTypes.Value.TryGetValue(entityType, out udiType) == false) throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("Value cannot be null or whitespace.", "id"); @@ -196,7 +212,7 @@ namespace Umbraco.Core public static Udi Create(string entityType, Guid id) { UdiType udiType; - if (!UdiTypes.TryGetValue(entityType, out udiType)) + if (UdiTypes.Value.TryGetValue(entityType, out udiType) == false) throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); if (udiType != UdiType.GuidUdi) throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", entityType)); @@ -208,7 +224,7 @@ namespace Umbraco.Core internal static Udi Create(Uri uri) { UdiType udiType; - if (!UdiTypes.TryGetValue(uri.Host, out udiType)) + if (UdiTypes.Value.TryGetValue(uri.Host, out udiType) == false) throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri"); if (udiType == UdiType.GuidUdi) return new GuidUdi(uri); @@ -219,7 +235,7 @@ namespace Umbraco.Core public void EnsureType(params string[] validTypes) { - if (!validTypes.Contains(EntityType)) + if (validTypes.Contains(EntityType) == false) throw new Exception(string.Format("Unexpected entity type \"{0}\".", EntityType)); } @@ -260,7 +276,7 @@ namespace Umbraco.Core public static bool operator !=(Udi udi1, Udi udi2) { - return !(udi1 == udi2); + return (udi1 == udi2) == false; } } diff --git a/src/Umbraco.Core/UdiEntityType.cs b/src/Umbraco.Core/UdiEntityType.cs index 4e43cb06c5..d89376f3d9 100644 --- a/src/Umbraco.Core/UdiEntityType.cs +++ b/src/Umbraco.Core/UdiEntityType.cs @@ -7,7 +7,6 @@ namespace Umbraco.Core public static partial class Constants { - /// /// Defines well-known entity types. /// @@ -15,42 +14,66 @@ namespace Umbraco.Core /// but entity types are strings and so can be extended beyond what is defined here. public static class UdiEntityType { + [UdiType(UdiType.Unknown)] public const string Unknown = "unknown"; // guid entity types + [UdiType(UdiType.GuidUdi)] public const string AnyGuid = "any-guid"; // that one is for tests - + [UdiType(UdiType.GuidUdi)] public const string Document = "document"; + [UdiType(UdiType.GuidUdi)] public const string Media = "media"; + [UdiType(UdiType.GuidUdi)] public const string Member = "member"; + [UdiType(UdiType.GuidUdi)] public const string DictionaryItem = "dictionary-item"; + [UdiType(UdiType.GuidUdi)] public const string Macro = "macro"; + [UdiType(UdiType.GuidUdi)] public const string Template = "template"; + [UdiType(UdiType.GuidUdi)] public const string DocumentType = "document-type"; + [UdiType(UdiType.GuidUdi)] public const string DocumentTypeContainer = "document-type-container"; + [UdiType(UdiType.GuidUdi)] public const string MediaType = "media-type"; + [UdiType(UdiType.GuidUdi)] public const string MediaTypeContainer = "media-type-container"; + [UdiType(UdiType.GuidUdi)] public const string DataType = "data-type"; + [UdiType(UdiType.GuidUdi)] public const string DataTypeContainer = "data-type-container"; + [UdiType(UdiType.GuidUdi)] public const string MemberType = "member-type"; + [UdiType(UdiType.GuidUdi)] public const string MemberGroup = "member-group"; + [UdiType(UdiType.GuidUdi)] public const string RelationType = "relation-type"; // string entity types + [UdiType(UdiType.StringUdi)] public const string AnyString = "any-string"; // that one is for tests + [UdiType(UdiType.StringUdi)] public const string MediaFile = "media-file"; + [UdiType(UdiType.StringUdi)] public const string TemplateFile = "template-file"; + [UdiType(UdiType.StringUdi)] public const string Script = "script"; + [UdiType(UdiType.StringUdi)] public const string Stylesheet = "stylesheet"; + [UdiType(UdiType.StringUdi)] public const string PartialView = "partial-view"; + [UdiType(UdiType.StringUdi)] public const string PartialViewMacro = "partial-view-macro"; + [UdiType(UdiType.StringUdi)] public const string Xslt = "xslt"; public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType) @@ -127,6 +150,15 @@ namespace Umbraco.Core } } + [AttributeUsage(AttributeTargets.Field)] + internal class UdiTypeAttribute : Attribute + { + public UdiType UdiType { get; private set; } + public UdiTypeAttribute(UdiType udiType) + { + UdiType = udiType; + } + } } } \ No newline at end of file From 0078a5d8cc80e778dfbb21691c5543907d7c6c4d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Jan 2017 16:54:01 +1100 Subject: [PATCH 006/105] Getting the media picker wired up for udi --- src/Umbraco.Core/Constants-PropertyEditors.cs | 4 +- .../mediapicker/mediapicker.controller.js | 17 ++++-- .../Models/ContentEditing/EntityBasic.cs | 3 + .../MediaPickerPropertyEditor.cs | 58 ++++++++++++------- .../MultipleMediaPickerPropertyEditor.cs | 32 +--------- 5 files changed, 58 insertions(+), 56 deletions(-) diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 2712fffdfa..832c87bbb4 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -204,11 +204,9 @@ namespace Umbraco.Core /// public const string MediaPicker2Alias = "Umbraco.MediaPicker2"; - [Obsolete("This is an obsoleted picker, use MultipleMediaPicker2Alias instead")] + [Obsolete("This is an obsoleted picker, use MemberPicker2Alias instead")] public const string MultipleMediaPickerAlias = "Umbraco.MultipleMediaPicker"; - public const string MultipleMediaPicker2Alias = "Umbraco.MultipleMediaPicker2"; - /// /// Guid for the Member Picker datatype. /// diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index 6945d995cd..dafe6cc3c7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -40,7 +40,13 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl } $scope.images.push(media); - $scope.ids.push(media.id); + + if ($scope.model.config.idType === "udi") { + $scope.ids.push(media.udi); + } + else { + $scope.ids.push(media.id); + } } }); @@ -81,9 +87,12 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl $scope.images.push(media); - //TODO: Determine if we are storing UDI vs INT - - $scope.ids.push(media.id); + if ($scope.model.config.idType === "udi") { + $scope.ids.push(media.udi); + } + else { + $scope.ids.push(media.id); + } }); $scope.sync(); diff --git a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs index b17b876e76..9f6e5b28da 100644 --- a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs @@ -6,8 +6,10 @@ using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Models.Validation; +using Umbraco.Core.Serialization; namespace Umbraco.Web.Models.ContentEditing { @@ -29,6 +31,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "udi")] [ReadOnly(true)] + [JsonConverter(typeof(UdiJsonConverter))] public Udi Udi { get; set; } [DataMember(Name = "icon")] diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index f776d74829..169d0d4639 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -16,6 +16,26 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.MediaPickerAlias, "(Obsolete) Media Picker", PropertyEditorValueTypes.Integer, "mediapicker", Group = "media", Icon = "icon-picture", IsDeprecated = true)] public class MediaPickerPropertyEditor : MediaPickerPropertyEditor2 { + public MediaPickerPropertyEditor() + { + InternalPreValues = new Dictionary + { + {"multiPicker", "0"}, + {"onlyImages", "0"}, + {"idType", "id"} + }; + } + + protected override PreValueEditor CreatePreValueEditor() + { + return new SingleMediaPickerPreValueEditor(); + } + + internal class SingleMediaPickerPreValueEditor : PreValueEditor + { + [PreValueField("startNodeId", "Start node", "mediapicker")] + public int StartNodeId { get; set; } + } } /// @@ -28,38 +48,36 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { - {"multiPicker", "0"}, - {"onlyImages", "0"} + {"idType", "udi"} }; } protected IDictionary InternalPreValues; - - protected override PropertyValueEditor CreateValueEditor() - { - return new SingleMediaPickerValueEditor(); - } - + public override IDictionary DefaultPreValues { get { return InternalPreValues; } set { InternalPreValues = value; } } - protected override PreValueEditor CreatePreValueEditor() - { - return new SingleMediaPickerPreValueEditor(); - } + protected override PreValueEditor CreatePreValueEditor() + { + return new MediaPickerPreValueEditor(); + } + + internal class MediaPickerPreValueEditor : PreValueEditor + { + [PreValueField("multiPicker", "Pick multiple items", "boolean")] + public bool MultiPicker { get; set; } + + [PreValueField("onlyImages", "Pick only images", "boolean", Description = "Only let the editor choose images from media.")] + public bool OnlyImages { get; set; } - internal class SingleMediaPickerValueEditor : PropertyValueEditor - { - override - } - - internal class SingleMediaPickerPreValueEditor : PreValueEditor - { + [PreValueField("disableFolderSelect", "Disable folder select", "boolean", Description = "Do not allow folders to be picked.")] + public bool DisableFolderSelect { get; set; } + [PreValueField("startNodeId", "Start node", "mediapicker")] - public int StartNodeId { get; set; } + public int StartNodeId { get; set; } } } } diff --git a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs index cd002a103b..716e71711f 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs @@ -7,37 +7,11 @@ namespace Umbraco.Web.PropertyEditors [Obsolete("This editor is obsolete, use MultipleMediaPickerPropertyEditor2 instead which stores UDI")] [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "(Obsolete) Media Picker", "mediapicker", Group = "media", Icon = "icon-pictures-alt-2", IsDeprecated = true)] public class MultipleMediaPickerPropertyEditor : MediaPickerPropertyEditor - { - - } - - [PropertyEditor(Constants.PropertyEditors.MultipleMediaPicker2Alias, "Media Picker", PropertyEditorValueTypes.Text, "mediapicker", Group = "media", Icon = "icon-pictures-alt-2", IsDeprecated = true)] - public class MultipleMediaPickerPropertyEditor2 : MediaPickerPropertyEditor2 - { - public MultipleMediaPickerPropertyEditor2() + { + public MultipleMediaPickerPropertyEditor() { //clear the pre-values so it defaults to a multiple picker. InternalPreValues.Clear(); } - - protected override PreValueEditor CreatePreValueEditor() - { - return new MediaPickerPreValueEditor(); - } - - internal class MediaPickerPreValueEditor : PreValueEditor - { - [PreValueField("multiPicker", "Pick multiple items", "boolean")] - public bool MultiPicker { get; set; } - - [PreValueField("onlyImages", "Pick only images", "boolean", Description = "Only let the editor choose images from media.")] - public bool OnlyImages { get; set; } - - [PreValueField("disableFolderSelect", "Disable folder select", "boolean", Description = "Do not allow folders to be picked.")] - public bool DisableFolderSelect { get; set; } - - [PreValueField("startNodeId", "Start node", "mediapicker")] - public int StartNodeId { get; set; } - } - } + } } \ No newline at end of file From 8d598cd37c6c9712642f581be133a733468f45d6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Jan 2017 20:40:45 +1100 Subject: [PATCH 007/105] Gets EntityController working with UDIs, INT and GUID for GetByIds --- src/Umbraco.Core/Udi.cs | 2 + src/Umbraco.Core/UdiTypeConverter.cs | 37 +++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Web/Editors/EntityController.cs | 75 ++++++++++++++++++ .../Editors/EntityControllerActionSelector.cs | 68 ----------------- .../EntityControllerConfigurationAttribute.cs | 15 +--- .../Editors/FromJsonPathAttribute.cs | 76 +++++++++++++++++++ .../ParameterSwapControllerActionSelector.cs | 72 +++++++++++++++--- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 9 files changed, 254 insertions(+), 94 deletions(-) create mode 100644 src/Umbraco.Core/UdiTypeConverter.cs delete mode 100644 src/Umbraco.Web/Editors/EntityControllerActionSelector.cs create mode 100644 src/Umbraco.Web/Editors/FromJsonPathAttribute.cs diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index 6267af67dc..3161b4100e 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Reflection; using Umbraco.Core.Deploy; @@ -11,6 +12,7 @@ namespace Umbraco.Core /// Represents an entity identifier. /// /// An Udi can be fully qualified or "closed" eg umb://document/{guid} or "open" eg umb://document. + [TypeConverter(typeof(UdiTypeConverter))] public abstract class Udi : IComparable { private static readonly Lazy> UdiTypes; diff --git a/src/Umbraco.Core/UdiTypeConverter.cs b/src/Umbraco.Core/UdiTypeConverter.cs new file mode 100644 index 0000000000..110b899454 --- /dev/null +++ b/src/Umbraco.Core/UdiTypeConverter.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Umbraco.Core +{ + /// + /// A custom type converter for UDI + /// + /// + /// Primarily this is used so that WebApi can auto-bind a string parameter to a UDI instance + /// + internal class UdiTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + { + return true; + } + return base.CanConvertFrom(context, sourceType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string) + { + Udi udi; + if (Udi.TryParse((string)value, out udi)) + { + return udi; + } + } + return base.ConvertFrom(context, culture, value); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 05854890f7..830fc9a7c5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1410,6 +1410,7 @@ + diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 104c451273..6cdedfd352 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.Net; using System.Text; @@ -29,6 +30,7 @@ using Examine.SearchCriteria; using Umbraco.Web.Dynamics; using umbraco; using System.Text.RegularExpressions; +using System.Web.Http.Controllers; using Umbraco.Core.Xml; namespace Umbraco.Web.Editors @@ -43,6 +45,20 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class EntityController : UmbracoAuthorizedJsonController { + + /// + /// Configures this controller with a custom action selector + /// + private class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + //we are not also including the Udi[] overload because that is HttpPost only so there won't be any ambiguity + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]) ))); + } + } + /// /// Returns an Umbraco alias given a string /// @@ -241,6 +257,13 @@ namespace Umbraco.Web.Editors return GetResultForId(id, type); } + /// + /// Get entities by integer ids + /// + /// + /// + /// + [HttpGet] public IEnumerable GetByIds([FromUri]int[] ids, UmbracoEntityTypes type) { if (ids == null) @@ -250,6 +273,58 @@ namespace Umbraco.Web.Editors return GetResultForIds(ids, type); } + /// + /// Get entities by GUID ids + /// + /// + /// + /// + [HttpGet] + public IEnumerable GetByIds([FromUri]Guid[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForKeys(ids, type); + } + + /// + /// Get entities by string ids - will try to convert to the correct id type (int, guid, udi) + /// + /// + /// + /// + /// + /// We only allow for POST because there could be quite a lot of Ids + /// + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromUri]UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + if (ids.Length == 0) + { + return Enumerable.Empty(); + } + + //all udi types will need to be the same in this list so we'll determine by the first + //currently we only support GuidIdi for this method + + var guidUdi = ids[0] as GuidUdi; + if (guidUdi != null) + { + return GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid), type); + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [Obsolete("Use GetyByIds instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetByKeys([FromUri]Guid[] ids, UmbracoEntityTypes type) { if (ids == null) diff --git a/src/Umbraco.Web/Editors/EntityControllerActionSelector.cs b/src/Umbraco.Web/Editors/EntityControllerActionSelector.cs deleted file mode 100644 index 9cfab1bdcd..0000000000 --- a/src/Umbraco.Web/Editors/EntityControllerActionSelector.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Web; -using System.Web.Http.Controllers; -using Umbraco.Core; - -namespace Umbraco.Web.Editors -{ - /// - /// This allows for calling GetById/GetByIds with a GUID... so it will automatically route to GetByKey/GetByKeys - /// - internal class EntityControllerActionSelector : ApiControllerActionSelector - { - - public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext) - { - if (controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Path).InvariantEndsWith("GetById")) - { - var id = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get("id"); - - if (id != null) - { - Guid parsed; - if (Guid.TryParse(id, out parsed)) - { - var controllerType = controllerContext.Controller.GetType(); - var method = controllerType.GetMethod("GetByKey"); - if (method != null) - { - return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, method); - } - } - } - } - - if (controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Path).InvariantEndsWith("GetByIds")) - { - var ids = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).GetValues("ids"); - - if (ids != null) - { - var allmatched = true; - foreach (var id in ids) - { - Guid parsed; - if (Guid.TryParse(id, out parsed) == false) - { - allmatched = false; - } - } - if (allmatched) - { - var controllerType = controllerContext.Controller.GetType(); - var method = controllerType.GetMethod("GetByKeys"); - if (method != null) - { - return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, method); - } - } - } - } - - - - return base.SelectAction(controllerContext); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs b/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs index 7ef4ef206a..571b29ed01 100644 --- a/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs +++ b/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs @@ -4,18 +4,5 @@ using Umbraco.Web.WebApi; namespace Umbraco.Web.Editors { - /// - /// This get's applied to the EntityController in order to have a custom IHttpActionSelector assigned to it - /// - /// - /// NOTE: It is SOOOO important to remember that you cannot just assign this in the 'initialize' method of a webapi - /// controller as it will assign it GLOBALLY which is what you def do not want to do. - /// - internal class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new EntityControllerActionSelector()); - } - } + } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs b/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs new file mode 100644 index 0000000000..3cadd2b6ca --- /dev/null +++ b/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs @@ -0,0 +1,76 @@ +using System.Net.Http; +using System.Web.Http.Controllers; +using System.Web.Http.ModelBinding; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Umbraco.Web.Editors +{ + /// + /// Used to bind a value from an inner json property + /// + /// + /// An example would be if you had json like: + /// { ids: [1,2,3,4] } + /// + /// And you had an action like: GetByIds(int[] ids, UmbracoEntityTypes type) + /// + /// The ids array will not bind because the object being sent up is an object and not an array so the + /// normal json formatter will not figure this out. + /// + /// This would also let you bind sub levels of the JSON being sent up too if you wanted with any jsonpath + /// + internal class FromJsonPathAttribute : ModelBinderAttribute + { + private readonly string _jsonPath; + + public FromJsonPathAttribute() + { + } + + public FromJsonPathAttribute(string jsonPath) : base(typeof(JsonPathBinder)) + { + _jsonPath = jsonPath; + } + + public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter) + { + var config = parameter.Configuration; + var binder = new JsonPathBinder(_jsonPath); + var valueProviderFactories = GetValueProviderFactories(config); + + return new ModelBinderParameterBinding(parameter, binder, valueProviderFactories); + } + + private class JsonPathBinder : IModelBinder + { + private readonly string _jsonPath; + + public JsonPathBinder(string jsonPath) + { + _jsonPath = jsonPath; + } + + public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) + { + var requestContent = new HttpMessageContent(actionContext.Request); + var strJson = requestContent.HttpRequestMessage.Content.ReadAsStringAsync().Result; + var json = JsonConvert.DeserializeObject(strJson); + + //if no explicit json path then use the model name + var match = json.SelectToken(_jsonPath ?? bindingContext.ModelName); + + if (match == null) + { + return false; + } + + bindingContext.Model = match.ToObject(bindingContext.ModelType); + + return true; + } + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs index 7f17fb9f8b..75916f6272 100644 --- a/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs +++ b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs @@ -1,7 +1,15 @@ using System; +using System.Collections; using System.Linq; +using System.Net.Http; +using System.Net.Http.Formatting; using System.Web; +using System.Web.Http; using System.Web.Http.Controllers; +using System.Web.Http.Validation; +using System.Web.Http.ValueProviders; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core; namespace Umbraco.Web.Editors @@ -12,6 +20,8 @@ namespace Umbraco.Web.Editors /// /// As an example, lets say we have 2 methods: GetChildren(int id) and GetChildren(Guid id), by default Web Api won't allow this since /// it won't know what to select, but if this Tuple is passed in new Tuple{string, string}("GetChildren", "id") + /// + /// This supports POST values too however only for JSON values /// internal class ParameterSwapControllerActionSelector : ApiControllerActionSelector { @@ -25,28 +35,68 @@ namespace Umbraco.Web.Editors { _actions = actions; } + public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext) { var found = _actions.FirstOrDefault(x => controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Path).InvariantEndsWith(x.ActionName)); if (found != null) { - var id = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get(found.ParamName); - - if (id != null) + if (controllerContext.Request.Method == HttpMethod.Get) { - var idTypes = found.SupportedTypes; + var requestParam = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get(found.ParamName); - foreach (var idType in idTypes) + if (requestParam != null) { - var converted = id.TryConvertTo(idType); - if (converted) + var paramTypes = found.SupportedTypes; + + foreach (var paramType in paramTypes) { - var method = MatchByType(idType, controllerContext, found); - if (method != null) - return method; + var converted = requestParam.TryConvertTo(paramType); + if (converted) + { + var method = MatchByType(paramType, controllerContext, found); + if (method != null) + return method; + } } - } + } + } + else if (controllerContext.Request.Method == HttpMethod.Post) + { + + var requestContent = new HttpMessageContent(controllerContext.Request); + var strJson = requestContent.HttpRequestMessage.Content.ReadAsStringAsync().Result; + var json = JsonConvert.DeserializeObject(strJson); + + var requestParam = json[found.ParamName]; + + if (requestParam != null) + { + var paramTypes = found.SupportedTypes; + + foreach (var paramType in paramTypes) + { + try + { + var converted = requestParam.ToObject(paramType); + if (converted != null) + { + var method = MatchByType(paramType, controllerContext, found); + if (method != null) + return method; + } + } + catch (JsonReaderException) + { + //can't convert + } + catch (JsonSerializationException) + { + //can't convert + } + } + } } } return base.SelectAction(controllerContext); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8800dc45f5..ca602cdbd8 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -285,6 +285,7 @@ + @@ -456,7 +457,6 @@ - From ed019a90e2b43208eb85a8e7b84fdd78f3c3116f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 30 Jan 2017 14:25:10 +0100 Subject: [PATCH 008/105] start on mini list view for pickers --- .../src/less/components/umb-table.less | 13 ++ .../treepicker/treepicker.controller.js | 100 ++++++++++++- .../overlays/treepicker/treepicker.html | 141 ++++++++++++++---- 3 files changed, 216 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index bc8e19cf2b..7f4afc3de7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -256,3 +256,16 @@ input.umb-table__input { flex: 1 1 25%; max-width: 25%; } + +.umb-table--condensed { + + .umb-table-cell:first-of-type:not(.not-fixed) { + padding-top: 10px; + padding-bottom: 10px; + } + + .umb-table-body__icon { + font-size: 20px; + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index e69b5ee3ce..8bcf427d7e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -1,6 +1,6 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", - function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { + function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource) { var tree = null; var dialogOptions = $scope.model; @@ -106,13 +106,16 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", //iterate children _.each(args.children, function (child) { + console.log("child", child); + //check if any of the items are list views, if so we need to add some custom // children: A node to activate the search, any nodes that have already been // selected in the search if (child.metaData.isContainer) { child.hasChildren = true; child.children = [ - { + { + id: child.id, level: child.level + 1, hasChildren: false, parent: function () { @@ -177,13 +180,26 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", if (args.node.metaData.listViewNode) { //check if list view 'search' node was selected - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + //alert("list view"); + + $scope.showMiniListView = true; + + console.log(args); + + $scope.pagination = { + pageSize: 10, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + + getPagedChildren(args.node); + + $scope.miniListView.nodeName = args.node.metaData.listViewNode.name; + $scope.miniListView.nodeId = args.node.metaData.listViewNode.id; - //add transition classes - var listViewNode = args.node.parent(); - listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); } else if (args.node.metaData.isSearchResult) { //check if the item selected was a search result from a list view @@ -504,4 +520,72 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); }); + + /* Mini List View */ + $scope.miniListView = {}; + + $scope.nextPage = function(pageNumber) { + $scope.pagination.pageNumber = pageNumber; + getPagedChildren($scope.miniListView.node); + }; + + $scope.prevPage = function(pageNumber) { + $scope.pagination.pageNumber = pageNumber; + getPagedChildren($scope.miniListView.node); + }; + + $scope.goToPage = function(pageNumber) { + $scope.pagination.pageNumber = pageNumber; + getPagedChildren($scope.miniListView.node); + }; + + $scope.selectListViewItem = function(item) { + select(item.name, item.id); + //toggle checked state + item.selected = item.selected === true ? false : true; + }; + + $scope.exitMiniListView = function() { + console.log($scope.miniListView); + $scope.showMiniListView = false; + }; + + $scope.searchMiniListView = function() { + searchMiniListView(); + }; + + function getPagedChildren(node) { + + // start load indicator + $scope.miniListView.loading = true; + + contentResource.getChildren(node.id, $scope.pagination) + .then(function (data) { + + // update children + $scope.miniListView.node = node; + $scope.miniListView.children = data.items; + + // update pagination + $scope.pagination.totalItems = data.totalItems; + $scope.pagination.totalPages = data.totalPages; + + // stop load indicator + $scope.miniListView.loading = false; + + }); + + } + + var searchMiniListView = _.debounce(function () { + + $scope.$apply(function () { + if ($scope.pagination.filter !== null && $scope.pagination.filter !== undefined) { + $scope.pagination.pageNumber = 1; + getPagedChildren($scope.miniListView.node, $scope.pagination); + } + }); + + }, 500); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index b009b4d45f..fe2c479e4e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -1,35 +1,116 @@
-
- - -
+
+
+ + +
- - + + -
- - -
+
+ + +
+
-
+
+ +
+
+ + + +

{{ miniListView.nodeName }}

+
+
+ +
+ + +
+
+
+ +
+ Name +
+
+ +
+
+
+ + +
+ + +
+
+ + +
+
{{ child.name }}
+
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + \ No newline at end of file From 4336f5201d3f5efd3cd09a4d608f892e67fb50d5 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 30 Jan 2017 22:34:04 +0100 Subject: [PATCH 009/105] show expand arrow for list views --- .../directives/components/tree/umbtreeitem.directive.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js index f4fe0db4e6..e519bb78b0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js @@ -74,11 +74,14 @@ angular.module("umbraco.directives") //toggle visibility of last 'ins' depending on children //visibility still ensure the space is "reserved", so both nodes with and without children are aligned. - if (!node.hasChildren) { - element.find("ins").last().css("visibility", "hidden"); + + console.log(node); + + if (node.hasChildren || node.metaData.isContainer) { + element.find("ins").last().css("visibility", "visible"); } else { - element.find("ins").last().css("visibility", "visible"); + element.find("ins").last().css("visibility", "hidden"); } var icon = element.find("i:first"); From bf797bccc7ea09966cd2ad6c31e39206d2a12d1b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 30 Jan 2017 22:35:19 +0100 Subject: [PATCH 010/105] open list view on expand --- .../treepicker/treepicker.controller.js | 44 +++++++++++++++++-- .../overlays/treepicker/treepicker.html | 15 ++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index 8bcf427d7e..a1ee1f031f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -1,6 +1,6 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", - function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource) { + function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) { var tree = null; var dialogOptions = $scope.model; @@ -101,6 +101,13 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", } function nodeExpandedHandler(ev, args) { + + console.log("args", args); + + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + if (angular.isArray(args.children)) { //iterate children @@ -111,6 +118,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", //check if any of the items are list views, if so we need to add some custom // children: A node to activate the search, any nodes that have already been // selected in the search + /* if (child.metaData.isContainer) { child.hasChildren = true; child.children = [ @@ -151,6 +159,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", }); }); } + */ //now we need to look in the already selected search results and // toggle the check boxes for those ones that are listed @@ -554,16 +563,43 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", searchMiniListView(); }; + function openMiniListView(node) { + + $scope.showMiniListView = true; + + $scope.pagination = { + pageSize: 10, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + + getPagedChildren(node); + + } + function getPagedChildren(node) { // start load indicator + $scope.miniListView.node = node; $scope.miniListView.loading = true; + + var promise = ""; - contentResource.getChildren(node.id, $scope.pagination) + if(node.nodeType === "content") { + promise = contentResource.getChildren(node.id, $scope.pagination); + } else if( node.nodeType === "member") { + promise = memberResource.getPagedResults(node.id, $scope.pagination); + } else if(node.nodeType === "media") { + promise = mediaResource.getChildren(node.id, $scope.pagination); + } + + promise .then(function (data) { - + // update children - $scope.miniListView.node = node; $scope.miniListView.children = data.items; // update pagination diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index fe2c479e4e..a37a63bb0f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -1,6 +1,7 @@
+
-
+
+
-
+
- - + + Back -

{{ miniListView.nodeName }}

+

+ + {{ miniListView.node.name }} +

From f192f241cfef14ca19f81ef7b772db8e98b7773f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 31 Jan 2017 13:48:47 +1100 Subject: [PATCH 011/105] U4-9450 Paged data queries return all property data in the entire database, not just for the paged subset --- .../Repositories/ContentRepository.cs | 9 +++++---- .../Persistence/Repositories/MediaRepository.cs | 10 +++++----- .../Repositories/MemberRepository.cs | 10 +++++----- .../Repositories/VersionableRepositoryBase.cs | 17 +++++++++++------ 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index b02f5304e6..f7b88ef46b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -859,7 +859,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsDocument", "nodeId"), - (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField, + (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds, isPaged:true), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -899,9 +899,10 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", /// /// The Id SQL without the outer join to just return all document ids - used to process the properties for the content item /// + /// True if this is a paged query /// /// - private IEnumerable ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = false) + private IEnumerable ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = false, bool isPaged = false) { // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sqlFull); @@ -967,7 +968,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", .ToDictionary(x => x.Id, x => x); // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sqlIds, defs); + var propertyData = GetPropertyCollection(sqlIds, defs, isPaged); // assign var dtoIndex = 0; @@ -1014,7 +1015,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", var docDef = new DocumentDefinition(dto.NodeId, versionId, content.UpdateDate, content.CreateDate, contentType); - var properties = GetPropertyCollection(docSql, new[] { docDef }); + var properties = GetPropertyCollection(docSql, new[] { docDef }, false); content.Properties = properties[dto.NodeId]; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 8cfb037c6c..8bcfc3558b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -146,7 +146,7 @@ namespace Umbraco.Core.Persistence.Repositories return ProcessQuery(sql, true); } - private IEnumerable ProcessQuery(Sql sql, bool withCache = false) + private IEnumerable ProcessQuery(Sql sql, bool withCache = false, bool isPaged = false) { // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sql); @@ -200,7 +200,7 @@ namespace Umbraco.Core.Persistence.Repositories } // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sql, defs); + var propertyData = GetPropertyCollection(sql, defs, isPaged); // assign var dtoIndex = 0; @@ -507,7 +507,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsContentVersion", "contentId"), - (sqlFull, sqlIds) => ProcessQuery(sqlFull), orderBy, orderDirection, orderBySystemField, + (sqlFull, sqlIds) => ProcessQuery(sqlFull, isPaged:true), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -515,7 +515,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Private method to create a media object from a ContentDto /// - /// + /// /// /// /// @@ -527,7 +527,7 @@ namespace Umbraco.Core.Persistence.Repositories var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType); - var properties = GetPropertyCollection(docSql, new[] { docDef }); + var properties = GetPropertyCollection(docSql, new[] { docDef }, false); media.Properties = properties[dto.NodeId]; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 8b3bf4a471..f83fdcef51 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -449,7 +449,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId); var media = factory.BuildEntity(dto); - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }); + var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }, false); media.Properties = properties[dto.NodeId]; @@ -624,7 +624,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsMember", "nodeId"), - (sqlFull, sqlIds) => ProcessQuery(sqlFull), orderBy, orderDirection, orderBySystemField, + (sqlFull, sqlIds) => ProcessQuery(sqlFull, isPaged:true), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -664,7 +664,7 @@ namespace Umbraco.Core.Persistence.Repositories return base.GetEntityPropertyNameForOrderBy(orderBy); } - private IEnumerable ProcessQuery(Sql sql, bool withCache = false) + private IEnumerable ProcessQuery(Sql sql, bool withCache = false, bool isPaged = false) { // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sql); @@ -704,7 +704,7 @@ namespace Umbraco.Core.Persistence.Repositories } // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sql, defs); + var propertyData = GetPropertyCollection(sql, defs, isPaged); // assign var dtoIndex = 0; @@ -741,7 +741,7 @@ namespace Umbraco.Core.Persistence.Repositories var docDef = new DocumentDefinition(dto.ContentVersionDto.NodeId, versionId, member.UpdateDate, member.CreateDate, memberType); - var properties = GetPropertyCollection(docSql, new[] { docDef }); + var properties = GetPropertyCollection(docSql, new[] { docDef }, false); member.Properties = properties[dto.ContentVersionDto.NodeId]; diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 31fc4a3f68..26c53a091f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -465,7 +465,7 @@ namespace Umbraco.Core.Persistence.Repositories // the pageResult, then the GetAll will actually return ALL records in the db. if (pagedResult.Items.Any()) { - //Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query + //Create the inner paged query that was used above to get the paged result, we'll use that as the inner sub query var args = sqlNodeIdsWithSort.Arguments; string sqlStringCount, sqlStringPage; Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); @@ -486,7 +486,10 @@ namespace Umbraco.Core.Persistence.Repositories GetFilteredSqlForPagedResults(fullQueryWithPagedInnerJoin, defaultFilter), orderDirection, orderBy, orderBySystemField, nodeIdSelect); - return processQuery(fullQuery, sqlNodeIdsWithSort); + //get the id query in the paged format + var idPagedQuery = new Sql(sqlStringPage, args); + + return processQuery(fullQuery, idPagedQuery); } else { @@ -498,15 +501,17 @@ namespace Umbraco.Core.Persistence.Repositories protected IDictionary GetPropertyCollection( Sql docSql, - IReadOnlyCollection documentDefs) + IReadOnlyCollection documentDefs, + bool isPaged) { if (documentDefs.Count == 0) return new Dictionary(); //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use // the statement to go get the property data for all of the items by using an inner join var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal)); - //now remove everything from an Orderby clause and beyond - if (parsedOriginalSql.InvariantContains("ORDER BY ")) + + //now remove everything from an Orderby clause and beyond if this is unpaged data + if (isPaged == false && parsedOriginalSql.InvariantContains("ORDER BY ")) { parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); } @@ -524,7 +529,7 @@ WHERE EXISTS( INNER JOIN cmsPropertyType ON b.datatypeNodeId = cmsPropertyType.dataTypeId INNER JOIN - (" + string.Format(parsedOriginalSql, "DISTINCT cmsContent.contentType") + @") as docData + (" + string.Format(parsedOriginalSql, "cmsContent.contentType") + @") as docData ON cmsPropertyType.contentTypeId = docData.contentType WHERE a.id = b.id)", docSql.Arguments); From 2f67a485aaa2712932af5bba321c93f7508a27dd Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 31 Jan 2017 14:52:48 +1100 Subject: [PATCH 012/105] Ensures that the sqlIds query is used for paging when processing members/media --- .../Repositories/MediaRepository.cs | 29 +++++++++++----- .../Repositories/MemberRepository.cs | 33 +++++++++++++------ 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 8bcfc3558b..c4957ca9fe 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); } - return ProcessQuery(sql); + return ProcessQuery(sql, sql); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -78,7 +78,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate() .OrderBy(x => x.SortOrder, SqlSyntax); - return ProcessQuery(sql); + return ProcessQuery(sql, sql); } #endregion @@ -143,13 +143,25 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) .OrderByDescending(x => x.VersionDate, SqlSyntax); - return ProcessQuery(sql, true); + return ProcessQuery(sql, sql, true); } - private IEnumerable ProcessQuery(Sql sql, bool withCache = false, bool isPaged = false) + /// + /// This is the underlying method that processes most queries for this repository + /// + /// + /// The full SQL to select all media data + /// + /// + /// The Id SQL to just return all media ids - used to process the properties for the media item + /// + /// True if this is a paged query + /// + /// + private IEnumerable ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = false, bool isPaged = false) { // fetch returns a list so it's ok to iterate it in this method - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sqlFull); var content = new IMedia[dtos.Count]; var defs = new List(); @@ -200,7 +212,7 @@ namespace Umbraco.Core.Persistence.Repositories } // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sql, defs, isPaged); + var propertyData = GetPropertyCollection(sqlIds, defs, isPaged); // assign var dtoIndex = 0; @@ -257,7 +269,8 @@ namespace Umbraco.Core.Persistence.Repositories query = query .Where(x => x.NodeId > baseId, SqlSyntax) .OrderBy(x => x.NodeId, SqlSyntax); - var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) + var sql = SqlSyntax.SelectTop(query, groupSize); + var xmlItems = ProcessQuery(sql, sql) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) .ToList(); @@ -507,7 +520,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsContentVersion", "contentId"), - (sqlFull, sqlIds) => ProcessQuery(sqlFull, isPaged:true), orderBy, orderDirection, orderBySystemField, + (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds, isPaged: true), orderBy, orderDirection, orderBySystemField, filterCallback); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index f83fdcef51..0cb5be4ce0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); } - return ProcessQuery(sql); + return ProcessQuery(sql, sql); } @@ -90,7 +90,7 @@ namespace Umbraco.Core.Persistence.Repositories baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)) .OrderBy(x => x.SortOrder); - return ProcessQuery(baseQuery); + return ProcessQuery(baseQuery, baseQuery); } else { @@ -98,7 +98,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate() .OrderBy(x => x.SortOrder); - return ProcessQuery(sql); + return ProcessQuery(sql, sql); } } @@ -385,7 +385,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) .OrderByDescending(x => x.VersionDate, SqlSyntax); - return ProcessQuery(sql, true); + return ProcessQuery(sql, sql, true); } public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) @@ -408,7 +408,8 @@ namespace Umbraco.Core.Persistence.Repositories query = query .Where(x => x.NodeId > baseId) .OrderBy(x => x.NodeId, SqlSyntax); - var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) + var sql = SqlSyntax.SelectTop(query, groupSize); + var xmlItems = ProcessQuery(sql, sql) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) .ToList(); @@ -540,7 +541,7 @@ namespace Umbraco.Core.Persistence.Repositories .OrderByDescending(x => x.VersionDate) .OrderBy(x => x.SortOrder); - return ProcessQuery(sql); + return ProcessQuery(sql, sql); } @@ -624,7 +625,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsMember", "nodeId"), - (sqlFull, sqlIds) => ProcessQuery(sqlFull, isPaged:true), orderBy, orderDirection, orderBySystemField, + (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds, isPaged:true), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -664,10 +665,22 @@ namespace Umbraco.Core.Persistence.Repositories return base.GetEntityPropertyNameForOrderBy(orderBy); } - private IEnumerable ProcessQuery(Sql sql, bool withCache = false, bool isPaged = false) + /// + /// This is the underlying method that processes most queries for this repository + /// + /// + /// The full SQL to select all member data + /// + /// + /// The Id SQL to just return all member ids - used to process the properties for the member item + /// + /// True if this is a paged query + /// + /// + private IEnumerable ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = false, bool isPaged = false) { // fetch returns a list so it's ok to iterate it in this method - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sqlFull); var content = new IMember[dtos.Count]; var defs = new List(); @@ -704,7 +717,7 @@ namespace Umbraco.Core.Persistence.Repositories } // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sql, defs, isPaged); + var propertyData = GetPropertyCollection(sqlIds, defs, isPaged); // assign var dtoIndex = 0; From 86b2dac231e771c1e0639ae2d2d9fff0d7121c35 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 31 Jan 2017 17:20:44 +1100 Subject: [PATCH 013/105] Needed to change the 2nd query to use a new PagingSqlQuery object due to the way that < SQL 2012 formats it's paging query which is just not compatible with how we were parsing the queries for properties... i don't actually think it was working for a long tme. --- .../Repositories/ContentRepository.cs | 21 ++- .../Repositories/MediaRepository.cs | 19 +-- .../Repositories/MemberRepository.cs | 25 ++- .../Repositories/VersionableRepositoryBase.cs | 159 ++++++++++++++---- 4 files changed, 156 insertions(+), 68 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index f7b88ef46b..aedf701524 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -84,7 +84,7 @@ namespace Umbraco.Core.Persistence.Repositories var sqlBaseFull = GetBaseQuery(BaseQueryType.Full); var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids); - return ProcessQuery(translate(sqlBaseFull), translate(sqlBaseIds)); + return ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds))); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -103,7 +103,7 @@ namespace Umbraco.Core.Persistence.Repositories var translatorFull = new SqlTranslator(sqlBaseFull, query); var translatorIds = new SqlTranslator(sqlBaseIds, query); - return ProcessQuery(translate(translatorFull), translate(translatorIds)); + return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds))); } #endregion @@ -225,7 +225,7 @@ namespace Umbraco.Core.Persistence.Repositories var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.Full)); var sqlIds = translate(baseId, GetBaseQuery(BaseQueryType.Ids)); - var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), SqlSyntax.SelectTop(sqlIds, groupSize)) + var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), new PagingSqlQuery(SqlSyntax.SelectTop(sqlIds, groupSize))) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) .ToList(); @@ -260,7 +260,7 @@ namespace Umbraco.Core.Persistence.Repositories var sqlFull = translate(GetBaseQuery(BaseQueryType.Full)); var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids)); - return ProcessQuery(sqlFull, sqlIds, true); + return ProcessQuery(sqlFull, new PagingSqlQuery(sqlIds), true); } public override IContent GetByVersion(Guid versionId) @@ -679,7 +679,7 @@ namespace Umbraco.Core.Persistence.Repositories var sqlIds = GetBaseQuery(BaseQueryType.Ids); var translatorIds = new SqlTranslator(sqlIds, query); - return ProcessQuery(translate(translatorFull), translate(translatorIds), true); + return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true); } /// @@ -859,7 +859,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsDocument", "nodeId"), - (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds, isPaged:true), orderBy, orderDirection, orderBySystemField, + (sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -896,13 +896,12 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", /// /// The full SQL with the outer join to return all data required to create an IContent /// - /// + /// /// The Id SQL without the outer join to just return all document ids - used to process the properties for the content item /// - /// True if this is a paged query /// /// - private IEnumerable ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = false, bool isPaged = false) + private IEnumerable ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false) { // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sqlFull); @@ -968,7 +967,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", .ToDictionary(x => x.Id, x => x); // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sqlIds, defs, isPaged); + var propertyData = GetPropertyCollection(pagingSqlQuery, defs); // assign var dtoIndex = 0; @@ -1015,7 +1014,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", var docDef = new DocumentDefinition(dto.NodeId, versionId, content.UpdateDate, content.CreateDate, contentType); - var properties = GetPropertyCollection(docSql, new[] { docDef }, false); + var properties = GetPropertyCollection(docSql, new[] { docDef }); content.Properties = properties[dto.NodeId]; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index c4957ca9fe..56b5e2ed9d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); } - return ProcessQuery(sql, sql); + return ProcessQuery(sql, new PagingSqlQuery(sql)); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -78,7 +78,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate() .OrderBy(x => x.SortOrder, SqlSyntax); - return ProcessQuery(sql, sql); + return ProcessQuery(sql, new PagingSqlQuery(sql)); } #endregion @@ -143,7 +143,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) .OrderByDescending(x => x.VersionDate, SqlSyntax); - return ProcessQuery(sql, sql, true); + return ProcessQuery(sql, new PagingSqlQuery(sql), true); } /// @@ -152,13 +152,12 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The full SQL to select all media data /// - /// + /// /// The Id SQL to just return all media ids - used to process the properties for the media item /// - /// True if this is a paged query /// /// - private IEnumerable ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = false, bool isPaged = false) + private IEnumerable ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false) { // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sqlFull); @@ -212,7 +211,7 @@ namespace Umbraco.Core.Persistence.Repositories } // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sqlIds, defs, isPaged); + var propertyData = GetPropertyCollection(pagingSqlQuery, defs); // assign var dtoIndex = 0; @@ -270,7 +269,7 @@ namespace Umbraco.Core.Persistence.Repositories .Where(x => x.NodeId > baseId, SqlSyntax) .OrderBy(x => x.NodeId, SqlSyntax); var sql = SqlSyntax.SelectTop(query, groupSize); - var xmlItems = ProcessQuery(sql, sql) + var xmlItems = ProcessQuery(sql, new PagingSqlQuery(sql)) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) .ToList(); @@ -520,7 +519,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsContentVersion", "contentId"), - (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds, isPaged: true), orderBy, orderDirection, orderBySystemField, + (sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -540,7 +539,7 @@ namespace Umbraco.Core.Persistence.Repositories var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType); - var properties = GetPropertyCollection(docSql, new[] { docDef }, false); + var properties = GetPropertyCollection(new PagingSqlQuery(docSql), new[] { docDef }); media.Properties = properties[dto.NodeId]; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 0cb5be4ce0..9fdee650b3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); } - return ProcessQuery(sql, sql); + return ProcessQuery(sql, new PagingSqlQuery(sql)); } @@ -90,7 +90,7 @@ namespace Umbraco.Core.Persistence.Repositories baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)) .OrderBy(x => x.SortOrder); - return ProcessQuery(baseQuery, baseQuery); + return ProcessQuery(baseQuery, new PagingSqlQuery(baseQuery)); } else { @@ -98,7 +98,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate() .OrderBy(x => x.SortOrder); - return ProcessQuery(sql, sql); + return ProcessQuery(sql, new PagingSqlQuery(sql)); } } @@ -385,7 +385,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) .OrderByDescending(x => x.VersionDate, SqlSyntax); - return ProcessQuery(sql, sql, true); + return ProcessQuery(sql, new PagingSqlQuery(sql), true); } public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) @@ -409,7 +409,7 @@ namespace Umbraco.Core.Persistence.Repositories .Where(x => x.NodeId > baseId) .OrderBy(x => x.NodeId, SqlSyntax); var sql = SqlSyntax.SelectTop(query, groupSize); - var xmlItems = ProcessQuery(sql, sql) + var xmlItems = ProcessQuery(sql, new PagingSqlQuery(sql)) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) .ToList(); @@ -450,7 +450,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId); var media = factory.BuildEntity(dto); - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }, false); + var properties = GetPropertyCollection(new PagingSqlQuery(sql), new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }); media.Properties = properties[dto.NodeId]; @@ -541,7 +541,7 @@ namespace Umbraco.Core.Persistence.Repositories .OrderByDescending(x => x.VersionDate) .OrderBy(x => x.SortOrder); - return ProcessQuery(sql, sql); + return ProcessQuery(sql, new PagingSqlQuery(sql)); } @@ -625,7 +625,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsMember", "nodeId"), - (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds, isPaged:true), orderBy, orderDirection, orderBySystemField, + (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -671,13 +671,12 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The full SQL to select all member data /// - /// + /// /// The Id SQL to just return all member ids - used to process the properties for the member item /// - /// True if this is a paged query /// /// - private IEnumerable ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = false, bool isPaged = false) + private IEnumerable ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false) { // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sqlFull); @@ -717,7 +716,7 @@ namespace Umbraco.Core.Persistence.Repositories } // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sqlIds, defs, isPaged); + var propertyData = GetPropertyCollection(pagingSqlQuery, defs); // assign var dtoIndex = 0; @@ -754,7 +753,7 @@ namespace Umbraco.Core.Persistence.Repositories var docDef = new DocumentDefinition(dto.ContentVersionDto.NodeId, versionId, member.UpdateDate, member.CreateDate, memberType); - var properties = GetPropertyCollection(docSql, new[] { docDef }, false); + var properties = GetPropertyCollection(docSql, new[] { docDef }); member.Properties = properties[dto.ContentVersionDto.NodeId]; diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 26c53a091f..6b18fc70b4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -431,7 +431,7 @@ namespace Umbraco.Core.Persistence.Repositories /// orderBy protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Tuple nodeIdSelect, - Func> processQuery, + Func, IEnumerable> processQuery, string orderBy, Direction orderDirection, bool orderBySystemField, @@ -485,11 +485,8 @@ namespace Umbraco.Core.Persistence.Repositories var fullQuery = GetSortedSqlForPagedResults( GetFilteredSqlForPagedResults(fullQueryWithPagedInnerJoin, defaultFilter), orderDirection, orderBy, orderBySystemField, nodeIdSelect); - - //get the id query in the paged format - var idPagedQuery = new Sql(sqlStringPage, args); - - return processQuery(fullQuery, idPagedQuery); + + return processQuery(fullQuery, new PagingSqlQuery(Database, sqlNodeIdsWithSort, pageIndex, pageSize)); } else { @@ -499,20 +496,47 @@ namespace Umbraco.Core.Persistence.Repositories return result; } + /// + /// Gets the property collection for a non-paged query + /// + /// + /// + /// protected IDictionary GetPropertyCollection( - Sql docSql, - IReadOnlyCollection documentDefs, - bool isPaged) + Sql sql, + IReadOnlyCollection documentDefs) + { + return GetPropertyCollection(new PagingSqlQuery(sql), documentDefs); + } + + /// + /// Gets the property collection for a query + /// + /// + /// + /// + protected IDictionary GetPropertyCollection( + PagingSqlQuery pagingSqlQuery, + IReadOnlyCollection documentDefs) { if (documentDefs.Count == 0) return new Dictionary(); + //initialize to the query passed in + var docSql = pagingSqlQuery.PrePagedSql; + //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use // the statement to go get the property data for all of the items by using an inner join var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal)); - - //now remove everything from an Orderby clause and beyond if this is unpaged data - if (isPaged == false && parsedOriginalSql.InvariantContains("ORDER BY ")) + + if (pagingSqlQuery.HasPaging) { + //if this is a paged query, build the paged query with the custom column substitution, then re-assign + docSql = pagingSqlQuery.BuildPagedQuery("{0}"); + parsedOriginalSql = docSql.SQL; + } + else if (parsedOriginalSql.InvariantContains("ORDER BY ")) + { + //now remove everything from an Orderby clause and beyond if this is unpaged data parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); } @@ -652,28 +676,7 @@ ORDER BY contentNodeId, propertytypeid return result; } - - public class DocumentDefinition - { - /// - /// Initializes a new instance of the class. - /// - public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition) - { - Id = id; - Version = version; - VersionDate = versionDate; - CreateDate = createDate; - Composition = composition; - } - - public int Id { get; set; } - public Guid Version { get; set; } - public DateTime VersionDate { get; set; } - public DateTime CreateDate { get; set; } - public IContentTypeComposition Composition { get; set; } - } - + protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy) { // Translate the passed order by field (which were originally defined for in-memory object sorting @@ -769,5 +772,93 @@ ORDER BY contentNodeId, propertytypeid /// /// protected abstract Sql GetBaseQuery(BaseQueryType queryType); + + + internal class DocumentDefinition + { + /// + /// Initializes a new instance of the class. + /// + public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition) + { + Id = id; + Version = version; + VersionDate = versionDate; + CreateDate = createDate; + Composition = composition; + } + + public int Id { get; set; } + public Guid Version { get; set; } + public DateTime VersionDate { get; set; } + public DateTime CreateDate { get; set; } + public IContentTypeComposition Composition { get; set; } + } + + /// + /// An object representing a query that may contain paging information + /// + internal class PagingSqlQuery + { + public Sql PrePagedSql { get; private set; } + + public PagingSqlQuery(Sql prePagedSql) + { + PrePagedSql = prePagedSql; + } + + public virtual bool HasPaging + { + get { return false; } + } + + public virtual Sql BuildPagedQuery(string selectColumns) + { + throw new InvalidOperationException("This query has no paging information"); + } + } + + /// + /// An object representing a query that contains paging information + /// + /// + internal class PagingSqlQuery : PagingSqlQuery + { + private readonly Database _db; + private readonly long _pageIndex; + private readonly int _pageSize; + + public PagingSqlQuery(Database db, Sql prePagedSql, long pageIndex, int pageSize) : base(prePagedSql) + { + _db = db; + _pageIndex = pageIndex; + _pageSize = pageSize; + } + + public override bool HasPaging + { + get { return _pageSize > 0; } + } + + /// + /// Creates a paged query based on the original query and subtitutes the selectColumns specified + /// + /// + /// + public override Sql BuildPagedQuery(string selectColumns) + { + if (HasPaging == false) throw new InvalidOperationException("This query has no paging information"); + + var resultSql = string.Format("SELECT {0} {1}", selectColumns, PrePagedSql.SQL.Substring(PrePagedSql.SQL.IndexOf("FROM", StringComparison.Ordinal))); + + //this query is meant to be paged so we need to generate the paging syntax + //Create the inner paged query that was used above to get the paged result, we'll use that as the inner sub query + var args = PrePagedSql.Arguments; + string sqlStringCount, sqlStringPage; + _db.BuildPageQueries(_pageIndex * _pageSize, _pageSize, resultSql, ref args, out sqlStringCount, out sqlStringPage); + + return new Sql(sqlStringPage, args); + } + } } } \ No newline at end of file From e95cb14d4804a8fb8e5e04fb2b74a875a3fe212f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 31 Jan 2017 19:11:05 +1100 Subject: [PATCH 014/105] Gets FromJsonPath working for EntityController to have GetByIds with all types of Ids for both GET and POST --- src/Umbraco.Core/TypeExtensions.cs | 21 +++++++ .../src/common/resources/entity.resource.js | 19 ++---- src/Umbraco.Web/Editors/EntityController.cs | 19 ++++-- .../Editors/FromJsonPathAttribute.cs | 29 ++++++++- .../ParameterSwapControllerActionSelector.cs | 61 ++++++++++++------- 5 files changed, 108 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/TypeExtensions.cs b/src/Umbraco.Core/TypeExtensions.cs index 3208c1fbe8..76dc79c219 100644 --- a/src/Umbraco.Core/TypeExtensions.cs +++ b/src/Umbraco.Core/TypeExtensions.cs @@ -425,7 +425,28 @@ namespace Umbraco.Core assemblyName.FullName.StartsWith("App_Code.") ? "App_Code" : assemblyName.Name); } + /// + /// If the given is an array or some other collection + /// comprised of 0 or more instances of a "subtype", get that type + /// + /// the source type + /// + internal static Type GetEnumeratedType(this Type type) + { + if (typeof(IEnumerable).IsAssignableFrom(type) == false) + return null; + // provided by Array + var elType = type.GetElementType(); + if (null != elType) return elType; + + // otherwise provided by collection + var elTypes = type.GetGenericArguments(); + if (elTypes.Length > 0) return elTypes[0]; + + // otherwise is not an 'enumerated' type + return null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 5e0f5deada..3defb691aa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -166,24 +166,17 @@ function entityResource($q, $http, umbRequestHelper) { */ getByIds: function (ids, type) { - var query = ""; - _.each(ids, function(item) { - query += "ids=" + item + "&"; - }); - - // if ids array is empty we need a empty variable in the querystring otherwise the service returns a error - if (ids.length === 0) { - query += "ids=&"; - } - - query += "type=" + type; + var query = "type=" + type; return umbRequestHelper.resourcePromise( - $http.get( + $http.post( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetByIds", - query)), + query), + { + ids: ids + }), 'Failed to retrieve entity data for ids ' + ids); }, diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 6cdedfd352..40cec56a8e 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -55,7 +55,7 @@ namespace Umbraco.Web.Editors { //we are not also including the Udi[] overload because that is HttpPost only so there won't be any ambiguity controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]) ))); + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[]) ))); } } @@ -263,8 +263,12 @@ namespace Umbraco.Web.Editors /// /// /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// [HttpGet] - public IEnumerable GetByIds([FromUri]int[] ids, UmbracoEntityTypes type) + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) { if (ids == null) { @@ -279,8 +283,12 @@ namespace Umbraco.Web.Editors /// /// /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// [HttpGet] - public IEnumerable GetByIds([FromUri]Guid[] ids, UmbracoEntityTypes type) + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) { if (ids == null) { @@ -288,7 +296,7 @@ namespace Umbraco.Web.Editors } return GetResultForKeys(ids, type); } - + /// /// Get entities by string ids - will try to convert to the correct id type (int, guid, udi) /// @@ -296,8 +304,9 @@ namespace Umbraco.Web.Editors /// /// /// - /// We only allow for POST because there could be quite a lot of Ids + /// We allow for POST because there could be quite a lot of Ids /// + [HttpGet] [HttpPost] public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromUri]UmbracoEntityTypes type) { diff --git a/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs b/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs index 3cadd2b6ca..bb275f8fa2 100644 --- a/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs +++ b/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs @@ -1,8 +1,12 @@ +using System.Collections.Generic; using System.Net.Http; +using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; +using System.Web.Http.ValueProviders; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Core; namespace Umbraco.Web.Editors { @@ -23,6 +27,7 @@ namespace Umbraco.Web.Editors internal class FromJsonPathAttribute : ModelBinderAttribute { private readonly string _jsonPath; + private readonly FromUriAttribute _fromUriAttribute = new FromUriAttribute(); public FromJsonPathAttribute() { @@ -33,10 +38,17 @@ namespace Umbraco.Web.Editors _jsonPath = jsonPath; } + public override IEnumerable GetValueProviderFactories(HttpConfiguration configuration) + { + return _fromUriAttribute.GetValueProviderFactories(configuration); + } + public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter) { var config = parameter.Configuration; - var binder = new JsonPathBinder(_jsonPath); + //get the default binder, we'll use that if it's a GET or if the body is empty + var underlyingBinder = base.GetModelBinder(config, parameter.ParameterType); + var binder = new JsonPathBinder(underlyingBinder, _jsonPath); var valueProviderFactories = GetValueProviderFactories(config); return new ModelBinderParameterBinding(parameter, binder, valueProviderFactories); @@ -44,17 +56,30 @@ namespace Umbraco.Web.Editors private class JsonPathBinder : IModelBinder { + private readonly IModelBinder _underlyingBinder; private readonly string _jsonPath; - public JsonPathBinder(string jsonPath) + public JsonPathBinder(IModelBinder underlyingBinder, string jsonPath) { + _underlyingBinder = underlyingBinder; _jsonPath = jsonPath; } public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { + if (actionContext.Request.Method == HttpMethod.Get) + { + return _underlyingBinder.BindModel(actionContext, bindingContext); + } + var requestContent = new HttpMessageContent(actionContext.Request); var strJson = requestContent.HttpRequestMessage.Content.ReadAsStringAsync().Result; + + if (strJson.IsNullOrWhiteSpace()) + { + return _underlyingBinder.BindModel(actionContext, bindingContext); + } + var json = JsonConvert.DeserializeObject(strJson); //if no explicit json path then use the model name diff --git a/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs index 75916f6272..6eb2856912 100644 --- a/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs +++ b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs @@ -42,33 +42,24 @@ namespace Umbraco.Web.Editors if (found != null) { - if (controllerContext.Request.Method == HttpMethod.Get) + HttpActionDescriptor method; + if (TryBindFromUri(controllerContext, found, out method)) { - var requestParam = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get(found.ParamName); - - if (requestParam != null) - { - var paramTypes = found.SupportedTypes; - - foreach (var paramType in paramTypes) - { - var converted = requestParam.TryConvertTo(paramType); - if (converted) - { - var method = MatchByType(paramType, controllerContext, found); - if (method != null) - return method; - } - } - } + return method; } - else if (controllerContext.Request.Method == HttpMethod.Post) - { + //if it's a post we can try to read from the body and bind from th + if (controllerContext.Request.Method == HttpMethod.Post) + { var requestContent = new HttpMessageContent(controllerContext.Request); var strJson = requestContent.HttpRequestMessage.Content.ReadAsStringAsync().Result; var json = JsonConvert.DeserializeObject(strJson); + if (json == null) + { + return base.SelectAction(controllerContext); + } + var requestParam = json[found.ParamName]; if (requestParam != null) @@ -82,7 +73,7 @@ namespace Umbraco.Web.Editors var converted = requestParam.ToObject(paramType); if (converted != null) { - var method = MatchByType(paramType, controllerContext, found); + method = MatchByType(paramType, controllerContext, found); if (method != null) return method; } @@ -102,6 +93,34 @@ namespace Umbraco.Web.Editors return base.SelectAction(controllerContext); } + private bool TryBindFromUri(HttpControllerContext controllerContext, ParameterSwapInfo found, out HttpActionDescriptor method) + { + var requestParam = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get(found.ParamName); + + if (requestParam != null) + { + var paramTypes = found.SupportedTypes; + + foreach (var paramType in paramTypes) + { + //check if this is IEnumerable and if so this will get it's type + //we need to know this since the requestParam will always just be a string + var enumType = paramType.GetEnumeratedType(); + + var converted = requestParam.TryConvertTo(enumType ?? paramType); + if (converted) + { + method = MatchByType(paramType, controllerContext, found); + if (method != null) + return true; + } + } + } + + method = null; + return false; + } + private static ReflectedHttpActionDescriptor MatchByType(Type idType, HttpControllerContext controllerContext, ParameterSwapInfo found) { var controllerType = controllerContext.Controller.GetType(); From 77b04d74d4dd8c4b464984480bd0cb16c64a51e2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 31 Jan 2017 10:00:44 +0100 Subject: [PATCH 015/105] load mini list view for pickers with a start node --- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../src/less/components/umb-pagination.less | 3 ++ .../treepicker/treepicker.controller.js | 28 +++++++++++----- .../overlays/treepicker/treepicker.html | 32 +++++++++---------- .../src/views/components/umb-pagination.html | 2 +- 5 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umb-pagination.less diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 6fbf12e815..c6c63f1057 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -118,6 +118,7 @@ @import "components/umb-avatar.less"; @import "components/umb-progress-bar.less"; @import "components/umb-querybuilder.less"; +@import "components/umb-pagination.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-pagination.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-pagination.less new file mode 100644 index 0000000000..8d3d563cab --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-pagination.less @@ -0,0 +1,3 @@ +.umb-pagination ul { + box-shadow: none; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index a1ee1f031f..8127cd8de6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -38,11 +38,6 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", } } } - - // Search is only working for content, media and member section so we will remove it from everything else - if($scope.section === "content" || $scope.section === "media" || $scope.section === "member" ) { - $scope.enableSearh = true; - } //create the custom query string param for this tree $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; @@ -72,6 +67,21 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", entityType = "Media"; } + // Search and listviews is only working for content, media and member section so we will remove it from everything else + if ($scope.section === "content" || $scope.section === "media" || $scope.section === "member") { + $scope.enableSearh = true; + + //if a alternative startnode is used, we need to check if it is a container + if (dialogOptions.startNodeId) { + + entityResource.getById(dialogOptions.startNodeId, entityType).then(function (node) { + if (node.metaData.IsContainer) { + openMiniListView(node); + } + }); + } + } + //Configures filtering if (dialogOptions.filter) { @@ -588,11 +598,13 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", var promise = ""; - if(node.nodeType === "content") { + console.log(entityType); + + if (entityType === "Document") { promise = contentResource.getChildren(node.id, $scope.pagination); - } else if( node.nodeType === "member") { + } else if (entityType === "Member") { promise = memberResource.getPagedResults(node.id, $scope.pagination); - } else if(node.nodeType === "media") { + } else if (entityType === "Media") { promise = mediaResource.getChildren(node.id, $scope.pagination); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index a37a63bb0f..e2a0554b47 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -38,33 +38,31 @@
-
-
- - Back - -

- - {{ miniListView.node.name }} -

+
+ +
+ Back
+ +
+ +

{{ miniListView.node.name }}

+
+
- +
-
- -
- Name -
-
-
[JsonProperty("validation", ItemConverterType = typeof(ManifestValidatorConverter))] public List Validators { get; private set; } + + /// + /// This allows for custom configuration to be injected into the pre-value editor + /// + [JsonProperty("config")] + public IDictionary Config { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index 02d5a62432..74d35201c2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -27,7 +27,8 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig description: preVals[i].description, label: preVals[i].label, view: preVals[i].view, - value: preVals[i].value + value: preVals[i].value, + config: preVals[i].config, }); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js index 38dd99c204..86e95a8794 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js @@ -13,9 +13,15 @@ function mediaPickerController($scope, dialogService, entityResource, $log, icon multiPicker: false, entityType: "Media", section: "media", - treeAlias: "media" + treeAlias: "media", + idType: "int" }; + //combine the dialogOptions with any values returned from the server + if ($scope.model.config) { + angular.extend(dialogOptions, $scope.model.config); + } + $scope.openContentPicker = function() { $scope.contentPickerOverlay = dialogOptions; $scope.contentPickerOverlay.view = "treePicker"; @@ -53,18 +59,21 @@ function mediaPickerController($scope, dialogService, entityResource, $log, icon }; $scope.add = function (item) { + + var itemId = dialogOptions.idType === "udi" ? item.udi : item.id; + var currIds = _.map($scope.renderModel, function (i) { - return i.id; + return dialogOptions.idType === "udi" ? i.udi : i.id; }); - if (currIds.indexOf(item.id) < 0) { + if (currIds.indexOf(itemId) < 0) { item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon}); + $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, udi: item.udi }); } }; var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { var currIds = _.map($scope.renderModel, function (i) { - return i.id; + return dialogOptions.idType === "udi" ? i.udi : i.id; }); $scope.model.value = trim(currIds.join(), ","); }); @@ -79,7 +88,7 @@ function mediaPickerController($scope, dialogService, entityResource, $log, icon entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) { _.each(data, function (item, i) { item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon }); + $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, udi: item.udi }); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js index d2ff7bd778..48edf6e3a1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js @@ -12,26 +12,29 @@ angular.module('umbraco') multiPicker: false, entityType: "Document", type: "content", - treeAlias: "content" - }; + treeAlias: "content", + idType: "int" + }; + + //combine the config with any values returned from the server + if ($scope.model.config) { + angular.extend(config, $scope.model.config); + } if($scope.model.value){ $scope.ids = $scope.model.value.split(','); entityResource.getByIds($scope.ids, config.entityType).then(function (data) { _.each(data, function (item, i) { item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon}); - }); + $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, udi: item.udi }); + }); }); } $scope.openContentPicker = function() { - $scope.treePickerOverlay = {}; - $scope.treePickerOverlay.section = config.type; - $scope.treePickerOverlay.treeAlias = config.treeAlias; - $scope.treePickerOverlay.multiPicker = config.multiPicker; + $scope.treePickerOverlay = config; $scope.treePickerOverlay.view = "treePicker"; - $scope.treePickerOverlay.show = true; + $scope.treePickerOverlay.show = true; $scope.treePickerOverlay.submit = function(model) { @@ -64,12 +67,15 @@ angular.module('umbraco') $scope.ids = []; }; - $scope.add =function(item){ - if($scope.ids.indexOf(item.id) < 0){ + $scope.add = function (item) { + + var itemId = config.idType === "udi" ? item.udi : item.id; + + if ($scope.ids.indexOf(itemId) < 0){ item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.ids.push(item.id); - $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon}); + $scope.ids.push(itemId); + $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon, udi: item.udi}); $scope.model.value = trim($scope.ids.join(), ","); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js index ff5e31b8eb..5a53e00e1c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js @@ -12,7 +12,12 @@ angular.module('umbraco') $scope.model.value = { type: "content" }; - } + } + if (!$scope.model.config) { + $scope.model.config = { + idType: "int" + }; + } if($scope.model.value.id && $scope.model.value.type !== "member"){ var ent = "Document"; @@ -29,7 +34,8 @@ angular.module('umbraco') $scope.openContentPicker =function(){ $scope.treePickerOverlay = { - view: "treepicker", + view: "treepicker", + idType: $scope.model.config.idType, section: $scope.model.value.type, treeAlias: $scope.model.value.type, multiPicker: false, @@ -67,6 +73,6 @@ angular.module('umbraco') $scope.clear(); item.icon = iconHelper.convertFromLegacyIcon(item.icon); $scope.node = item; - $scope.model.value.id = item.id; + $scope.model.value.id = $scope.model.config.idType === "udi" ? item.udi : item.id; } }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 27df035914..01da9cda18 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -58,7 +58,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper startNode: { query: "", type: "content", - id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker + id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker } }; @@ -105,10 +105,11 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.clear(); $scope.add(data); } - angularHelper.getCurrentForm($scope).$setDirty(); + angularHelper.getCurrentForm($scope).$setDirty(); }, treeAlias: $scope.model.config.startNode.type, - section: $scope.model.config.startNode.type + section: $scope.model.config.startNode.type, + idType: "int" }; //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the @@ -147,9 +148,10 @@ function contentPickerController($scope, entityResource, editorState, iconHelper if ($scope.model.config.startNode.query) { var rootId = $routeParams.id; entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) { - dialogOptions.startNodeId = ent.id; + dialogOptions.startNodeId = $scope.model.config.idType === "udi" ? ent.udi : ent.id; }); - } else { + } + else { dialogOptions.startNodeId = $scope.model.config.startNode.id; } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 77ab9ff6e5..0e191954d6 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Editors public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) { controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(string)))); + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(Udi), typeof(string)))); } } @@ -187,6 +187,7 @@ namespace Umbraco.Web.Editors .Select(Mapper.Map>); } + #region GetChildren /// /// Returns the child media objects - using the entity INT id /// @@ -244,7 +245,7 @@ namespace Umbraco.Web.Editors Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") - { + { var entity = Services.EntityService.GetByKey(id); if (entity != null) { @@ -253,6 +254,39 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + /// + /// Returns the child media objects - using the entity UDI id + /// + /// + /// + /// + /// + /// + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Udi id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + var entity = Services.EntityService.GetByKey(guidUdi.Guid); + if (entity != null) + { + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] @@ -275,7 +309,8 @@ namespace Umbraco.Web.Editors } throw new HttpResponseException(HttpStatusCode.NotFound); - } + } + #endregion /// /// Moves an item to the recycle bin, if it is already there then it will permanently delete it diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index db2a806572..879ffd3d0a 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -40,7 +40,7 @@ namespace Umbraco.Web.Editors public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) { controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetAllowedChildren", "contentId", typeof(int), typeof(Guid), typeof(string)))); + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetAllowedChildren", "contentId", typeof(int), typeof(Guid), typeof(Udi), typeof(string)))); } } @@ -187,6 +187,7 @@ namespace Umbraco.Web.Editors } + #region GetAllowedChildren /// /// Returns the allowed child content type objects for the content item id passed in - based on an INT id /// @@ -247,10 +248,30 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - - [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] + + /// + /// Returns the allowed child content type objects for the content item id passed in - based on a UDI id + /// + /// + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public IEnumerable GetAllowedChildren(Udi contentId) + { + var guidUdi = contentId as GuidUdi; + if (guidUdi != null) + { + var entity = ApplicationContext.Services.EntityService.GetByKey(guidUdi.Guid); + if (entity != null) + { + return GetAllowedChildren(entity.Id); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [Obsolete("Do not use this method, use either the overload with INT, GUID or UDI instead, this will be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] - [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] public IEnumerable GetAllowedChildren(string contentId) { foreach (var type in new[] { typeof(int), typeof(Guid) }) @@ -260,11 +281,12 @@ namespace Umbraco.Web.Editors { //oooh magic! will auto select the right overload return GetAllowedChildren((dynamic)parsed.Result); - } + } } throw new HttpResponseException(HttpStatusCode.NotFound); - } + } + #endregion /// /// Move the media type diff --git a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs index 1e8a7ba088..6879701bde 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs @@ -1,4 +1,5 @@ -using System.Runtime.Serialization; +using System.Collections.Generic; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { @@ -33,5 +34,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "view", IsRequired = true)] public string View { get; set; } + /// + /// This allows for custom configuration to be injected into the pre-value editor + /// + [DataMember(Name = "config")] + public IDictionary Config { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index 4f798dc41e..ef66f8740e 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Umbraco.Core; using Umbraco.Core.PropertyEditors; @@ -16,7 +17,18 @@ namespace Umbraco.Web.PropertyEditors public ContentPickerPropertyEditor() { InternalPreValues["idType"] = "int"; - } + } + + /// + /// overridden to change the pre-value picker to use INT ids + /// + /// + protected override PreValueEditor CreatePreValueEditor() + { + var preValEditor = base.CreatePreValueEditor(); + preValEditor.Fields.Single(x => x.Key == "startNodeId").Config["idType"] = "int"; + return preValEditor; + } } /// @@ -52,12 +64,27 @@ namespace Umbraco.Web.PropertyEditors internal class ContentPickerPreValueEditor : PreValueEditor { - [PreValueField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = " Opens the node in a dialog")] - public string ShowOpenButton { get; set; } - - [PreValueField("startNodeId", "Start node", "treepicker")] - public int StartNodeId { get; set; } - + public ContentPickerPreValueEditor() + { + //create the fields + Fields.Add(new PreValueField() + { + Key = "showOpenButton", + View = "boolean", + Name = "Show open button (this feature is in preview!)", + Description = "Opens the node in a dialog" + }); + Fields.Add(new PreValueField() + { + Key = "startNodeId", + View = "treepicker", + Name = "Start node", + Config = new Dictionary + { + {"idType", "udi"} + } + }); + } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index 3b16aa2cb7..5da7bf38eb 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -67,17 +67,40 @@ namespace Umbraco.Web.PropertyEditors internal class MediaPickerPreValueEditor : PreValueEditor { - [PreValueField("multiPicker", "Pick multiple items", "boolean")] - public bool MultiPicker { get; set; } - - [PreValueField("onlyImages", "Pick only images", "boolean", Description = "Only let the editor choose images from media.")] - public bool OnlyImages { get; set; } - - [PreValueField("disableFolderSelect", "Disable folder select", "boolean", Description = "Do not allow folders to be picked.")] - public bool DisableFolderSelect { get; set; } - - [PreValueField("startNodeId", "Start node", "mediapicker")] - public int StartNodeId { get; set; } + public MediaPickerPreValueEditor() + { + //create the fields + Fields.Add(new PreValueField() + { + Key = "multiPicker", + View = "boolean", + Name = "Pick multiple items" + }); + Fields.Add(new PreValueField() + { + Key = "onlyImages", + View = "boolean", + Name = "Pick only images", + Description = "Only let the editor choose images from media." + }); + Fields.Add(new PreValueField() + { + Key = "disableFolderSelect", + View = "boolean", + Name = "Disable folder select", + Description = "Do not allow folders to be picked." + }); + Fields.Add(new PreValueField() + { + Key = "startNodeId", + View = "mediapicker", + Name = "Start node", + Config = new Dictionary + { + {"idType", "udi"} + } + }); + } } } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index c3e257962a..fbba130543 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -14,6 +15,17 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues["idType"] = "int"; } + + /// + /// overridden to change the pre-value picker to use INT ids + /// + /// + protected override PreValueEditor CreatePreValueEditor() + { + var preValEditor = base.CreatePreValueEditor(); + preValEditor.Fields.Single(x => x.Key == "startNode").Config["idType"] = "int"; + return preValEditor; + } } [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePicker2Alias, "Multinode Treepicker", PropertyEditorValueTypes.Text, "contentpicker", Group="pickers", Icon="icon-page-add")] @@ -45,20 +57,46 @@ namespace Umbraco.Web.PropertyEditors internal class MultiNodePickerPreValueEditor : PreValueEditor { - [PreValueField("startNode", "Node type", "treesource")] - public string StartNode { get; set; } - - [PreValueField("filter", "Allow items of type", "textstring", Description = "Separate with comma")] - public string Filter { get; set; } - - [PreValueField("minNumber", "Minimum number of items", "number")] - public string MinNumber { get; set; } - - [PreValueField("maxNumber", "Maximum number of items", "number")] - public string MaxNumber { get; set; } - - [PreValueField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = " Opens the node in a dialog")] - public string ShowOpenButton { get; set; } + public MultiNodePickerPreValueEditor() + { + //create the fields + Fields.Add(new PreValueField() + { + Key = "startNode", + View = "treesource", + Name = "Node type", + Config = new Dictionary + { + {"idType", "udi"} + } + }); + Fields.Add(new PreValueField() + { + Key = "filter", + View = "textstring", + Name = "Allow items of type", + Description = "Separate with comma" + }); + Fields.Add(new PreValueField() + { + Key = "minNumber", + View = "number", + Name = "Minimum number of items" + }); + Fields.Add(new PreValueField() + { + Key = "maxNumber", + View = "number", + Name = "Maximum number of items" + }); + Fields.Add(new PreValueField() + { + Key = "showOpenButton", + View = "boolean", + Name = "Show open button (this feature is in preview!)", + Description = "Opens the node in a dialog" + }); + } /// /// This ensures the multiPicker pre-val is set based on the maxNumber of nodes set diff --git a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs index 716e71711f..8bd23a24a6 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Umbraco.Core; using Umbraco.Core.PropertyEditors; @@ -12,6 +13,17 @@ namespace Umbraco.Web.PropertyEditors { //clear the pre-values so it defaults to a multiple picker. InternalPreValues.Clear(); + } + + /// + /// overridden to change the pre-value picker to use INT ids + /// + /// + protected override PreValueEditor CreatePreValueEditor() + { + var preValEditor = base.CreatePreValueEditor(); + preValEditor.Fields.Single(x => x.Key == "startNodeId").Config["idType"] = "int"; + return preValEditor; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 5e8172b29f..2a5e28496e 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -300,24 +300,31 @@ namespace Umbraco.Web.Trees } /// - /// Get an entity via an id that can be either an integer or a Guid + /// Get an entity via an id that can be either an integer, Guid or UDI /// /// /// internal IUmbracoEntity GetEntityFromId(string id) { IUmbracoEntity entity; + Guid idGuid = Guid.Empty; int idInt; + Udi idUdi; + if (Guid.TryParse(id, out idGuid)) { entity = Services.EntityService.GetByKey(idGuid, UmbracoObjectType); - } else if (int.TryParse(id, out idInt)) { entity = Services.EntityService.Get(idInt, UmbracoObjectType); } + else if (Udi.TryParse(id, out idUdi)) + { + var guidUdi = idUdi as GuidUdi; + entity = guidUdi != null ? Services.EntityService.GetByKey(guidUdi.Guid, UmbracoObjectType) : null; + } else { return null; From cda47ed5368be1061cd3edc96ce14eb06f60dfcf Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 6 Feb 2017 14:46:48 +0100 Subject: [PATCH 047/105] add client side logic for scripts folder creation --- .../src/common/resources/codefile.resource.js | 32 +++++++++++++++ .../src/views/scripts/create.controller.js | 40 +++++++++++++++++-- .../src/views/scripts/create.html | 8 ++-- 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js index e83bb4f14e..b6a56d336b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js @@ -205,6 +205,38 @@ function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) { "codeFileApiBaseUrl", "GetScaffold?type=" + type + "&id=" + id + "&snippetName=" + snippetName)), "Failed to get scaffold for" + type); + }, + + /** + * @ngdoc method + * @name umbraco.resources.codefileResource#createContainer + * @methodOf umbraco.resources.codefileResource + * + * @description + * Creates a container/folder + * + * ##usage + *
+         * codefileResource.createContainer("partialViews", "folder%2ffolder", "folder")
+         *    .then(function(data) {
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {string} File type: (scripts, partialViews, partialViewMacros). + * @param {string} Parent Id: url encoded path + * @param {string} Container name + * @returns {Promise} resourcePromise object. + * + */ + + createContainer: function(type, parentId, name) { + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl( + "codeFileApiBaseUrl", + "PostCreateContainer", + { type: type, parentId: parentId, name: name })), + 'Failed to create a folder under parent id ' + parentId); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/scripts/create.controller.js index 1729693d83..89ff9e6643 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/create.controller.js @@ -1,13 +1,15 @@ (function () { "use strict"; - function ScriptsCreateController($scope, $location, navigationService) { + function ScriptsCreateController($scope, $location, navigationService, formHelper, codefileResource, localizationService) { var vm = this; var node = $scope.dialogOptions.currentNode; + var localizeCreateFolder = localizationService.localize("defaultdialog_createFolder"); vm.creatingFolder = false; vm.folderName = ""; + vm.createFolderError = ""; vm.fileExtension = ""; vm.createFile = createFile; @@ -23,8 +25,40 @@ vm.creatingFolder = true; } - function createFolder() { - + function createFolder(form) { + + if (formHelper.submitForm({scope: $scope, formCtrl: form, statusMessage: localizeCreateFolder})) { + + codefileResource.createContainer("scripts", node.id, vm.folderName).then(function(path) { + + navigationService.hideMenu(); + + navigationService.syncTree({ + tree: "scripts", + path: path, + forceReload: true, + activate: true + }); + + formHelper.resetForm({ + scope: $scope + }); + + var section = appState.getSectionState("currentSection"); + + }, function(err) { + + vm.createFolderError = err; + + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + } + } } diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/create.html b/src/Umbraco.Web.UI.Client/src/views/scripts/create.html index d57ead3b15..22b238606d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/create.html @@ -22,12 +22,12 @@
-
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{vm.createFolderError.errorMsg}}
+

{{vm.createFolderError.data.message}}

From 00982d4278e293a06adb8a3fa70c75eafe7c937c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 6 Feb 2017 15:04:46 +0100 Subject: [PATCH 048/105] add client side logic for partial views folder creation --- .../views/partialviews/create.controller.js | 37 ++++++++++++++++++- .../src/views/partialviews/create.html | 10 ++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.controller.js index 5aeaa28935..6fc2fff3a7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.controller.js @@ -1,14 +1,17 @@ (function () { "use strict"; - function PartialViewsCreateController($scope, codefileResource, $location, navigationService) { + function PartialViewsCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService) { var vm = this; var node = $scope.dialogOptions.currentNode; + var localizeCreateFolder = localizationService.localize("defaultdialog_createFolder"); vm.snippets = []; vm.showSnippets = false; vm.creatingFolder = false; + vm.createFolderError = ""; + vm.folderName = ""; vm.createPartialView = createPartialView; vm.showCreateFolder = showCreateFolder; @@ -39,8 +42,38 @@ vm.creatingFolder = true; } - function createFolder() { + function createFolder(form) { + if (formHelper.submitForm({scope: $scope, formCtrl: form, statusMessage: localizeCreateFolder})) { + codefileResource.createContainer("partialViews", node.id, vm.folderName).then(function(path) { + + navigationService.hideMenu(); + + navigationService.syncTree({ + tree: "partialViews", + path: path, + forceReload: true, + activate: true + }); + + formHelper.resetForm({ + scope: $scope + }); + + var section = appState.getSectionState("currentSection"); + + }, function(err) { + + vm.createFolderError = err; + + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + } } function showCreateFromSnippet() { diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html index fc9bf98c4f..7aae1f871a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html @@ -47,12 +47,12 @@
+ ng-submit="vm.createFolder(createFolderForm)" + val-form-manager> -
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{vm.createFolderError.errorMsg}}
+

{{vm.createFolderError.data.message}}

From 999de5ae1ddab5e429d26804f4b3e738d201ee1b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 6 Feb 2017 15:05:13 +0100 Subject: [PATCH 049/105] add client side logic for partial view macros folder creation --- .../partialviewmacros/create.controller.js | 37 ++++++++++++++++++- .../src/views/partialviewmacros/create.html | 10 ++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.controller.js index 3b3fb0f9f5..46d2e1d648 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.controller.js @@ -1,14 +1,17 @@ (function () { "use strict"; - function PartialViewMacrosCreateController($scope, codefileResource, $location, navigationService) { + function PartialViewMacrosCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService) { var vm = this; var node = $scope.dialogOptions.currentNode; + var localizeCreateFolder = localizationService.localize("defaultdialog_createFolder"); vm.snippets = []; vm.showSnippets = false; vm.creatingFolder = false; + vm.createFolderError = ""; + vm.folderName = ""; vm.createPartialViewMacro = createPartialViewMacro; vm.showCreateFolder = showCreateFolder; @@ -39,8 +42,38 @@ vm.creatingFolder = true; } - function createFolder() { + function createFolder(form) { + if (formHelper.submitForm({scope: $scope, formCtrl: form, statusMessage: localizeCreateFolder})) { + codefileResource.createContainer("partialViewMacros", node.id, vm.folderName).then(function(path) { + + navigationService.hideMenu(); + + navigationService.syncTree({ + tree: "partialViewMacros", + path: path, + forceReload: true, + activate: true + }); + + formHelper.resetForm({ + scope: $scope + }); + + var section = appState.getSectionState("currentSection"); + + }, function(err) { + + vm.createFolderError = err; + + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + } } function showCreateFromSnippet() { diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html index aa8796ae7a..0c5a74c4b0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html @@ -47,12 +47,12 @@
+ ng-submit="vm.createFolder(createFolderForm)" + val-form-manager> -
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{vm.createFolderError.errorMsg}}
+

{{vm.createFolderError.data.message}}

From c36000c4ddb50d52286718dba4559426231dec40 Mon Sep 17 00:00:00 2001 From: Emil Wangaa Date: Tue, 7 Feb 2017 10:55:38 +0100 Subject: [PATCH 050/105] Adds create container endpoint for creation folders for scripts, partialviews, partialviewmacros --- src/Umbraco.Core/Services/FileService.cs | 22 +++++++- src/Umbraco.Core/Services/IFileService.cs | 2 + .../src/common/resources/codefile.resource.js | 2 +- .../partialviewmacros/create.controller.js | 6 +- .../views/partialviews/create.controller.js | 6 +- .../src/views/scripts/create.controller.js | 6 +- src/Umbraco.Web/Editors/CodeFileController.cs | 56 ++++++++++++++++++- 7 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 2105f7af2c..3b842e6875 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -667,7 +667,27 @@ namespace Umbraco.Core.Services .ToArray(); return empty.Union(files.Except(empty)); - } + } + + public void CreatePartialViewFolder(string folderPath) + { + var uow = _fileUowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreatePartialViewRepository(uow)) + { + ((PartialViewRepository)repository).AddFolder(folderPath); + uow.Commit(); + } + } + + public void CreatePartialViewMacroFolder(string folderPath) + { + var uow = _fileUowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreatePartialViewMacroRepository(uow)) + { + ((PartialViewMacroRepository)repository).AddFolder(folderPath); + uow.Commit(); + } + } public void DeletePartialViewFolder(string folderPath) { diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index 453d199bfe..e8f1065219 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -11,6 +11,8 @@ namespace Umbraco.Core.Services public interface IFileService : IService { IEnumerable GetPartialViewSnippetNames(params string[] filterNames); + void CreatePartialViewFolder(string folderPath); + void CreatePartialViewMacroFolder(string folderPath); void DeletePartialViewFolder(string folderPath); void DeletePartialViewMacroFolder(string folderPath); IPartialView GetPartialView(string path); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js index b6a56d336b..44454f21e3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js @@ -235,7 +235,7 @@ function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) { $http.post(umbRequestHelper.getApiUrl( "codeFileApiBaseUrl", "PostCreateContainer", - { type: type, parentId: parentId, name: name })), + { type: type, parentId: parentId, name: encodeURIComponent(name) })), 'Failed to create a folder under parent id ' + parentId); } diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.controller.js index 46d2e1d648..6132ba3f89 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function PartialViewMacrosCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService) { + function PartialViewMacrosCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService, appState) { var vm = this; var node = $scope.dialogOptions.currentNode; @@ -45,13 +45,13 @@ function createFolder(form) { if (formHelper.submitForm({scope: $scope, formCtrl: form, statusMessage: localizeCreateFolder})) { - codefileResource.createContainer("partialViewMacros", node.id, vm.folderName).then(function(path) { + codefileResource.createContainer("partialViewMacros", node.id, vm.folderName).then(function (saved) { navigationService.hideMenu(); navigationService.syncTree({ tree: "partialViewMacros", - path: path, + path: saved.path, forceReload: true, activate: true }); diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.controller.js index 6fc2fff3a7..95d224b890 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function PartialViewsCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService) { + function PartialViewsCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService, appState) { var vm = this; var node = $scope.dialogOptions.currentNode; @@ -45,13 +45,13 @@ function createFolder(form) { if (formHelper.submitForm({scope: $scope, formCtrl: form, statusMessage: localizeCreateFolder})) { - codefileResource.createContainer("partialViews", node.id, vm.folderName).then(function(path) { + codefileResource.createContainer("partialViews", node.id, vm.folderName).then(function(saved) { navigationService.hideMenu(); navigationService.syncTree({ tree: "partialViews", - path: path, + path: saved.path, forceReload: true, activate: true }); diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/scripts/create.controller.js index 89ff9e6643..bb3aedeb4a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/create.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function ScriptsCreateController($scope, $location, navigationService, formHelper, codefileResource, localizationService) { + function ScriptsCreateController($scope, $location, navigationService, formHelper, codefileResource, localizationService, appState) { var vm = this; var node = $scope.dialogOptions.currentNode; @@ -29,13 +29,13 @@ if (formHelper.submitForm({scope: $scope, formCtrl: form, statusMessage: localizeCreateFolder})) { - codefileResource.createContainer("scripts", node.id, vm.folderName).then(function(path) { + codefileResource.createContainer("scripts", node.id, vm.folderName).then(function (saved) { navigationService.hideMenu(); navigationService.syncTree({ tree: "scripts", - path: path, + path: saved.path, forceReload: true, activate: true }); diff --git a/src/Umbraco.Web/Editors/CodeFileController.cs b/src/Umbraco.Web/Editors/CodeFileController.cs index bcb6630c0a..6775f84a61 100644 --- a/src/Umbraco.Web/Editors/CodeFileController.cs +++ b/src/Umbraco.Web/Editors/CodeFileController.cs @@ -56,6 +56,61 @@ namespace Umbraco.Web.Editors } } + /// + /// Used to create a container/folder in 'partialViews', 'partialViewMacros' or 'scripts' + /// + /// 'partialViews', 'partialViewMacros' or 'scripts' + /// The virtual path of the parent. + /// The name of the container/folder + /// + [HttpPost] + public CodeFileDisplay PostCreateContainer(string type, string parentId, string name) + { + if (string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(name)) + { + throw new HttpResponseException(HttpStatusCode.BadRequest); + } + + // if the parentId is root (-1) then we just need an empty string as we are + // creating the path below and we don't wan't -1 in the path + if (parentId == Core.Constants.System.Root.ToInvariantString()) + { + parentId = string.Empty; + } + + name = System.Web.HttpUtility.UrlDecode(name); + + if (parentId.IsNullOrWhiteSpace() == false) + { + parentId = System.Web.HttpUtility.UrlDecode(parentId); + name = parentId.EnsureEndsWith("/") + name; + } + + var virtualPath = string.Empty; + switch (type) + { + case Core.Constants.Trees.PartialViews: + virtualPath = NormalizeVirtualPath(name, SystemDirectories.PartialViews); + Services.FileService.CreatePartialViewFolder(virtualPath); + break; + case Core.Constants.Trees.PartialViewMacros: + virtualPath = NormalizeVirtualPath(name, SystemDirectories.MacroPartials); + Services.FileService.CreatePartialViewMacroFolder(virtualPath); + break; + case Core.Constants.Trees.Scripts: + virtualPath = NormalizeVirtualPath(name, SystemDirectories.Scripts); + Services.FileService.CreateScriptFolder(virtualPath); + break; + + } + + return new CodeFileDisplay + { + VirtualPath = virtualPath, + Path = Url.GetTreePathFromFilePath(virtualPath) + }; + } + /// /// Used to get a specific file from disk via the FileService /// @@ -70,7 +125,6 @@ namespace Umbraco.Web.Editors } virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath); - switch (type) { From 857063ebd873bea4536bb4f410ebc370269206b5 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 8 Feb 2017 09:53:52 +0100 Subject: [PATCH 051/105] =?UTF-8?q?fixes:=20U4-9465=20Mini=20list=20view:?= =?UTF-8?q?=20don=E2=80=99t=20show=20expand=20arrow=20on=20list=20views=20?= =?UTF-8?q?in=20trees=20not=20supporting=20mini=20list=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/directives/components/tree/umbtree.directive.js | 5 +++-- .../directives/components/tree/umbtreeitem.directive.js | 7 +++---- .../src/views/common/overlays/treepicker/treepicker.html | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 75d0144982..7a74716ea1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -21,7 +21,8 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat customtreeparams: '@', eventhandler: '=', enablecheckboxes: '@', - enablelistviewsearch: '@' + enablelistviewsearch: '@', + enablelistviewexpand: '@' }, compile: function(element, attrs) { @@ -35,7 +36,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat '' + '
'; template += '
    ' + - '' + + '' + '
' + '' + ''; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js index 8d30e071a6..b32942791c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js @@ -27,6 +27,7 @@ angular.module("umbraco.directives") section: '@', eventhandler: '=', currentNode: '=', + enablelistviewexpand: '@', node: '=', tree: '=' }, @@ -74,10 +75,8 @@ angular.module("umbraco.directives") //toggle visibility of last 'ins' depending on children //visibility still ensure the space is "reserved", so both nodes with and without children are aligned. - - console.log(node); - if (node.hasChildren || node.metaData.isContainer) { + if (node.hasChildren || node.metaData.isContainer && scope.enablelistviewexpand === "true") { element.find("ins").last().css("visibility", "visible"); } else { @@ -230,7 +229,7 @@ angular.module("umbraco.directives") setupNodeDom(scope.node, scope.tree); - var template = '
'; + var template = '
'; var newElement = angular.element(template); $compile(newElement)(scope); element.append(newElement); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index a4b3c75982..4ecb6c1b9e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -30,6 +30,7 @@ customtreeparams="{{customTreeParams}}" eventhandler="dialogTreeEventHandler" enablelistviewsearch="true" + enablelistviewexpand="true" enablecheckboxes="{{multiPicker}}">
From c060856782e221e907d493e2378c4c7e3e4cca7d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 8 Feb 2017 11:09:09 +0100 Subject: [PATCH 052/105] fix breadcrumb for pickers with a startNode --- .../treepicker/treepicker.controller.js | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index bdcd942474..125384b57e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -492,19 +492,12 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", angular.forEach(miniListViewsHistory, function(historyItem, index){ if(parseInt(historyItem.node.id) === parseInt(ancestor.id)) { - // load the list view from history $scope.miniListViews = []; $scope.miniListViews.push(historyItem); - // remove from history - miniListViewsHistory.splice(index, miniListViewsHistory.length - index); - found = true; - // get ancestors - entityResource.getAncestors(historyItem.node.id, entityType) - .then(function (ancestors) { - $scope.breadcrumb = ancestors; - }); + getAncestors(historyItem.node); + found = true; } }); @@ -570,10 +563,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", }); // get ancestors - entityResource.getAncestors(node.id, entityType) - .then(function (ancestors) { - $scope.breadcrumb = ancestors; - }); + getAncestors(node); } @@ -619,6 +609,30 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", } + function getAncestors(node) { + entityResource.getAncestors(node.id, entityType) + .then(function (ancestors) { + + // if there is a start node remove all ancestors before that one + if(dialogOptions.startNodeId && dialogOptions.startNodeId !== -1) { + var found = false; + $scope.breadcrumb = []; + angular.forEach(ancestors, function(ancestor){ + if(parseInt(ancestor.id) === parseInt(dialogOptions.startNodeId)) { + found = true; + } + if(found) { + $scope.breadcrumb.push(ancestor); + } + }); + + } else { + $scope.breadcrumb = ancestors; + } + + }); + } + $scope.getMiniListViewAnimation = function() { if(goingForward) { return 'umb-mini-list-view--forward'; From 7bcb2b11f611402504e9ecd71af34c14368f2953 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 8 Feb 2017 14:43:59 +0100 Subject: [PATCH 053/105] Makes the (un)install summary accessible to events --- src/Umbraco.Core/Events/ImportPackageEventArgs.cs | 5 +++++ src/Umbraco.Core/Events/UninstallPackageEventArgs.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs index 8596629731..6dd13f2433 100644 --- a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs @@ -22,5 +22,10 @@ namespace Umbraco.Core.Events { get { return _packageMetaData; } } + + public IEnumerable InstallationSummary + { + get { return EventObject; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs b/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs index 1371942c2b..324867a8f7 100644 --- a/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs +++ b/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs @@ -22,5 +22,10 @@ namespace Umbraco.Core.Events { get { return _packageMetaData; } } + + public IEnumerable UninstallationSummary + { + get { return EventObject; } + } } } \ No newline at end of file From 308ed3bba79d07f43e59393e06b15fe9e104cb8d Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 8 Feb 2017 14:44:48 +0100 Subject: [PATCH 054/105] Removed files that are not found on disk don't get updated to list their full path --- src/Umbraco.Web/Editors/PackageInstallController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 99e37b610c..2a08d3955f 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -222,11 +222,10 @@ namespace Umbraco.Web.Editors file = string.Format("/{0}", file); var filePath = IOHelper.MapPath(file); + removedFiles.Add(filePath); + if (File.Exists(filePath)) - { - removedFiles.Add(filePath); File.Delete(filePath); - } } pack.Data.Files.Remove(file); } From 7648c76f6ff904c4729e3e5f1f55f01c1f2fb74b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 8 Feb 2017 14:45:07 +0100 Subject: [PATCH 055/105] List full path of each file installed --- src/Umbraco.Core/IO/IOHelper.cs | 20 ++++++++++++++++--- .../PackageInstance/InstalledPackage.cs | 12 +++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 286acf0285..de27f13748 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -4,11 +4,11 @@ using System.Globalization; using System.Reflection; using System.IO; using System.Configuration; +using System.Linq; using System.Web; using System.Text.RegularExpressions; using System.Web.Hosting; using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; namespace Umbraco.Core.IO { @@ -351,7 +351,21 @@ namespace Umbraco.Core.IO writer.Write(contents); } } - - } + + } + + /// + /// Checks if a given path is a full path including drive letter + /// + /// + /// + // From: http://stackoverflow.com/a/35046453/5018 + internal static bool IsFullPath(this string path) + { + return string.IsNullOrWhiteSpace(path) == false + && path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1 + && Path.IsPathRooted(path) + && Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false; + } } } diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs index a5ae2f5015..dc66592cff 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs @@ -145,6 +145,18 @@ namespace umbraco.cms.businesslogic.packager { var dataTypes = TryGetIntegerIds(Data.DataTypes).Select(dataTypeService.GetDataTypeDefinitionById).ToList(); var dictionaryItems = TryGetIntegerIds(Data.DictionaryItems).Select(localizationService.GetDictionaryItemById).ToList(); var languages = TryGetIntegerIds(Data.Languages).Select(localizationService.GetLanguageById).ToList(); + + for (var i = 0; i < Data.Files.Count; i++) + { + var filePath = Data.Files[i]; + if (filePath.IsFullPath()) + continue; + + filePath = filePath.TrimStart('~'); + if (filePath.StartsWith("/") == false) + filePath = string.Format("/{0}", filePath); + Data.Files[i] = IOHelper.MapPath(filePath); + } return new InstallationSummary { From 843796c9a4ebb131e39cf8b69dd84864b759da20 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 8 Feb 2017 14:58:54 +0100 Subject: [PATCH 056/105] refactor move logic from tree controller into to mini-list-view component --- .../components/umbminilistview.directive.js | 203 ++++++++++++++++-- .../treepicker/treepicker.controller.js | 195 ++--------------- .../overlays/treepicker/treepicker.html | 43 +--- .../views/components/umb-mini-list-view.html | 122 ++++++----- 4 files changed, 278 insertions(+), 285 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index c45f77b44c..631004e218 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -1,33 +1,194 @@ -(function() { +(function () { 'use strict'; - function MiniListViewDirective() { + function MiniListViewDirective($q, contentResource, memberResource, mediaResource, entityResource) { function link(scope, el, attr, ctrl) { scope.search = ""; + scope.miniListViews = []; + scope.breadcrumb = []; - scope.openChild = function(event, child) { - if(scope.onOpenChild) { - scope.onOpenChild({"child": child}); - event.stopPropagation(); - } - }; + var miniListViewsHistory = []; + var goingForward = true; - scope.selectChild = function(child) { - if(scope.onSelectChild) { - scope.onSelectChild({"child": child}); - } - }; + function onInit() { + open(scope.node); + } - scope.searchMiniListView = function() { - if (scope.search !== null && scope.search !== undefined) { - if(scope.onSearch) { - scope.onSearch({"search": scope.search}); + function open(node) { + + goingForward = true; + + var miniListView = { + node: node, + loading: true, + pagination: { + pageSize: 10, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true } + }; + + // start loading animation on node + node.loading = true; + + getChildrenForMiniListView(miniListView) + .then(function (data) { + + // stop loading animation on node + node.loading = false; + + // clear and push mini list view in dom so we only render 1 view + scope.miniListViews = []; + scope.miniListViews.push(miniListView); + + // store in history so we quickly can navigate back + miniListViewsHistory.push(miniListView); + + }); + + // get ancestors + getAncestors(node); + + } + + function getChildrenForMiniListView(miniListView) { + + // setup promise + var deferred = $q.defer(); + + // start loading animation list view + miniListView.loading = true; + + // setup the correct resource depending on section + var resource = ""; + + if (scope.entityType === "Member") { + resource = memberResource.getPagedResults; + } else if (scope.entityType === "Media") { + resource = mediaResource.getChildren; + } else { + resource = contentResource.getChildren; + } + + resource(miniListView.node.id, miniListView.pagination) + .then(function (data) { + + // update children + miniListView.children = data.items; + + // update pagination + miniListView.pagination.totalItems = data.totalItems; + miniListView.pagination.totalPages = data.totalPages; + + // stop load indicator + miniListView.loading = false; + + deferred.resolve(data); + }); + + return deferred.promise; + + } + + scope.openNode = function(event, node) { + open(node); + event.stopPropagation(); + }; + + scope.selectNode = function(node) { + if(scope.onSelect) { + scope.onSelect({'node': node}); } }; + /* Pagination */ + scope.goToPage = function(pageNumber, miniListView) { + // set new page number + miniListView.pagination.pageNumber = pageNumber; + // get children + getChildrenForMiniListView(miniListView); + }; + + /* Breadcrumb */ + scope.clickBreadcrumb = function(ancestor) { + + var found = false; + goingForward = false; + + angular.forEach(miniListViewsHistory, function(historyItem, index){ + if(Number(historyItem.node.id) === Number(ancestor.id)) { + // load the list view from history + scope.miniListViews = []; + scope.miniListViews.push(historyItem); + // get ancestors + getAncestors(historyItem.node); + found = true; + } + }); + + if(!found) { + // if we can't find the view in the history + miniListViewsHistory = []; + scope.miniListViews = []; + } + + }; + + function getAncestors(node) { + entityResource.getAncestors(node.id, scope.entityType) + .then(function (ancestors) { + + // if there is a start node remove all ancestors before that one + if(scope.startNodeId && scope.startNodeId !== -1) { + var found = false; + scope.breadcrumb = []; + angular.forEach(ancestors, function(ancestor){ + if(Number(ancestor.id) === Number(scope.startNodeId)) { + found = true; + } + if(found) { + scope.breadcrumb.push(ancestor); + } + }); + + } else { + scope.breadcrumb = ancestors; + } + + }); + } + + /* Search */ + scope.searchMiniListView = function(search, miniListView) { + // set search value + miniListView.pagination.filter = search; + // start loading animation list view + miniListView.loading = true; + searchMiniListView(miniListView); + }; + + var searchMiniListView = _.debounce(function (miniListView) { + scope.$apply(function () { + getChildrenForMiniListView(miniListView); + }); + }, 500); + + /* Animation */ + scope.getMiniListViewAnimation = function() { + if(goingForward) { + return 'umb-mini-list-view--forward'; + } else { + return 'umb-mini-list-view--backwards'; + } + }; + + onInit(); + } var directive = { @@ -36,11 +197,9 @@ templateUrl: 'views/components/umb-mini-list-view.html', scope: { node: "=", - children: "=", - onSelectChild: "&", - onOpenChild: "&", - onSearch: "&", - loadingChildren: "=" + entityType: "@", + startNodeId: "=", + onSelect: "&" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index 125384b57e..baf775d4d9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -22,17 +22,17 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", $scope.init = function(contentType) { if(contentType === "content") { - entityType = "Document"; + $scope.entityType = "Document"; if(!$scope.model.title) { $scope.model.title = localizationService.localize("defaultdialogs_selectContent"); } } else if(contentType === "member") { - entityType = "Member"; + $scope.entityType = "Member"; if(!$scope.model.title) { $scope.model.title = localizationService.localize("defaultdialogs_selectMember"); } } else if(contentType === "media") { - entityType = "Media"; + $scope.entityType = "Media"; if(!$scope.model.title) { $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); } @@ -49,7 +49,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", }); // Allow the entity type to be passed in but defaults to Document for backwards compatibility. - var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; + $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; //min / max values @@ -61,10 +61,10 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", } if (dialogOptions.section === "member") { - entityType = "Member"; + $scope.entityType = "Member"; } else if (dialogOptions.section === "media") { - entityType = "Media"; + $scope.entityType = "Media"; } // Search and listviews is only working for content, media and member section so we will remove it from everything else @@ -73,7 +73,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", //if a alternative startnode is used, we need to check if it is a container if (dialogOptions.startNodeId && dialogOptions.startNodeId !== -1) { - entityResource.getById(dialogOptions.startNodeId, entityType).then(function (node) { + entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function (node) { if (node.metaData.IsContainer) { openMiniListView(node); } @@ -193,7 +193,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", multiSelectItem(entity); } else { //otherwise we have to get it from the server - entityResource.getById(id, entityType).then(function (ent) { + entityResource.getById(id, $scope.entityType).then(function (ent) { multiSelectItem(ent); }); } @@ -218,7 +218,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", multiSelectItem(entity); } else { //otherwise we have to get it from the server - entityResource.getById(id, entityType).then(function (ent) { + entityResource.getById(id, $scope.entityType).then(function (ent) { multiSelectItem(ent); }); } @@ -235,7 +235,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", $scope.model.submit($scope.model); } else { //otherwise we have to get it from the server - entityResource.getById(id, entityType).then(function (ent) { + entityResource.getById(id, $scope.entityType).then(function (ent) { $scope.model.selection.push(ent); $scope.model.submit($scope.model); }); @@ -316,7 +316,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", } $scope.multiSubmit = function (result) { - entityResource.getByIds(result, entityType).then(function (ents) { + entityResource.getByIds(result, $scope.entityType).then(function (ents) { $scope.submit(ents); }); }; @@ -466,179 +466,14 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); }); - /* --- Mini List View --- */ - $scope.miniListViews = []; - $scope.breadcrumb = []; - var miniListViewsHistory = []; - var goingForward = true; - - $scope.goToPage = function(pageNumber, miniListView) { - // set new page number - miniListView.pagination.pageNumber = pageNumber; - // get children - getChildrenForMiniListView(miniListView); - }; - - $scope.selectListViewItem = function(item) { - select(item.name, item.id); + $scope.selectListViewNode = function(node) { + select(node.name, node.id); //toggle checked state - item.selected = item.selected === true ? false : true; + node.selected = node.selected === true ? false : true; }; - $scope.clickBreadcrumb = function(ancestor) { - - var found = false; - goingForward = false; - - angular.forEach(miniListViewsHistory, function(historyItem, index){ - if(parseInt(historyItem.node.id) === parseInt(ancestor.id)) { - // load the list view from history - $scope.miniListViews = []; - $scope.miniListViews.push(historyItem); - // get ancestors - getAncestors(historyItem.node); - found = true; - } - }); - - if(!found) { - // if we can't find the view in the history - miniListViewsHistory = []; - $scope.miniListViews = []; - } - - }; - - $scope.searchMiniListView = function(search, miniListView) { - // set search value - miniListView.pagination.filter = search; - // start loading animation list view - miniListView.loading = true; - searchMiniListView(miniListView); - }; - - $scope.test = function(node) { - openMiniListView(node); - }; - - var searchMiniListView = _.debounce(function (miniListView) { - $scope.$apply(function () { - getChildrenForMiniListView(miniListView); - }); - }, 500); - function openMiniListView(node) { - - goingForward = true; - - var miniListView = { - node: node, - loading: true, - pagination: { - pageSize: 10, - pageNumber: 1, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true - } - }; - - // start loading animation on node - node.loading = true; - - getChildrenForMiniListView(miniListView) - .then(function(data){ - - // stop loading animation on node - node.loading = false; - - // clear and push mini list view in dom so we only render 1 view - $scope.miniListViews = []; - $scope.miniListViews.push(miniListView); - - // store in history so we quickly can navigate back - miniListViewsHistory.push(miniListView); - - }); - - // get ancestors - getAncestors(node); - - } - - function getChildrenForMiniListView(miniListView) { - - // setup promise - var deferred = $q.defer(); - - // start loading animation list view - miniListView.loading = true; - - // setup the correct resource depending on section - var resource = ""; - - if (entityType === "Document") { - resource = contentResource.getChildren; - } else if (entityType === "Member") { - resource = memberResource.getPagedResults; - } else if (entityType === "Media") { - resource = mediaResource.getChildren; - } - - resource(miniListView.node.id, miniListView.pagination) - .then(function (data) { - - console.log("data, data, data", data); - - // update children - miniListView.children = data.items; - - // update pagination - miniListView.pagination.totalItems = data.totalItems; - miniListView.pagination.totalPages = data.totalPages; - - // stop load indicator - miniListView.loading = false; - - deferred.resolve(data); - - }); - - return deferred.promise; - - } - - function getAncestors(node) { - entityResource.getAncestors(node.id, entityType) - .then(function (ancestors) { - - // if there is a start node remove all ancestors before that one - if(dialogOptions.startNodeId && dialogOptions.startNodeId !== -1) { - var found = false; - $scope.breadcrumb = []; - angular.forEach(ancestors, function(ancestor){ - if(parseInt(ancestor.id) === parseInt(dialogOptions.startNodeId)) { - found = true; - } - if(found) { - $scope.breadcrumb.push(ancestor); - } - }); - - } else { - $scope.breadcrumb = ancestors; - } - - }); - } - - $scope.getMiniListViewAnimation = function() { - if(goingForward) { - return 'umb-mini-list-view--forward'; - } else { - return 'umb-mini-list-view--backwards'; - } + $scope.miniListView = node; } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index 4ecb6c1b9e..f89ff9faf8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -1,6 +1,6 @@
-
+
- -
-
- -

{{ miniListView.node.name }}

-
- - - - - - - -
- - -
- -
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html index db3c6b1f3e..8bb584807d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html @@ -1,65 +1,91 @@
-
+
- -
-
- -
- -
- - -
- -
-
+
+ +

{{ miniListView.node.name }}

- -
+ + - -
- -
+
- -
-
-
-   - - + +
+
+ +
+
-
{{ child.name }}
- -
- - + +
+ + +
+ +
+ + +
+
+
+   + + +
+
+
{{ child.name }}
+
+ + +
+ + +
+ + +
+ +
+
- -
- -
+
+
+ +
From 548f9fd01fcc278becb520e67eef7a3dc5189d59 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 8 Feb 2017 15:02:03 +0100 Subject: [PATCH 057/105] open mini list view before children are loaded --- .../components/umbminilistview.directive.js | 35 +++++-------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 631004e218..8e5fa8cee2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - function MiniListViewDirective($q, contentResource, memberResource, mediaResource, entityResource) { + function MiniListViewDirective(contentResource, memberResource, mediaResource, entityResource) { function link(scope, el, attr, ctrl) { @@ -33,23 +33,15 @@ } }; - // start loading animation on node - node.loading = true; + // clear and push mini list view in dom so we only render 1 view + scope.miniListViews = []; + scope.miniListViews.push(miniListView); - getChildrenForMiniListView(miniListView) - .then(function (data) { + // store in history so we quickly can navigate back + miniListViewsHistory.push(miniListView); - // stop loading animation on node - node.loading = false; - - // clear and push mini list view in dom so we only render 1 view - scope.miniListViews = []; - scope.miniListViews.push(miniListView); - - // store in history so we quickly can navigate back - miniListViewsHistory.push(miniListView); - - }); + // get children + getChildrenForMiniListView(miniListView); // get ancestors getAncestors(node); @@ -58,9 +50,6 @@ function getChildrenForMiniListView(miniListView) { - // setup promise - var deferred = $q.defer(); - // start loading animation list view miniListView.loading = true; @@ -77,22 +66,14 @@ resource(miniListView.node.id, miniListView.pagination) .then(function (data) { - // update children miniListView.children = data.items; - // update pagination miniListView.pagination.totalItems = data.totalItems; miniListView.pagination.totalPages = data.totalPages; - // stop load indicator miniListView.loading = false; - - deferred.resolve(data); }); - - return deferred.promise; - } scope.openNode = function(event, node) { From e4802b592f14065a0442c0185e58bd8f620cab73 Mon Sep 17 00:00:00 2001 From: Emil Wangaa Date: Wed, 8 Feb 2017 15:43:31 +0100 Subject: [PATCH 058/105] Centralized the tree menu logic for partialviews, partialviewmacros and scripts to the FileSystemTreeController and implemented logic to match what we do for Document Types --- .../Trees/FileSystemTreeController.cs | 69 +++++++++++++++++-- .../Trees/PartialViewMacrosTreeController.cs | 42 ++--------- .../Trees/PartialViewsTreeController.cs | 34 --------- src/Umbraco.Web/Trees/ScriptTreeController.cs | 37 +--------- 4 files changed, 68 insertions(+), 114 deletions(-) diff --git a/src/Umbraco.Web/Trees/FileSystemTreeController.cs b/src/Umbraco.Web/Trees/FileSystemTreeController.cs index 61ee8c4ff9..21981ca3c0 100644 --- a/src/Umbraco.Web/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web/Trees/FileSystemTreeController.cs @@ -1,10 +1,10 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Net.Http.Formatting; +using umbraco.BusinessLogic.Actions; +using Umbraco.Core; using Umbraco.Core.IO; +using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Trees @@ -27,7 +27,7 @@ namespace Umbraco.Web.Trees /// protected virtual void OnRenderFolderNode(ref TreeNode treeNode) { } - protected override Models.Trees.TreeNodeCollection GetTreeNodes(string id, System.Net.Http.Formatting.FormDataCollection queryStrings) + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { string orgPath = ""; string path = ""; @@ -90,6 +90,63 @@ namespace Umbraco.Web.Trees } return nodes; - } + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + //if root node no need to visit the filesystem so lets just create the menu and return it + if (id == Constants.System.Root.ToInvariantString()) + { + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + //create action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + //refresh action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + + return menu; + } + + string path; + if (string.IsNullOrEmpty(id) == false) + { + var orgPath = System.Web.HttpUtility.UrlDecode(id); + path = IOHelper.MapPath(FilePath + "/" + orgPath); + } + else + { + path = IOHelper.MapPath(FilePath); + } + + var dirInfo = new DirectoryInfo(path); + //check if it's a directory + if (dirInfo.Attributes == FileAttributes.Directory) + { + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + //create action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + //refresh action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + + var hasChildren = dirInfo.GetFiles().Length > 0 || dirInfo.GetDirectories().Length > 0; + //We can only delete folders if it doesn't have any children (folders or files) + if (hasChildren == false) + { + //delete action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + } + } + //if it's not a directory then we only allow to delete the item + else + { + //delete action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + } + + return menu; + } } } diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs index 61d9c17cef..9643823e7b 100644 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs @@ -1,16 +1,13 @@ using Umbraco.Core; using Umbraco.Core.IO; -using umbraco.BusinessLogic.Actions; using Umbraco.Web.Models.Trees; -using System.Net.Http.Formatting; -using Umbraco.Core.Services; namespace Umbraco.Web.Trees { - /// - /// Tree for displaying partial view macros in the developer app - /// - [Tree(Constants.Applications.Developer, "partialViewMacros", "Partial View Macro Files", sortOrder: 6)] + /// + /// Tree for displaying partial view macros in the developer app + /// + [Tree(Constants.Applications.Developer, "partialViewMacros", "Partial View Macro Files", sortOrder: 6)] public class PartialViewMacrosTreeController : FileSystemTreeController { protected override string FilePath @@ -28,37 +25,6 @@ namespace Umbraco.Web.Trees get { return "icon-article"; } } - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - //set the default to create - menu.DefaultMenuAlias = ActionNew.Instance.Alias; - //create action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - //refresh action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - - return menu; - } - - if (id.EndsWith(FileSearchPattern.TrimStart("*")) == false) - { - //set the default to create - menu.DefaultMenuAlias = ActionNew.Instance.Alias; - //create action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - //refresh action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - } - - // TODO: Wire up new delete dialog - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - return menu; - } - protected override void OnRenderFileNode(ref TreeNode treeNode) { base.OnRenderFileNode(ref treeNode); diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs index 96da9a2e33..07a0a557af 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -1,9 +1,6 @@ using Umbraco.Core; using Umbraco.Core.IO; -using umbraco.BusinessLogic.Actions; using Umbraco.Web.Models.Trees; -using System.Net.Http.Formatting; -using Umbraco.Core.Services; namespace Umbraco.Web.Trees { @@ -27,37 +24,6 @@ namespace Umbraco.Web.Trees get { return "icon-article"; } } - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - //set the default to create - menu.DefaultMenuAlias = ActionNew.Instance.Alias; - //create action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - //refresh action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - - return menu; - } - - if (id.EndsWith(FileSearchPattern.TrimStart("*")) == false) - { - //set the default to create - menu.DefaultMenuAlias = ActionNew.Instance.Alias; - //create action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - //refresh action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - } - - // TODO: Wire up new delete dialog - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - return menu; - } - protected override void OnRenderFileNode(ref TreeNode treeNode) { base.OnRenderFileNode(ref treeNode); diff --git a/src/Umbraco.Web/Trees/ScriptTreeController.cs b/src/Umbraco.Web/Trees/ScriptTreeController.cs index c7d4e728aa..f491989228 100644 --- a/src/Umbraco.Web/Trees/ScriptTreeController.cs +++ b/src/Umbraco.Web/Trees/ScriptTreeController.cs @@ -1,10 +1,6 @@ -using System.Linq; -using Umbraco.Core; +using Umbraco.Core; using Umbraco.Core.IO; -using umbraco.BusinessLogic.Actions; using Umbraco.Web.Models.Trees; -using System.Net.Http.Formatting; -using Umbraco.Core.Services; namespace Umbraco.Web.Trees { @@ -25,37 +21,6 @@ namespace Umbraco.Web.Trees get { return "icon-script"; } } - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - //set the default to create - menu.DefaultMenuAlias = ActionNew.Instance.Alias; - //create action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - //refresh action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - - return menu; - } - - if (id.EndsWith(FileSearchPattern.TrimStart("*")) == false) - { - //set the default to create - menu.DefaultMenuAlias = ActionNew.Instance.Alias; - //create action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - //refresh action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - } - - // TODO: Wire up new delete dialog - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - return menu; - } - protected override void OnRenderFolderNode(ref TreeNode treeNode) { //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. From a31cfb86f8bf958fd2a074a53edd6034e9fd0fb9 Mon Sep 17 00:00:00 2001 From: Emil Wangaa Date: Wed, 8 Feb 2017 15:57:53 +0100 Subject: [PATCH 059/105] Moved reload to be last menu option and added a separator for delete option to keep consistency --- src/Umbraco.Web/Trees/FileSystemTreeController.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Trees/FileSystemTreeController.cs b/src/Umbraco.Web/Trees/FileSystemTreeController.cs index 21981ca3c0..88f322971d 100644 --- a/src/Umbraco.Web/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web/Trees/FileSystemTreeController.cs @@ -128,16 +128,17 @@ namespace Umbraco.Web.Trees menu.DefaultMenuAlias = ActionNew.Instance.Alias; //create action menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - //refresh action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - + var hasChildren = dirInfo.GetFiles().Length > 0 || dirInfo.GetDirectories().Length > 0; //We can only delete folders if it doesn't have any children (folders or files) if (hasChildren == false) { //delete action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)), true); } + + //refresh action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); } //if it's not a directory then we only allow to delete the item else From bb31eb7c35efe293d5e05926875c0f9a8877f759 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 8 Feb 2017 16:19:47 +0100 Subject: [PATCH 060/105] Emit only relative paths for files in a package --- src/Umbraco.Core/IO/IOHelper.cs | 31 ++++++++++++++ .../Editors/PackageInstallController.cs | 40 +++++++++---------- .../PackageInstance/InstalledPackage.cs | 8 +--- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index de27f13748..5ce851bdb4 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -367,5 +367,36 @@ namespace Umbraco.Core.IO && Path.IsPathRooted(path) && Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false; } + + /// + /// Get properly formatted relative path from an existing absolute or relative path + /// + /// + /// + internal static string GetRelativePath(this string path) + { + if (path.IsFullPath()) + { + var rootDirectory = GetRootDirectorySafe(); + var relativePath = path.ToLowerInvariant().Replace(rootDirectory.ToLowerInvariant(), string.Empty); + path = relativePath; + } + + return path.EnsurePathIsPrefixed(); + } + + /// + /// Ensures that a path has `~/` as prefix + /// + /// + /// + internal static string EnsurePathIsPrefixed(this string path) + { + if (path.StartsWith("/") == false && path.StartsWith("\\") == false) + path = string.Format("/{0}", path); + if (path.StartsWith("~") == false) + path = string.Format("~{0}", path); + return path; + } } } diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 2a08d3955f..f3b990ca41 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -100,8 +100,6 @@ namespace Umbraco.Web.Editors if (found != null) { removedTemplates.Add(found); - // add the actual file here, since deleting the template cause a file delete - removedFiles.Add(found.VirtualPath); ApplicationContext.Services.FileService.DeleteTemplate(found.Alias, Security.GetUserId()); } pack.Data.Templates.Remove(nId.ToString()); @@ -117,7 +115,7 @@ namespace Umbraco.Web.Editors { removedMacros.Add(macro); Services.MacroService.Delete(macro); - } + } pack.Data.Macros.Remove(nId.ToString()); } @@ -140,8 +138,8 @@ namespace Umbraco.Web.Editors if (contentTypes.Any()) { var orderedTypes = from contentType in contentTypes - orderby contentType.ParentId descending, contentType.Id descending - select contentType; + orderby contentType.ParentId descending, contentType.Id descending + select contentType; foreach (var contentType in orderedTypes) { removedContentTypes.Add(contentType); @@ -159,7 +157,7 @@ namespace Umbraco.Web.Editors { removedDictionaryItems.Add(di); Services.LocalizationService.Delete(di); - } + } pack.Data.DictionaryItems.Remove(nId.ToString()); } @@ -173,7 +171,7 @@ namespace Umbraco.Web.Editors { removedDataTypes.Add(dtd); Services.DataTypeService.Delete(dtd); - } + } pack.Data.DataTypes.Remove(nId.ToString()); } @@ -214,15 +212,15 @@ namespace Umbraco.Web.Editors //Remove files foreach (var item in pack.Data.Files.ToArray()) { + removedFiles.Add(item.GetRelativePath()); + //here we need to try to find the file in question as most packages does not support the tilde char var file = IOHelper.FindFile(item); if (file != null) { if (file.StartsWith("/") == false) file = string.Format("/{0}", file); - var filePath = IOHelper.MapPath(file); - removedFiles.Add(filePath); if (File.Exists(filePath)) File.Delete(filePath); @@ -252,7 +250,7 @@ namespace Umbraco.Web.Editors if (refreshCache) { library.RefreshContent(); - } + } TreeDefinitionCollection.Instance.ReRegisterTrees(); global::umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers(); } @@ -272,8 +270,8 @@ namespace Umbraco.Web.Editors { Version pckVersion; return Version.TryParse(pck.Data.Version, out pckVersion) - ? new {package = pck, version = pckVersion} - : new {package = pck, version = new Version(0, 0, 0)}; + ? new { package = pck, version = pckVersion } + : new { package = pck, version = new Version(0, 0, 0) }; }) .Select(grouping => { @@ -344,7 +342,7 @@ namespace Umbraco.Web.Editors model.UmbracoVersion = ins.RequirementsType == RequirementsType.Strict ? string.Format("{0}.{1}.{2}", ins.RequirementsMajor, ins.RequirementsMinor, ins.RequirementsPatch) : string.Empty; - + //now we need to check for version comparison model.IsCompatible = true; if (ins.RequirementsType == RequirementsType.Strict) @@ -424,7 +422,7 @@ namespace Umbraco.Web.Editors { //TODO: Currently it has to be here, it's not ideal but that's the way it is right now var packageTempDir = IOHelper.MapPath(SystemDirectories.Data); - + //ensure it's there Directory.CreateDirectory(packageTempDir); @@ -440,14 +438,14 @@ namespace Umbraco.Web.Editors PopulateFromPackageData(model); var validate = ValidateInstalledInternal(model.Name, model.Version); - + if (validate == false) { //this package is already installed throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("packager/packageAlreadyInstalled"))); + Services.TextService.Localize("packager/packageAlreadyInstalled"))); } - + } else { @@ -456,7 +454,7 @@ namespace Umbraco.Web.Editors Services.TextService.Localize("media/disallowedFileType"), SpeechBubbleIcon.Warning)); } - + } return model; @@ -478,7 +476,7 @@ namespace Umbraco.Web.Editors //our repo guid using (var our = Repository.getByGuid("65194810-1f85-11dd-bd0b-0800200c9a66")) { - path = our.fetch(packageGuid, Security.CurrentUser.Id); + path = our.fetch(packageGuid, Security.CurrentUser.Id); } } @@ -522,12 +520,12 @@ namespace Umbraco.Web.Editors if (UmbracoVersion.Current < packageMinVersion) { throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("packager/targetVersionMismatch", new[] {packageMinVersion.ToString()}))); + Services.TextService.Localize("packager/targetVersionMismatch", new[] { packageMinVersion.ToString() }))); } } model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, tempPath); - model.Id = ins.CreateManifest( IOHelper.MapPath(model.TemporaryDirectoryPath), model.PackageGuid.ToString(), model.RepositoryGuid.ToString()); + model.Id = ins.CreateManifest(IOHelper.MapPath(model.TemporaryDirectoryPath), model.PackageGuid.ToString(), model.RepositoryGuid.ToString()); return model; } diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs index dc66592cff..172a9d6443 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs @@ -149,13 +149,7 @@ namespace umbraco.cms.businesslogic.packager { for (var i = 0; i < Data.Files.Count; i++) { var filePath = Data.Files[i]; - if (filePath.IsFullPath()) - continue; - - filePath = filePath.TrimStart('~'); - if (filePath.StartsWith("/") == false) - filePath = string.Format("/{0}", filePath); - Data.Files[i] = IOHelper.MapPath(filePath); + Data.Files[i] = filePath.GetRelativePath(); } return new InstallationSummary From 2785eaeda5b4f9aa7f94e25c41cad3d0f151790f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 8 Feb 2017 20:22:54 +0100 Subject: [PATCH 061/105] only show unpublished state for content --- .../src/views/components/umb-mini-list-view.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html index 8bb584807d..b08bd13952 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html @@ -54,14 +54,14 @@ ng-repeat="child in miniListView.children" ng-click="selectNode(child)" ng-class="{'-selected':child.selected}"> -
+
 
-
{{ child.name }}
+
{{ child.name }}
From f13427f5fb7350ea0fac72b60b490b57fda48216 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 8 Feb 2017 22:49:16 +0100 Subject: [PATCH 062/105] fixes to breadcrumb --- .../components/umbminilistview.directive.js | 59 ++++++++++--------- .../less/components/umb-mini-list-view.less | 9 ++- .../treepicker/treepicker.controller.js | 4 ++ .../overlays/treepicker/treepicker.html | 3 +- .../views/components/umb-mini-list-view.html | 22 ++++--- 5 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 8e5fa8cee2..1ef7452848 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - function MiniListViewDirective(contentResource, memberResource, mediaResource, entityResource) { + function MiniListViewDirective(contentResource, memberResource, mediaResource) { function link(scope, el, attr, ctrl) { @@ -43,8 +43,7 @@ // get children getChildrenForMiniListView(miniListView); - // get ancestors - getAncestors(node); + makeBreadcrumb(); } @@ -102,12 +101,15 @@ goingForward = false; angular.forEach(miniListViewsHistory, function(historyItem, index){ - if(Number(historyItem.node.id) === Number(ancestor.id)) { + // We need to make sure we can compare the two id's. + // Some id's are numbers (1) and others are string numbers. + // Members have string ids like "all-members". + if(historyItem.node.id.toString() === ancestor.id.toString()) { // load the list view from history scope.miniListViews = []; scope.miniListViews.push(historyItem); - // get ancestors - getAncestors(historyItem.node); + // clean up history - remove all children after + miniListViewsHistory.splice(index + 1, miniListViewsHistory.length); found = true; } }); @@ -118,30 +120,32 @@ scope.miniListViews = []; } + makeBreadcrumb(); + }; - function getAncestors(node) { - entityResource.getAncestors(node.id, scope.entityType) - .then(function (ancestors) { + scope.showBackButton = function() { + // don't show the back button if the start node is a list view + if(scope.node.metaData && scope.node.metaData.IsContainer || scope.node.isContainer) { + return false; + } else { + return true; + } + }; - // if there is a start node remove all ancestors before that one - if(scope.startNodeId && scope.startNodeId !== -1) { - var found = false; - scope.breadcrumb = []; - angular.forEach(ancestors, function(ancestor){ - if(Number(ancestor.id) === Number(scope.startNodeId)) { - found = true; - } - if(found) { - scope.breadcrumb.push(ancestor); - } - }); + scope.exitMiniListView = function() { + miniListViewsHistory = []; + scope.miniListViews = []; + if(scope.onClose) { + scope.onClose(); + } + }; - } else { - scope.breadcrumb = ancestors; - } - - }); + function makeBreadcrumb() { + scope.breadcrumb = []; + angular.forEach(miniListViewsHistory, function(historyItem){ + scope.breadcrumb.push(historyItem.node); + }); } /* Search */ @@ -180,7 +184,8 @@ node: "=", entityType: "@", startNodeId: "=", - onSelect: "&" + onSelect: "&", + onClose: "&" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less index 92871abd34..c32aee2de1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less @@ -14,10 +14,13 @@ } .umb-mini-list-view__back { - font-size: 12px; + font-size: 11px; margin-right: 5px; - color: @grayMed; - display: block; + color: @gray; +} + +.umb-mini-list-view__back-text { + text-decoration: underline; } .umb-mini-list-view__back:hover { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index baf775d4d9..1caff3ab31 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -472,6 +472,10 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", node.selected = node.selected === true ? false : true; }; + $scope.closeMiniListView = function() { + $scope.miniListView = undefined; + }; + function openMiniListView(node) { $scope.miniListView = node; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index f89ff9faf8..c9785ed9e1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -42,7 +42,8 @@ node="miniListView" entity-type="{{entityType}}" start-node-id="model.startNodeId" - on-select="selectListViewNode(node)"> + on-select="selectListViewNode(node)" + on-close="closeMiniListView()">
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html index b08bd13952..27c33ee9a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html @@ -7,13 +7,21 @@

{{ miniListView.node.name }}

- - +
+ + + + Back / + + + + + +
From 5f22c81994e10238c608ea57b6775f47f00d305c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 8 Feb 2017 23:37:33 +0100 Subject: [PATCH 063/105] =?UTF-8?q?fixes:=20U4-9467=20Mini=20list=20view:?= =?UTF-8?q?=20don=E2=80=99t=20animate=20list=20view=20if=20it=20is=20a=20s?= =?UTF-8?q?tart=20node?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../directives/components/umbminilistview.directive.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 1ef7452848..717caf15ed 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -11,6 +11,7 @@ var miniListViewsHistory = []; var goingForward = true; + var skipAnimation = true; function onInit() { open(scope.node); @@ -165,6 +166,13 @@ /* Animation */ scope.getMiniListViewAnimation = function() { + + // disable the first "slide-in-animation"" if the start node is a list view + if(scope.node.metaData && scope.node.metaData.IsContainer && skipAnimation || scope.node.isContainer && skipAnimation) { + skipAnimation = false; + return; + } + if(goingForward) { return 'umb-mini-list-view--forward'; } else { From 49c2ae367313f07c969c678ad64f007fc665a4ef Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 8 Feb 2017 23:37:48 +0100 Subject: [PATCH 064/105] cleanup --- .../directives/components/umbminilistview.directive.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 717caf15ed..1a04eeff65 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -103,7 +103,7 @@ angular.forEach(miniListViewsHistory, function(historyItem, index){ // We need to make sure we can compare the two id's. - // Some id's are numbers (1) and others are string numbers. + // Some id's are integers and others are strings. // Members have string ids like "all-members". if(historyItem.node.id.toString() === ancestor.id.toString()) { // load the list view from history @@ -116,11 +116,11 @@ }); if(!found) { - // if we can't find the view in the history - miniListViewsHistory = []; - scope.miniListViews = []; + // if we can't find the view in the history - close the list view + scope.exitMiniListView(); } + // update the breadcrumb makeBreadcrumb(); }; From 1de52482e37c1a832b2c4a6a998b50c8065993e5 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 9 Feb 2017 09:23:30 +0100 Subject: [PATCH 065/105] fix back arrow alignment --- .../src/less/components/umb-mini-list-view.less | 9 +++++++++ .../src/views/components/umb-mini-list-view.html | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less index c32aee2de1..1804b45260 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less @@ -17,10 +17,19 @@ font-size: 11px; margin-right: 5px; color: @gray; + display: flex; + align-items: center; +} + +.umb-mini-list-view__back-icon { + margin-right: 4px; + height: 11px; + line-height: 11px; } .umb-mini-list-view__back-text { text-decoration: underline; + margin-right: 5px; } .umb-mini-list-view__back:hover { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html index 27c33ee9a3..97884a6d9e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html @@ -10,7 +10,7 @@
- + Back / From 1901fbb6edd538236e2ee3bda2a368464130bcda Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 9 Feb 2017 10:42:15 +0100 Subject: [PATCH 066/105] add markup for member picker and content picker --- .../overlays/contentpicker/contentpicker.html | 70 +++++++++++-------- .../overlays/memberpicker/memberpicker.html | 69 ++++++++++-------- 2 files changed, 83 insertions(+), 56 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html index c22b539b38..43eab532d4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html @@ -1,34 +1,48 @@
-
- - +
+ +
+ + +
+ + + + +
+ + +
+
- - - -
- - -
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/memberpicker/memberpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/memberpicker/memberpicker.html index e4a8d36263..00475dddfe 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/memberpicker/memberpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/memberpicker/memberpicker.html @@ -1,34 +1,47 @@
-
- - -
+
- - +
+ + +
-
- - -
+ + + +
+ + +
+
+ + +
From 7da7c1e5a1e90a99ac6e8c50d53dee765cc5407f Mon Sep 17 00:00:00 2001 From: Arnold Visser Date: Thu, 9 Feb 2017 13:47:31 +0100 Subject: [PATCH 067/105] Addes all new/missing fields since last edit Checked against the English version --- src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 582 +++++++++++++++--- 1 file changed, 492 insertions(+), 90 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index ce89979502..84ef529bdb 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -27,6 +27,10 @@ Depubliceren Nodes opnieuw inladen Herpubliceer de site + Stel rechten voor pagina %0% in + Waarheen verplaatsen/ + Naar de onderstaande boomstructuur + Herstellen Rechten Vorige versies Klaar voor publicatie @@ -64,10 +68,10 @@ Tonen voor + Selectie ongedaan maken Selecteren Selecteer huidige map Doe iets anders - Vet Paragraaf uitspringen Voeg formulierveld in @@ -89,11 +93,14 @@ Opslaan Opslaan en publiceren Opslaan en verzenden voor goedkeuring + Sla list view op voorbeeld bekijken Voorbeeld bekijken is uitgeschakeld omdat er geen template is geselecteerd Stijl kiezen Stijlen tonen Tabel invoegen + Genereer models + Opslaan en models genereren Om het documenttype voor de geselecteerde inhoud te wijzigen, selecteert u eerst uit de lijst van geldige types voor deze locatie. @@ -133,7 +140,8 @@ Dit item is gewijzigd na publicatie Dit item is niet gepubliceerd Laatst gepubliceerd op - Nog geen items om weer te geven. + Er zijn geen items om weer te geven + Er zijn geen items om weer te geven. Mediatype Link naar media item(s) Ledengroep @@ -143,7 +151,9 @@ Pagina Titel Eigenschappen Dit document is gepubliceerd maar niet zichtbaar omdat de bovenliggende node '%0%' niet gepubliceerd is - Oeps: dit document is gepubliceerd, maar het is niet in de cache (interne serverfout) + Dit document is gepubliceerd, maar het is niet in de cache (interne serverfout) + Kan de Url niet ophalen + Dit document is gepubliceerd, maar de Url conflicteert met %0% Publiceren Publicatiestatus Publiceren op @@ -161,23 +171,34 @@ Bestand(en) verwijderen Link naar het document Lid van groep(en) - Geen lid van groep(en) - + Geen lid van groep(en) Kinderen Doel + Dit betekend de volgende tijd op de server: + Wat houd dit in?]]> Klik om te uploaden Plaats je bestanden hier... + Link naar media + Of klik hier om bestanden te kiezen + De toegestane bestandtypen zijn + Dit bestand heeft niet het juiste file-type. Dit bestand kan niet geupload worden. + Max file size is + + + Maak nieuwe member aan + Alle Members Waar wil je de nieuwe %0% aanmaken? Aanmaken onder Kies een type en een titel - "Documenttypes".]]> - "Mediatypes".]]> + Document Type zonder template + Nieuwe folder + Nieuw data type Open je website @@ -196,27 +217,27 @@ Done - + %0% item verwijderd %0% items verwijderd Item %0% van de %1% verwijderd Items %0% van de %1% verwijderd - + %0% item gepubliceerd %0% items gepubliceerd Item %0% van de %1% gepubliceerd Items %0% van de %1% gepubliceerd - + %0% item gedepubliceerd %0% items gedepubliceerd Item %0% van de %1% gedepubliceerd Items %0% van de %1% gedepubliceerd - + %0% item verplaatst %0% items verplaatst item %0% van de %1% verplaatst items %0% van de %1% verplaatst - + %0% item gekopieerd %0% items gekopieerd item %0% van de %1% gekopieerd @@ -269,21 +290,55 @@ Klik op de afbeelding voor volledige grootte Kies een item Toon cache item + Maak folder aan... + Relateer aan origineel + Descendants meenemen + De vriendelijkste community + Link naar pagina + Opent het gelinkte document in een nieuw venster of tab + Link naar media + Selecteer media + Selecteer icoon + Selecteer item + Selecteer link + Selecteer macro + Selecteer content + Selecteer member + Selecteer member group + Er zijn geen parameters voor deze macro + Externe login providers + Error details + Stacktrace + Inner Exception + Link je + De-Link je + account + Selecteer editor Cultuurnaam + Verander de key van het dictionary item. + + + - Typ je gebruikersnaam - Typ je wachtwoord + Typ jouw gebruikersnaam + Typ jouw wachtwoord + Bevestig jouw wachtwoord Benoem de %0%... Typ een naam... + Label... + Voer een omschrijving in... Typ om te zoeken... Typ om te filteren... Typ om tags toe te voegen (druk op enter na elke tag)... + Voer jouw email in @@ -331,10 +386,17 @@ %0% is niet in het correcte formaat + Een error ontvangen van de server Het opgegeven bestandstype is niet toegestaan ​​door de beheerder OPMERKING! Ondanks dat CodeMiror is ingeschakeld, is het uitgeschakeld in Internet Explorer omdat het niet stabiel genoeg is. Zowel de alias als de naam van het nieuwe eigenschappen type moeten worden ingevuld! Er is een probleem met de lees/schrijf rechten op een bestand of map + Error bij het laden van Partial View script (file: %0%) + Error bij het laden van userControl '%0%' + Error bij het laden van customControl (Assembly: %0%, Type: '%1%') + Error bij het laden van MacroEngine script (file: %0%) + "Error bij het parsen van XSLT file: %0% + "Error bij het laden van XSLT file: %0% Vul een titel in Selecteer een type U wilt een afbeelding groter maken dan de originele afmetingen. Weet je zeker dat je wilt doorgaan? @@ -355,102 +417,142 @@ Acties Toevoegen Alias + Alles Weet je het zeker? - Rand - of - Annuleren + Terug + Border + bij + Cancel Cel marge - Kiezen - Sluiten - Venster sluiten - Opmerking - Bevestigen - Verhouding behouden - Doorgaan - Kopiëren + Kies + Sluit + Sluit venster + Comment + Bevestig + Verhoudingen behouden + Ga verder + Copy Aanmaken - Databank + Database Datum Standaard - Verwijderen + Verwijder Verwijderd - Verwijderen... + Aan het verwijderen... Ontwerp Afmetingen - Beneden + Omlaag Download - Aanpassen - Aangepast + Bewerk + Bewerkt Elementen - E-mail + Email Fout - Zoeken - Hoogte + Vind + Hogte Help Icoon - Importeren - Binnenmarge + Import + Binnenste marge Invoegen Installeren - Uitvullen + Ongeldig + Justify + Label Taal - Lay-out - Bezig met laden - Geblokkeerd + Layout + Aan het laden + Gesloten Inloggen - Afmelden - Afmelden + Uitloggen + Uitloggen Macro - Verplaatsen - Meer + Verplicht + Verplaats + meer Naam Nieuw Volgende Nee - van - Ok - Openen + of + OK + Open of Wachtwoord Pad Placeholder ID - Een ogenblik geduld a.u.b. + Een ogenblik geduld aub... Vorige Eigenschappen - E-mail om formulier te ontvangen + Email om formulier resultaten te ontvangen Prullenbak - Overgebleven - Hernoemen + De prullenbak is leeg + Overblijvend + Hernoem Vernieuw Verplicht Opnieuw proberen Rechten - Zoek + Zoeken + We konden helaas niet vinden wat je zocht Server - Tonen - Toon pagina bij versturen - Formaat - Sorteren + Toon + Toon pagina na verzenden + Grootte + Sorteer Verstuur - Type - Type om te zoeken... + Typen + Typ om te zoeken... Omhoog - Bijwerken + Update Upgrade Upload Url Gebruiker Gebruikersnaam Waarde - Toon + Bekijk Welkom... Breedte Ja Map - Herschikken - Klaar met herschikken - Zoekresultaten + Herschik + Ik ben klaar met herschikken + Voorvertoning + Wachtwoord veranderen + naar + Lijstweergave + Aan het opslaan... + huidig + Embed + geselecteerd + + + Zwart + Groen + Geel + Oranje + Blauw + Rood + + + Tab toevoegen + Property toevoegen + Editor toevoegen + Template toevoegen + Child node toevoegen + Child toevoegen + + Data type bewerken + + Secties navigeren + + Shortcuts + Toon shortcuts + + Toggle lijstweergave + Toggle toestaan op root-niveau Achtergrondkleur @@ -528,6 +630,57 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Bekijken Umbraco %0% voor een nieuwe installatie of een upgrade van versie 3.0.

Druk op "volgende" om de wizard te starten.]]>
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cultuurcode Cultuurnaam @@ -543,12 +696,22 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Fijne woensdag Fijne donderdag Fijne vrijdag - Fijne zaterdag - + Fijne zaterdag log hieronder in + Inloggen met Sessie is verlopen - © 2001 - %0%
umbraco.com

]]>
+ Wachtwoord vergeten? + Er zal een email worden gestuurd naar het emailadres van jouw account. Hierin staat een link om je wachtwoord te resetten + Een email met daarin de wachtwoord reset uitleg zal worden gestuurd als het emailadres in onze database voorkomt. + Terug naar loginformulier + Geeft alsjeblieft een nieuw wachtwoord op + Je wachtwoord is aangepast + De link die je hebt aangeklikt is niet (meer) geldig. + Umbraco: Wachtwoord Reset + + De gebruikersnaam om in te loggen bij jouw Umbraco omgeving is: %0%

Klik hier om je wachtwoord te resetten of knip/plak deze URL in je browser:

%1%

]]> +
Dashboard @@ -646,6 +809,15 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Package versie Package versiehistorie Bekijk de package website + Package reeds geinstalleerd + Deze package kan niet worden geinstalleerd omdat minimaal Umbraco versie %0% benodigd is. + Aan het deinstalleren... + Aan het downloaden... + Aan het importeren... + Aan het installeren... + Aan het herstarten, een ongenblik geduld aub... + Geinstalleerd! Je browser zal nu automatisch ververst worden... + Kruk op "finish" om de installate te voltooien en de pagina te verversen. Plakken met alle opmaak (Niet aanbevolen) @@ -677,13 +849,18 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je %0% kan niet worden gepubliceerd omdat het item is gepland voor release. ]]> + + Inclusief ongepubliceerde kinderen Publicatie in uitvoering - even geduld... %0% van %1% pagina’s zijn gepubliceerd... @@ -698,16 +875,13 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Je hebt geen goedgekeurde kleuren geconfigureerd - Externe link toevoegen - Interne link toevoegen - Toevoegen - Bijschrift - Interne pagina - URL - Verplaats omlaag - Verplaats omhoog - Open in nieuw venster - Verwijder link + Externe link toevoegen + Interne link toevoegen + Bijschrift + Link + In een nieuw venster openen + Voer het bijschrijft in + Voer de link in Reset @@ -736,13 +910,17 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Instellingen Statistieken Vertaling - Gebruikers - Umbraco Contour - + Gebruikers Help Formulieren Analytics + + ga naar + Help onderwerpen voor + Video's voor + De beste Umbraco video tutorials + Standaard template Woordenboek sleutel @@ -762,6 +940,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Geen eigenschappen gedefinieerd op dit tabblad. Klik op de link "voeg een nieuwe eigenschap" aan de bovenkant om een ​​nieuwe eigenschap te creëren. Master Document Type Maak een bijbehorend template + Icon toevoegen Sort order @@ -771,7 +950,13 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
Sluit dit venster niet tijdens het sorteren]]>
- Publicatie werd geannuleerd door een 3rd party plug-in + Validatie + Validatiefouten moeten worden opgelost voor dit item kan worden opgeslagen + Mislukt + Wegens onvoldoende rechten kon deze handeling kon niet worden uitegevoerd + Geannuleerd + Uitvoering is g eannuleerd door de plugin van een 3e partij + Publicatie werd geannuleerd doordeeen plugin van een 3e partij Eigenschappen type bestaat al Eigenschappen type aangemaakt Data type: %1%]]> @@ -806,6 +991,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Bestand opgeslagen Bestand opgeslagen zonder fouten Taal opgeslagen + Media Type opgeslagen + Member Type opgeslagen Python script niet opgeslagen Python script kon niet worden opgeslagen door een fout Python script opeslagen! @@ -824,6 +1011,11 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Partial view opgeslagen zonder fouten! Partial view niet opgeslagen Er is een fout opgetreden bij het opslaan van het bestand. + Script view opgeslagen + Script view opgeslagen zonder fouten! + Script view niet opgeslagen + Er is een fout opgetreden bij het opslaan van dit bestand. + Er is een fout opgetreden bij het opslaan van dit bestand. Gebruik CSS syntax bijv: h1, .redHeader, .blueTex @@ -883,7 +1075,79 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Alle editors toelaten Alle rijconfiguraties toelaten + Instellen als standaard + Kies extra + Kies standaard + zijn toegevoegd + + + Composities + Er zijn nog geen tabs toegevoegd + Voeg een nieuwe tab toe + Voeg nog een tab toe + Inherited van + Voeg property toe + Verplicht label + + Zet list view aan + Laat de child nodes van het content item zien als een sorteer- en doorzoekbare lijstweergave zien. Deze child nodes worden dan niet in de boomstructuur getoond. + + Toegestane Templates + Kies welke templates toegestaan zijn om door de editors op dit content-type gebruikt te worden + Sta toe op root-niveau + Sta editors toe om content van dit type aan te maken op root-niveau + Ja - sta content van dit type toe op root-niveau + + Toegestane child node types + Sta contetn van een bepaalde type toe om onder dit type aangemaakt te kunnen worden + + Kies child node + Overerfde tabs en properties van een bestaand document-type. Nieuwe tabs worden toegevoegd aan het huidige document-type of samengevoegd als een tab met de identieke naam al bestaat. + Dit content-type wordt gebruikt in een compositie en kan daarom niet zelf een compositie worden. + Er zijn geen content-typen beschikbaar om als compositie te gebruiken. + + Beschikbare editors + Herbruik + Editor instellingen + + Configuratie + + Ja, verwijder + + is naar onder geplaatst + is naar onder gecopierd + Selecteer de map om te verplaatsen + Selecteer de map om te kopieren + naar de boomstructuur onder + + Alle Document types + Alle documenten + Alle media items + + die gebruik maken van dit document type zullen permanent verwijderd worden. Bevestig aub dat je deze ook wilt verwijderen. + die gebruik maken van dit media type zullen permanent verwijderd worden. Bevestig aub dat je deze ook wilt verwijderen. + die gebruik maken van dit member type zullen permanent verwijderd worden. Bevestig aub dat je deze ook wilt verwijderen. + + en alle documenten van dit type + en alle media items van dit type + en alle leden van dit type + + die gebruik maken van deze editor zullen geupdate worden met deze nieuwe instellingen + + Lid kan bewerken + Toon in het profiel van leden + tab heeft geen sorteervolgorde + + + + Models aan het gereneren + dit kan enige tijd duren, geduld aub + Models gegenereerd + Models konden niet gegenereerd worden + Models generatie is mislukt, kijk in de Umbraco log voor details + + Alternatief veld Alternatieve tekst @@ -916,7 +1180,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Taken aan jou toegewezen - Sluit taak @@ -932,18 +1197,24 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Dit is een geautomatiseerde mail om u op de hoogte te brengen dat document '%1%' is aangevraagd voor vertaling naar '%5%' door %2%. - Ga naar http://%3%/Umbraco/translation/default.aspx?id=%4% om te bewerken. + Ga naar http://%3%/translation/details.aspx?id=%4% om te bewerken. + Of log in bij Umbraco om een overzicht te krijgen van al jouw vertalingen. - Een prettige dag! + + Met vriendelijke groet! - Dit is een bericht van uw Content Management Systeem. - + + De Umbraco Robot ]]> [%0%] Vertaalopdracht voor %1% Geen vertaal-gebruikers gevonden. Maak eerst een vertaal-gebruiker aan voordat je pagina's voor vertaling verstuurd Taken aangemaakt door jou - die je aanmaakte. Om een detailweergave met opmerkingen te zien, klik op "Detail" of op de paginanaam. Je kan ook de pagina in XML-formaat downloaden door op de "Download XML"-link te klikken. Om een vertalingstaak te sluiten, klik je op de "Sluiten"-knop in detailweergave.]]> + die je aanmaakte. + Om een detailweergave met opmerkingen te zien, klik op "Detail" of op de paginanaam. + Je kan ook de pagina in XML-formaat downloaden door op de "Download XML"-link te klikken. + Om een vertalingstaak te sluiten, klik je op de "Sluiten"-knop in detailweergave.]]> De pagina '%0%' is verstuurd voor vertaling + Kies de taal waarin deze contetn vertaald moet worden Stuur voor vertaling Toegewezen door Taak geopend @@ -973,7 +1244,8 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Ledengroepen Rollen Ledentypes - Documenttypes + Documenttypen + RelatieTypen Packages Packages Python-bestanden @@ -985,6 +1257,7 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Stylesheets Sjablonen XSLT Bestanden + Analytics Nieuwe update beschikbaar @@ -1010,6 +1283,7 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Startnode in Mediabibliotheek Secties Blokkeer Umbraco toegang + Oude wachtwoord Wachtwoord Reset wachtwoord Je wachtwoord is veranderd! @@ -1030,10 +1304,138 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Gebruikerstype Gebruikerstypes Auteur + Vertaler Wijzig - Je profiel Je recente historie Sessie verloopt over + + + Validatie + Valideer als email + Valideer als nummer + Valideer als Url + ...of gebruik custom validatie + Veld is verplicht + + + + Waarde is insteld naar the aanbevolen waarde: '%0%'. + Waarde was '%1%' voor XPath '%2%' in configuratie bestand '%3%'. + De verwachtte waarde voor '%2%' is '%1%' in configuratie bestand '%3%', maar is '%0%'. + Onverwachte waarde '%0%' gevonden voor '%2%' in configuratie bestand '%3%'. + + + Custom foutmeldingen zijn ingesteld op '%0%'. + Custom foutmeldingen zijn momenteel '%0%'. Wij raden aan deze aan te passen naar '%1%' voor livegang. + Custom foutmeldingen aangepast naar '%0%'. + + Macro foutmeldingen zijn ingesteld op'%0%'. + Macro foutmeldingen zijn ingesteld op '%0%'. Dit zal er voor zorgen dat bepaalde, of alle, pagina's van de website niet geladen kunnen worden als er errors in een Macro zitten. Corrigeren zal deze waarde aanpassen naar '%1%'. + Macro foutmeldingen zijn aangepast naar '%0%'. + + + Try Skip IIS Custom foutmeldingen is ingesteld op '%0%'. IIS versie '%1%' wordt gebruikt. + Try Skip IIS Custom foutmeldingen is ingesteld op '%0%'. Het wordt voor de gebruikte IIS versie (%2%) aangeraden deze in te stellen op '%1%'. + Try Skip IIS Custom foutmeldingen ingesteld op '%0%'. + + + Het volgende bestand bestaat niet: '%0%'. + '%0%' kon niet gevonden worden in configuratie bestand '%1%'.]]> + Er is een fout opgetreden. Bekijk de log file voor de volledige fout: %0%. + + Members - Totaal XML: %0%, Totaal: %1%, Total incorrect: %2% + Media - Totaal XML: %0%, Totaal: %1%, Total incorrect: %2% + Content - Totaal XML: %0%, Totaal gepubliceerd: %1%, Total incorrect: %2% + + Het cerficaat van de website is ongeldig. + Cerficaat validatie foutmelding: '%0%' + Fout bij pingen van URL %0% - '%1%' + De site wordt momenteel %0% bekeken via HTTPS. + De appSetting 'umbracoUseSSL' in web.config staat op 'false'. Indien HTTPS gebruikt wordt moet deze op 'true' staan. + De appSetting 'umbracoUseSSL' in web.config is ingesteld op '%0%'. Cookies zijn %1% ingesteld als secure. + De 'umbracoUseSSL' waarde in web.config kon niet aangepast worden. Foutmelding: %0% + + + HTTPS inschakelen + Zet in de appSettings van de web.config de umbracoSSL instelling op 'true'. + De appSetting 'umbracoUseSSL' is nu ingesteld op 'true', cookies zullen als 'secure' worden aangemerkt. + + Fix + Cannot fix a check with a value comparison type of 'ShouldNotEqual'. + Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. + Value to fix check not provided. + + Debug compiliate mode staat uit. + Debug compiliate mode staat momenteel aan. Wij raden aan deze instelling uit te zetten voor livegang. + Debug compiliate mode uitgezet. + + Trace mode staat uit. + Trace mode staat momenteel aan. Wij raden aan deze instelling uit te zetten voor livegang. + Trace mode uitgezet. + + Alle mappen hebben de juiste permissie-instellingen!. + + %0%.]]> + %0%. Als deze niet in gebruik zijn voor deze omgeving hoeft er geen actie te worden ondernomen.]]> + + All files have the correct permissions set. + + %0%.]]> + %0%. Als deze niet in gebruik zijn voor deze omgeving hoeft er geen actie te worden ondernomen.]]> + + X-Frame-Options header of meta-tag om IFRAMEing door andere websites te voorkomen is aanwezig!]]> + X-Frame-Options header of meta-tag om IFRAMEing door andere websites te voorkomen is NIET aanwezig.]]> + Voorkom IFRAMEing via web.config + Voegt de instelling toe aan de httpProtocol/customHeaders section in web.config om IFRAMEing door andere websites te voorkomen. + De instelling om IFRAMEing door andere websites te voorkomen is toegevoegd aan de web.config! + Web.config kon niet aangepast worden door error: %0% + + + %0%.]]> + Er zijn geen headeres gevonden welke informatie over de gebruikte website technologie prijsgeven! + + In de Web.config werd system.net/mailsettings niet gevonden + In de Web.config sectie system.net/mailsettings is de host niet geconfigureerd. + SMTP instellingen zijn correct ingesteld en werken zoals verwacht. + De SMTP server geconfigureerd met host '%0%' en poort '%1%' kon niet gevonden worden. Controleer of de SMTP instellingen in Web.config file system.net/mailsettings correct zijn. + + %0%.]]> + %0%.]]> + + + URL tracker uitzetten + URL tracker aanzetten + Originele URL + Doorgestuurd naar + Er zijn geen redirects + Er wordt automatisch een redirect aangemaakt als een gepubliceerde pagina hernoemd of verplaatst wordt. + Verwijder + Weet je zeker dat je de redirect van '%0%' naar '%1%' wilt verwijderen? + Redirect URL verwijderd. + Fout bij verwijderen redirect URL. + Weet je zeker dat je de URL tracker wilt uitzetten? + URL tracker staat nu uit. + Fout bij het uitzetten van de URL Tracker. Meer informatie kan gevonden worden in de log file. + URL tracker staat nu aan. + Fout bij het aanzetten van de URL tracker. Meer informatie kan gevonden worden in de log file. From 8e5c57eb6088fb571992148b7634170f1fc9ed00 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 7 Feb 2017 08:18:46 +0100 Subject: [PATCH 068/105] U4-9322 - rename kabum in Scope.RobustExit --- src/Umbraco.Core/Scoping/Scope.cs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index d6aee52205..824ccd6d80 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -348,7 +348,7 @@ namespace Umbraco.Core.Scoping var completed = _completed.HasValue && _completed.Value; // deal with database - bool ex = false; + var databaseException = false; if (_database != null) { try @@ -360,7 +360,7 @@ namespace Umbraco.Core.Scoping } catch { - ex = true; + databaseException = true; throw; } finally @@ -368,7 +368,7 @@ namespace Umbraco.Core.Scoping _database.Dispose(); _database = null; - if (ex) + if (databaseException) RobustExit(false, true); } } @@ -376,9 +376,22 @@ namespace Umbraco.Core.Scoping RobustExit(completed, false); } - private void RobustExit(bool completed, bool kabum) + // this chains some try/finally blocks to + // - complete and dispose the scoped filesystems + // - deal with events if appropriate + // - remove the scope context if it belongs to this scope + // - deal with detachable scopes + // here, + // - completed indicates whether the scope has been completed + // can be true or false, but in both cases the scope is exiting + // in a normal way + // - onException indicates whether completing/aborting the database + // transaction threw an exception, in which case 'completed' has + // to be false + events don't trigger and we just to some cleanup + // to ensure we don't leave a scope around, etc + private void RobustExit(bool completed, bool onException) { - if (kabum) completed = false; + if (onException) completed = false; TryFinally(() => { @@ -392,7 +405,7 @@ namespace Umbraco.Core.Scoping }, () => { // deal with events - if (kabum == false && _eventDispatcher != null) + if (onException == false && _eventDispatcher != null) _eventDispatcher.ScopeExit(completed); }, () => { From 308a61895e085403fb91da5c1e89fc5431f1e19e Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 9 Feb 2017 14:53:03 +0100 Subject: [PATCH 069/105] deploy-219 - implement filesystem CanAddPhysical, AddFile from physical path --- src/Umbraco.Core/IO/FileSystemWrapper.cs | 19 ++++++++ src/Umbraco.Core/IO/IFileSystem.cs | 4 ++ src/Umbraco.Core/IO/PhysicalFileSystem.cs | 23 ++++++++++ src/Umbraco.Core/IO/ShadowFileSystem.cs | 43 ++++++++++++++++++- src/Umbraco.Core/IO/ShadowWrapper.cs | 17 ++++++++ src/Umbraco.Tests/IO/ShadowFileSystemTests.cs | 5 ++- 6 files changed, 107 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/IO/FileSystemWrapper.cs b/src/Umbraco.Core/IO/FileSystemWrapper.cs index 27e08330ed..97b8a2f8f6 100644 --- a/src/Umbraco.Core/IO/FileSystemWrapper.cs +++ b/src/Umbraco.Core/IO/FileSystemWrapper.cs @@ -109,5 +109,24 @@ namespace Umbraco.Core.IO var wrapped2 = Wrapped as IFileSystem2; return wrapped2 == null ? Wrapped.GetSize(path) : wrapped2.GetSize(path); } + + // explicitely implementing - not breaking + bool IFileSystem2.CanAddPhysical + { + get + { + var wrapped2 = Wrapped as IFileSystem2; + return wrapped2 != null && wrapped2.CanAddPhysical; + } + } + + // explicitely implementing - not breaking + void IFileSystem2.AddFile(string path, string physicalPath, bool overrideIfExists, bool copy) + { + var wrapped2 = Wrapped as IFileSystem2; + if (wrapped2 == null) + throw new NotSupportedException(); + wrapped2.AddFile(path, physicalPath, overrideIfExists, copy); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index 003b4891f5..e3e0f9e2d2 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -44,6 +44,10 @@ namespace Umbraco.Core.IO { long GetSize(string path); + bool CanAddPhysical { get; } + + void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false); + // TODO: implement these // //void CreateDirectory(string path); diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 33e4dc71e3..bf18d02e54 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -364,6 +364,29 @@ namespace Umbraco.Core.IO return file.Exists ? file.Length : -1; } + public bool CanAddPhysical { get { return true; } } + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + var fullPath = GetFullPath(path); + + if (File.Exists(fullPath)) + { + if (overrideIfExists == false) + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); + File.Delete(fullPath); + } + + var directory = Path.GetDirectoryName(fullPath); + if (directory == null) throw new InvalidOperationException("Could not get directory."); + Directory.CreateDirectory(directory); // ensure it exists + + if (copy) + File.Copy(physicalPath, fullPath); + else + File.Move(physicalPath, fullPath); + } + #region Helper Methods protected virtual void EnsureDirectory(string path) diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs index 1e5da10bdc..be7c90acea 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystem.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs @@ -33,8 +33,16 @@ namespace Umbraco.Core.IO { try { - using (var stream = _sfs.OpenFile(kvp.Key)) - _fs.AddFile(kvp.Key, stream, true); + var fs2 = _fs as IFileSystem2; + if (fs2 != null && fs2.CanAddPhysical) + { + fs2.AddFile(kvp.Key, _sfs.GetFullPath(kvp.Key)); // overwrite, move + } + else + { + using (var stream = _sfs.OpenFile(kvp.Key)) + _fs.AddFile(kvp.Key, stream, true); + } } catch (Exception e) { @@ -282,5 +290,36 @@ namespace Umbraco.Core.IO if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path."); return _sfs.GetSize(path); } + + public bool CanAddPhysical { get { return true; } } + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + ShadowNode sf; + var normPath = NormPath(path); + if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); + + var parts = normPath.Split('/'); + for (var i = 0; i < parts.Length - 1; i++) + { + var dirPath = string.Join("/", parts.Take(i + 1)); + ShadowNode sd; + if (Nodes.TryGetValue(dirPath, out sd)) + { + if (sd.IsFile) throw new InvalidOperationException("Invalid path."); + if (sd.IsDelete) Nodes[dirPath] = new ShadowNode(false, true); + } + else + { + if (_fs.DirectoryExists(dirPath)) continue; + if (_fs.FileExists(dirPath)) throw new InvalidOperationException("Invalid path."); + Nodes[dirPath] = new ShadowNode(false, true); + } + } + + _sfs.AddFile(path, physicalPath, overrideIfExists, copy); + Nodes[normPath] = new ShadowNode(false, false); + } } } diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index 7c8bd55830..503791226f 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -164,5 +164,22 @@ namespace Umbraco.Core.IO var filesystem2 = filesystem as IFileSystem2; return filesystem2 == null ? filesystem.GetSize(path) : filesystem2.GetSize(path); } + + public bool CanAddPhysical + { + get + { + var fileSystem2 = FileSystem as IFileSystem2; + return fileSystem2 != null && fileSystem2.CanAddPhysical; + } + } + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + var fileSystem2 = FileSystem as IFileSystem2; + if (fileSystem2 == null) + throw new NotSupportedException(); + fileSystem2.AddFile(path, physicalPath, overrideIfExists, copy); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 8e79544052..971ef25046 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -364,7 +364,8 @@ namespace Umbraco.Tests.IO ss.Complete(); - Assert.IsTrue(File.Exists(path + "/ShadowSystem/path/to/some/dir/f1.txt")); // *not* cleaning + // yes we are cleaning now + //Assert.IsTrue(File.Exists(path + "/ShadowSystem/path/to/some/dir/f1.txt")); // *not* cleaning Assert.IsTrue(File.Exists(path + "/ShadowTests/path/to/some/dir/f1.txt")); Assert.IsFalse(File.Exists(path + "/ShadowTests/sub/sub/f2.txt")); } @@ -558,7 +559,7 @@ namespace Umbraco.Tests.IO Assert.AreEqual(1, ae.InnerExceptions.Count); e = ae.InnerExceptions[0]; Assert.IsNotNull(e.InnerException); - Assert.IsInstanceOf(e.InnerException); + Assert.IsInstanceOf(e.InnerException); } // still, the rest of the changes has been applied ok From a898c2d69792493e16ec698e836e5fabb2b5826a Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 10 Feb 2017 10:36:48 +1100 Subject: [PATCH 070/105] Fixes ParameterSwapControllerActionSelector when there is an empty parameter (not null but without a value) --- .../ParameterSwapControllerActionSelector.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs index 3cb94ef8e6..53e25c146a 100644 --- a/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs +++ b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs @@ -97,10 +97,20 @@ namespace Umbraco.Web.Editors { var requestParam = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get(found.ParamName); - if (requestParam != null) + requestParam = (requestParam == null) ? null : requestParam.Trim(); + var paramTypes = found.SupportedTypes; + + if (requestParam == string.Empty && paramTypes.Length > 0) { - var paramTypes = found.SupportedTypes; - + //if it's empty then in theory we can select any of the actions since they'll all need to deal with empty or null parameters + //so we'll try to use the first one available + method = MatchByType(paramTypes[0], controllerContext, found); + if (method != null) + return true; + } + + if (requestParam != null) + { foreach (var paramType in paramTypes) { //check if this is IEnumerable and if so this will get it's type From bc1758f56978646d0e24f22f9b84a535d70e4342 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 10 Feb 2017 10:45:35 +1100 Subject: [PATCH 071/105] Updates the content picker to not make an unneeded request --- .../contentpicker/contentpicker.controller.js | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 01da9cda18..471e97e875 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -241,26 +241,36 @@ function contentPickerController($scope, entityResource, editorState, iconHelper unsubscribe(); }); - //load current data + var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - entityResource.getByIds(modelIds, entityType).then(function (data) { + //load current data if anything selected + if (modelIds.length > 0) { + entityResource.getByIds(modelIds, entityType).then(function(data) { - _.each(modelIds, function (id, i) { - var entity = _.find(data, function (d) { - return $scope.model.config.idType === "udi" ? (d.udi == id) : (d.id == id); - }); - - if (entity) { - setEntityUrl(entity); - } - - }); + _.each(modelIds, + function(id, i) { + var entity = _.find(data, + function(d) { + return $scope.model.config.idType === "udi" ? (d.udi == id) : (d.id == id); + }); + if (entity) { + setEntityUrl(entity); + } + + }); + + //everything is loaded, start the watch on the model + startWatch(); + + }); + } + else { //everything is loaded, start the watch on the model startWatch(); - - }); + } + function setEntityUrl(entity) { From 2652a583e0d6c4b0061fd6adcfb5336858219fbd Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 10 Feb 2017 10:48:43 +1100 Subject: [PATCH 072/105] updates mediapicker.controller pre value to not make an uneeded request --- .../views/prevalueeditors/mediapicker.controller.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js index 86e95a8794..e5c09f420e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js @@ -85,12 +85,15 @@ function mediaPickerController($scope, dialogService, entityResource, $log, icon //load media data var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) { - _.each(data, function (item, i) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, udi: item.udi }); + if (modelIds.length > 0) { + entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) { + _.each(data, function (item, i) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, udi: item.udi }); + }); }); - }); + } + } From dbe489c0f2f2f1238f50747872fa6ae1e0138e68 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 10 Feb 2017 12:27:50 +1100 Subject: [PATCH 073/105] U4-9513 Empty recycle bin SQL logic can SQL to timeout due to query inefficiencies --- .../Repositories/RecycleBinRepository.cs | 13 ++-- .../Services/ContentServiceTests.cs | 63 +++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index f17a0dd0d2..d63c338542 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -63,6 +63,7 @@ namespace Umbraco.Core.Persistence.Repositories FormatDeleteStatement("cmsContentVersion", "ContentId"), FormatDeleteStatement("cmsContentXml", "nodeId"), FormatDeleteStatement("cmsContent", "nodeId"), + //TODO: Why is this being done? We just delete this exact data in the next line "UPDATE umbracoNode SET parentID = '" + RecycleBinId + "' WHERE trashed = '1' AND nodeObjectType = @NodeObjectType", "DELETE FROM umbracoNode WHERE trashed = '1' AND nodeObjectType = @NodeObjectType" }; @@ -91,14 +92,18 @@ namespace Umbraco.Core.Persistence.Repositories } } + /// + /// A delete statement taht will delete anything in the table specified where it's PK (keyName) is found in the + /// list of umbracoNode.id that have trashed flag set + /// + /// + /// + /// private string FormatDeleteStatement(string tableName, string keyName) { - //This query works with sql ce and sql server: - //DELETE FROM umbracoUser2NodeNotify WHERE umbracoUser2NodeNotify.nodeId IN - //(SELECT nodeId FROM umbracoUser2NodeNotify as TB1 INNER JOIN umbracoNode as TB2 ON TB1.nodeId = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = 'C66BA18E-EAF3-4CFF-8A22-41B16D66A972') return string.Format( - "DELETE FROM {0} WHERE {0}.{1} IN (SELECT TB1.{1} FROM {0} as TB1 INNER JOIN umbracoNode as TB2 ON TB1.{1} = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)", + "DELETE FROM {0} WHERE {0}.{1} IN (SELECT id FROM umbracoNode WHERE trashed = '1' AND nodeObjectType = @NodeObjectType)", tableName, keyName); } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 0078d3e83e..e89d520f0f 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1339,6 +1339,69 @@ namespace Umbraco.Tests.Services Assert.That(contents.Any(), Is.False); } + [Test] + public void Can_Empty_RecycleBin_With_Content_That_Has_All_Related_Data() + { + // Arrange + //need to: + // * add relations + // * add permissions + // * add notifications + // * public access + // * tags + // * domain + // * published & preview data + // * multiple versions + + var contentType = MockedContentTypes.CreateAllTypesContentType("test", "test"); + ServiceContext.ContentTypeService.Save(contentType, 0); + + object obj = + new + { + tags = "Hello,World" + }; + var content1 = MockedContent.CreateBasicContent(contentType); + content1.PropertyValues(obj); + content1.ResetDirtyProperties(false); + ServiceContext.ContentService.Save(content1, 0); + Assert.IsTrue(ServiceContext.ContentService.PublishWithStatus(content1, 0).Success); + var content2 = MockedContent.CreateBasicContent(contentType); + content2.PropertyValues(obj); + content2.ResetDirtyProperties(false); + ServiceContext.ContentService.Save(content2, 0); + Assert.IsTrue(ServiceContext.ContentService.PublishWithStatus(content2, 0).Success); + + ServiceContext.RelationService.Save(new RelationType(Constants.ObjectTypes.DocumentGuid, Constants.ObjectTypes.DocumentGuid, "test")); + Assert.IsNotNull(ServiceContext.RelationService.Relate(content1, content2, "test")); + + ServiceContext.PublicAccessService.Save(new PublicAccessEntry(content1, content2, content2, new List + { + new PublicAccessRule + { + RuleType = "test", + RuleValue = "test" + } + })); + Assert.IsTrue(ServiceContext.PublicAccessService.AddRule(content1, "test2", "test2").Success); + + Assert.IsNotNull(ServiceContext.NotificationService.CreateNotification(ServiceContext.UserService.GetUserById(0), content1, "test")); + + ServiceContext.ContentService.AssignContentPermission(content1, 'A', new[] {0}); + + Assert.IsTrue(ServiceContext.DomainService.Save(new UmbracoDomain("www.test.com", "en-AU") + { + RootContentId = content1.Id + }).Success); + + // Act + ServiceContext.ContentService.EmptyRecycleBin(); + var contents = ServiceContext.ContentService.GetContentInRecycleBin(); + + // Assert + Assert.That(contents.Any(), Is.False); + } + [Test] public void Can_Move_Content() { From 91157caf0e6773c7eb7b3dba54b93b176c9489f1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 10 Feb 2017 14:23:46 +1100 Subject: [PATCH 074/105] U4-8835 cmsMember.LoginName needs a DB index --- src/Umbraco.Core/Models/Rdbms/MemberDto.cs | 1 + src/Umbraco.Core/Umbraco.Core.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs index e5f7b3f17c..cbe9f909f8 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs @@ -22,6 +22,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("LoginName")] [Length(1000)] [Constraint(Default = "''")] + [Index(IndexTypes.NonClustered, Name = "IX_cmsMember_LoginName")] public string LoginName { get; set; } [Column("Password")] diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 94267cf21f..1b15234fd1 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -479,6 +479,7 @@ + From 5aed6334c4f68e31df357028d8937b905223dede Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 10 Feb 2017 14:24:33 +1100 Subject: [PATCH 075/105] missing file --- .../AddIndexToCmsMemberLoginName.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs new file mode 100644 index 0000000000..beff4b3210 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs @@ -0,0 +1,42 @@ +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + public class AddIndexToCmsMemberLoginName : MigrationBase + { + public AddIndexToCmsMemberLoginName(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) + .Select(x => new DbIndexDefinition() + { + TableName = x.Item1, + IndexName = x.Item2, + ColumnName = x.Item3, + IsUnique = x.Item4 + }).ToArray(); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false) + { + Create.Index("IX_cmsMember_LoginName").OnTable("cmsMember") + .OnColumn("LoginName") + .Ascending() + .WithOptions() + .NonClustered(); + } + } + + public override void Down() + { + Delete.Index("IX_cmsMember_LoginName").OnTable("cmsMember"); + } + } +} \ No newline at end of file From 2b82520067444bb3f7906ec9f3436f3219d13f0f Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 10 Feb 2017 15:38:53 +1100 Subject: [PATCH 076/105] U4-8998 umbracoRelation table needs unique index on columns: parentId, childId, relType --- src/Umbraco.Core/Models/Rdbms/RelationDto.cs | 1 + .../AddIndexToUmbracoNodePath.cs | 9 +-- .../AddIndexesToUmbracoRelation.cs | 68 +++++++++++++++++++ .../SqlSyntax/SqlSyntaxProviderExtensions.cs | 18 ++++- src/Umbraco.Core/Services/RelationService.cs | 4 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../SqlCeSyntaxProviderTests.cs | 13 ++++ 7 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelation.cs diff --git a/src/Umbraco.Core/Models/Rdbms/RelationDto.cs b/src/Umbraco.Core/Models/Rdbms/RelationDto.cs index 368904a5cb..d3d741a191 100644 --- a/src/Umbraco.Core/Models/Rdbms/RelationDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RelationDto.cs @@ -16,6 +16,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("parentId")] [ForeignKey(typeof(NodeDto), Name = "FK_umbracoRelation_umbracoNode")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelation_parentChildType", ForColumns = "parentId,childId,relType")] public int ParentId { get; set; } [Column("childId")] diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUmbracoNodePath.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUmbracoNodePath.cs index 229311dd9f..e59252390a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUmbracoNodePath.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUmbracoNodePath.cs @@ -14,14 +14,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero public override void Up() { - var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); //make sure it doesn't already exist if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodePath")) == false) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelation.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelation.cs new file mode 100644 index 0000000000..88c1378e49 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelation.cs @@ -0,0 +1,68 @@ +using System; +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + public class AddIndexesToUmbracoRelation : MigrationBase + { + public AddIndexesToUmbracoRelation(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + //Ensure this executes in a defered block which will be done inside of the migration transaction + this.Execute.Code(database => + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelation_parentChildType")) == false) + { + //We need to check if this index has corrupted data and then clear that data + var duplicates = database.Fetch("SELECT parentId,childId,relType FROM umbracoRelation GROUP BY parentId,childId,relType HAVING COUNT(*) > 1"); + if (duplicates.Count > 0) + { + //need to fix this there cannot be duplicates so we'll take the latest entries, it's really not going to matter though + foreach (var duplicate in duplicates) + { + var ids = database.Fetch("SELECT id FROM umbracoRelation WHERE parentId=@parentId AND childId=@childId AND relType=@relType ORDER BY datetime DESC", + new { parentId = duplicate.parentId, childId = duplicate.childId, relType = duplicate.relType }); + + if (ids.Count == 1) + { + //this is just a safety check, this should absolutely never happen + throw new InvalidOperationException("Duplicates were detected but could not be discovered"); + } + + //delete the others + ids = ids.Skip(0).ToList(); + + //iterate in groups of 2000 to avoid the max sql parameter limit + foreach (var idGroup in ids.InGroupsOf(2000)) + { + database.Execute("DELETE FROM umbracoRelation WHERE id IN (@ids)", new { ids = idGroup }); + } + } + } + } + return ""; + }); + + Create.Index("IX_umbracoRelation_parentChildType").OnTable("umbracoRelation") + .OnColumn("parentId").Ascending() + .OnColumn("childId").Ascending() + .OnColumn("relType").Ascending() + .WithOptions() + .Unique(); + } + + public override void Down() + { + Delete.Index("IX_umbracoNodePath").OnTable("umbracoNode"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs index 1231765f20..f0bafdacf7 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs @@ -1,7 +1,23 @@ -namespace Umbraco.Core.Persistence.SqlSyntax +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.SqlSyntax { internal static class SqlSyntaxProviderExtensions { + public static IEnumerable GetDefinedIndexesDefinitions(this ISqlSyntaxProvider sql, Database db) + { + return sql.GetDefinedIndexes(db) + .Select(x => new DbIndexDefinition() + { + TableName = x.Item1, + IndexName = x.Item2, + ColumnName = x.Item3, + IsUnique = x.Item4 + }).ToArray(); + } + /// /// Returns the quotes tableName.columnName combo /// diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs index ec42d4bd74..76222810d2 100644 --- a/src/Umbraco.Core/Services/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -215,7 +215,7 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.GetUnitOfWork(commit: true)) { var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ChildId == id || x.ParentId == id); + var query = new Query().Where(x => x.ParentId == id || x.ChildId == id); return repository.GetByQuery(query); } } @@ -230,7 +230,7 @@ namespace Umbraco.Core.Services if (relationType == null) return Enumerable.Empty(); var relationRepo = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => (x.ChildId == id || x.ParentId == id) && x.RelationTypeId == relationType.Id); + var query = new Query().Where(x => (x.ParentId == id || x.ChildId == id) && x.RelationTypeId == relationType.Id); return relationRepo.GetByQuery(query); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 94267cf21f..9be8679626 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -479,6 +479,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index fafddb8dfd..105b3d0c11 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs @@ -121,6 +121,19 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, Assert.AreEqual("CREATE UNIQUE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString()); } + [Test] + public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex_Multi_Columnn() + { + var sqlSyntax = new SqlServerSyntaxProvider(); + var createExpression = new CreateIndexExpression(DatabaseProviders.SqlServer, new[] { DatabaseProviders.SqlServer }, sqlSyntax) + { + Index = { Name = "IX_AB" } + }; + var builder = new CreateIndexBuilder(createExpression); + builder.OnTable("TheTable").OnColumn("A").Ascending().OnColumn("B").Ascending().WithOptions().Unique(); + Assert.AreEqual("CREATE UNIQUE NONCLUSTERED INDEX [IX_AB] ON [TheTable] ([A],[B])", createExpression.ToString()); + } + [Test] public void CreateIndexBuilder_SqlServer_Clustered_CreatesClusteredIndex() { From aa0eaf590c1bc16c70b133172140a12cf3d5fa86 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 10 Feb 2017 15:51:41 +1100 Subject: [PATCH 077/105] adds indexes to relation type --- .../Models/Rdbms/RelationTypeDto.cs | 2 + ...s => AddIndexesToUmbracoRelationTables.cs} | 57 +++++++++++++------ src/Umbraco.Core/Umbraco.Core.csproj | 2 +- 3 files changed, 43 insertions(+), 18 deletions(-) rename src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/{AddIndexesToUmbracoRelation.cs => AddIndexesToUmbracoRelationTables.cs} (52%) diff --git a/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs index 54052f58a3..d13ce33520 100644 --- a/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs @@ -29,11 +29,13 @@ namespace Umbraco.Core.Models.Rdbms public Guid ChildObjectType { get; set; } [Column("name")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")] public string Name { get; set; } [Column("alias")] [NullSetting(NullSetting = NullSettings.Null)] [Length(100)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")] public string Alias { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelation.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelationTables.cs similarity index 52% rename from src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelation.cs rename to src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelationTables.cs index 88c1378e49..b8c0d78ef1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelationTables.cs @@ -6,21 +6,22 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero { [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] - public class AddIndexesToUmbracoRelation : MigrationBase + public class AddIndexesToUmbracoRelationTables : MigrationBase { - public AddIndexesToUmbracoRelation(ISqlSyntaxProvider sqlSyntax, ILogger logger) + public AddIndexesToUmbracoRelationTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) { } public override void Up() { - //Ensure this executes in a defered block which will be done inside of the migration transaction - this.Execute.Code(database => - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database); + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database).ToArray(); - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelation_parentChildType")) == false) + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelation_parentChildType")) == false) + { + //This will remove any corrupt/duplicate data in the relation table before the index is applied + //Ensure this executes in a defered block which will be done inside of the migration transaction + this.Execute.Code(database => { //We need to check if this index has corrupted data and then clear that data var duplicates = database.Fetch("SELECT parentId,childId,relType FROM umbracoRelation GROUP BY parentId,childId,relType HAVING COUNT(*) > 1"); @@ -48,16 +49,38 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero } } } - } - return ""; - }); + return ""; + }); + + //unique index to prevent duplicates - and for better perf + Create.Index("IX_umbracoRelation_parentChildType").OnTable("umbracoRelation") + .OnColumn("parentId").Ascending() + .OnColumn("childId").Ascending() + .OnColumn("relType").Ascending() + .WithOptions() + .Unique(); + } + + //need indexes on alias and name for relation type since these are queried against + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_alias")) == false) + { + Create.Index("IX_umbracoRelationType_alias").OnTable("umbracoRelationType") + .OnColumn("alias") + .Ascending() + .WithOptions() + .NonClustered(); + } + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_name")) == false) + { + Create.Index("IX_umbracoRelationType_name").OnTable("umbracoRelationType") + .OnColumn("name") + .Ascending() + .WithOptions() + .NonClustered(); + } - Create.Index("IX_umbracoRelation_parentChildType").OnTable("umbracoRelation") - .OnColumn("parentId").Ascending() - .OnColumn("childId").Ascending() - .OnColumn("relType").Ascending() - .WithOptions() - .Unique(); } public override void Down() diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 9be8679626..1ff334b03c 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -479,7 +479,7 @@ - + From aaa9c8f194464de32600d0180c5ba559d8a18d4b Mon Sep 17 00:00:00 2001 From: Emil Wangaa Date: Fri, 10 Feb 2017 09:24:14 +0100 Subject: [PATCH 078/105] Fixes delete of folders in partialviews,partialviewmacros and scripts --- src/Umbraco.Web/Editors/CodeFileController.cs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Editors/CodeFileController.cs b/src/Umbraco.Web/Editors/CodeFileController.cs index bcb6630c0a..e41311126c 100644 --- a/src/Umbraco.Web/Editors/CodeFileController.cs +++ b/src/Umbraco.Web/Editors/CodeFileController.cs @@ -1,5 +1,6 @@ using AutoMapper; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; @@ -70,7 +71,6 @@ namespace Umbraco.Web.Editors } virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath); - switch (type) { @@ -212,29 +212,46 @@ namespace Umbraco.Web.Editors { if (string.IsNullOrWhiteSpace(type) == false && string.IsNullOrWhiteSpace(virtualPath) == false) { + virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath); + switch (type) { case Core.Constants.Trees.PartialViews: + if (IsDirectory(virtualPath, SystemDirectories.PartialViews)) + { + Services.FileService.DeletePartialViewFolder(virtualPath); + return Request.CreateResponse(HttpStatusCode.OK); + } if (Services.FileService.DeletePartialView(virtualPath, Security.CurrentUser.Id)) { return Request.CreateResponse(HttpStatusCode.OK); } - return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View found with the specified path"); + return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View or folder found with the specified path"); case Core.Constants.Trees.PartialViewMacros: + if (IsDirectory(virtualPath, SystemDirectories.MacroPartials)) + { + Services.FileService.DeletePartialViewMacroFolder(virtualPath); + return Request.CreateResponse(HttpStatusCode.OK); + } if (Services.FileService.DeletePartialViewMacro(virtualPath, Security.CurrentUser.Id)) { return Request.CreateResponse(HttpStatusCode.OK); } - return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View Macro found with the specified path"); + return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View Macro or folder found with the specified path"); case Core.Constants.Trees.Scripts: + if (IsDirectory(virtualPath, SystemDirectories.Scripts)) + { + Services.FileService.DeleteScriptFolder(virtualPath); + return Request.CreateResponse(HttpStatusCode.OK); + } if (Services.FileService.GetScriptByName(virtualPath) != null) { Services.FileService.DeleteScript(virtualPath, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); } - return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Script found with the specified path"); + return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Script or folder found with the specified path"); default: return Request.CreateResponse(HttpStatusCode.NotFound); @@ -388,5 +405,12 @@ namespace Umbraco.Web.Editors return value; } + + private bool IsDirectory(string virtualPath, string systemDirectory) + { + var path = IOHelper.MapPath(systemDirectory + "/" + virtualPath); + var dirInfo = new DirectoryInfo(path); + return dirInfo.Attributes == FileAttributes.Directory; + } } } From 7889f82a9aa26a2a2abea7b381765f08c748f3b7 Mon Sep 17 00:00:00 2001 From: Claus Date: Fri, 10 Feb 2017 09:25:16 +0100 Subject: [PATCH 079/105] added a check so the 'ensure' wont fail if the path is already as it should be. added unit test for this method. --- src/Umbraco.Core/IO/IOHelper.cs | 6 ++++-- src/Umbraco.Tests/IO/IOHelperTest.cs | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 5ce851bdb4..2ee0463435 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -382,7 +382,7 @@ namespace Umbraco.Core.IO path = relativePath; } - return path.EnsurePathIsPrefixed(); + return path.EnsurePathIsApplicationRootPrefixed(); } /// @@ -390,8 +390,10 @@ namespace Umbraco.Core.IO /// /// /// - internal static string EnsurePathIsPrefixed(this string path) + internal static string EnsurePathIsApplicationRootPrefixed(this string path) { + if (path.StartsWith("~/")) + return path; if (path.StartsWith("/") == false && path.StartsWith("\\") == false) path = string.Format("/{0}", path); if (path.StartsWith("~") == false) diff --git a/src/Umbraco.Tests/IO/IOHelperTest.cs b/src/Umbraco.Tests/IO/IOHelperTest.cs index 559ba1a2f3..e42491454b 100644 --- a/src/Umbraco.Tests/IO/IOHelperTest.cs +++ b/src/Umbraco.Tests/IO/IOHelperTest.cs @@ -56,5 +56,14 @@ namespace Umbraco.Tests.IO Assert.AreEqual(IOHelper.MapPath(SystemDirectories.WebServices, true), IOHelper.MapPath(SystemDirectories.WebServices, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Xslt, true), IOHelper.MapPath(SystemDirectories.Xslt, false)); } + + [Test] + public void EnsurePathIsApplicationRootPrefixed() + { + //Assert + Assert.AreEqual("~/Views/Template.cshtml", IOHelper.EnsurePathIsApplicationRootPrefixed("Views/Template.cshtml")); + Assert.AreEqual("~/Views/Template.cshtml", IOHelper.EnsurePathIsApplicationRootPrefixed("/Views/Template.cshtml")); + Assert.AreEqual("~/Views/Template.cshtml", IOHelper.EnsurePathIsApplicationRootPrefixed("~/Views/Template.cshtml")); + } } } From 0dc0e681744a9887f4c5ab6b65cb8843de99d72a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 10 Feb 2017 10:36:58 +0100 Subject: [PATCH 080/105] reset pagination when searching --- .../common/directives/components/umbminilistview.directive.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 1a04eeff65..281242621a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -153,6 +153,8 @@ scope.searchMiniListView = function(search, miniListView) { // set search value miniListView.pagination.filter = search; + // reset pagination + miniListView.pagination.pageNumber = 1; // start loading animation list view miniListView.loading = true; searchMiniListView(miniListView); From 8cf3110708b4ac4437cc91ba6964c75dc28c84e4 Mon Sep 17 00:00:00 2001 From: Claus Date: Fri, 10 Feb 2017 11:41:46 +0100 Subject: [PATCH 081/105] fixing typo and missing file ref in csproj. --- .../Persistence/Repositories/RecycleBinRepository.cs | 2 +- src/Umbraco.Tests.Benchmarks/Program.cs | 2 +- src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index d63c338542..6c1d1c2008 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -93,7 +93,7 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// A delete statement taht will delete anything in the table specified where it's PK (keyName) is found in the + /// A delete statement that will delete anything in the table specified where its PK (keyName) is found in the /// list of umbracoNode.id that have trashed flag set /// /// diff --git a/src/Umbraco.Tests.Benchmarks/Program.cs b/src/Umbraco.Tests.Benchmarks/Program.cs index c8487f677c..8373d72db3 100644 --- a/src/Umbraco.Tests.Benchmarks/Program.cs +++ b/src/Umbraco.Tests.Benchmarks/Program.cs @@ -14,7 +14,7 @@ namespace Umbraco.Tests.Benchmarks typeof(ModelToSqlExpressionHelperBenchmarks), typeof(XmlBenchmarks), typeof(LinqCastBenchmarks), - typeof(DeepCloneBenchmarks), + //typeof(DeepCloneBenchmarks), typeof(XmlPublishedContentInitBenchmarks), }); diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index d1fdcaa68f..50328b2ecf 100644 --- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -85,7 +85,6 @@ - From 82c18955b0564e3321169887e083cba89005e305 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 10 Feb 2017 14:38:50 +0100 Subject: [PATCH 082/105] Version 7.6-alpha060 --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 0294012380..cb641006d1 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.6.0 -alpha056 \ No newline at end of file +alpha060 diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 7fc240f4f0..8288c293e4 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha056")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha060")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index a741f65e4e..a21c9b7bab 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). ///
/// The version comment. - public static string CurrentComment { get { return "alpha056"; } } + public static string CurrentComment { get { return "alpha060"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx From 771f8d92f21760bc5b8f24c76c53f45a3b513ea4 Mon Sep 17 00:00:00 2001 From: Claus Date: Fri, 10 Feb 2017 14:44:56 +0100 Subject: [PATCH 083/105] adding in missing extension method stuff for some udis. --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 2 +- .../Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Core/UdiGetterExtensions.cs | 22 ++++++++++++++----- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 0294012380..b753b5efce 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.6.0 -alpha056 \ No newline at end of file +alpha061 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 7fc240f4f0..19e4078131 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha056")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha061")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index a741f65e4e..755de31ec4 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). ///
/// The version comment. - public static string CurrentComment { get { return "alpha056"; } } + public static string CurrentComment { get { return "alpha061"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx diff --git a/src/Umbraco.Core/UdiGetterExtensions.cs b/src/Umbraco.Core/UdiGetterExtensions.cs index 1bd018a754..a9750fe464 100644 --- a/src/Umbraco.Core/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/UdiGetterExtensions.cs @@ -285,18 +285,30 @@ namespace Umbraco.Core var member = entity as IMember; if (member != null) return member.GetUdi(); - var contentBase = entity as IContentBase; - if (contentBase != null) return contentBase.GetUdi(); + var stylesheet = entity as Stylesheet; + if (stylesheet != null) return stylesheet.GetUdi(); + + var script = entity as Script; + if (script != null) return script.GetUdi(); + + var dictionaryItem = entity as IDictionaryItem; + if (dictionaryItem != null) return dictionaryItem.GetUdi(); var macro = entity as IMacro; if (macro != null) return macro.GetUdi(); + var partialView = entity as IPartialView; + if (partialView != null) return partialView.GetUdi(); + + var xsltFile = entity as IXsltFile; + if (xsltFile != null) return xsltFile.GetUdi(); + + var contentBase = entity as IContentBase; + if (contentBase != null) return contentBase.GetUdi(); + var relationType = entity as IRelationType; if (relationType != null) return relationType.GetUdi(); - var dictionaryItem = entity as IDictionaryItem; - if (dictionaryItem != null) return dictionaryItem.GetUdi(); - throw new NotSupportedException(string.Format("Entity type {0} is not supported.", entity.GetType().FullName)); } } From 67fc8b6215c9a16d559cd35ab1f75eccc89a552c Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 10 Feb 2017 15:50:31 +0100 Subject: [PATCH 084/105] Scope - fix media files deletion --- src/Umbraco.Core/Events/DeleteEventArgs.cs | 2 +- .../Events/IDeletingMediaFilesEventArgs.cs | 9 +++ .../Events/RecycleBinEventArgs.cs | 4 +- .../Events/ScopeEventDispatcher.cs | 13 ++++ src/Umbraco.Core/IO/MediaFileSystem.cs | 63 ++++++++++++++++++- .../Interfaces/IDeleteMediaFilesRepository.cs | 6 +- src/Umbraco.Core/Services/ContentService.cs | 20 +++--- src/Umbraco.Core/Services/MediaService.cs | 6 -- src/Umbraco.Core/Services/MemberService.cs | 21 +++---- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 10 files changed, 110 insertions(+), 35 deletions(-) create mode 100644 src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index 8a0fdaf290..df13363b95 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Umbraco.Core.Events { - public class DeleteEventArgs : CancellableObjectEventArgs>, IEquatable> + public class DeleteEventArgs : CancellableObjectEventArgs>, IEquatable>, IDeletingMediaFilesEventArgs { /// /// Constructor accepting multiple entities that are used in the delete operation diff --git a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs new file mode 100644 index 0000000000..45681042ba --- /dev/null +++ b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + internal interface IDeletingMediaFilesEventArgs + { + List MediaFilesToDelete { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index c6049cb7a6..c6d7c659b7 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Events { - public class RecycleBinEventArgs : CancellableEventArgs, IEquatable + public class RecycleBinEventArgs : CancellableEventArgs, IEquatable, IDeletingMediaFilesEventArgs { public RecycleBinEventArgs(Guid nodeObjectType, Dictionary> allPropertyData, bool emptiedSuccessfully) : base(false) @@ -97,6 +97,8 @@ namespace Umbraco.Core.Events /// public List Files { get; private set; } + public List MediaFilesToDelete { get { return Files; } } + /// /// Gets the list of all property data associated with a content id /// diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs index 6eb6ee3b85..31331a2d8d 100644 --- a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs +++ b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using Umbraco.Core.IO; namespace Umbraco.Core.Events { @@ -117,9 +118,21 @@ namespace Umbraco.Core.Events if (_events == null) return; + var mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem; + if (RaiseEvents && completed) + { foreach (var e in _events) + { e.RaiseEvent(); + + // fixme - not sure I like doing it here - but then where? how? + var delete = e.Args as IDeletingMediaFilesEventArgs; + if (delete != null && delete.MediaFilesToDelete.Count > 0) + mediaFileSystem.DeleteMediaFiles(delete.MediaFilesToDelete); + } + } + _events.Clear(); } } diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index 6f32ef6da0..d9281a7590 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -24,6 +25,7 @@ namespace Umbraco.Core.IO { private readonly IContentSection _contentConfig; private readonly UploadAutoFillProperties _uploadAutoFillProperties; + private readonly ILogger _logger; private readonly object _folderCounterLock = new object(); private long _folderCounter; @@ -42,6 +44,7 @@ namespace Umbraco.Core.IO public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig, ILogger logger) : base(wrapped) { + _logger = logger; _contentConfig = contentConfig; _uploadAutoFillProperties = new UploadAutoFillProperties(this, logger, contentConfig); } @@ -99,7 +102,7 @@ namespace Umbraco.Core.IO } else { - // new scheme: path is "-/" OR "--" + // new scheme: path is "/" where xuid is a combination of cuid and puid // default media filesystem maps to "~/media/" // assumes that cuid and puid keys can be trusted - and that a single property type // for a single content cannot store two different files with the same name @@ -435,6 +438,64 @@ namespace Umbraco.Core.IO } } + public void DeleteMediaFiles(IEnumerable files) + { + files = files.Distinct(); + + Parallel.ForEach(files, file => + { + try + { + if (file.IsNullOrWhiteSpace()) return; + + if (FileExists(file) == false) return; + DeleteFile(file, true); + + if (UseTheNewMediaPathScheme == false) + { + // old scheme: filepath is "/" OR "-" + // remove the directory if any + var dir = Path.GetDirectoryName(file); + if (string.IsNullOrWhiteSpace(dir) == false) + DeleteDirectory(dir, true); + } + else + { + // new scheme: path is "/" where xuid is a combination of cuid and puid + // remove the directory + var dir = Path.GetDirectoryName(file); + DeleteDirectory(dir, true); + } + + // I don't even understand... + /* + + var relativeFilePath = GetRelativePath(file); // fixme - should be relative already + if (FileExists(relativeFilePath) == false) return; + + var parentDirectory = Path.GetDirectoryName(relativeFilePath); + + // don't want to delete the media folder if not using directories. + if (_contentSection.UploadAllowDirectories && parentDirectory != GetRelativePath("/")) + { + //issue U4-771: if there is a parent directory the recursive parameter should be true + DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false); + } + else + { + DeleteFile(file, true); + } + + */ + } + catch (Exception e) + { + _logger.Error("Failed to delete attached file \"" + file + "\".", e); + } + }); + } + + #endregion #region GenerateThumbnails diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs index 005c1d62ba..8a5a99b32a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Persistence.Repositories { + // cannot kill in v7 because it is public, kill in v8 + [Obsolete("Use MediaFileSystem.DeleteMediaFiles instead.", false)] public interface IDeleteMediaFilesRepository { /// @@ -9,6 +12,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// + [Obsolete("Use MediaFileSystem.DeleteMediaFiles instead.", false)] bool DeleteMediaFiles(IEnumerable files); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index af06c7e0ba..2455a53f55 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -179,7 +179,7 @@ namespace Umbraco.Core.Services uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, contentTypeAlias, parentId)); //Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this, uow.Events); - // fixme + // fixme var auditRepo = RepositoryFactory.CreateAuditRepository(uow); auditRepo.AddOrUpdate(new AuditItem(content.Id, string.Format("Content '{0}' was created", name), AuditType.New, content.CreatorId)); uow.Commit(); @@ -1051,9 +1051,9 @@ namespace Umbraco.Core.Services UnPublish(content, userId); } content.WriterId = userId; - content.ChangeTrashedState(true); + content.ChangeTrashedState(true); repository.AddOrUpdate(content); - + //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId foreach (var descendant in descendants) { @@ -1063,14 +1063,14 @@ namespace Umbraco.Core.Services descendant.ChangeTrashedState(true, descendant.ParentId); repository.AddOrUpdate(descendant); - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); } uow.Commit(); uow.Events.Dispatch(Trashed, this, new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), "Trashed"); } - + Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); return OperationStatus.Success(evtMsgs); @@ -1285,9 +1285,6 @@ namespace Umbraco.Core.Services var args = new DeleteEventArgs(content, false, evtMsgs); uow.Events.Dispatch(Deleted, this, args, "Deleted"); // fixme why the event name?! - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); } Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); @@ -1335,7 +1332,7 @@ namespace Umbraco.Core.Services /// /// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. - /// + /// /// Id of the /// Optional Id of the user issueing the delete operation public void DeleteContentOfTypes(IEnumerable contentTypeIds, int userId = 0) @@ -1344,7 +1341,7 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.GetUnitOfWork()) { var repository = RepositoryFactory.CreateContentRepository(uow); - + //track the 'root' items of the collection of nodes discovered to delete, we need to use //these items to lookup descendants that are not of this doc type so they can be transfered //to the recycle bin @@ -1559,9 +1556,6 @@ namespace Umbraco.Core.Services success = repository.EmptyRecycleBin(); - if (success) - repository.DeleteMediaFiles(files); - uow.Events.Dispatch(EmptiedRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, entities, files, success)); uow.Commit(); } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 9f5ec811c9..08494a3662 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -831,9 +831,6 @@ namespace Umbraco.Core.Services var args = new DeleteEventArgs(media, false, evtMsgs); uow.Events.Dispatch(Deleted, this, args); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); } Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); @@ -958,9 +955,6 @@ namespace Umbraco.Core.Services success = repository.EmptyRecycleBin(); // FIXME shouldn't we commit here?! uow.Events.Dispatch(EmptiedRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, entities, files, success)); - - if (success) - repository.DeleteMediaFiles(files); } } Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, -21); diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 7c7afdd2a6..841d79f687 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -45,7 +45,7 @@ namespace Umbraco.Core.Services /// /// Gets the default MemberType alias /// - /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll + /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll /// return the first type that is not an admin, otherwise if there's only one we will return that one. /// Alias of the default MemberType public string GetDefaultMemberType() @@ -86,7 +86,7 @@ namespace Umbraco.Core.Services /// /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method /// - /// This method exists so that Umbraco developers can use one entry point to create/update + /// This method exists so that Umbraco developers can use one entry point to create/update /// Members if they choose to. /// The Member to save the password for /// The password to encrypt and save @@ -776,7 +776,7 @@ namespace Umbraco.Core.Services /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -792,7 +792,7 @@ namespace Umbraco.Core.Services /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -806,7 +806,7 @@ namespace Umbraco.Core.Services /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -836,7 +836,7 @@ namespace Umbraco.Core.Services /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -921,7 +921,7 @@ namespace Umbraco.Core.Services /// public IMember GetByUsername(string username) { - //TODO: Somewhere in here, whether at this level or the repository level, we need to add + //TODO: Somewhere in here, whether at this level or the repository level, we need to add // a caching mechanism since this method is used by all the membership providers and could be // called quite a bit when dealing with members. @@ -953,9 +953,6 @@ namespace Umbraco.Core.Services var args = new DeleteEventArgs(member, false); uow.Events.Dispatch(Deleted, this, args); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); } } @@ -963,7 +960,7 @@ namespace Umbraco.Core.Services /// Saves an ///
/// to Save - /// Optional parameter to raise events. + /// Optional parameter to raise events. /// Default is True otherwise set to False to not raise events public void Save(IMember entity, bool raiseEvents = true) { @@ -1004,7 +1001,7 @@ namespace Umbraco.Core.Services /// Saves a list of objects ///
/// to save - /// Optional parameter to raise events. + /// Optional parameter to raise events. /// Default is True otherwise set to False to not raise events public void Save(IEnumerable entities, bool raiseEvents = true) { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 94267cf21f..8565144dee 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -313,6 +313,7 @@ + From 4f9bbdf7f724ba11273a545fd2d3090abccb4cbe Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 6 Feb 2017 18:26:33 +0100 Subject: [PATCH 085/105] Scope - Fix BulkInsertRecords, ensure a db connection is open --- .../Persistence/PetaPocoExtensions.cs | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index 2db9fcfdb4..8626714a43 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -105,8 +105,8 @@ namespace Umbraco.Core.Persistence // try to update var rowCount = updateCommand.IsNullOrWhiteSpace() - ? db.Update(poco) - : db.Update(updateCommand, updateArgs); + ? db.Update(poco) + : db.Update(updateCommand, updateArgs); if (rowCount > 0) return RecordPersistenceType.Update; @@ -162,7 +162,7 @@ namespace Umbraco.Core.Persistence [Obsolete("Use the DatabaseSchemaHelper instead")] public static void CreateTable(this Database db) - where T : new() + where T : new() { var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider); creator.CreateTable(); @@ -170,7 +170,7 @@ namespace Umbraco.Core.Persistence [Obsolete("Use the DatabaseSchemaHelper instead")] public static void CreateTable(this Database db, bool overwrite) - where T : new() + where T : new() { var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider); creator.CreateTable(overwrite); @@ -231,6 +231,24 @@ namespace Umbraco.Core.Persistence ISqlSyntaxProvider syntaxProvider, bool useNativeSqlPlatformBulkInsert = true, bool commitTrans = false) + { + db.OpenSharedConnection(); + try + { + return BulkInsertRecordsTry(db, collection, tr, syntaxProvider, useNativeSqlPlatformBulkInsert, commitTrans); + } + finally + { + db.CloseSharedConnection(); + } + } + + public static int BulkInsertRecordsTry(this Database db, + IEnumerable collection, + Transaction tr, + ISqlSyntaxProvider syntaxProvider, + bool useNativeSqlPlatformBulkInsert = true, + bool commitTrans = false) { if (commitTrans && tr == null) throw new ArgumentNullException("tr", "The transaction cannot be null if commitTrans is true."); @@ -241,15 +259,15 @@ namespace Umbraco.Core.Persistence return 0; } - var pd = Database.PocoData.ForType(typeof(T)); - if (pd == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T)); + var pd = Database.PocoData.ForType(typeof (T)); + if (pd == null) throw new InvalidOperationException("Could not find PocoData for " + typeof (T)); try { int processed = 0; var usedNativeSqlPlatformInserts = useNativeSqlPlatformBulkInsert - && NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed); + && NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed); if (usedNativeSqlPlatformInserts == false) { @@ -283,19 +301,13 @@ namespace Umbraco.Core.Persistence } if (commitTrans) - { - if (tr == null) throw new ArgumentNullException("The transaction cannot be null if commitTrans is true"); tr.Complete(); - } return processed; } catch { if (commitTrans) - { - if (tr == null) throw new ArgumentNullException("The transaction cannot be null if commitTrans is true"); tr.Dispose(); - } throw; } @@ -337,13 +349,16 @@ namespace Umbraco.Core.Persistence IEnumerable collection, out string[] sql) { + if (db == null) throw new ArgumentNullException("db"); + if (db.Connection == null) throw new ArgumentException("db.Connection is null."); + var tableName = db.EscapeTableName(pd.TableInfo.TableName); //get all columns to include and format for sql var cols = string.Join(", ", pd.Columns - .Where(c => IncludeColumn(pd, c)) - .Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray()); + .Where(c => IncludeColumn(pd, c)) + .Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray()); var itemArray = collection.ToArray(); @@ -357,9 +372,9 @@ namespace Umbraco.Core.Persistence // 4168 / 262 = 15.908... = there will be 16 trans in total //all items will be included if we have disabled db parameters - var itemsPerTrans = Math.Floor(2000.00 / paramsPerItem); + var itemsPerTrans = Math.Floor(2000.00/paramsPerItem); //there will only be one transaction if we have disabled db parameters - var numTrans = Math.Ceiling(itemArray.Length / itemsPerTrans); + var numTrans = Math.Ceiling(itemArray.Length/itemsPerTrans); var sqlQueries = new List(); var commands = new List(); @@ -367,8 +382,8 @@ namespace Umbraco.Core.Persistence for (var tIndex = 0; tIndex < numTrans; tIndex++) { var itemsForTrans = itemArray - .Skip(tIndex * (int)itemsPerTrans) - .Take((int)itemsPerTrans); + .Skip(tIndex*(int) itemsPerTrans) + .Take((int) itemsPerTrans); var cmd = db.CreateCommand(db.Connection, string.Empty); var pocoValues = new List(); @@ -418,7 +433,6 @@ namespace Umbraco.Core.Persistence /// The number of records inserted private static bool NativeSqlPlatformBulkInsertRecords(Database db, ISqlSyntaxProvider syntaxProvider, Database.PocoData pd, IEnumerable collection, out int processed) { - var dbConnection = db.Connection; //unwrap the profiled connection if there is one @@ -447,7 +461,6 @@ namespace Umbraco.Core.Persistence //could not use the SQL server's specific bulk insert operations processed = 0; return false; - } /// From 74e8390bdaf662f84d07b92bb843a8dae7697d00 Mon Sep 17 00:00:00 2001 From: Stephan Date: Sat, 11 Feb 2017 09:54:54 +0100 Subject: [PATCH 086/105] Scope - enable http vs call context switching --- src/Umbraco.Core/Scoping/IScopeInternal.cs | 1 + src/Umbraco.Core/Scoping/IScopeProvider.cs | 6 +- src/Umbraco.Core/Scoping/NoScope.cs | 6 +- src/Umbraco.Core/Scoping/Scope.cs | 45 ++++-- src/Umbraco.Core/Scoping/ScopeProvider.cs | 147 ++++++++++++------ .../TestHelpers/BaseDatabaseFactoryTest.cs | 2 +- 6 files changed, 141 insertions(+), 66 deletions(-) diff --git a/src/Umbraco.Core/Scoping/IScopeInternal.cs b/src/Umbraco.Core/Scoping/IScopeInternal.cs index 86276fc7f9..6b2204bfbe 100644 --- a/src/Umbraco.Core/Scoping/IScopeInternal.cs +++ b/src/Umbraco.Core/Scoping/IScopeInternal.cs @@ -7,6 +7,7 @@ namespace Umbraco.Core.Scoping internal interface IScopeInternal : IScope { IScopeInternal ParentScope { get; } + bool CallContext { get; } EventsDispatchMode DispatchMode { get; } IsolationLevel IsolationLevel { get; } UmbracoDatabase DatabaseOrNull { get; } diff --git a/src/Umbraco.Core/Scoping/IScopeProvider.cs b/src/Umbraco.Core/Scoping/IScopeProvider.cs index 6833eac699..b47beb2475 100644 --- a/src/Umbraco.Core/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/IScopeProvider.cs @@ -24,7 +24,8 @@ namespace Umbraco.Core.Scoping IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, - bool? scopeFileSystems = null); + bool? scopeFileSystems = null, + bool callContext = false); /// /// Creates a detached scope. @@ -44,10 +45,11 @@ namespace Umbraco.Core.Scoping /// Attaches a scope. /// /// The scope to attach. + /// A value indicating whether to force usage of call context. /// /// Only a scope created by can be attached. /// - void AttachScope(IScope scope); + void AttachScope(IScope scope, bool callContext = false); /// /// Detaches a scope. diff --git a/src/Umbraco.Core/Scoping/NoScope.cs b/src/Umbraco.Core/Scoping/NoScope.cs index b08f10ad0e..b77fb715d2 100644 --- a/src/Umbraco.Core/Scoping/NoScope.cs +++ b/src/Umbraco.Core/Scoping/NoScope.cs @@ -29,6 +29,9 @@ namespace Umbraco.Core.Scoping public Guid InstanceId { get { return _instanceId; } } #endif + /// + public bool CallContext { get { return false; } } + /// public RepositoryCacheMode RepositoryCacheMode { @@ -100,8 +103,7 @@ namespace Umbraco.Core.Scoping if (_database != null) _database.Dispose(); - _scopeProvider.AmbientScope = null; - _scopeProvider.AmbientContext = null; + _scopeProvider.SetAmbient(null); _disposed = true; GC.SuppressFinalize(this); diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index 824ccd6d80..bcb62cad90 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -19,6 +19,7 @@ namespace Umbraco.Core.Scoping private readonly EventsDispatchMode _dispatchMode; private readonly bool? _scopeFileSystem; private readonly ScopeContext _scopeContext; + private bool _callContext; private bool _disposed; private bool? _completed; @@ -36,7 +37,8 @@ namespace Umbraco.Core.Scoping IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, - bool? scopeFileSystems = null) + bool? scopeFileSystems = null, + bool callContext = false) { _scopeProvider = scopeProvider; _scopeContext = scopeContext; @@ -44,6 +46,7 @@ namespace Umbraco.Core.Scoping _repositoryCacheMode = repositoryCacheMode; _dispatchMode = dispatchMode; _scopeFileSystem = scopeFileSystems; + _callContext = callContext; Detachable = detachable; #if DEBUG_SCOPES @@ -98,20 +101,20 @@ namespace Umbraco.Core.Scoping IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, - bool? scopeFileSystems = null) - : this(scopeProvider, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems) - { - } + bool? scopeFileSystems = null, + bool callContext = false) + : this(scopeProvider, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext) + { } // initializes a new scope in a nested scopes chain, with its parent public Scope(ScopeProvider scopeProvider, Scope parent, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, - bool? scopeFileSystems = null) - : this(scopeProvider, parent, null, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems) - { - } + bool? scopeFileSystems = null, + bool callContext = false) + : this(scopeProvider, parent, null, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext) + { } // initializes a new scope, replacing a NoScope instance public Scope(ScopeProvider scopeProvider, NoScope noScope, @@ -119,8 +122,9 @@ namespace Umbraco.Core.Scoping IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, - bool? scopeFileSystems = null) - : this(scopeProvider, null, scopeContext, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems) + bool? scopeFileSystems = null, + bool callContext = false) + : this(scopeProvider, null, scopeContext, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext) { // steal everything from NoScope _database = noScope.DatabaseOrNull; @@ -135,6 +139,18 @@ namespace Umbraco.Core.Scoping public Guid InstanceId { get { return _instanceId; } } #endif + // a value indicating whether to force call-context + public bool CallContext + { + get + { + if (_callContext) return true; + if (ParentScope != null) return ParentScope.CallContext; + return false; + } + set { _callContext = value; } + } + public bool ScopedFileSystems { get @@ -331,7 +347,7 @@ namespace Umbraco.Core.Scoping #endif var parent = ParentScope; - _scopeProvider.AmbientScope = parent; + _scopeProvider.SetAmbientScope(parent); if (parent != null) parent.ChildCompleted(_completed); @@ -418,7 +434,7 @@ namespace Umbraco.Core.Scoping } finally { - _scopeProvider.AmbientContext = null; + _scopeProvider.SetAmbient(null); } } }, () => @@ -426,8 +442,7 @@ namespace Umbraco.Core.Scoping if (Detachable) { // get out of the way, restore original - _scopeProvider.AmbientScope = OrigScope; - _scopeProvider.AmbientContext = OrigContext; + _scopeProvider.SetAmbient(OrigScope, OrigContext); } }); } diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index 2f00e5b862..327e9fad8f 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Data; -using System.Linq; using System.Runtime.Remoting.Messaging; using System.Web; using Umbraco.Core.Events; @@ -24,17 +22,17 @@ namespace Umbraco.Core.Scoping SafeCallContext.Register( () => { - var scope = StaticAmbientScope; - var context = StaticAmbientContext; - StaticAmbientScope = null; - StaticAmbientContext = null; + var scope = AmbientContextScope; + var context = AmbientContextContext; + AmbientContextScope = null; + AmbientContextContext = null; return Tuple.Create(scope, context); }, o => { // cannot re-attached over leaked scope/context // except of course over NoScope (which leaks) - var ambientScope = StaticAmbientScope; + var ambientScope = AmbientContextScope; if (ambientScope != null) { var ambientNoScope = ambientScope as NoScope; @@ -44,11 +42,11 @@ namespace Umbraco.Core.Scoping // this should rollback any pending transaction ambientNoScope.Dispose(); } - if (StaticAmbientContext != null) throw new Exception("Found leaked context when restoring call context."); + if (AmbientContextContext != null) throw new Exception("Found leaked context when restoring call context."); var t = (Tuple)o; - StaticAmbientScope = t.Item1; - StaticAmbientContext = t.Item2; + AmbientContextScope = t.Item1; + AmbientContextContext = t.Item2; }); } @@ -58,7 +56,7 @@ namespace Umbraco.Core.Scoping internal const string ContextItemKey = "Umbraco.Core.Scoping.ScopeContext"; - private static ScopeContext CallContextContextValue + private static ScopeContext CallContextContext { get { return (ScopeContext)CallContext.LogicalGetData(ContextItemKey); } set @@ -68,7 +66,7 @@ namespace Umbraco.Core.Scoping } } - private static ScopeContext HttpContextContextValue + private static ScopeContext HttpContextContext { get { return (ScopeContext)HttpContext.Current.Items[ContextItemKey]; } set @@ -80,23 +78,27 @@ namespace Umbraco.Core.Scoping } } - private static ScopeContext StaticAmbientContext + private static ScopeContext AmbientContextContext { - get { return HttpContext.Current == null ? CallContextContextValue : HttpContextContextValue; } + get + { + // try http context, fallback onto call context + var value = HttpContext.Current == null ? null : HttpContextContext; + return value ?? CallContextContext; + } set { - if (HttpContext.Current == null) - CallContextContextValue = value; - else - HttpContextContextValue = value; + // clear both + if (HttpContext.Current != null) + HttpContextContext = value; + CallContextContext = value; } } /// public ScopeContext AmbientContext { - get { return StaticAmbientContext; } - set { StaticAmbientContext = value; } + get { return AmbientContextContext; } } #endregion @@ -109,7 +111,7 @@ namespace Umbraco.Core.Scoping // only 1 instance which can be disposed and disposed again private static readonly ScopeReference StaticScopeReference = new ScopeReference(new ScopeProvider(null)); - private static IScopeInternal CallContextValue + private static IScopeInternal CallContextScope { get { return (IScopeInternal) CallContext.LogicalGetData(ScopeItemKey); } set @@ -125,7 +127,7 @@ namespace Umbraco.Core.Scoping } } - private static IScopeInternal HttpContextValue + private static IScopeInternal HttpContextScope { get { return (IScopeInternal) HttpContext.Current.Items[ScopeItemKey]; } set @@ -150,33 +152,79 @@ namespace Umbraco.Core.Scoping } } - private static IScopeInternal StaticAmbientScope + private static IScopeInternal AmbientContextScope { - get { return HttpContext.Current == null ? CallContextValue : HttpContextValue; } + get + { + // try http context, fallback onto call context + var value = HttpContext.Current == null ? null : HttpContextScope; + return value ?? CallContextScope; + } set { - if (HttpContext.Current == null) - CallContextValue = value; - else - HttpContextValue = value; + // clear both + if (HttpContext.Current != null) + HttpContextScope = value; + CallContextScope = value; } } /// public IScopeInternal AmbientScope { - get { return StaticAmbientScope; } - set { StaticAmbientScope = value; } + get { return AmbientContextScope; } + } + + public void SetAmbientScope(IScopeInternal value) + { + if (value != null && value.CallContext) + { + if (HttpContext.Current != null) + HttpContextScope = null; // clear http context + CallContextScope = value; // set call context + } + else + { + CallContextScope = null; // clear call context + AmbientContextScope = value; // set appropriate context (maybe null) + } } /// public IScopeInternal GetAmbientOrNoScope() { - return AmbientScope ?? (AmbientScope = new NoScope(this)); + return AmbientScope ?? (AmbientContextScope = new NoScope(this)); } #endregion + public void SetAmbient(IScopeInternal scope, ScopeContext context = null) + { + if (scope != null && scope.CallContext) + { + // clear http context + if (HttpContext.Current != null) + { + HttpContextScope = null; + HttpContextContext = null; + } + + // set call context + CallContextScope = scope; + CallContextContext = context; + } + else + { + // clear call context + CallContextScope = null; + CallContextContext = null; + + // set appropriate context (maybe null) + AmbientContextScope = scope; + AmbientContextContext = context; + } + } + /// public IScope CreateDetachedScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, @@ -188,7 +236,7 @@ namespace Umbraco.Core.Scoping } /// - public void AttachScope(IScope other) + public void AttachScope(IScope other, bool callContext = false) { var otherScope = other as Scope; if (otherScope == null) @@ -199,8 +247,9 @@ namespace Umbraco.Core.Scoping otherScope.OrigScope = AmbientScope; otherScope.OrigContext = AmbientContext; - AmbientScope = otherScope; - AmbientContext = otherScope.Context; + + otherScope.CallContext = callContext; + SetAmbient(otherScope, otherScope.Context); } /// @@ -221,8 +270,7 @@ namespace Umbraco.Core.Scoping if (scope.Detachable == false) throw new InvalidOperationException("Ambient scope is not detachable."); - AmbientScope = scope.OrigScope; - AmbientContext = scope.OrigContext; + SetAmbient(scope.OrigScope, scope.OrigContext); scope.OrigScope = null; scope.OrigContext = null; return scope; @@ -233,15 +281,18 @@ namespace Umbraco.Core.Scoping IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, - bool? scopeFileSystems = null) + bool? scopeFileSystems = null, + bool callContext = false) { var ambient = AmbientScope; if (ambient == null) { - var context = AmbientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, false, context, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems); - if (AmbientContext == null) AmbientContext = context; // assign only if scope creation did not throw! - return AmbientScope = scope; + var ambientContext = AmbientContext; + var newContext = ambientContext == null ? new ScopeContext() : null; + var scope = new Scope(this, false, newContext, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext); + // assign only if scope creation did not throw! + SetAmbient(scope, newContext ?? ambientContext); + return scope; } // replace noScope with a real one @@ -255,16 +306,20 @@ namespace Umbraco.Core.Scoping var database = noScope.DatabaseOrNull; if (database != null && database.InTransaction) throw new Exception("NoScope is in a transaction."); - var context = AmbientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, noScope, context, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems); - if (AmbientContext == null) AmbientContext = context; // assign only if scope creation did not throw! - return AmbientScope = scope; + var ambientContext = AmbientContext; + var newContext = ambientContext == null ? new ScopeContext() : null; + var scope = new Scope(this, noScope, newContext, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext); + // assign only if scope creation did not throw! + SetAmbient(scope, newContext ?? ambientContext); + return scope; } var ambientScope = ambient as Scope; if (ambientScope == null) throw new Exception("Ambient scope is not a Scope instance."); - return AmbientScope = new Scope(this, ambientScope, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems); + var nested = new Scope(this, ambientScope, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext); + SetAmbient(nested, AmbientContext); + return nested; } /// diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 70f48b12fb..43e1ae679b 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -76,7 +76,7 @@ namespace Umbraco.Tests.TestHelpers var scopeProvider = new ScopeProvider(null); if (scopeProvider.AmbientScope != null) scopeProvider.AmbientScope.Dispose(); - scopeProvider.AmbientScope = null; + scopeProvider.SetAmbientScope(null); base.Initialize(); From d554417fde98c27256a7e3a9998a76309f65e7a9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 13 Feb 2017 16:47:04 +1100 Subject: [PATCH 087/105] U4-9517 BackOfficeSignInManager doesn't automatically keep LastLoginDate set on users --- src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs | 1 + src/Umbraco.Core/Models/Identity/IdentityUser.cs | 7 ++++++- src/Umbraco.Core/Security/BackOfficeSignInManager.cs | 6 +++++- src/Umbraco.Core/Security/BackOfficeUserStore.cs | 6 ++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index 0dc95a8987..fab34e5f17 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -13,6 +13,7 @@ namespace Umbraco.Core.Models.Identity public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { config.CreateMap() + .ForMember(user => user.LastLoginDateUtc, expression => expression.MapFrom(user => user.LastLoginDate.ToUniversalTime())) .ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email)) .ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id)) .ForMember(user => user.LockoutEndDateUtc, expression => expression.MapFrom(user => user.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null)) diff --git a/src/Umbraco.Core/Models/Identity/IdentityUser.cs b/src/Umbraco.Core/Models/Identity/IdentityUser.cs index cba4fc514a..c867dcf622 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUser.cs @@ -24,12 +24,17 @@ namespace Umbraco.Core.Models.Identity /// /// public IdentityUser() - { + { this.Claims = new List(); this.Roles = new List(); this.Logins = new List(); } + /// + /// Last login date + /// + public virtual DateTime? LastLoginDateUtc { get; set; } + /// /// Email /// diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index 7c65b43291..7f52958323 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -114,7 +114,7 @@ namespace Umbraco.Core.Security AllowRefresh = true, IssuedUtc = nowUtc, ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes) - }, userIdentity, rememberBrowserIdentity); + }, userIdentity, rememberBrowserIdentity); } else { @@ -127,6 +127,10 @@ namespace Umbraco.Core.Security }, userIdentity); } + //track the last login date + user.LastLoginDateUtc = DateTime.UtcNow; + await UserManager.UpdateAsync(user); + _logger.WriteCore(TraceEventType.Information, 0, string.Format( "Login attempt succeeded for username {0} from IP address {1}", diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 08c6f54dcf..889c7004d7 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -625,6 +625,12 @@ namespace Umbraco.Core.Security var anythingChanged = false; //don't assign anything if nothing has changed as this will trigger //the track changes of the model + if ((user.LastLoginDate != default(DateTime) && identityUser.LastLoginDateUtc.HasValue == false) + || identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value) + { + anythingChanged = true; + user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime(); + } if (user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false) { anythingChanged = true; From c64e3a8908b17f9ab9d56dc1bd7581978f2dc392 Mon Sep 17 00:00:00 2001 From: Stephan Date: Sat, 11 Feb 2017 11:54:22 +0100 Subject: [PATCH 088/105] Scope - refactor event dispatching --- src/Umbraco.Core/Events/EventsDispatchMode.cs | 29 ---- .../Events/ScopeEventDispatcher.cs | 145 ++++-------------- .../Events/ScopeEventDispatcherBase.cs | 101 ++++++++++++ src/Umbraco.Core/Scoping/ActionTime.cs | 13 -- src/Umbraco.Core/Scoping/IScopeInternal.cs | 1 - src/Umbraco.Core/Scoping/IScopeProvider.cs | 4 +- src/Umbraco.Core/Scoping/NoScope.cs | 1 - src/Umbraco.Core/Scoping/Scope.cs | 35 ++--- src/Umbraco.Core/Scoping/ScopeProvider.cs | 12 +- src/Umbraco.Core/Umbraco.Core.csproj | 3 +- .../Scoping/ScopeEventDispatcherTests.cs | 95 +++++------- src/Umbraco.Tests/Scoping/ScopedXmlTests.cs | 8 +- 12 files changed, 188 insertions(+), 259 deletions(-) delete mode 100644 src/Umbraco.Core/Events/EventsDispatchMode.cs create mode 100644 src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs delete mode 100644 src/Umbraco.Core/Scoping/ActionTime.cs diff --git a/src/Umbraco.Core/Events/EventsDispatchMode.cs b/src/Umbraco.Core/Events/EventsDispatchMode.cs deleted file mode 100644 index 23fe013af9..0000000000 --- a/src/Umbraco.Core/Events/EventsDispatchMode.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Umbraco.Core.Events -{ - public enum EventsDispatchMode - { - // in 7.5 we'd do: - // - // using (var uow = ...) - // { ... } - // Done.RaiseEvent(...); - // - // and so the event would trigger only *after* the transaction has completed, - // so actually PassThrough is more aggressive than what we had in 7.5 and should - // not be used. now in 7.6 we do: - // - // using (var uow = ...) - // { - // ... - // uow.Events.Dispatch(Done, ...); - // } - // - // so the event can be collected, so the default "kinda compatible" more has to be - // the Scope mode, and Passive is for Deploy only - - Unspecified = 0, - PassThrough, // both Doing and Done trigger immediately - Scope, // Doing triggers immediately, Done queued and triggered when & if the scope completes - Passive // Doing never triggers, Done queued and needs to be handled by custom code - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs index 31331a2d8d..93315a9946 100644 --- a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs +++ b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs @@ -1,139 +1,48 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; using Umbraco.Core.IO; namespace Umbraco.Core.Events -{ - +{ /// /// This event manager is created for each scope and is aware of if it is nested in an outer scope /// /// /// The outer scope is the only scope that can raise events, the inner scope's will defer to the outer scope /// - internal class ScopeEventDispatcher : IEventDispatcher + internal class ScopeEventDispatcher : ScopeEventDispatcherBase { - private readonly EventsDispatchMode _mode; - private List _events; + public ScopeEventDispatcher() + : base(true) + { } - public ScopeEventDispatcher(EventsDispatchMode mode) - { - _mode = mode; - } - - private List Events { get { return _events ?? (_events = new List()); } } - - private bool PassThroughCancelable { get { return _mode == EventsDispatchMode.PassThrough || _mode == EventsDispatchMode.Scope; } } - - private bool PassThrough { get { return _mode == EventsDispatchMode.PassThrough; } } - - private bool RaiseEvents { get { return _mode == EventsDispatchMode.Scope; } } - - public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null) - { - if (eventHandler == null) return args.Cancel; - if (PassThroughCancelable == false) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string eventName = null) - where TArgs : CancellableEventArgs - { - if (eventHandler == null) return args.Cancel; - if (PassThroughCancelable == false) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) - where TArgs : CancellableEventArgs - { - if (eventHandler == null) return args.Cancel; - if (PassThroughCancelable == false) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null) - { - if (eventHandler == null) return; - if (PassThrough) - eventHandler(sender, args); - else - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); - } - - public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string eventName = null) - { - if (eventHandler == null) return; - if (PassThrough) - eventHandler(sender, args); - else - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); - } - - public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) - { - if (eventHandler == null) return; - if (PassThrough) - eventHandler(sender, args); - else - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); - } - - public IEnumerable GetEvents(EventDefinitionFilter filter) - { - if (_events == null) - return Enumerable.Empty(); - - switch (filter) - { - case EventDefinitionFilter.All: - return _events; - case EventDefinitionFilter.FirstIn: - var l1 = new OrderedHashSet(); - foreach (var e in _events) - { - l1.Add(e); - } - return l1; - case EventDefinitionFilter.LastIn: - var l2 = new OrderedHashSet(keepOldest:false); - foreach (var e in _events) - { - l2.Add(e); - } - return l2; - default: - throw new ArgumentOutOfRangeException("filter", filter, null); - } - } - - public void ScopeExit(bool completed) + protected override void ScopeExitCompleted() { // fixme - we'd need to de-duplicate events somehow, etc - and the deduplication should be last in wins - if (_events == null) return; - - var mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem; - - if (RaiseEvents && completed) + foreach (var e in GetEvents(EventDefinitionFilter.All)) { - foreach (var e in _events) - { - e.RaiseEvent(); + e.RaiseEvent(); - // fixme - not sure I like doing it here - but then where? how? - var delete = e.Args as IDeletingMediaFilesEventArgs; - if (delete != null && delete.MediaFilesToDelete.Count > 0) - mediaFileSystem.DeleteMediaFiles(delete.MediaFilesToDelete); + // fixme - not sure I like doing it here - but then where? how? + var delete = e.Args as IDeletingMediaFilesEventArgs; + if (delete != null && delete.MediaFilesToDelete.Count > 0) + MediaFileSystem.DeleteMediaFiles(delete.MediaFilesToDelete); + } + } + + private MediaFileSystem _mediaFileSystem; + + private MediaFileSystem MediaFileSystem + { + get + { + if (_mediaFileSystem != null) return _mediaFileSystem; + + // fixme - insane! reading config goes cross AppDomain and serializes context? + using (new SafeCallContext()) + { + return _mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem; } } - - _events.Clear(); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs new file mode 100644 index 0000000000..d8462d18b3 --- /dev/null +++ b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Events +{ + public abstract class ScopeEventDispatcherBase : IEventDispatcher + { + private List _events; + private readonly bool _raiseCancelable; + + protected ScopeEventDispatcherBase(bool raiseCancelable) + { + _raiseCancelable = raiseCancelable; + } + + private List Events { get { return _events ?? (_events = new List()); } } + + public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null) + { + if (eventHandler == null) return args.Cancel; + if (_raiseCancelable == false) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) return args.Cancel; + if (_raiseCancelable == false) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) return args.Cancel; + if (_raiseCancelable == false) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null) + { + if (eventHandler == null) return; + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string eventName = null) + { + if (eventHandler == null) return; + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) + { + if (eventHandler == null) return; + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public IEnumerable GetEvents(EventDefinitionFilter filter) + { + if (_events == null) + return Enumerable.Empty(); + + switch (filter) + { + case EventDefinitionFilter.All: + return _events; + case EventDefinitionFilter.FirstIn: + var l1 = new OrderedHashSet(); + foreach (var e in _events) + { + l1.Add(e); + } + return l1; + case EventDefinitionFilter.LastIn: + var l2 = new OrderedHashSet(keepOldest: false); + foreach (var e in _events) + { + l2.Add(e); + } + return l2; + default: + throw new ArgumentOutOfRangeException("filter", filter, null); + } + } + + public void ScopeExit(bool completed) + { + if (_events == null) return; + if (completed) + ScopeExitCompleted(); + _events.Clear(); + } + + protected abstract void ScopeExitCompleted(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Scoping/ActionTime.cs b/src/Umbraco.Core/Scoping/ActionTime.cs deleted file mode 100644 index 7cb5e3ed35..0000000000 --- a/src/Umbraco.Core/Scoping/ActionTime.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Umbraco.Core.Scoping -{ - [Flags] - public enum ActionTime - { - None = 0, - BeforeCommit = 1, - BeforeEvents = 2, - BeforeDispose = 4 - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Scoping/IScopeInternal.cs b/src/Umbraco.Core/Scoping/IScopeInternal.cs index 6b2204bfbe..c1c28b41fe 100644 --- a/src/Umbraco.Core/Scoping/IScopeInternal.cs +++ b/src/Umbraco.Core/Scoping/IScopeInternal.cs @@ -8,7 +8,6 @@ namespace Umbraco.Core.Scoping { IScopeInternal ParentScope { get; } bool CallContext { get; } - EventsDispatchMode DispatchMode { get; } IsolationLevel IsolationLevel { get; } UmbracoDatabase DatabaseOrNull { get; } EventMessages MessagesOrNull { get; } diff --git a/src/Umbraco.Core/Scoping/IScopeProvider.cs b/src/Umbraco.Core/Scoping/IScopeProvider.cs index b47beb2475..9e6bd6fc78 100644 --- a/src/Umbraco.Core/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/IScopeProvider.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Scoping IScope CreateScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, + IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false); @@ -38,7 +38,7 @@ namespace Umbraco.Core.Scoping IScope CreateDetachedScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, + IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null); /// diff --git a/src/Umbraco.Core/Scoping/NoScope.cs b/src/Umbraco.Core/Scoping/NoScope.cs index b77fb715d2..c2d89ff632 100644 --- a/src/Umbraco.Core/Scoping/NoScope.cs +++ b/src/Umbraco.Core/Scoping/NoScope.cs @@ -110,7 +110,6 @@ namespace Umbraco.Core.Scoping } public IScopeInternal ParentScope { get { return null; } } - public EventsDispatchMode DispatchMode { get {return EventsDispatchMode.Unspecified; } } public IsolationLevel IsolationLevel { get {return IsolationLevel.Unspecified; } } public bool ScopedFileSystems { get { return false; } } public void ChildCompleted(bool? completed) { } diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index bcb62cad90..f088ca1993 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -16,7 +16,6 @@ namespace Umbraco.Core.Scoping private readonly ScopeProvider _scopeProvider; private readonly IsolationLevel _isolationLevel; private readonly RepositoryCacheMode _repositoryCacheMode; - private readonly EventsDispatchMode _dispatchMode; private readonly bool? _scopeFileSystem; private readonly ScopeContext _scopeContext; private bool _callContext; @@ -36,7 +35,7 @@ namespace Umbraco.Core.Scoping Scope parent, ScopeContext scopeContext, bool detachable, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, + IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false) { @@ -44,7 +43,7 @@ namespace Umbraco.Core.Scoping _scopeContext = scopeContext; _isolationLevel = isolationLevel; _repositoryCacheMode = repositoryCacheMode; - _dispatchMode = dispatchMode; + _eventDispatcher = eventDispatcher; _scopeFileSystem = scopeFileSystems; _callContext = callContext; Detachable = detachable; @@ -77,9 +76,9 @@ namespace Umbraco.Core.Scoping if (repositoryCacheMode != RepositoryCacheMode.Unspecified && parent.RepositoryCacheMode != repositoryCacheMode) throw new ArgumentException("Cannot be different from parent.", "repositoryCacheMode"); - // cannot specify a different mode! - if (_dispatchMode != EventsDispatchMode.Unspecified && parent._dispatchMode != dispatchMode) - throw new ArgumentException("Cannot be different from parent.", "dispatchMode"); + // cannot specify a dispatcher! + if (_eventDispatcher != null) + throw new ArgumentException("Cannot be specified on nested scope.", "eventDispatcher"); // cannot specify a different fs scope! if (scopeFileSystems != null && parent._scopeFileSystem != scopeFileSystems) @@ -100,20 +99,20 @@ namespace Umbraco.Core.Scoping ScopeContext scopeContext, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, + IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false) - : this(scopeProvider, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext) + : this(scopeProvider, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext) { } // initializes a new scope in a nested scopes chain, with its parent public Scope(ScopeProvider scopeProvider, Scope parent, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, + IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false) - : this(scopeProvider, parent, null, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext) + : this(scopeProvider, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext) { } // initializes a new scope, replacing a NoScope instance @@ -121,10 +120,10 @@ namespace Umbraco.Core.Scoping ScopeContext scopeContext, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, + IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false) - : this(scopeProvider, null, scopeContext, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext) + : this(scopeProvider, null, scopeContext, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext) { // steal everything from NoScope _database = noScope.DatabaseOrNull; @@ -160,16 +159,6 @@ namespace Umbraco.Core.Scoping } } - public EventsDispatchMode DispatchMode - { - get - { - if (_dispatchMode != EventsDispatchMode.Unspecified) return _dispatchMode; - if (ParentScope != null) return ParentScope.DispatchMode; - return EventsDispatchMode.Scope; - } - } - /// public RepositoryCacheMode RepositoryCacheMode { @@ -306,7 +295,7 @@ namespace Umbraco.Core.Scoping { EnsureNotDisposed(); if (ParentScope != null) return ParentScope.Events; - return _eventDispatcher ?? (_eventDispatcher = new ScopeEventDispatcher(DispatchMode)); + return _eventDispatcher ?? (_eventDispatcher = new ScopeEventDispatcher()); } } diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index 327e9fad8f..66d35106c3 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -229,10 +229,10 @@ namespace Umbraco.Core.Scoping public IScope CreateDetachedScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, + IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null) { - return new Scope(this, true, null, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems); + return new Scope(this, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); } /// @@ -280,7 +280,7 @@ namespace Umbraco.Core.Scoping public IScope CreateScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified, + IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false) { @@ -289,7 +289,7 @@ namespace Umbraco.Core.Scoping { var ambientContext = AmbientContext; var newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, false, newContext, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext); + var scope = new Scope(this, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext); // assign only if scope creation did not throw! SetAmbient(scope, newContext ?? ambientContext); return scope; @@ -308,7 +308,7 @@ namespace Umbraco.Core.Scoping throw new Exception("NoScope is in a transaction."); var ambientContext = AmbientContext; var newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, noScope, newContext, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext); + var scope = new Scope(this, noScope, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext); // assign only if scope creation did not throw! SetAmbient(scope, newContext ?? ambientContext); return scope; @@ -317,7 +317,7 @@ namespace Umbraco.Core.Scoping var ambientScope = ambient as Scope; if (ambientScope == null) throw new Exception("Ambient scope is not a Scope instance."); - var nested = new Scope(this, ambientScope, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext); + var nested = new Scope(this, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext); SetAmbient(nested, AmbientContext); return nested; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8565144dee..9fcff37763 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -314,6 +314,7 @@ + @@ -362,7 +363,6 @@ - @@ -559,7 +559,6 @@ - diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 7d5754529e..b68e2a3f9f 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; @@ -8,7 +9,6 @@ using Umbraco.Core.Scoping; namespace Umbraco.Tests.Scoping { - [TestFixture] public class ScopeEventDispatcherTests { @@ -21,11 +21,15 @@ namespace Umbraco.Tests.Scoping DoThing3 = null; } - [TestCase(EventsDispatchMode.PassThrough, true, true)] - [TestCase(EventsDispatchMode.PassThrough, true, false)] - [TestCase(EventsDispatchMode.PassThrough, false, true)] - [TestCase(EventsDispatchMode.PassThrough, false, false)] - public void PassThroughCancelable(EventsDispatchMode mode, bool cancel, bool complete) + [TestCase(false, true, true)] + [TestCase(false, true, false)] + [TestCase(false, false, true)] + [TestCase(false, false, false)] + [TestCase(true, true, true)] + [TestCase(true, true, false)] + [TestCase(true, false, true)] + [TestCase(true, false, false)] + public void EventsHandling(bool passive, bool cancel, bool complete) { var counter1 = 0; var counter2 = 0; @@ -34,7 +38,7 @@ namespace Umbraco.Tests.Scoping DoThing2 += (sender, args) => { counter2++; }; var scopeProvider = new ScopeProvider(Mock.Of()); - using (var scope = scopeProvider.CreateScope(dispatchMode: mode)) + using (var scope = scopeProvider.CreateScope(eventDispatcher: passive ? new PassiveEventDispatcher() : null)) { var cancelled = scope.Events.DispatchCancelable(DoThing1, this, new SaveEventArgs("test")); if (cancelled == false) @@ -43,22 +47,15 @@ namespace Umbraco.Tests.Scoping scope.Complete(); } - var expected1 = mode == EventsDispatchMode.Passive ? 0 : 1; + var expected1 = passive ? 0 : 1; Assert.AreEqual(expected1, counter1); - var expected2 = -1; - switch (mode) - { - case EventsDispatchMode.PassThrough: - expected2 = cancel ? 0 : 1; - break; - case EventsDispatchMode.Scope: - expected2 = cancel ? 0 : (complete ? 1 : 0); - break; - case EventsDispatchMode.Passive: - expected2 = 0; - break; - } + int expected2; + if (passive) + expected2 = 0; + else + expected2 = cancel ? 0 : (complete ? 1 : 0); + Assert.AreEqual(expected2, counter2); } @@ -70,7 +67,7 @@ namespace Umbraco.Tests.Scoping DoThing3 += OnDoThingFail; var scopeProvider = new ScopeProvider(Mock.Of()); - using (var scope = scopeProvider.CreateScope(dispatchMode: EventsDispatchMode.Passive)) + using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); @@ -94,45 +91,14 @@ namespace Umbraco.Tests.Scoping [TestCase(true)] [TestCase(false)] - public void EventsDispatchmode_PassThrough(bool complete) - { - var counter = 0; - - DoThing1 += (sender, args) => { counter++; }; - DoThing2 += (sender, args) => { counter++; }; - DoThing3 += (sender, args) => { counter++; }; - - var scopeProvider = new ScopeProvider(Mock.Of()); - using (var scope = scopeProvider.CreateScope(dispatchMode: EventsDispatchMode.PassThrough)) - { - scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); - scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); - scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); - - // events have not been queued - Assert.IsEmpty(scope.Events.GetEvents(EventDefinitionFilter.All)); - - // events have been raised - Assert.AreEqual(3, counter); - - if (complete) - scope.Complete(); - } - - // nothing has changed - Assert.AreEqual(3, counter); - } - - [TestCase(true)] - [TestCase(false)] - public void EventsDispatchMode_Passive(bool complete) + public void EventsDispatching_Passive(bool complete) { DoThing1 += OnDoThingFail; DoThing2 += OnDoThingFail; DoThing3 += OnDoThingFail; var scopeProvider = new ScopeProvider(Mock.Of()); - using (var scope = scopeProvider.CreateScope(dispatchMode: EventsDispatchMode.Passive)) + using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); @@ -150,7 +116,7 @@ namespace Umbraco.Tests.Scoping [TestCase(true)] [TestCase(false)] - public void EventsDispatchMode_Scope(bool complete) + public void EventsDispatching_Scope(bool complete) { var counter = 0; IScope ambientScope = null; @@ -170,7 +136,7 @@ namespace Umbraco.Tests.Scoping }; Guid guid; - using (var scope = scopeProvider.CreateScope(dispatchMode: EventsDispatchMode.Scope)) + using (var scope = scopeProvider.CreateScope()) { Assert.IsNotNull(scopeProvider.AmbientContext); guid = scopeProvider.Context.Enlist("value", Guid.NewGuid, (c, o) => { }); @@ -181,6 +147,7 @@ namespace Umbraco.Tests.Scoping // events have been queued Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count()); + Assert.AreEqual(0, counter); if (complete) scope.Complete(); @@ -215,5 +182,17 @@ namespace Umbraco.Tests.Scoping public static event EventHandler> DoThing2; public static event TypedEventHandler> DoThing3; - } + + public class PassiveEventDispatcher : ScopeEventDispatcherBase + { + public PassiveEventDispatcher() + : base(false) + { } + + protected override void ScopeExitCompleted() + { + // do nothing + } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 179c924f7b..6267e1dd1c 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -213,12 +213,8 @@ namespace Umbraco.Tests.Scoping var scopeProvider = ApplicationContext.Current.ScopeProvider as IScopeProviderInternal; Assert.IsNotNull(scopeProvider); - if (complete) - // because some event handlers trigger xml refresh with directly uses the DB - Assert.IsNotNull(scopeProvider.AmbientScope); - else - // because nothing happened - Assert.IsNull(scopeProvider.AmbientScope); + // ambient scope may be null, or maybe not, depending on whether the code that + // was called did proper scoped work, or some direct (NoScope) use of the database Assert.IsNull(scopeProvider.AmbientContext); // limited number of clones! From 12dd01dd29381beab6d6cd54da3edb3568afd7b4 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 13 Feb 2017 09:04:19 +0100 Subject: [PATCH 089/105] Scope - refactor call context --- src/Umbraco.Core/ObjectExtensions.cs | 5 + src/Umbraco.Core/Scoping/IScopeProvider.cs | 5 +- src/Umbraco.Core/Scoping/Scope.cs | 2 +- src/Umbraco.Core/Scoping/ScopeProvider.cs | 332 +++++++++++------- src/Umbraco.Tests/Scoping/LeakTests.cs | 14 +- src/Umbraco.Tests/Scoping/ScopeTests.cs | 53 +++ .../TestHelpers/BaseDatabaseFactoryTest.cs | 7 +- 7 files changed, 288 insertions(+), 130 deletions(-) diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index fcdc2f52a6..6a1befaad8 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -647,5 +647,10 @@ namespace Umbraco.Core return "[GetPropertyValueException]"; } } + + internal static Guid AsGuid(this object value) + { + return value is Guid ? (Guid) value : Guid.Empty; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Scoping/IScopeProvider.cs b/src/Umbraco.Core/Scoping/IScopeProvider.cs index 9e6bd6fc78..754fb63aa7 100644 --- a/src/Umbraco.Core/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/IScopeProvider.cs @@ -1,4 +1,5 @@ -using System.Data; +using System; +using System.Data; using Umbraco.Core.Events; #if DEBUG_SCOPES using System.Collections.Generic; @@ -66,7 +67,9 @@ namespace Umbraco.Core.Scoping ScopeContext Context { get; } #if DEBUG_SCOPES + Dictionary CallContextObjects { get; } IEnumerable ScopeInfos { get; } + ScopeInfo GetScopeInfo(IScope scope); #endif } } diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index f088ca1993..4730495ba9 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -336,7 +336,7 @@ namespace Umbraco.Core.Scoping #endif var parent = ParentScope; - _scopeProvider.SetAmbientScope(parent); + _scopeProvider.AmbientScope = parent; if (parent != null) parent.ChildCompleted(_completed); diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index 66d35106c3..7101258447 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -1,9 +1,14 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Data; using System.Runtime.Remoting.Messaging; using System.Web; using Umbraco.Core.Events; using Umbraco.Core.Persistence; +#if DEBUG_SCOPES +using System.Linq; +#endif namespace Umbraco.Core.Scoping { @@ -22,17 +27,17 @@ namespace Umbraco.Core.Scoping SafeCallContext.Register( () => { - var scope = AmbientContextScope; - var context = AmbientContextContext; - AmbientContextScope = null; - AmbientContextContext = null; + var scope = GetCallContextObject(ScopeItemKey); + var context = GetCallContextObject(ContextItemKey); + SetCallContextObject(ScopeItemKey, null); + SetCallContextObject(ContextItemKey, null); return Tuple.Create(scope, context); }, o => { // cannot re-attached over leaked scope/context // except of course over NoScope (which leaks) - var ambientScope = AmbientContextScope; + var ambientScope = AmbientScopeInternal; if (ambientScope != null) { var ambientNoScope = ambientScope as NoScope; @@ -42,63 +47,185 @@ namespace Umbraco.Core.Scoping // this should rollback any pending transaction ambientNoScope.Dispose(); } - if (AmbientContextContext != null) throw new Exception("Found leaked context when restoring call context."); + if (AmbientContextInternal != null) throw new Exception("Found leaked context when restoring call context."); - var t = (Tuple)o; - AmbientContextScope = t.Item1; - AmbientContextContext = t.Item2; + var t = (Tuple) o; + SetCallContextObject(ScopeItemKey, t.Item1); + SetCallContextObject(ContextItemKey, t.Item2); }); } public IDatabaseFactory2 DatabaseFactory { get; private set; } + #region Context + + // objects that go into the logical call context better be serializable else they'll eventually + // cause issues whenever some cross-AppDomain code executes - could be due to ReSharper running + // tests, any other things (see https://msdn.microsoft.com/en-us/library/dn458353(v=vs.110).aspx), + // but we don't want to make all of our objects serializable since they are *not* meant to be + // used in cross-AppDomain scenario anyways. + // in addition, whatever goes into the logical call context is serialized back and forth any + // time cross-AppDomain code executes, so if we put an "object" there, we'll can *another* + // "object" instance - and so we cannot use a random object as a key. + // so what we do is: we register a guid in the call context, and we keep a table mapping those + // guids to the actual objects. the guid serializes back and forth without causing any issue, + // and we can retrieve the actual objects from the table. + // only issue: how are we supposed to clear the table? we can't, really. objects should take + // care of de-registering themselves from context. + // everything we use does, except the NoScope scope, which just stays there + // + // during tests, NoScope can to into call context... nothing much we can do about it + + private static readonly object StaticCallContextObjectsLock = new object(); + private static readonly Dictionary StaticCallContextObjects + = new Dictionary(); + +#if DEBUG_SCOPES + public Dictionary CallContextObjects + { + get + { + lock (StaticCallContextObjectsLock) + { + // capture in a dictionary + return StaticCallContextObjects.ToDictionary(x => x.Key, x => x.Value); + } + } + } +#endif + + private static T GetCallContextObject(string key) + where T : class + { + var objectKey = CallContext.LogicalGetData(key).AsGuid(); + lock (StaticCallContextObjectsLock) + { + object callContextObject; + return StaticCallContextObjects.TryGetValue(objectKey, out callContextObject) ? (T)callContextObject : null; + } + } + + private static void SetCallContextObject(string key, object value) + { +#if DEBUG_SCOPES + // manage the 'context' that contains the scope (null, "http" or "call") + // first, null-register the existing value + var ambientKey = CallContext.LogicalGetData(ScopeItemKey).AsGuid(); + object o = null; + lock (StaticCallContextObjectsLock) + { + if (ambientKey != default (Guid)) + StaticCallContextObjects.TryGetValue(ambientKey, out o); + } + var ambientScope = o as IScope; + if (ambientScope != null) RegisterContext(ambientScope, null); + // then register the new value + var scope = value as IScope; + if (scope != null) RegisterContext(scope, "call"); +#endif + if (value == null) + { + var objectKey = CallContext.LogicalGetData(key).AsGuid(); + CallContext.FreeNamedDataSlot(key); + if (objectKey == default (Guid)) return; + lock (StaticCallContextObjectsLock) + { + StaticCallContextObjects.Remove(objectKey); + } + } + else + { + // note - we are *not* detecting an already-existing value + // because our code in this class *always* sets to null before + // setting to a real value + var objectKey = Guid.NewGuid(); + lock (StaticCallContextObjectsLock) + { + StaticCallContextObjects.Add(objectKey, value); + } + CallContext.LogicalSetData(key, objectKey); + } + } + + internal static Func HttpContextItemsGetter { get; set; } + + private static IDictionary HttpContextItems + { + get + { + return HttpContextItemsGetter == null + ? (HttpContext.Current == null ? null : HttpContext.Current.Items) + : HttpContextItemsGetter(); + } + } + + public static T GetHttpContextObject(string key, bool required = true) + where T : class + { + var httpContextItems = HttpContextItems; + if (httpContextItems != null) + return (T)httpContextItems[key]; + if (required) + throw new Exception("HttpContext.Current is null."); + return null; + } + + private static bool SetHttpContextObject(string key, object value, bool required = true) + { + var httpContextItems = HttpContextItems; + if (httpContextItems == null) + { + if (required) + throw new Exception("HttpContext.Current is null."); + return false; + } +#if DEBUG_SCOPES + // manage the 'context' that contains the scope (null, "http" or "call") + // first, null-register the existing value + var ambientScope = (IScope)httpContextItems[ScopeItemKey]; + if (ambientScope != null) RegisterContext(ambientScope, null); + // then register the new value + var scope = value as IScope; + if (scope != null) RegisterContext(scope, "http"); +#endif + if (value == null) + httpContextItems.Remove(key); + else + httpContextItems[key] = value; + return true; + } + + #endregion + #region Ambient Context internal const string ContextItemKey = "Umbraco.Core.Scoping.ScopeContext"; - private static ScopeContext CallContextContext - { - get { return (ScopeContext)CallContext.LogicalGetData(ContextItemKey); } - set - { - if (value == null) CallContext.FreeNamedDataSlot(ContextItemKey); - else CallContext.LogicalSetData(ContextItemKey, value); - } - } - - private static ScopeContext HttpContextContext - { - get { return (ScopeContext)HttpContext.Current.Items[ContextItemKey]; } - set - { - if (value == null) - HttpContext.Current.Items.Remove(ContextItemKey); - else - HttpContext.Current.Items[ContextItemKey] = value; - } - } - - private static ScopeContext AmbientContextContext + internal static ScopeContext AmbientContextInternal { get { // try http context, fallback onto call context - var value = HttpContext.Current == null ? null : HttpContextContext; - return value ?? CallContextContext; + var value = GetHttpContextObject(ContextItemKey, false); + return value ?? GetCallContextObject(ContextItemKey); } set { // clear both - if (HttpContext.Current != null) - HttpContextContext = value; - CallContextContext = value; + SetHttpContextObject(ContextItemKey, null, false); + SetCallContextObject(ContextItemKey, null); + if (value == null) return; + + // set http/call context + if (SetHttpContextObject(ContextItemKey, value, false) == false) + SetCallContextObject(ContextItemKey, value); } } /// public ScopeContext AmbientContext { - get { return AmbientContextContext; } + get { return AmbientContextInternal; } } #endregion @@ -111,117 +238,72 @@ namespace Umbraco.Core.Scoping // only 1 instance which can be disposed and disposed again private static readonly ScopeReference StaticScopeReference = new ScopeReference(new ScopeProvider(null)); - private static IScopeInternal CallContextScope - { - get { return (IScopeInternal) CallContext.LogicalGetData(ScopeItemKey); } - set - { -#if DEBUG_SCOPES - // manage the 'context' that contains the scope (null, "http" or "lcc") - var ambientScope = (IScope) CallContext.LogicalGetData(ScopeItemKey); - if (ambientScope != null) RegisterContext(ambientScope, null); - if (value != null) RegisterContext(value, "lcc"); -#endif - if (value == null) CallContext.FreeNamedDataSlot(ScopeItemKey); - else CallContext.LogicalSetData(ScopeItemKey, value); - } - } - - private static IScopeInternal HttpContextScope - { - get { return (IScopeInternal) HttpContext.Current.Items[ScopeItemKey]; } - set - { -#if DEBUG_SCOPES - // manage the 'context' that contains the scope (null, "http" or "lcc") - var ambientScope = (IScope) HttpContext.Current.Items[ScopeItemKey]; - if (ambientScope != null) RegisterContext(ambientScope, null); - if (value != null) RegisterContext(value, "http"); -#endif - if (value == null) - { - HttpContext.Current.Items.Remove(ScopeItemKey); - HttpContext.Current.Items.Remove(ScopeRefItemKey); - } - else - { - HttpContext.Current.Items[ScopeItemKey] = value; - if (HttpContext.Current.Items[ScopeRefItemKey] == null) - HttpContext.Current.Items[ScopeRefItemKey] = StaticScopeReference; - } - } - } - - private static IScopeInternal AmbientContextScope + internal static IScopeInternal AmbientScopeInternal { get { // try http context, fallback onto call context - var value = HttpContext.Current == null ? null : HttpContextScope; - return value ?? CallContextScope; + var value = GetHttpContextObject(ScopeItemKey, false); + return value ?? GetCallContextObject(ScopeItemKey); } set { // clear both - if (HttpContext.Current != null) - HttpContextScope = value; - CallContextScope = value; + SetHttpContextObject(ScopeItemKey, null, false); + SetHttpContextObject(ScopeRefItemKey, null, false); + SetCallContextObject(ScopeItemKey, null); + if (value == null) return; + + // set http/call context + if (value.CallContext == false && SetHttpContextObject(ScopeItemKey, value, false)) + SetHttpContextObject(ScopeRefItemKey, StaticScopeReference); + else + SetCallContextObject(ScopeItemKey, value); } } /// public IScopeInternal AmbientScope { - get { return AmbientContextScope; } - } - - public void SetAmbientScope(IScopeInternal value) - { - if (value != null && value.CallContext) - { - if (HttpContext.Current != null) - HttpContextScope = null; // clear http context - CallContextScope = value; // set call context - } - else - { - CallContextScope = null; // clear call context - AmbientContextScope = value; // set appropriate context (maybe null) - } + get { return AmbientScopeInternal; } + internal set { AmbientScopeInternal = value; } } /// public IScopeInternal GetAmbientOrNoScope() { - return AmbientScope ?? (AmbientContextScope = new NoScope(this)); + return AmbientScope ?? (AmbientScope = new NoScope(this)); } #endregion public void SetAmbient(IScopeInternal scope, ScopeContext context = null) { - if (scope != null && scope.CallContext) + // clear all + SetHttpContextObject(ScopeItemKey, null, false); + SetHttpContextObject(ScopeRefItemKey, null, false); + SetCallContextObject(ScopeItemKey, null); + SetHttpContextObject(ContextItemKey, null, false); + SetCallContextObject(ContextItemKey, null); + if (scope == null) { - // clear http context - if (HttpContext.Current != null) - { - HttpContextScope = null; - HttpContextContext = null; - } + if (context != null) + throw new ArgumentException("Must be null if scope is null.", "context"); + return; + } - // set call context - CallContextScope = scope; - CallContextContext = context; + if (context == null) + throw new ArgumentNullException("context"); + + if (scope.CallContext == false && SetHttpContextObject(ScopeItemKey, scope, false)) + { + SetHttpContextObject(ScopeRefItemKey, StaticScopeReference); + SetHttpContextObject(ContextItemKey, context); } else { - // clear call context - CallContextScope = null; - CallContextContext = null; - - // set appropriate context (maybe null) - AmbientContextScope = scope; - AmbientContextContext = context; + SetCallContextObject(ScopeItemKey, scope); + SetCallContextObject(ContextItemKey, context); } } @@ -377,11 +459,20 @@ namespace Umbraco.Core.Scoping } } + public ScopeInfo GetScopeInfo(IScope scope) + { + lock (StaticScopeInfosLock) + { + return StaticScopeInfos.FirstOrDefault(x => x.Scope == scope); + } + } + //private static void Log(string message, UmbracoDatabase database) //{ // LogHelper.Debug(message + " (" + (database == null ? "" : database.InstanceSid) + ")."); //} + // register a scope and capture its ctor stacktrace public void RegisterScope(IScope scope) { lock (StaticScopeInfosLock) @@ -391,7 +482,8 @@ namespace Umbraco.Core.Scoping } } - // 'context' that contains the scope (null, "http" or "lcc") + // register that a scope is in a 'context' + // 'context' that contains the scope (null, "http" or "call") public static void RegisterContext(IScope scope, string context) { lock (StaticScopeInfosLock) diff --git a/src/Umbraco.Tests/Scoping/LeakTests.cs b/src/Umbraco.Tests/Scoping/LeakTests.cs index 1e4fd00feb..55b80062a4 100644 --- a/src/Umbraco.Tests/Scoping/LeakTests.cs +++ b/src/Umbraco.Tests/Scoping/LeakTests.cs @@ -2,6 +2,7 @@ using System.Data; using System.Runtime.Remoting.Messaging; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Persistence; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; @@ -38,11 +39,14 @@ namespace Umbraco.Tests.Scoping _database.BeginTransaction(); // opens and maintains a connection // the test is leaking a scope with a non-null database - var contextScope = CallContext.LogicalGetData(ScopeProvider.ScopeItemKey); - Assert.IsNotNull(contextScope); - Assert.IsInstanceOf(CallContext.LogicalGetData(ScopeProvider.ScopeItemKey)); - Assert.IsNotNull(((NoScope) contextScope).DatabaseOrNull); - Assert.AreSame(_database, ((NoScope)contextScope).DatabaseOrNull); + var contextGuid = CallContext.LogicalGetData(ScopeProvider.ScopeItemKey).AsGuid(); + Assert.AreNotEqual(Guid.Empty, contextGuid); + + // only if Core.DEBUG_SCOPES are defined + //var contextScope = DatabaseContext.ScopeProvider.CallContextObjects[contextGuid] as NoScope; + //Assert.IsNotNull(contextScope); + //Assert.IsNotNull(contextScope.DatabaseOrNull); + //Assert.AreSame(_database, contextScope.DatabaseOrNull); // save the connection _connection = _database.Connection; diff --git a/src/Umbraco.Tests/Scoping/ScopeTests.cs b/src/Umbraco.Tests/Scoping/ScopeTests.cs index 446b7ea9f0..87884abd26 100644 --- a/src/Umbraco.Tests/Scoping/ScopeTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeTests.cs @@ -1,4 +1,9 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Remoting.Messaging; +using System.Web; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Persistence; @@ -97,6 +102,54 @@ namespace Umbraco.Tests.Scoping Assert.IsNull(scopeProvider.AmbientScope); } + [Test] + public void NestedMigrateScope() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + Assert.IsNull(scopeProvider.AmbientScope); + var httpContextItems = new Hashtable(); + ScopeProvider.HttpContextItemsGetter = () => httpContextItems; + try + { + using (var scope = scopeProvider.CreateScope()) + { + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + Assert.AreSame(scope, httpContextItems[ScopeProvider.ScopeItemKey]); + + // only if Core.DEBUG_SCOPES are defined + //Assert.IsEmpty(scopeProvider.CallContextObjects); + + using (var nested = scopeProvider.CreateScope(callContext: true)) + { + Assert.IsInstanceOf(nested); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(nested, scopeProvider.AmbientScope); + Assert.AreSame(scope, ((Scope) nested).ParentScope); + + // it's moved over to call context + Assert.IsNull(httpContextItems[ScopeProvider.ScopeItemKey]); + var callContextKey = CallContext.LogicalGetData(ScopeProvider.ScopeItemKey).AsGuid(); + Assert.AreNotEqual(Guid.Empty, callContextKey); + + // only if Core.DEBUG_SCOPES are defined + //var ccnested = scopeProvider.CallContextObjects[callContextKey]; + //Assert.AreSame(nested, ccnested); + } + + // it's naturally back in http context + Assert.AreSame(scope, httpContextItems[ScopeProvider.ScopeItemKey]); + } + Assert.IsNull(scopeProvider.AmbientScope); + } + finally + { + ScopeProvider.HttpContextItemsGetter = null; + } + } + [Test] public void NestedCreateScopeContext() { diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 43e1ae679b..2370ad1dfd 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -72,11 +72,12 @@ namespace Umbraco.Tests.TestHelpers GetDbProviderName(), Logger); - // fixme - bah - this is needed to reset static properties? Stephen to update this note + // ensure we start tests in a clean state ie without any scope in context + // anything that used a true 'Scope' would have removed it, but there could + // be a rogue 'NoScope' there - and we want to make sure it is gone var scopeProvider = new ScopeProvider(null); if (scopeProvider.AmbientScope != null) - scopeProvider.AmbientScope.Dispose(); - scopeProvider.SetAmbientScope(null); + scopeProvider.AmbientScope.Dispose(); // removes scope from context base.Initialize(); From 90b43584d2e59d452b86eedeb7a6e96604769343 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 13 Feb 2017 19:57:21 +1100 Subject: [PATCH 090/105] Oops, fixes issue with attemping to reduce code but the 'section' parameter isn't the same in the config 'type' so needs to be manually applied like it was before. Fixes MultipleMediaPickerPropertyEditor to be multiple by default (is a bug in current versions too) --- .../src/views/prevalueeditors/treepicker.controller.js | 3 ++- .../PropertyEditors/MultipleMediaPickerPropertyEditor.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js index 48edf6e3a1..cf81d1cda6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js @@ -32,7 +32,8 @@ angular.module('umbraco') } $scope.openContentPicker = function() { - $scope.treePickerOverlay = config; + $scope.treePickerOverlay = config; + $scope.treePickerOverlay.section = config.type; $scope.treePickerOverlay.view = "treePicker"; $scope.treePickerOverlay.show = true; diff --git a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs index 8bd23a24a6..12aec22e31 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs @@ -7,12 +7,12 @@ namespace Umbraco.Web.PropertyEditors { [Obsolete("This editor is obsolete, use MultipleMediaPickerPropertyEditor2 instead which stores UDI")] [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "(Obsolete) Media Picker", "mediapicker", Group = "media", Icon = "icon-pictures-alt-2", IsDeprecated = true)] - public class MultipleMediaPickerPropertyEditor : MediaPickerPropertyEditor + public class MultipleMediaPickerPropertyEditor : MediaPickerPropertyEditor2 { public MultipleMediaPickerPropertyEditor() { - //clear the pre-values so it defaults to a multiple picker. - InternalPreValues.Clear(); + //default it to multi picker + InternalPreValues["multiPicker"] = "1"; } /// From deb8d652c56a6d04a558d30a93113ca09b9e0ac3 Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 13 Feb 2017 12:44:16 +0100 Subject: [PATCH 091/105] fixing PartialViewsTreeController reference in trees.Release.config. --- src/Umbraco.Web.UI/config/trees.Release.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/config/trees.Release.config b/src/Umbraco.Web.UI/config/trees.Release.config index 9b2b8e8b6f..74cce91001 100644 --- a/src/Umbraco.Web.UI/config/trees.Release.config +++ b/src/Umbraco.Web.UI/config/trees.Release.config @@ -11,7 +11,7 @@ - + From b519d68e1a8811715a9712698c39f5b80a86cbf2 Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 13 Feb 2017 13:34:35 +0100 Subject: [PATCH 092/105] fixing PartialViewMacrosTree and ScriptTree in trees.Release.config. --- src/Umbraco.Web.UI/config/trees.Release.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/config/trees.Release.config b/src/Umbraco.Web.UI/config/trees.Release.config index 74cce91001..dc29fe6417 100644 --- a/src/Umbraco.Web.UI/config/trees.Release.config +++ b/src/Umbraco.Web.UI/config/trees.Release.config @@ -14,7 +14,7 @@ - + @@ -25,7 +25,7 @@ - + From d504d8f54e3503d5e48cdc75b0a1366d2a63696f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 13 Feb 2017 15:54:39 +0000 Subject: [PATCH 093/105] Update Core Deploy EntityTypes for Forms (Workflow is part of main form artifact) and we dont deploy records. Changing to PreValue & DataSources --- .../Constants-DeployEntityType.cs | 20 +++++++++---------- src/Umbraco.Core/Constants-ObjectTypes.cs | 16 +++++++-------- src/Umbraco.Core/Models/UmbracoObjectTypes.cs | 16 +++++++-------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Core/Constants-DeployEntityType.cs b/src/Umbraco.Core/Constants-DeployEntityType.cs index 0f91a3c086..a62ca3d8ef 100644 --- a/src/Umbraco.Core/Constants-DeployEntityType.cs +++ b/src/Umbraco.Core/Constants-DeployEntityType.cs @@ -40,8 +40,8 @@ namespace Umbraco.Core // forms public const string FormsForm = "forms-form"; - public const string FormsWorkflow = "forms-workflow"; - public const string FormsRecord = "forms-record"; + public const string FormsPreValue = "forms-prevalue"; + public const string FormsDataSource = "forms-datasource"; // string entity types @@ -89,10 +89,10 @@ namespace Umbraco.Core return RelationType; case UmbracoObjectTypes.FormsForm: return FormsForm; - case UmbracoObjectTypes.FormsWorkflow: - return FormsWorkflow; - case UmbracoObjectTypes.FormsRecord: - return FormsRecord; + case UmbracoObjectTypes.FormsPreValue: + return FormsPreValue; + case UmbracoObjectTypes.FormsDataSource: + return FormsDataSource; } throw new NotSupportedException(string.Format("UmbracoObjectType \"{0}\" does not have a matching EntityType.", umbracoObjectType)); } @@ -131,10 +131,10 @@ namespace Umbraco.Core return UmbracoObjectTypes.RelationType; case FormsForm: return UmbracoObjectTypes.FormsForm; - case FormsWorkflow: - return UmbracoObjectTypes.FormsWorkflow; - case FormsRecord: - return UmbracoObjectTypes.FormsRecord; + case FormsPreValue: + return UmbracoObjectTypes.FormsPreValue; + case FormsDataSource: + return UmbracoObjectTypes.FormsDataSource; } throw new NotSupportedException( string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType)); diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 7bba427f12..d364c1379c 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -174,24 +174,24 @@ namespace Umbraco.Core public static readonly Guid FormsFormGuid = new Guid(FormsForm); /// - /// Guid for a Forms Workflow. + /// Guid for a Forms PreValue Source. /// - public const string FormsWorkflow = "42D7BF9B-A362-4FEE-B45A-674D5C064B70"; + public const string FormsPreValue = "42D7BF9B-A362-4FEE-B45A-674D5C064B70"; /// - /// Guid for a Forms Workflow. + /// Guid for a Forms PreValue Source. /// - public static readonly Guid FormsWorkflowGuid = new Guid(FormsWorkflow); + public static readonly Guid FormsPreValueGuid = new Guid(FormsPreValue); /// - /// Guid for a Forms Record. + /// Guid for a Forms DataSource. /// - public const string FormsRecord = "CFED6CE4-9359-443E-9977-9956FEB1D867"; + public const string FormsDataSource = "CFED6CE4-9359-443E-9977-9956FEB1D867"; /// - /// Guid for a Forms Record. + /// Guid for a Forms DataSource. /// - public static readonly Guid FormsRecordGuid = new Guid(FormsRecord); + public static readonly Guid FormsDataSourceGuid = new Guid(FormsDataSource); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index b7bf07af34..68e6e1a815 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -153,17 +153,17 @@ namespace Umbraco.Core.Models FormsForm, /// - /// Forms Workflow + /// Forms PreValue /// - [UmbracoObjectType(Constants.ObjectTypes.FormsWorkflow)] - [FriendlyName("Workflow")] - FormsWorkflow, + [UmbracoObjectType(Constants.ObjectTypes.FormsPreValue)] + [FriendlyName("PreValue")] + FormsPreValue, /// - /// Forms Record + /// Forms DataSource /// - [UmbracoObjectType(Constants.ObjectTypes.FormsRecord)] - [FriendlyName("Record")] - FormsRecord + [UmbracoObjectType(Constants.ObjectTypes.FormsDataSource)] + [FriendlyName("DataSource")] + FormsDataSource } } \ No newline at end of file From f5a204d1def48a776c9cd8d0fd76a82e81b6ca4c Mon Sep 17 00:00:00 2001 From: "aaron.morey" Date: Tue, 14 Feb 2017 09:14:05 +1100 Subject: [PATCH 094/105] U4-9491 feedback to implement into core css, be more specific with class naming & with usage and to implement a lighter red color for appearance --- src/Umbraco.Web.UI.Client/lib/bootstrap/less/variables.less | 3 +++ src/Umbraco.Web.UI.Client/src/less/main.less | 5 +++++ .../src/views/components/property/umb-property.html | 6 +++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/variables.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/variables.less index cb5d4d3152..2ae83c8031 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/variables.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/variables.less @@ -246,6 +246,9 @@ @infoBackground: #d9edf7; @infoBorder: darken(spin(@infoBackground, -10), 7%); +// Property / Control States +// ------------------------- +@controlRequiredColor: #ee5f5b; // Tooltips and popovers // ------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 85ab4e6e56..f977684c25 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -156,6 +156,11 @@ h5.-black { margin-left: 0; } +/* CONTROL VALIDATION */ +.umb-control-required { + color: @controlRequiredColor; +} + .controls-row { padding-bottom: 5px; margin-left: 240px; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 8bdd6234df..b24e0985af 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -8,9 +8,9 @@ From f30d7fc68cec0464a943b85ed6ecc32d42bb01a8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 14 Feb 2017 14:29:23 +1100 Subject: [PATCH 095/105] Fixes nuspec to be net45 as a min --- build/NuSpecs/UmbracoCms.Core.nuspec | 62 +++++++++---------- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 2 +- .../Configuration/UmbracoVersion.cs | 2 +- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 4abb26377b..ce837f7189 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -44,37 +44,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index b753b5efce..0a909cc729 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.6.0 -alpha061 \ No newline at end of file +alpha062 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 19e4078131..e7d7b208fc 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha061")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha062")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 755de31ec4..9e325c9afd 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "alpha061"; } } + public static string CurrentComment { get { return "alpha062"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx From b58d8677b3e152844914dbd41bf2ee5177a9d166 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 14 Feb 2017 09:09:05 +0100 Subject: [PATCH 096/105] Scope - bugfixing --- .../Scoping/IInstanceIdentifiable.cs | 9 ++ src/Umbraco.Core/Scoping/IScope.cs | 6 +- src/Umbraco.Core/Scoping/NoScope.cs | 2 - src/Umbraco.Core/Scoping/Scope.cs | 18 ++- src/Umbraco.Core/Scoping/ScopeContext.cs | 5 +- src/Umbraco.Core/Scoping/ScopeProvider.cs | 104 ++++++++++++------ src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Tests/Scoping/ScopeTests.cs | 86 +++++++++++++-- 8 files changed, 174 insertions(+), 57 deletions(-) create mode 100644 src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs diff --git a/src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs b/src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs new file mode 100644 index 0000000000..4c88e1c1b5 --- /dev/null +++ b/src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Core.Scoping +{ + public interface IInstanceIdentifiable + { + Guid InstanceId { get; } + } +} diff --git a/src/Umbraco.Core/Scoping/IScope.cs b/src/Umbraco.Core/Scoping/IScope.cs index a0d994b2f2..0386401346 100644 --- a/src/Umbraco.Core/Scoping/IScope.cs +++ b/src/Umbraco.Core/Scoping/IScope.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Scoping /// /// Represents a scope. /// - public interface IScope : IDisposable + public interface IScope : IDisposable, IInstanceIdentifiable { /// /// Gets the scope database. @@ -39,9 +39,5 @@ namespace Umbraco.Core.Scoping /// Completes the scope. /// void Complete(); - -#if DEBUG_SCOPES - Guid InstanceId { get; } -#endif } } diff --git a/src/Umbraco.Core/Scoping/NoScope.cs b/src/Umbraco.Core/Scoping/NoScope.cs index c2d89ff632..a6e265c1f2 100644 --- a/src/Umbraco.Core/Scoping/NoScope.cs +++ b/src/Umbraco.Core/Scoping/NoScope.cs @@ -24,10 +24,8 @@ namespace Umbraco.Core.Scoping #endif } -#if DEBUG_SCOPES private readonly Guid _instanceId = Guid.NewGuid(); public Guid InstanceId { get { return _instanceId; } } -#endif /// public bool CallContext { get { return false; } } diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index 4730495ba9..dff4a18017 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -133,10 +133,8 @@ namespace Umbraco.Core.Scoping throw new Exception("NoScope instance is not free."); } -#if DEBUG_SCOPES private readonly Guid _instanceId = Guid.NewGuid(); public Guid InstanceId { get { return _instanceId; } } -#endif // a value indicating whether to force call-context public bool CallContext @@ -189,6 +187,8 @@ namespace Umbraco.Core.Scoping // the parent scope (in a nested scopes chain) public IScopeInternal ParentScope { get; set; } + public bool Attached { get; set; } + // the original scope (when attaching a detachable scope) public IScopeInternal OrigScope { get; set; } @@ -329,7 +329,18 @@ namespace Umbraco.Core.Scoping EnsureNotDisposed(); if (this != _scopeProvider.AmbientScope) + { +#if DEBUG_SCOPES + var ambient = _scopeProvider.AmbientScope; + Logging.LogHelper.Debug("Dispose error (" + (ambient == null ? "no" : "other") + " ambient)"); + if (ambient == null) + throw new InvalidOperationException("Not the ambient scope (no ambient scope)."); + var infos = _scopeProvider.GetScopeInfo(ambient); + throw new InvalidOperationException("Not the ambient scope (see current ambient ctor stack trace).\r\n" + infos.CtorStack); +#else throw new InvalidOperationException("Not the ambient scope."); +#endif + } #if DEBUG_SCOPES _scopeProvider.Disposed(this); @@ -432,6 +443,9 @@ namespace Umbraco.Core.Scoping { // get out of the way, restore original _scopeProvider.SetAmbient(OrigScope, OrigContext); + Attached = false; + OrigScope = null; + OrigContext = null; } }); } diff --git a/src/Umbraco.Core/Scoping/ScopeContext.cs b/src/Umbraco.Core/Scoping/ScopeContext.cs index 7503271b5a..cca0be560d 100644 --- a/src/Umbraco.Core/Scoping/ScopeContext.cs +++ b/src/Umbraco.Core/Scoping/ScopeContext.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Umbraco.Core.Scoping { - public class ScopeContext + public class ScopeContext : IInstanceIdentifiable { private Dictionary _enlisted; @@ -27,6 +27,9 @@ namespace Umbraco.Core.Scoping throw new AggregateException("Exceptions were thrown by listed actions.", exceptions); } + private readonly Guid _instanceId = Guid.NewGuid(); + public Guid InstanceId { get { return _instanceId; } } + private IDictionary Enlisted { get diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index 7101258447..c500a633d0 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -37,7 +37,7 @@ namespace Umbraco.Core.Scoping { // cannot re-attached over leaked scope/context // except of course over NoScope (which leaks) - var ambientScope = AmbientScopeInternal; + var ambientScope = GetCallContextObject(ScopeItemKey); if (ambientScope != null) { var ambientNoScope = ambientScope as NoScope; @@ -47,7 +47,8 @@ namespace Umbraco.Core.Scoping // this should rollback any pending transaction ambientNoScope.Dispose(); } - if (AmbientContextInternal != null) throw new Exception("Found leaked context when restoring call context."); + if (GetCallContextObject(ContextItemKey) != null) + throw new Exception("Found leaked context when restoring call context."); var t = (Tuple) o; SetCallContextObject(ScopeItemKey, t.Item1); @@ -101,27 +102,41 @@ namespace Umbraco.Core.Scoping lock (StaticCallContextObjectsLock) { object callContextObject; - return StaticCallContextObjects.TryGetValue(objectKey, out callContextObject) ? (T)callContextObject : null; + if (StaticCallContextObjects.TryGetValue(objectKey, out callContextObject)) + { +#if DEBUG_SCOPES + //Logging.LogHelper.Debug("GotObject " + objectKey.ToString("N").Substring(0, 8)); +#endif + return (T) callContextObject; + } +#if DEBUG_SCOPES + //Logging.LogHelper.Debug("MissedObject " + objectKey.ToString("N").Substring(0, 8)); +#endif + return null; } } - private static void SetCallContextObject(string key, object value) + private static void SetCallContextObject(string key, IInstanceIdentifiable value) { #if DEBUG_SCOPES // manage the 'context' that contains the scope (null, "http" or "call") - // first, null-register the existing value - var ambientKey = CallContext.LogicalGetData(ScopeItemKey).AsGuid(); - object o = null; - lock (StaticCallContextObjectsLock) + // only for scopes of course! + if (key == ScopeItemKey) { - if (ambientKey != default (Guid)) - StaticCallContextObjects.TryGetValue(ambientKey, out o); + // first, null-register the existing value + var ambientKey = CallContext.LogicalGetData(ScopeItemKey).AsGuid(); + object o = null; + lock (StaticCallContextObjectsLock) + { + if (ambientKey != default(Guid)) + StaticCallContextObjects.TryGetValue(ambientKey, out o); + } + var ambientScope = o as IScope; + if (ambientScope != null) RegisterContext(ambientScope, null); + // then register the new value + var scope = value as IScope; + if (scope != null) RegisterContext(scope, "call"); } - var ambientScope = o as IScope; - if (ambientScope != null) RegisterContext(ambientScope, null); - // then register the new value - var scope = value as IScope; - if (scope != null) RegisterContext(scope, "call"); #endif if (value == null) { @@ -130,6 +145,9 @@ namespace Umbraco.Core.Scoping if (objectKey == default (Guid)) return; lock (StaticCallContextObjectsLock) { +#if DEBUG_SCOPES + //Logging.LogHelper.Debug("RemoveObject " + objectKey.ToString("N").Substring(0, 8)); +#endif StaticCallContextObjects.Remove(objectKey); } } @@ -138,9 +156,12 @@ namespace Umbraco.Core.Scoping // note - we are *not* detecting an already-existing value // because our code in this class *always* sets to null before // setting to a real value - var objectKey = Guid.NewGuid(); + var objectKey = value.InstanceId; lock (StaticCallContextObjectsLock) { +#if DEBUG_SCOPES + //Logging.LogHelper.Debug("AddObject " + objectKey.ToString("N").Substring(0, 8)); +#endif StaticCallContextObjects.Add(objectKey, value); } CallContext.LogicalSetData(key, objectKey); @@ -181,12 +202,16 @@ namespace Umbraco.Core.Scoping } #if DEBUG_SCOPES // manage the 'context' that contains the scope (null, "http" or "call") - // first, null-register the existing value - var ambientScope = (IScope)httpContextItems[ScopeItemKey]; - if (ambientScope != null) RegisterContext(ambientScope, null); - // then register the new value - var scope = value as IScope; - if (scope != null) RegisterContext(scope, "http"); + // only for scopes of course! + if (key == ScopeItemKey) + { + // first, null-register the existing value + var ambientScope = (IScope)httpContextItems[ScopeItemKey]; + if (ambientScope != null) RegisterContext(ambientScope, null); + // then register the new value + var scope = value as IScope; + if (scope != null) RegisterContext(scope, "http"); + } #endif if (value == null) httpContextItems.Remove(key); @@ -195,7 +220,7 @@ namespace Umbraco.Core.Scoping return true; } - #endregion +#endregion #region Ambient Context @@ -292,9 +317,6 @@ namespace Umbraco.Core.Scoping return; } - if (context == null) - throw new ArgumentNullException("context"); - if (scope.CallContext == false && SetHttpContextObject(ScopeItemKey, scope, false)) { SetHttpContextObject(ScopeRefItemKey, StaticScopeReference); @@ -327,6 +349,10 @@ namespace Umbraco.Core.Scoping if (otherScope.Detachable == false) throw new ArgumentException("Not a detachable scope."); + if (otherScope.Attached) + throw new InvalidOperationException("Already attached."); + + otherScope.Attached = true; otherScope.OrigScope = AmbientScope; otherScope.OrigContext = AmbientContext; @@ -355,6 +381,7 @@ namespace Umbraco.Core.Scoping SetAmbient(scope.OrigScope, scope.OrigContext); scope.OrigScope = null; scope.OrigContext = null; + scope.Attached = false; return scope; } @@ -446,7 +473,7 @@ namespace Umbraco.Core.Scoping // all scope instances that are currently beeing tracked private static readonly object StaticScopeInfosLock = new object(); - private static readonly List StaticScopeInfos = new List(); + private static readonly Dictionary StaticScopeInfos = new Dictionary(); public IEnumerable ScopeInfos { @@ -454,7 +481,7 @@ namespace Umbraco.Core.Scoping { lock (StaticScopeInfosLock) { - return StaticScopeInfos.ToArray(); // capture in an array + return StaticScopeInfos.Values.ToArray(); // capture in an array } } } @@ -463,7 +490,8 @@ namespace Umbraco.Core.Scoping { lock (StaticScopeInfosLock) { - return StaticScopeInfos.FirstOrDefault(x => x.Scope == scope); + ScopeInfo scopeInfo; + return StaticScopeInfos.TryGetValue(scope, out scopeInfo) ? scopeInfo : null; } } @@ -477,8 +505,9 @@ namespace Umbraco.Core.Scoping { lock (StaticScopeInfosLock) { - if (StaticScopeInfos.Any(x => x.Scope == scope)) throw new Exception("oops: already registered."); - StaticScopeInfos.Add(new ScopeInfo(scope, Environment.StackTrace)); + if (StaticScopeInfos.ContainsKey(scope)) throw new Exception("oops: already registered."); + //Logging.LogHelper.Debug("Register " + scope.InstanceId.ToString("N").Substring(0, 8)); + StaticScopeInfos[scope] = new ScopeInfo(scope, Environment.StackTrace); } } @@ -488,13 +517,17 @@ namespace Umbraco.Core.Scoping { lock (StaticScopeInfosLock) { - var info = StaticScopeInfos.FirstOrDefault(x => x.Scope == scope); + ScopeInfo info; + if (StaticScopeInfos.TryGetValue(scope, out info) == false) info = null; if (info == null) { if (context == null) return; throw new Exception("oops: unregistered scope."); } + //Logging.LogHelper.Debug("Register context " + (context ?? "null") + " for " + scope.InstanceId.ToString("N").Substring(0, 8)); if (context == null) info.NullStack = Environment.StackTrace; + //if (context == null) + // Logging.LogHelper.Debug("STACK\r\n" + info.NullStack); info.Context = context; } } @@ -503,11 +536,12 @@ namespace Umbraco.Core.Scoping { lock (StaticScopeInfosLock) { - var info = StaticScopeInfos.FirstOrDefault(x => x.Scope == scope); - if (info != null) + if (StaticScopeInfos.ContainsKey(scope)) { // enable this by default - StaticScopeInfos.Remove(info); + //Console.WriteLine("unregister " + scope.InstanceId.ToString("N").Substring(0, 8)); + StaticScopeInfos.Remove(scope); + //Logging.LogHelper.Debug("Remove " + scope.InstanceId.ToString("N").Substring(0, 8)); // instead, enable this to keep *all* scopes // beware, there can be a lot of scopes! diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 9fcff37763..738634c858 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -559,6 +559,7 @@ + diff --git a/src/Umbraco.Tests/Scoping/ScopeTests.cs b/src/Umbraco.Tests/Scoping/ScopeTests.cs index 87884abd26..747e5b1f40 100644 --- a/src/Umbraco.Tests/Scoping/ScopeTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeTests.cs @@ -1,15 +1,12 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Runtime.Remoting.Messaging; -using System.Web; -using Moq; +using System.Threading; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Persistence; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; -using Umbraco.Core.Events; namespace Umbraco.Tests.Scoping { @@ -106,8 +103,8 @@ namespace Umbraco.Tests.Scoping public void NestedMigrateScope() { var scopeProvider = DatabaseContext.ScopeProvider; - Assert.IsNull(scopeProvider.AmbientScope); + var httpContextItems = new Hashtable(); ScopeProvider.HttpContextItemsGetter = () => httpContextItems; try @@ -531,17 +528,82 @@ namespace Umbraco.Tests.Scoping } [Test] - public void CallContextScope() + public void CallContextScope1() { var scopeProvider = DatabaseContext.ScopeProvider; - var scope = scopeProvider.CreateScope(); - Assert.IsNotNull(scopeProvider.AmbientScope); - using (new SafeCallContext()) + using (var scope = scopeProvider.CreateScope()) { - Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.IsNotNull(scopeProvider.AmbientContext); + using (new SafeCallContext()) + { + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientContext); + + using (var newScope = scopeProvider.CreateScope()) + { + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientScope.ParentScope); + Assert.IsNotNull(scopeProvider.AmbientContext); + } + + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientContext); + } + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + } + + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientContext); + } + + [Test] + public void CallContextScope2() + { + var scopeProvider = DatabaseContext.ScopeProvider; + Assert.IsNull(scopeProvider.AmbientScope); + + var httpContextItems = new Hashtable(); + ScopeProvider.HttpContextItemsGetter = () => httpContextItems; + try + { + using (var scope = scopeProvider.CreateScope()) + { + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.IsNotNull(scopeProvider.AmbientContext); + using (new SafeCallContext()) + { + // pretend it's another thread + ScopeProvider.HttpContextItemsGetter = null; + + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientContext); + + using (var newScope = scopeProvider.CreateScope()) + { + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientScope.ParentScope); + Assert.IsNotNull(scopeProvider.AmbientContext); + } + + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientContext); + + // back to original thread + ScopeProvider.HttpContextItemsGetter = () => httpContextItems; + } + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + } + + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientContext); + } + finally + { + ScopeProvider.HttpContextItemsGetter = null; } - Assert.IsNotNull(scopeProvider.AmbientScope); - Assert.AreSame(scope, scopeProvider.AmbientScope); } [Test] From 55c5b416938719e716e767669475ee4223e1b10d Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 14 Feb 2017 09:13:44 +0100 Subject: [PATCH 097/105] Scope - report completion --- src/Umbraco.Core/Scoping/IScope.cs | 4 +++- src/Umbraco.Core/Scoping/NoScope.cs | 2 +- src/Umbraco.Core/Scoping/Scope.cs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Scoping/IScope.cs b/src/Umbraco.Core/Scoping/IScope.cs index 0386401346..4f178b80bc 100644 --- a/src/Umbraco.Core/Scoping/IScope.cs +++ b/src/Umbraco.Core/Scoping/IScope.cs @@ -38,6 +38,8 @@ namespace Umbraco.Core.Scoping /// /// Completes the scope. /// - void Complete(); + /// A value indicating whether the scope has been successfully completed. + /// Can return false if any child scope has not completed. + bool Complete(); } } diff --git a/src/Umbraco.Core/Scoping/NoScope.cs b/src/Umbraco.Core/Scoping/NoScope.cs index a6e265c1f2..2d8fa245b0 100644 --- a/src/Umbraco.Core/Scoping/NoScope.cs +++ b/src/Umbraco.Core/Scoping/NoScope.cs @@ -76,7 +76,7 @@ namespace Umbraco.Core.Scoping } /// - public void Complete() + public bool Complete() { throw new NotSupportedException(); } diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index dff4a18017..ab66be1bd2 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -300,10 +300,11 @@ namespace Umbraco.Core.Scoping } /// - public void Complete() + public bool Complete() { if (_completed.HasValue == false) _completed = true; + return _completed.Value; } public void Reset() From 507d7dd4f088250d0bd2791b77e313ebe7289bf4 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 14 Feb 2017 09:17:24 +0100 Subject: [PATCH 098/105] Scope - forgot to complete some scopes --- src/Umbraco.Core/Services/ContentService.cs | 1 - src/Umbraco.Core/Services/ContentTypeService.cs | 6 +++++- src/Umbraco.Core/Services/MemberTypeService.cs | 8 ++++---- src/Umbraco.Web/Scheduling/LogScrubber.cs | 3 ++- src/Umbraco.Web/Scheduling/ScheduledPublishing.cs | 3 ++- .../DataServices/UmbracoContentService.cs | 12 ++++++++---- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 2455a53f55..0723431ada 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1604,7 +1604,6 @@ namespace Umbraco.Core.Services // fixme mess! using (var scope = UowProvider.ScopeProvider.CreateScope()) { - using (var uow = UowProvider.GetUnitOfWork()) { if (uow.Events.DispatchCancelable(Copying, this, new CopyEventArgs(content, copy, parentId))) diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 43ed68b5a2..32d14f6656 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -756,6 +756,7 @@ namespace Umbraco.Core.Services using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess { scope.Events.Dispatch(SavedContentType, this, new SaveEventArgs(contentType, false)); + scope.Complete(); } Audit(AuditType.Save, "Save ContentType performed by user", userId, contentType.Id); } @@ -800,8 +801,9 @@ namespace Umbraco.Core.Services using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess { scope.Events.Dispatch(SavedContentType, this, new SaveEventArgs(asArray, false)); + scope.Complete(); } - Audit(AuditType.Save, "Save ContentTypes performed by user", userId, -1); + Audit(AuditType.Save, "Save ContentTypes performed by user", userId, -1); } /// @@ -1211,6 +1213,7 @@ namespace Umbraco.Core.Services using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess { scope.Events.Dispatch(SavedMediaType, this, new SaveEventArgs(mediaType, false)); + scope.Complete(); } Audit(AuditType.Save, "Save MediaType performed by user", userId, mediaType.Id); } @@ -1255,6 +1258,7 @@ namespace Umbraco.Core.Services using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess { scope.Events.Dispatch(SavedMediaType, this, new SaveEventArgs(asArray, false)); + scope.Complete(); } Audit(AuditType.Save, "Save MediaTypes performed by user", userId, -1); } diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs index 4634288357..be55a335b3 100644 --- a/src/Umbraco.Core/Services/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Services private readonly IMemberService _memberService; private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - + public MemberTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberService memberService) : base(provider, repositoryFactory, logger, eventMessagesFactory) @@ -110,7 +110,7 @@ namespace Umbraco.Core.Services public void Save(IEnumerable memberTypes, int userId = 0) { var asArray = memberTypes.ToArray(); - + using (new WriteLock(Locker)) { using (var uow = UowProvider.GetUnitOfWork()) @@ -137,7 +137,7 @@ namespace Umbraco.Core.Services uow.Events.Dispatch(Saved, this, new SaveEventArgs(asArray, false)); } } - + } public void Delete(IMemberType memberType, int userId = 0) @@ -167,7 +167,7 @@ namespace Umbraco.Core.Services public void Delete(IEnumerable memberTypes, int userId = 0) { var asArray = memberTypes.ToArray(); - + using (new WriteLock(Locker)) { using (var scope = UowProvider.ScopeProvider.CreateScope()) diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index f18f5ad603..760304574d 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -79,10 +79,11 @@ namespace Umbraco.Web.Scheduling // running on a background task, and Log.CleanLogs uses the old SqlHelper, // better wrap in a scope and ensure it's all cleaned up and nothing leaks - using (ApplicationContext.Current.ScopeProvider.CreateScope()) + using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) using (DisposableTimer.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { Log.CleanLogs(GetLogScrubbingMaximumAge(_settings)); + scope.Complete(); } return true; // repeat diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 5ff7963f50..d7ed241275 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -85,10 +85,11 @@ namespace Umbraco.Web.Scheduling // running on a background task, requires its own (safe) scope // (GetAuthenticationHeaderValue uses UserService to load the current user, hence requires a database) // (might not need a scope but we don't know really) - using (ApplicationContext.Current.ScopeProvider.CreateScope()) + using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) { //pass custom the authorization header request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext); + scope.Complete(); } var result = await wc.SendAsync(request, token); diff --git a/src/UmbracoExamine/DataServices/UmbracoContentService.cs b/src/UmbracoExamine/DataServices/UmbracoContentService.cs index cd6acca8eb..b52f60a2f2 100644 --- a/src/UmbracoExamine/DataServices/UmbracoContentService.cs +++ b/src/UmbracoExamine/DataServices/UmbracoContentService.cs @@ -57,7 +57,7 @@ namespace UmbracoExamine.DataServices [Obsolete("This should no longer be used, latest content will be indexed by using the IContentService directly")] public XDocument GetLatestContentByXPath(string xpath) { - using (ApplicationContext.Current.ScopeProvider.CreateScope()) + using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) { var xmlContent = XDocument.Parse(""); var rootContent = _applicationContext.Services.ContentService.GetRootContent(); @@ -67,6 +67,7 @@ namespace UmbracoExamine.DataServices xmlContent.Root.Add(c.ToDeepXml(_applicationContext.Services.PackagingService)); } var result = ((IEnumerable)xmlContent.XPathEvaluate(xpath)).Cast(); + scope.Complete(); return result.ToXDocument(); } } @@ -79,9 +80,11 @@ namespace UmbracoExamine.DataServices /// public bool IsProtected(int nodeId, string path) { - using (ApplicationContext.Current.ScopeProvider.CreateScope()) + using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) { - return _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId)); + var ret = _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId)); + scope.Complete(); + return ret; } } @@ -92,11 +95,12 @@ namespace UmbracoExamine.DataServices public IEnumerable GetAllUserPropertyNames() { - using (ApplicationContext.Current.ScopeProvider.CreateScope()) + using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) { try { var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias"); + scope.Complete(); return result; } catch (Exception ex) From f889b5206c446b31196fa4c6c92218a983582cf5 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 14 Feb 2017 11:10:09 +0100 Subject: [PATCH 099/105] Scope - handle publishing events --- .../Cache/CacheRefresherEventHandler.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 59084f13c4..b1b9039528 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -243,6 +243,20 @@ namespace Umbraco.Web.Cache #region Publishing + // IPublishingStrategy (obsolete) events are proxied into ContentService, which works fine when + // events are actually raised, but not when they are handled by HandleEvents, so we have to have + // these proxy methods that are *not* registered against any event *but* used by HandleEvents. + + static void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs e) + { + ContentService_UnPublished(sender, e); + } + + static void PublishingStrategy_Published(IPublishingStrategy sender, PublishEventArgs e) + { + ContentService_Published(sender, e); + } + static void ContentService_UnPublished(IPublishingStrategy sender, PublishEventArgs e) { if (e.PublishedEntities.Any()) From 0074f579706a0407c43bc6ad21e1c7aad1c9e35f Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 14 Feb 2017 11:10:33 +0100 Subject: [PATCH 100/105] Version 7.6-alpha063 --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 0a909cc729..cb4b663ecb 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.6.0 -alpha062 \ No newline at end of file +alpha063 diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index e7d7b208fc..ac9db0f47a 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha062")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha063")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 9e325c9afd..a80322aa7e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "alpha062"; } } + public static string CurrentComment { get { return "alpha063"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx From c9294e5281f3bda736b4d30460eaa45eed280e55 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 14 Feb 2017 11:37:42 +0000 Subject: [PATCH 101/105] In the init DB creation the Media & Member Picker was inserted into the DB the wrong way round - now assigned with the correct name for the DataType --- .../Persistence/Migrations/Initial/BaseDataCreation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index bfd48f81e7..7dc57ba7f6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -138,8 +138,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //New UDI pickers with newer Ids _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); //TODO: We're not creating these for 7.0 From 0c613347aec424d136485aac73e590f0a54b6cc8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 15 Feb 2017 21:22:08 +1100 Subject: [PATCH 102/105] Updates ClearPublished logic to be done in a single SQL statement instead of several --- .../Persistence/Repositories/ContentRepository.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 78128d2b7d..da9ebb2a00 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -785,13 +785,8 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", public void ClearPublished(IContent content) { - // race cond! - var documentDtos = Database.Fetch("WHERE nodeId=@id AND published=@published", new { id = content.Id, published = true }); - foreach (var documentDto in documentDtos) - { - documentDto.Published = false; - Database.Update(documentDto); - } + var sql = "UPDATE cmsDocument SET published=0 WHERE nodeId=@id AND published=1"; + Database.Update(sql, new {id = content.Id}); } /// From 975c97cfb0c89a9c5d18c57e473c26b7b189879d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 15 Feb 2017 21:24:45 +1100 Subject: [PATCH 103/105] Doh! Changed update to Execute --- src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index da9ebb2a00..37e5e80fe9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -786,7 +786,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", public void ClearPublished(IContent content) { var sql = "UPDATE cmsDocument SET published=0 WHERE nodeId=@id AND published=1"; - Database.Update(sql, new {id = content.Id}); + Database.Execute(sql, new {id = content.Id}); } /// From 4d1e64b279059aee78268b3abbf1b51b147bf258 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 15 Feb 2017 21:51:14 +1100 Subject: [PATCH 104/105] U4-9512 Missing index on umbracoUser2NodePermission --- .../Models/Rdbms/User2NodePermissionDto.cs | 1 + .../AddIndexToCmsMemberLoginName.cs | 9 +---- .../AddIndexToUser2NodePermission.cs | 34 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 4 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUser2NodePermission.cs diff --git a/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs b/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs index 1e6662735f..f0d769a21e 100644 --- a/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs @@ -15,6 +15,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("nodeId")] [ForeignKey(typeof(NodeDto))] + [Index(IndexTypes.NonClustered, Name = "IX_umbracoUser2NodePermission_nodeId")] public int NodeId { get; set; } [Column("permission")] diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs index beff4b3210..0cb4f297b1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs @@ -14,14 +14,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero public override void Up() { - var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); //make sure it doesn't already exist if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUser2NodePermission.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUser2NodePermission.cs new file mode 100644 index 0000000000..b510ef428c --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUser2NodePermission.cs @@ -0,0 +1,34 @@ +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + public class AddIndexToUser2NodePermission : MigrationBase + { + public AddIndexToUser2NodePermission(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoUser2NodePermission_nodeId")) == false) + { + Create.Index("IX_umbracoUser2NodePermission_nodeId").OnTable("umbracoUser2NodePermission") + .OnColumn("nodeId") + .Ascending() + .WithOptions() + .NonClustered(); + } + } + + public override void Down() + { + Delete.Index("IX_umbracoUser2NodePermission_nodeId").OnTable("cmsMember"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c5eadfb131..b20e541404 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -484,6 +484,7 @@ + From 33d35fd9f7890765654ac3a695ffc4f8fc1557ee Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 16 Feb 2017 10:01:37 +0100 Subject: [PATCH 105/105] bumping version just to be sure. --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index cb4b663ecb..f8d3c5e1c7 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.6.0 -alpha063 +alpha064 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index ac9db0f47a..24866498b8 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha063")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha064")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index a80322aa7e..32d96a46b3 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "alpha063"; } } + public static string CurrentComment { get { return "alpha064"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx