diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec
index 2536316906..0f31f77d04 100644
--- a/build/NuSpecs/UmbracoCms.Core.nuspec
+++ b/build/NuSpecs/UmbracoCms.Core.nuspec
@@ -32,9 +32,9 @@
-
+
-
+
@@ -104,4 +104,4 @@
-
\ No newline at end of file
+
diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt
index 1672ad53d0..07f33971ff 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
-alpha048
+alpha055
\ No newline at end of file
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index b092457e25..40455f708c 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-alpha048")]
\ No newline at end of file
+[assembly: AssemblyInformationalVersion("7.6.0-alpha055")]
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs
index 0c1a202b66..3b9ee9c63a 100644
--- a/src/Umbraco.Core/Cache/CacheKeys.cs
+++ b/src/Umbraco.Core/Cache/CacheKeys.cs
@@ -64,7 +64,8 @@ namespace Umbraco.Core.Cache
[UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:";
- [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
+ [Obsolete("No longer used and will be removed in v8")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string PropertyTypeCacheKey = "UmbracoPropertyTypeCache";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
index cd759cba82..1637763e6e 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 "alpha048"; } }
+ public static string CurrentComment { get { return "alpha055"; } }
// 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/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs
index d7f4576137..2e3d652d7e 100644
--- a/src/Umbraco.Core/Constants-Conventions.cs
+++ b/src/Umbraco.Core/Constants-Conventions.cs
@@ -4,7 +4,8 @@ using Umbraco.Core.Models;
namespace Umbraco.Core
{
- public static partial class Constants
+
+ public static partial class Constants
{
///
/// Defines the identifiers for property-type alias conventions that are used within the Umbraco core.
diff --git a/src/Umbraco.Core/Constants-DatabaseProviders.cs b/src/Umbraco.Core/Constants-DatabaseProviders.cs
new file mode 100644
index 0000000000..2a64ade081
--- /dev/null
+++ b/src/Umbraco.Core/Constants-DatabaseProviders.cs
@@ -0,0 +1,12 @@
+namespace Umbraco.Core
+{
+ public static partial class Constants
+ {
+ public static class DatabaseProviders
+ {
+ public const string SqlCe = "System.Data.SqlServerCe.4.0";
+ public const string SqlServer = "System.Data.SqlClient";
+ public const string MySql = "MySql.Data.MySqlClient";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Constants-DeployEntityType.cs b/src/Umbraco.Core/Constants-DeployEntityType.cs
new file mode 100644
index 0000000000..f622661dff
--- /dev/null
+++ b/src/Umbraco.Core/Constants-DeployEntityType.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core
+{
+
+ public static partial class Constants
+ {
+
+ ///
+ /// Defines well-known entity types.
+ ///
+ /// 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
+ {
+ // guid entity types
+
+ public const string AnyGuid = "any-guid"; // that one is for tests
+
+ public const string Document = "document";
+ public const string Media = "media";
+ public const string Member = "member";
+
+ public const string DictionaryItem = "dictionary-item";
+ public const string Macro = "macro";
+ public const string Template = "template";
+
+ public const string DocumentType = "document-type";
+ public const string DocumentTypeContainer = "document-type-container";
+ public const string MediaType = "media-type";
+ public const string MediaTypeContainer = "media-type-container";
+ public const string DataType = "data-type";
+ public const string DataTypeContainer = "data-type-container";
+ public const string MemberType = "member-type";
+ public const string MemberGroup = "member-group";
+
+ public const string RelationType = "relation-type";
+
+ // string entity types
+
+ public const string AnyString = "any-string"; // that one is for tests
+
+ public const string MediaFile = "media-file";
+ public const string TemplateFile = "template-file";
+ public const string Script = "script";
+ public const string Stylesheet = "stylesheet";
+ public const string PartialView = "partial-view";
+ public const string PartialViewMacro = "partial-view-macro";
+ public const string Xslt = "xslt";
+
+ public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType)
+ {
+ switch (umbracoObjectType)
+ {
+ case UmbracoObjectTypes.Document:
+ return Document;
+ case UmbracoObjectTypes.Media:
+ return Media;
+ case UmbracoObjectTypes.Member:
+ return Member;
+ case UmbracoObjectTypes.Template:
+ return Template;
+ case UmbracoObjectTypes.DocumentType:
+ return DocumentType;
+ case UmbracoObjectTypes.DocumentTypeContainer:
+ return DocumentTypeContainer;
+ case UmbracoObjectTypes.MediaType:
+ return MediaType;
+ case UmbracoObjectTypes.MediaTypeContainer:
+ return MediaTypeContainer;
+ case UmbracoObjectTypes.DataType:
+ return DataType;
+ case UmbracoObjectTypes.DataTypeContainer:
+ return DataTypeContainer;
+ case UmbracoObjectTypes.MemberType:
+ return MemberType;
+ case UmbracoObjectTypes.MemberGroup:
+ return MemberGroup;
+ case UmbracoObjectTypes.Stylesheet:
+ return Stylesheet;
+ case UmbracoObjectTypes.RelationType:
+ return RelationType;
+ }
+ throw new NotSupportedException(string.Format("UmbracoObjectType \"{0}\" does not have a matching EntityType.", umbracoObjectType));
+ }
+
+ public static UmbracoObjectTypes ToUmbracoObjectType(string entityType)
+ {
+ switch (entityType)
+ {
+ case Document:
+ return UmbracoObjectTypes.Document;
+ case Media:
+ return UmbracoObjectTypes.Media;
+ case Member:
+ return UmbracoObjectTypes.Member;
+ case Template:
+ return UmbracoObjectTypes.Template;
+ case DocumentType:
+ return UmbracoObjectTypes.DocumentType;
+ case DocumentTypeContainer:
+ return UmbracoObjectTypes.DocumentTypeContainer;
+ case MediaType:
+ return UmbracoObjectTypes.MediaType;
+ case MediaTypeContainer:
+ return UmbracoObjectTypes.MediaTypeContainer;
+ case DataType:
+ return UmbracoObjectTypes.DataType;
+ case DataTypeContainer:
+ return UmbracoObjectTypes.DataTypeContainer;
+ case MemberType:
+ return UmbracoObjectTypes.MemberType;
+ case MemberGroup:
+ return UmbracoObjectTypes.MemberGroup;
+ case Stylesheet:
+ return UmbracoObjectTypes.Stylesheet;
+ case RelationType:
+ return UmbracoObjectTypes.RelationType;
+ }
+ throw new NotSupportedException(
+ string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType));
+ }
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Constants-DeploySelector.cs b/src/Umbraco.Core/Constants-DeploySelector.cs
new file mode 100644
index 0000000000..cd9c48a6f5
--- /dev/null
+++ b/src/Umbraco.Core/Constants-DeploySelector.cs
@@ -0,0 +1,17 @@
+namespace Umbraco.Core
+{
+ public static partial class Constants
+ {
+ ///
+ /// Contains the valid selector values.
+ ///
+ public static class DeploySelector
+ {
+ public const string This = "this";
+ public const string ThisAndChildren = "this-and-children";
+ public const string ThisAndDescendants = "this-and-descendants";
+ public const string ChildrenOfThis = "children";
+ public const string DescendantsOfThis = "descendants";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs
new file mode 100644
index 0000000000..bd2e1c5acf
--- /dev/null
+++ b/src/Umbraco.Core/Constants-Security.cs
@@ -0,0 +1,33 @@
+using System;
+using System.ComponentModel;
+
+namespace Umbraco.Core
+{
+ public static partial class Constants
+ {
+ public static class Security
+ {
+
+ public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
+ public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie";
+ public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN";
+ public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken";
+ public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie";
+
+ ///
+ /// The prefix used for external identity providers for their authentication type
+ ///
+ ///
+ /// By default we don't want to interfere with front-end external providers and their default setup, for back office the
+ /// providers need to be setup differently and each auth type for the back office will be prefixed with this value
+ ///
+ public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco.";
+
+ public const string StartContentNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode";
+ public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode";
+ public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp";
+ public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid";
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs
index d1ac6fbb38..b38417c39b 100644
--- a/src/Umbraco.Core/Constants-System.cs
+++ b/src/Umbraco.Core/Constants-System.cs
@@ -29,12 +29,6 @@
public const string UmbracoConnectionName = "umbracoDbDSN";
public const string UmbracoMigrationName = "Umbraco";
}
-
- public static class DatabaseProviders
- {
- public const string SqlCe = "System.Data.SqlServerCe.4.0";
- public const string SqlServer = "System.Data.SqlClient";
- public const string MySql = "MySql.Data.MySqlClient";
- }
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs
index f596820506..67b5698610 100644
--- a/src/Umbraco.Core/Constants-Web.cs
+++ b/src/Umbraco.Core/Constants-Web.cs
@@ -29,30 +29,6 @@ namespace Umbraco.Core
public const string AuthCookieName = "UMB_UCONTEXT";
}
-
- public static class Security
- {
-
- public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
- public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie";
- public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN";
- public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken";
- public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie";
-
- ///
- /// The prefix used for external identity providers for their authentication type
- ///
- ///
- /// By default we don't want to interfere with front-end external providers and their default setup, for back office the
- /// providers need to be setup differently and each auth type for the back office will be prefixed with this value
- ///
- public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco.";
-
- public const string StartContentNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode";
- public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode";
- public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp";
- public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid";
-
- }
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs
index e98cd31dbf..2a6b044664 100644
--- a/src/Umbraco.Core/CoreBootManager.cs
+++ b/src/Umbraco.Core/CoreBootManager.cs
@@ -415,7 +415,7 @@ namespace Umbraco.Core
if (currentTry == 5)
{
- throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database.");
+ throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but Umbraco cannot connect to the database.");
}
}
diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs
index 483e124e35..837e59ca12 100644
--- a/src/Umbraco.Core/DatabaseContext.cs
+++ b/src/Umbraco.Core/DatabaseContext.cs
@@ -241,15 +241,10 @@ namespace Umbraco.Core
var path = Path.Combine(GlobalSettings.FullpathToRoot, "App_Data", "Umbraco.sdf");
if (File.Exists(path) == false)
{
- var engine = new SqlCeEngine(connectionString);
- engine.CreateDatabase();
-
- // SD: Pretty sure this should be in a using clause but i don't want to cause unknown side-effects here
- // since it's been like this for quite some time
- //using (var engine = new SqlCeEngine(connectionString))
- //{
- // engine.CreateDatabase();
- //}
+ using (var engine = new SqlCeEngine(connectionString))
+ {
+ engine.CreateDatabase();
+ }
}
Initialize(providerName);
diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs
new file mode 100644
index 0000000000..b507c54415
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Provides a base class to all artifacts.
+ ///
+ public abstract class ArtifactBase : IArtifact
+ where TUdi : Udi
+ {
+ protected ArtifactBase(TUdi udi, IEnumerable dependencies = null)
+ {
+ if (udi == null)
+ throw new ArgumentNullException("udi");
+ Udi = udi;
+
+ Dependencies = dependencies ?? Enumerable.Empty();
+ _checksum = new Lazy(GetChecksum);
+ }
+
+ private readonly Lazy _checksum;
+ private IEnumerable _dependencies;
+
+ protected abstract string GetChecksum();
+
+ #region Abstract implementation of IArtifactSignature
+
+ Udi IArtifactSignature.Udi
+ {
+ get { return Udi; }
+ }
+
+ public TUdi Udi { get; set; }
+
+ [JsonIgnore]
+ public string Checksum
+ {
+ get { return _checksum.Value; }
+ }
+
+ public IEnumerable Dependencies
+ {
+ get { return _dependencies; }
+ set { _dependencies = value.OrderBy(x => x.Udi); }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/ArtifactDependency.cs b/src/Umbraco.Core/Deploy/ArtifactDependency.cs
new file mode 100644
index 0000000000..41e349d636
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/ArtifactDependency.cs
@@ -0,0 +1,40 @@
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Represents an artifact dependency.
+ ///
+ ///
+ /// Dependencies have an order property which indicates whether it must be respected when ordering artifacts.
+ /// Dependencies have a mode which can be Match or Exist depending on whether the checksum should match.
+ ///
+ public class ArtifactDependency
+ {
+ ///
+ /// Initializes a new instance of the ArtifactDependency class with an entity identifier and a mode.
+ ///
+ /// The entity identifier of the artifact that is a dependency.
+ /// A value indicating whether the dependency is ordering.
+ /// The dependency mode.
+ public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode)
+ {
+ Udi = udi;
+ Ordering = ordering;
+ Mode = mode;
+ }
+
+ ///
+ /// Gets the entity id of the artifact that is a dependency.
+ ///
+ public Udi Udi { get; private set; }
+
+ ///
+ /// Gets a value indicating whether the dependency is ordering.
+ ///
+ public bool Ordering { get; private set; }
+
+ ///
+ /// Gets the dependency mode.
+ ///
+ public ArtifactDependencyMode Mode { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs
new file mode 100644
index 0000000000..fd036f4628
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Represents a collection of distinct .
+ ///
+ /// The collection cannot contain duplicates and modes are properly managed.
+ public class ArtifactDependencyCollection : ICollection
+ {
+ private readonly Dictionary _dependencies
+ = new Dictionary();
+
+ public IEnumerator GetEnumerator()
+ {
+ return _dependencies.Values.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public void Add(ArtifactDependency item)
+ {
+ if (_dependencies.ContainsKey(item.Udi))
+ {
+ var exist = _dependencies[item.Udi];
+ if (item.Mode == ArtifactDependencyMode.Exist || item.Mode == exist.Mode)
+ return;
+ }
+
+ _dependencies[item.Udi] = item;
+ }
+
+ public void Clear()
+ {
+ _dependencies.Clear();
+ }
+
+ public bool Contains(ArtifactDependency item)
+ {
+ return _dependencies.ContainsKey(item.Udi) &&
+ (_dependencies[item.Udi].Mode == item.Mode || _dependencies[item.Udi].Mode == ArtifactDependencyMode.Match);
+ }
+
+ public void CopyTo(ArtifactDependency[] array, int arrayIndex)
+ {
+ _dependencies.Values.CopyTo(array, arrayIndex);
+ }
+
+ public bool Remove(ArtifactDependency item)
+ {
+ throw new NotSupportedException();
+ }
+
+ public int Count
+ {
+ get { return _dependencies.Count; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return false; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs
new file mode 100644
index 0000000000..7ee5c2f220
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Indicates the mode of the dependency.
+ ///
+ public enum ArtifactDependencyMode
+ {
+ ///
+ /// The dependency must match exactly.
+ ///
+ Match,
+
+ ///
+ /// The dependency must exist.
+ ///
+ Exist
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs
new file mode 100644
index 0000000000..3723e483cb
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs
@@ -0,0 +1,50 @@
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Represent the state of an artifact being deployed.
+ ///
+ public abstract class ArtifactDeployState
+ {
+ ///
+ /// Creates a new instance of the class from an artifact and an entity.
+ ///
+ /// The type of the artifact.
+ /// The type of the entity.
+ /// The artifact.
+ /// The entity.
+ /// The service connector deploying the artifact.
+ /// The next pass number.
+ /// A deploying artifact.
+ public static ArtifactDeployState Create(TArtifact art, TEntity entity, IServiceConnector connector, int nextPass)
+ where TArtifact : IArtifact
+ {
+ return new ArtifactDeployState(art, entity, connector, nextPass);
+ }
+
+ ///
+ /// Gets the artifact.
+ ///
+ public IArtifact Artifact
+ {
+ get { return GetArtifactAsIArtifact(); }
+ }
+
+ ///
+ /// Gets the artifact as an .
+ ///
+ /// The artifact, as an .
+ /// This is because classes that inherit from this class cannot override the Artifact property
+ /// with a property that specializes the return type, and so they need to 'new' the property.
+ protected abstract IArtifact GetArtifactAsIArtifact();
+
+ ///
+ /// Gets or sets the service connector in charge of deploying the artifact.
+ ///
+ public IServiceConnector Connector { get; set; }
+
+ ///
+ /// Gets or sets the next pass number.
+ ///
+ public int NextPass { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs
new file mode 100644
index 0000000000..b4d2be23cd
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs
@@ -0,0 +1,48 @@
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Represent the state of an artifact being deployed.
+ ///
+ /// The type of the artifact.
+ /// The type of the entity.
+ public class ArtifactDeployState : ArtifactDeployState
+ where TArtifact : IArtifact
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ArtifactDeployState()
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The artifact.
+ /// The entity.
+ /// The service connector deploying the artifact.
+ /// The next pass number.
+ public ArtifactDeployState(TArtifact art, TEntity entity, IServiceConnector connector, int nextPass)
+ {
+ Artifact = art;
+ Entity = entity;
+ Connector = connector;
+ NextPass = nextPass;
+ }
+
+ ///
+ /// Gets or sets the artifact.
+ ///
+ public new TArtifact Artifact { get; set; }
+
+ ///
+ /// Gets or sets the entity.
+ ///
+ public TEntity Entity { get; set; }
+
+ ///
+ protected sealed override IArtifact GetArtifactAsIArtifact()
+ {
+ return Artifact;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/ArtifactSignature.cs b/src/Umbraco.Core/Deploy/ArtifactSignature.cs
new file mode 100644
index 0000000000..4bea6a5ce8
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/ArtifactSignature.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Umbraco.Core.Deploy
+{
+ public sealed class ArtifactSignature : IArtifactSignature
+ {
+ public ArtifactSignature(Udi udi, string checksum, IEnumerable dependencies = null)
+ {
+ Udi = udi;
+ Checksum = checksum;
+ Dependencies = dependencies ?? Enumerable.Empty();
+ }
+
+ public Udi Udi { get; private set; }
+
+ public string Checksum { get; private set; }
+
+ public IEnumerable Dependencies { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/Difference.cs b/src/Umbraco.Core/Deploy/Difference.cs
new file mode 100644
index 0000000000..90329afdd6
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/Difference.cs
@@ -0,0 +1,28 @@
+namespace Umbraco.Core.Deploy
+{
+ public class Difference
+ {
+ public Difference(string title, string text = null, string category = null)
+ {
+ Title = title;
+ Text = text;
+ Category = category;
+ }
+
+ public string Title { get; set; }
+ public string Text { get; set; }
+ public string Category { get; set; }
+
+ public override string ToString()
+ {
+ var s = Title;
+ if (!string.IsNullOrWhiteSpace(Category)) s += string.Format("[{0}]", Category);
+ if (!string.IsNullOrWhiteSpace(Text))
+ {
+ if (s.Length > 0) s += ":";
+ s += Text;
+ }
+ return s;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/Direction.cs b/src/Umbraco.Core/Deploy/Direction.cs
new file mode 100644
index 0000000000..b0e1c1dc0a
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/Direction.cs
@@ -0,0 +1,8 @@
+namespace Umbraco.Core.Deploy
+{
+ public enum Direction
+ {
+ ToArtifact,
+ FromArtifact
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/IArtifact.cs b/src/Umbraco.Core/Deploy/IArtifact.cs
new file mode 100644
index 0000000000..f39b7b433f
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IArtifact.cs
@@ -0,0 +1,8 @@
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Represents an artifact ie an object that can be transfered between environments.
+ ///
+ public interface IArtifact : IArtifactSignature
+ { }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/IArtifactSignature.cs b/src/Umbraco.Core/Deploy/IArtifactSignature.cs
new file mode 100644
index 0000000000..83b112586b
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IArtifactSignature.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Represents the signature of an artifact.
+ ///
+ public interface IArtifactSignature
+ {
+ ///
+ /// Gets the entity unique identifier of this artifact.
+ ///
+ ///
+ /// The project identifier is independent from the state of the artifact, its data
+ /// values, dependencies, anything. It never changes and fully identifies the artifact.
+ /// What an entity uses as a unique identifier will influence what we can transfer
+ /// between environments. Eg content type "Foo" on one environment is not necessarily the
+ /// same as "Foo" on another environment, if guids are used as unique identifiers. What is
+ /// used should be documented for each entity, along with the consequences of the choice.
+ ///
+ Udi Udi { get; }
+
+ ///
+ /// Gets the checksum of this artifact.
+ ///
+ ///
+ /// The checksum depends on the artifact's properties, and on the identifiers of all its dependencies,
+ /// but not on their checksums. So the checksum changes when any of the artifact's properties changes,
+ /// or when the list of dependencies changes. But not if one of these dependencies change.
+ /// It is assumed that checksum collisions cannot happen ie that no two different artifact's
+ /// states will ever produce the same checksum, so that if two artifacts have the same checksum then
+ /// they are identical.
+ ///
+ string Checksum { get; }
+
+ ///
+ /// Gets the dependencies of this artifact.
+ ///
+ IEnumerable Dependencies { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/IDeployContext.cs b/src/Umbraco.Core/Deploy/IDeployContext.cs
new file mode 100644
index 0000000000..7d4066e015
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IDeployContext.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Represents a deployment context.
+ ///
+ public interface IDeployContext
+ {
+ ///
+ /// Gets the unique identifier of the deployment.
+ ///
+ Guid SessionId { get; }
+
+ ///
+ /// Gets the file source.
+ ///
+ /// The file source is used to obtain files from the source environment.
+ IFileSource FileSource { get; }
+
+ ///
+ /// Gets the next number in a numerical sequence.
+ ///
+ /// The next sequence number.
+ /// Can be used to uniquely number things during a deployment.
+ int NextSeq();
+
+ ///
+ /// Gets items.
+ ///
+ IDictionary Items { get; }
+
+ ///
+ /// Gets item.
+ ///
+ /// The type of the item.
+ /// The key of the item.
+ /// The item with the specified key and type, if any, else null.
+ T Item(string key) where T : class;
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/IFileSource.cs b/src/Umbraco.Core/Deploy/IFileSource.cs
new file mode 100644
index 0000000000..ea73d2b571
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IFileSource.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Represents a file source, ie a mean for a target environment involved in a
+ /// deployment to obtain the content of files being deployed.
+ ///
+ public interface IFileSource
+ {
+ ///
+ /// Gets the content of a file as a stream.
+ ///
+ /// A file entity identifier.
+ /// A stream with read access to the file content.
+ ///
+ /// Returns null if no content could be read.
+ /// The caller should ensure that the stream is properly closed/disposed.
+ ///
+ Stream GetFileStream(StringUdi udi);
+
+ ///
+ /// Gets the content of a file as a stream.
+ ///
+ /// A file entity identifier.
+ /// A cancellation token.
+ /// A stream with read access to the file content.
+ ///
+ /// Returns null if no content could be read.
+ /// The caller should ensure that the stream is properly closed/disposed.
+ ///
+ Task GetFileStreamAsync(StringUdi udi, CancellationToken token);
+
+ ///
+ /// Gets the content of a file as a string.
+ ///
+ /// A file entity identifier.
+ /// A string containing the file content.
+ /// Returns null if no content could be read.
+ string GetFileContent(StringUdi udi);
+
+ ///
+ /// Gets the content of a file as a string.
+ ///
+ /// A file entity identifier.
+ /// A cancellation token.
+ /// A string containing the file content.
+ /// Returns null if no content could be read.
+ Task GetFileContentAsync(StringUdi udi, CancellationToken token);
+
+ ///
+ /// Gets the length of a file.
+ ///
+ /// A file entity identifier.
+ /// The length of the file, or -1 if the file does not exist.
+ long GetFileLength(StringUdi udi);
+
+ ///
+ /// Gets the length of a file.
+ ///
+ /// A file entity identifier.
+ /// A cancellation token.
+ /// The length of the file, or -1 if the file does not exist.
+ Task GetFileLengthAsync(StringUdi udi, CancellationToken token);
+
+ // fixme - doc
+ void GetFiles(IEnumerable udis, IFileStore fileStore);
+
+ Task GetFilesAsync(IEnumerable udis, IFileStore fileStore, CancellationToken token);
+
+ /////
+ ///// Gets the content of a file as a bytes array.
+ /////
+ ///// A file entity identifier.
+ ///// A byte array containing the file content.
+ //byte[] GetFileBytes(StringUdi Udi);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/IFileStore.cs b/src/Umbraco.Core/Deploy/IFileStore.cs
new file mode 100644
index 0000000000..0cd7425eb9
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IFileStore.cs
@@ -0,0 +1,9 @@
+using System.IO;
+
+namespace Umbraco.Core.Deploy
+{
+ public interface IFileStore
+ {
+ void SaveStream(StringUdi udi, Stream stream);
+ }
+}
diff --git a/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs
new file mode 100644
index 0000000000..65fde1ec0b
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Defines methods that can convert a grid cell value to / from an environment-agnostic string.
+ ///
+ /// Grid cell values may contain values such as content identifiers, that would be local
+ /// to one environment, and need to be converted in order to be deployed.
+ public interface IGridCellValueConnector
+ {
+ ///
+ /// Gets a value indicating whether the connector supports a specified grid editor alias.
+ ///
+ /// The grid editor alias.
+ /// A value indicating whether the connector supports the grid editor alias.
+ /// Note that can be string.Empty to indicate the "default" connector.
+ bool IsConnector(string alias);
+
+ ///
+ /// Gets the value to be deployed from the control value as a string.
+ ///
+ /// The control containing the value.
+ /// The property where the control is located. Do not modify - only used for context
+ /// The dependencies of the property.
+ /// The grid cell value to be deployed.
+ /// Note that
+ string GetValue(GridValue.GridControl gridControl, Property property, ICollection dependencies);
+
+ ///
+ /// Allows you to modify the value of a control being deployed.
+ ///
+ /// The control being deployed.
+ /// The property where the is located. Do not modify - only used for context.
+ /// Follows the pattern of the property value connectors (). The SetValue method is used to modify the value of the .
+ /// Note that only the value should be modified - not the .
+ /// The should only be used to assist with context data relevant when setting the value.
+ void SetValue(GridValue.GridControl gridControl, Property property);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/IImageSourceParser.cs b/src/Umbraco.Core/Deploy/IImageSourceParser.cs
new file mode 100644
index 0000000000..d8e8d860ac
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IImageSourceParser.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Provides methods to parse image tag sources in property values.
+ ///
+ public interface IImageSourceParser
+ {
+ ///
+ /// Parses an Umbraco property value and produces an artifact property value.
+ ///
+ /// The property value.
+ /// A list of dependencies.
+ /// The parsed value.
+ /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies.
+ string ToArtifact(string value, ICollection dependencies);
+
+ ///
+ /// Parses an artifact property value and produces an Umbraco property value.
+ ///
+ /// The artifact property value.
+ /// The parsed value.
+ /// Turns umb://media/... into /media/....
+ string FromArtifact(string value);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs
new file mode 100644
index 0000000000..c5906c2060
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Provides methods to parse local link tags in property values.
+ ///
+ public interface ILocalLinkParser
+ {
+ ///
+ /// Parses an Umbraco property value and produces an artifact property value.
+ ///
+ /// The property value.
+ /// A list of dependencies.
+ /// The parsed value.
+ /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the dependencies.
+ string ToArtifact(string value, ICollection dependencies);
+
+ ///
+ /// Parses an artifact property value and produces an Umbraco property value.
+ ///
+ /// The artifact property value.
+ /// The parsed value.
+ /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}.
+ string FromArtifact(string value);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/IMacroParser.cs b/src/Umbraco.Core/Deploy/IMacroParser.cs
new file mode 100644
index 0000000000..9cde8ef8b6
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IMacroParser.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Deploy
+{
+ public interface IMacroParser
+ {
+ ///
+ /// Parses an Umbraco property value and produces an artifact property value.
+ ///
+ /// Property value.
+ /// A list of dependencies.
+ /// Parsed value.
+ string ToArtifact(string value, ICollection dependencies);
+
+ ///
+ /// Parses an artifact property value and produces an Umbraco property value.
+ ///
+ /// Artifact property value.
+ /// Parsed value.
+ string FromArtifact(string value);
+
+ ///
+ /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier.
+ ///
+ /// Value to attempt to convert
+ /// Alias of the editor used for the parameter
+ /// Collection to add dependencies to when performing ToArtifact
+ /// Indicates which action is being performed (to or from artifact)
+ /// Value with converted identifiers
+ string ReplaceAttributeValue(string value, string editorAlias, ICollection dependencies, Direction direction);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/IPreValueConnector.cs b/src/Umbraco.Core/Deploy/IPreValueConnector.cs
new file mode 100644
index 0000000000..4ef898cc74
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IPreValueConnector.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Defines methods that can convert a preValue to / from an environment-agnostic string.
+ ///
+ /// PreValues may contain values such as content identifiers, that would be local
+ /// to one environment, and need to be converted in order to be deployed.
+ public interface IPreValueConnector
+ {
+ ///
+ /// Gets the property editor aliases that the value converter supports by default.
+ ///
+ IEnumerable PropertyEditorAliases { get; }
+
+ ///
+ /// Gets the environment-agnostic preValues corresponding to environment-specific preValues.
+ ///
+ /// The environment-specific preValues.
+ /// The dependencies.
+ ///
+ IDictionary ConvertToDeploy(IDictionary preValues, ICollection dependencies);
+
+ ///
+ /// Gets the environment-specific preValues corresponding to environment-agnostic preValues.
+ ///
+ /// The environment-agnostic preValues.
+ ///
+ IDictionary ConvertToLocalEnvironment(IDictionary preValues);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/IPrettyArtifact.cs b/src/Umbraco.Core/Deploy/IPrettyArtifact.cs
new file mode 100644
index 0000000000..38c0ddee4c
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IPrettyArtifact.cs
@@ -0,0 +1,8 @@
+namespace Umbraco.Core.Deploy
+{
+ public interface IPrettyArtifact
+ {
+ string Name { get; }
+ string Alias { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs
new file mode 100644
index 0000000000..d1c0bf61de
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Umbraco.Core;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Connects to an Umbraco service.
+ ///
+ public interface IServiceConnector
+ {
+ ///
+ /// Gets an artifact.
+ ///
+ /// The entity identifier of the artifact.
+ /// The corresponding artifact, or null.
+ IArtifact GetArtifact(Udi udi);
+
+ ///
+ /// Gets an artifact.
+ ///
+ /// The entity.
+ /// The corresponding artifact.
+ IArtifact GetArtifact(object entity);
+
+ ///
+ /// Initializes processing for an artifact.
+ ///
+ /// The artifact.
+ /// The deploy context.
+ /// The mapped artifact.
+ ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context);
+
+ ///
+ /// Processes an artifact.
+ ///
+ /// The mapped artifact.
+ /// The deploy context.
+ /// The processing pass number.
+ void Process(ArtifactDeployState dart, IDeployContext context, int pass);
+
+ ///
+ /// Explodes a range into udis.
+ ///
+ /// The range.
+ /// The list of udis where to add the new udis.
+ /// Also, it's cool to have a method named Explode. Kaboom!
+ void Explode(UdiRange range, List udis);
+
+ ///
+ /// Gets a named range for a specified udi and selector.
+ ///
+ /// The udi.
+ /// The selector.
+ /// The named range for the specified udi and selector.
+ NamedUdiRange GetRange(Udi udi, string selector);
+
+ ///
+ /// Gets a named range for specified entity type, identifier and selector.
+ ///
+ /// The entity type.
+ /// The identifier.
+ /// The selector.
+ /// The named range for the specified entity type, identifier and selector.
+ ///
+ /// This is temporary. At least we thought it would be, in sept. 2016. What day is it now?
+ /// At the moment our UI has a hard time returning proper udis, mainly because Core's tree do
+ /// not manage guids but only ints... so we have to provide a way to support it. The string id here
+ /// can be either a real string (for string udis) or an "integer as a string", using the value "-1" to
+ /// indicate the "root" i.e. an open udi.
+ ///
+ NamedUdiRange GetRange(string entityType, string sid, string selector);
+
+ ///
+ /// Compares two artifacts.
+ ///
+ /// The first artifact.
+ /// The second artifact.
+ /// A collection of differences to append to, if not null.
+ /// A boolean value indicating whether the artifacts are identical.
+ /// ServiceConnectorBase{TArtifact} provides a very basic default implementation.
+ bool Compare(IArtifact art1, IArtifact art2, ICollection differences = null);
+ }
+
+}
diff --git a/src/Umbraco.Core/Deploy/IValueConnector.cs b/src/Umbraco.Core/Deploy/IValueConnector.cs
new file mode 100644
index 0000000000..a93e5a05a4
--- /dev/null
+++ b/src/Umbraco.Core/Deploy/IValueConnector.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core.Deploy
+{
+ ///
+ /// Defines methods that can convert a property value to / from an environment-agnostic string.
+ ///
+ /// Property values may contain values such as content identifiers, that would be local
+ /// to one environment, and need to be converted in order to be deployed. Connectors also deal
+ /// with serializing to / from string.
+ public interface IValueConnector
+ {
+ ///
+ /// Gets the property editor aliases that the value converter supports by default.
+ ///
+ IEnumerable PropertyEditorAliases { get; }
+
+ ///
+ /// Gets the deploy property corresponding to a content property.
+ ///
+ /// The content property.
+ /// The content dependencies.
+ /// The deploy property value.
+ string GetValue(Property property, ICollection dependencies);
+
+ ///
+ /// Sets a content property value using a deploy property.
+ ///
+ /// The content item.
+ /// The property alias.
+ /// The deploy property value.
+ void SetValue(IContentBase content, string alias, string value);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Core/GuidUdi.cs
new file mode 100644
index 0000000000..fabdf46c93
--- /dev/null
+++ b/src/Umbraco.Core/GuidUdi.cs
@@ -0,0 +1,83 @@
+using System;
+
+namespace Umbraco.Core
+{
+ ///
+ /// Represents a guid-based entity identifier.
+ ///
+ public class GuidUdi : Udi
+ {
+ ///
+ /// The guid part of the identifier.
+ ///
+ public Guid Guid { get; private set; }
+
+ ///
+ /// Initializes a new instance of the GuidUdi class with an entity type and a guid.
+ ///
+ /// The entity type part of the udi.
+ /// The guid part of the udi.
+ public GuidUdi(string entityType, Guid guid)
+ : base(entityType, "umb://" + entityType + "/" + guid.ToString("N"))
+ {
+ Guid = guid;
+ }
+
+ ///
+ /// Initializes a new instance of the GuidUdi class with an uri value.
+ ///
+ /// The uri value of the udi.
+ public GuidUdi(Uri uriValue)
+ : base(uriValue)
+ {
+ Guid = Guid.Parse(uriValue.AbsolutePath.TrimStart('/'));
+ }
+
+ ///
+ /// Converts the string representation of an entity identifier into the equivalent GuidUdi instance.
+ ///
+ /// The string to convert.
+ /// A GuidUdi instance that contains the value that was parsed.
+ public new static GuidUdi Parse(string s)
+ {
+ var udi = Udi.Parse(s);
+ if (!(udi is GuidUdi))
+ throw new FormatException("String \"" + s + "\" is not a guid entity id.");
+ return (GuidUdi)udi;
+ }
+
+ public static bool TryParse(string s, out GuidUdi udi)
+ {
+ Udi tmp;
+ udi = null;
+ if (!TryParse(s, out tmp)) return false;
+ udi = tmp as GuidUdi;
+ return udi != null;
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as GuidUdi;
+ if (other == null) return false;
+ return EntityType == other.EntityType && Guid == other.Guid;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+
+ ///
+ public override bool IsRoot
+ {
+ get { return Guid == Guid.Empty; }
+ }
+
+ ///
+ public GuidUdi EnsureClosed()
+ {
+ base.EnsureNotRoot();
+ return this;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/GridValue.cs b/src/Umbraco.Core/Models/GridValue.cs
new file mode 100644
index 0000000000..87f6146af6
--- /dev/null
+++ b/src/Umbraco.Core/Models/GridValue.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// A model representing the value saved for the grid
+ ///
+ public class GridValue
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("sections")]
+ public IEnumerable Sections { get; set; }
+
+ public class GridSection
+ {
+ [JsonProperty("grid")]
+ public string Grid { get; set; }
+
+ [JsonProperty("rows")]
+ public IEnumerable Rows { get; set; }
+ }
+
+ public class GridRow
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("id")]
+ public Guid Id { get; set; }
+
+ [JsonProperty("areas")]
+ public IEnumerable Areas { get; set; }
+
+ [JsonProperty("styles")]
+ public JToken Styles { get; set; }
+
+ [JsonProperty("config")]
+ public JToken Config { get; set; }
+ }
+
+ public class GridArea
+ {
+ [JsonProperty("grid")]
+ public string Grid { get; set; }
+
+ [JsonProperty("controls")]
+ public IEnumerable Controls { get; set; }
+
+ [JsonProperty("styles")]
+ public JToken Styles { get; set; }
+
+ [JsonProperty("config")]
+ public JToken Config { get; set; }
+ }
+
+ public class GridControl
+ {
+ [JsonProperty("value")]
+ public JToken Value { get; set; }
+
+ [JsonProperty("editor")]
+ public GridEditor Editor { get; set; }
+
+ [JsonProperty("styles")]
+ public JToken Styles { get; set; }
+
+ [JsonProperty("config")]
+ public JToken Config { get; set; }
+ }
+
+ public class GridEditor
+ {
+ [JsonProperty("alias")]
+ public string Alias { get; set; }
+
+ [JsonProperty("view")]
+ public string View { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/IMediaType.cs b/src/Umbraco.Core/Models/IMediaType.cs
index 29e4b665ba..e8f7c16190 100644
--- a/src/Umbraco.Core/Models/IMediaType.cs
+++ b/src/Umbraco.Core/Models/IMediaType.cs
@@ -4,7 +4,7 @@ namespace Umbraco.Core.Models
{
///
/// Defines a ContentType, which Media is based on
- ///
public interface IMediaType : IContentTypeComposition
{
diff --git a/src/Umbraco.Core/NamedUdiRange.cs b/src/Umbraco.Core/NamedUdiRange.cs
new file mode 100644
index 0000000000..c87e5db82e
--- /dev/null
+++ b/src/Umbraco.Core/NamedUdiRange.cs
@@ -0,0 +1,34 @@
+namespace Umbraco.Core
+{
+ ///
+ /// Represents a complemented with a name.
+ ///
+ public class NamedUdiRange : UdiRange
+ {
+ ///
+ /// Initializes a new instance of the class with a and an optional selector.
+ ///
+ /// A .
+ /// An optional selector.
+ public NamedUdiRange(Udi udi, string selector = Constants.DeploySelector.This)
+ : base(udi, selector)
+ { }
+
+ ///
+ /// Initializes a new instance of the class with a , a name, and an optional selector.
+ ///
+ /// A .
+ /// A name.
+ /// An optional selector.
+ public NamedUdiRange(Udi udi, string name, string selector = Constants.DeploySelector.This)
+ : base(udi, selector)
+ {
+ Name = name;
+ }
+
+ ///
+ /// Gets or sets the name of the range.
+ ///
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs
index 5dcec8fed0..88de5a1d6c 100644
--- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs
@@ -27,15 +27,15 @@ namespace Umbraco.Core.Persistence.Factories
#region Implementation of IEntityFactory
- public IContent BuildEntity(DocumentDto dto)
+ public static IContent BuildEntity(DocumentDto dto, IContentType contentType)
{
- var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType);
+ var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, contentType);
try
{
content.DisableChangeTracking();
- content.Id = _id;
+ content.Id = dto.NodeId;
content.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId;
content.Name = dto.Text;
content.NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text;
@@ -49,8 +49,8 @@ namespace Umbraco.Core.Persistence.Factories
content.Published = dto.Published;
content.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate;
content.UpdateDate = dto.ContentVersionDto.VersionDate;
- content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?) null;
- content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?) null;
+ content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null;
+ content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null;
content.Version = dto.ContentVersionDto.VersionId;
content.PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished;
content.PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId;
@@ -64,6 +64,13 @@ namespace Umbraco.Core.Persistence.Factories
{
content.EnableChangeTracking();
}
+
+ }
+
+ [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")]
+ public IContent BuildEntity(DocumentDto dto)
+ {
+ return BuildEntity(dto, _contentType);
}
public DocumentDto BuildDto(IContent entity)
diff --git a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs
index 0fcb654cb7..5729bb125e 100644
--- a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs
@@ -27,15 +27,15 @@ namespace Umbraco.Core.Persistence.Factories
#region Implementation of IEntityFactory
- public IMedia BuildEntity(ContentVersionDto dto)
+ public static IMedia BuildEntity(ContentVersionDto dto, IMediaType contentType)
{
- var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, _contentType);
+ var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, contentType);
try
{
media.DisableChangeTracking();
- media.Id = _id;
+ media.Id = dto.NodeId;
media.Key = dto.ContentDto.NodeDto.UniqueId;
media.Path = dto.ContentDto.NodeDto.Path;
media.CreatorId = dto.ContentDto.NodeDto.UserId.Value;
@@ -55,6 +55,13 @@ namespace Umbraco.Core.Persistence.Factories
{
media.EnableChangeTracking();
}
+
+ }
+
+ [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")]
+ public IMedia BuildEntity(ContentVersionDto dto)
+ {
+ return BuildEntity(dto, _contentType);
}
public ContentVersionDto BuildDto(IMedia entity)
diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs
index 446bd426ad..f202d8c321 100644
--- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs
@@ -30,11 +30,11 @@ namespace Umbraco.Core.Persistence.Factories
_updateDate = updateDate;
}
- public IEnumerable BuildEntity(PropertyDataDto[] dtos)
+ public static IEnumerable BuildEntity(IReadOnlyCollection dtos, PropertyType[] compositionTypeProperties, DateTime createDate, DateTime updateDate)
{
var properties = new List();
- foreach (var propertyType in _compositionTypeProperties)
+ foreach (var propertyType in compositionTypeProperties)
{
var propertyDataDto = dtos.LastOrDefault(x => x.PropertyTypeId == propertyType.Id);
var property = propertyDataDto == null
@@ -47,8 +47,8 @@ namespace Umbraco.Core.Persistence.Factories
//on initial construction we don't want to have dirty properties tracked
property.DisableChangeTracking();
- property.CreateDate = _createDate;
- property.UpdateDate = _updateDate;
+ property.CreateDate = createDate;
+ property.UpdateDate = updateDate;
// http://issues.umbraco.org/issue/U4-1946
property.ResetDirtyProperties(false);
properties.Add(property);
@@ -57,12 +57,18 @@ namespace Umbraco.Core.Persistence.Factories
{
property.EnableChangeTracking();
}
-
+
}
return properties;
}
+ [Obsolete("Use the static method instead, there's no reason to allocate one of these classes everytime we want to map values")]
+ public IEnumerable BuildEntity(PropertyDataDto[] dtos)
+ {
+ return BuildEntity(dtos, _compositionTypeProperties, _createDate, _updateDate);
+ }
+
public IEnumerable BuildDto(IEnumerable properties)
{
var propertyDataDtos = new List();
diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs
index b265a5b587..7f5e479af6 100644
--- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs
+++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs
@@ -20,6 +20,7 @@ namespace Umbraco.Core.Persistence.Querying
_mapper = mapper;
}
+ [Obsolete("Use the overload the specifies a SqlSyntaxProvider")]
public ModelToSqlExpressionVisitor()
: this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T)))
{ }
diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
index e0a4f07e48..4569b95853 100644
--- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
+++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
@@ -18,14 +18,13 @@ namespace Umbraco.Core.Persistence.Querying
public PocoToSqlExpressionVisitor(ISqlSyntaxProvider syntaxProvider)
: base(syntaxProvider)
{
-
+ _pd = new Database.PocoData(typeof(T));
}
[Obsolete("Use the overload the specifies a SqlSyntaxProvider")]
public PocoToSqlExpressionVisitor()
- : base(SqlSyntaxContext.SqlSyntaxProvider)
- {
- _pd = new Database.PocoData(typeof(T));
+ : this(SqlSyntaxContext.SqlSyntaxProvider)
+ {
}
protected override string VisitMemberAccess(MemberExpression m)
diff --git a/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs b/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs
new file mode 100644
index 0000000000..4579fd98fb
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs
@@ -0,0 +1,9 @@
+namespace Umbraco.Core.Persistence.Repositories
+{
+ internal enum BaseQueryType
+ {
+ Full,
+ Ids,
+ Count
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
index 7f3e657dc2..765843af9f 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
@@ -55,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
- .Where(x => x.Newest)
+ .Where(x => x.Newest, SqlSyntax)
.OrderByDescending(x => x.VersionDate, SqlSyntax);
var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
@@ -70,63 +70,90 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IEnumerable PerformGetAll(params int[] ids)
{
- var sql = GetBaseQuery(false);
- if (ids.Any())
+ Func translate = s =>
{
- sql.Where("umbracoNode.id in (@ids)", new { ids });
- }
+ if (ids.Any())
+ {
+ s.Where("umbracoNode.id in (@ids)", new { ids });
+ }
+ //we only want the newest ones with this method
+ s.Where(x => x.Newest, SqlSyntax);
+ return s;
+ };
+
+ var sqlBaseFull = GetBaseQuery(BaseQueryType.Full);
+ var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
- //we only want the newest ones with this method
- sql.Where(x => x.Newest);
-
- return ProcessQuery(sql);
+ return ProcessQuery(translate(sqlBaseFull), translate(sqlBaseIds));
}
protected override IEnumerable PerformGetByQuery(IQuery query)
{
- var sqlClause = GetBaseQuery(false);
- var translator = new SqlTranslator(sqlClause, query);
- var sql = translator.Translate()
- .Where(x => x.Newest)
- .OrderByDescending(x => x.VersionDate)
- .OrderBy(x => x.SortOrder);
+ var sqlBaseFull = GetBaseQuery(BaseQueryType.Full);
+ var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
- return ProcessQuery(sql);
+ Func, Sql> translate = (translator) =>
+ {
+ return translator.Translate()
+ .Where(x => x.Newest, SqlSyntax)
+ .OrderByDescending(x => x.VersionDate, SqlSyntax)
+ .OrderBy(x => x.SortOrder, SqlSyntax);
+ };
+
+ var translatorFull = new SqlTranslator(sqlBaseFull, query);
+ var translatorIds = new SqlTranslator(sqlBaseIds, query);
+
+ return ProcessQuery(translate(translatorFull), translate(translatorIds));
}
#endregion
#region Overrides of PetaPocoRepositoryBase
-
- protected override Sql GetBaseQuery(bool isCount)
+ protected override Sql GetBaseQuery(BaseQueryType queryType)
{
- var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)",
+ var sql = new Sql();
+ sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsDocument.nodeId" : "*"))
+ .From(SqlSyntax)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.VersionId, right => right.VersionId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId);
+
+ if (queryType == BaseQueryType.Full)
+ {
+ //The only reason we apply this left outer join is to be able to pull back the DocumentPublishedReadOnlyDto
+ //information with the entire data set, so basically this will get both the latest document and also it's published
+ //version if it has one. When performing a count or when just retrieving Ids like in paging, this is unecessary
+ //and causes huge performance overhead for the SQL server, especially when sorting the result.
+ //To fix this perf overhead we'd need another index on :
+ // CREATE NON CLUSTERED INDEX ON cmsDocument.node + cmsDocument.published
+
+ var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)",
SqlSyntax.GetQuotedTableName("cmsDocument"),
SqlSyntax.GetQuotedTableName("cmsDocument2"),
SqlSyntax.GetQuotedColumnName("nodeId"),
SqlSyntax.GetQuotedColumnName("published"));
- var sql = new Sql();
- sql.Select(isCount ? "COUNT(*)" : "*")
- .From()
- .InnerJoin()
- .On(left => left.VersionId, right => right.VersionId)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
-
// cannot do this because PetaPoco does not know how to alias the table
//.LeftOuterJoin()
//.On(left => left.NodeId, right => right.NodeId)
// so have to rely on writing our own SQL
- .Append(sqlx/*, new { @published = true }*/)
+ sql.Append(sqlx /*, new { @published = true }*/);
+ }
+
+ sql.Where(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
- .Where(x => x.NodeObjectType == NodeObjectTypeId);
return sql;
}
+ protected override Sql GetBaseQuery(bool isCount)
+ {
+ return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.Full);
+ }
+
protected override string GetBaseWhereClause()
{
return "umbracoNode.id = @Id";
@@ -173,20 +200,32 @@ namespace Umbraco.Core.Persistence.Repositories
// not bring much safety - so this reverts to updating each record individually,
// and it may be slower in the end, but should be more resilient.
- var baseId = 0;
var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray();
+
+ Func translate = (bId, sql) =>
+ {
+ if (contentTypeIdsA.Length > 0)
+ {
+ sql.WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
+ }
+
+ sql
+ .Where(x => x.NodeId > bId && x.Trashed == false, SqlSyntax)
+ .Where(x => x.Published, SqlSyntax)
+ .OrderBy(x => x.NodeId, SqlSyntax);
+
+ return sql;
+ };
+
+ var baseId = 0;
+
while (true)
{
// get the next group of nodes
- var query = GetBaseQuery(false);
- if (contentTypeIdsA.Length > 0)
- query = query
- .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
- query = query
- .Where(x => x.NodeId > baseId && x.Trashed == false)
- .Where(x => x.Published)
- .OrderBy(x => x.NodeId, SqlSyntax);
- var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize))
+ 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))
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
.ToList();
@@ -212,17 +251,23 @@ namespace Umbraco.Core.Persistence.Repositories
public override IEnumerable GetAllVersions(int id)
{
- var sql = GetBaseQuery(false)
- .Where(GetBaseWhereClause(), new { Id = id })
- .OrderByDescending(x => x.VersionDate, SqlSyntax);
- return ProcessQuery(sql, true);
+ Func translate = s =>
+ {
+ return s.Where(GetBaseWhereClause(), new {Id = id})
+ .OrderByDescending(x => x.VersionDate, SqlSyntax);
+ };
+
+ var sqlFull = translate(GetBaseQuery(BaseQueryType.Full));
+ var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids));
+
+ return ProcessQuery(sqlFull, sqlIds, true);
}
public override IContent GetByVersion(Guid versionId)
{
var sql = GetBaseQuery(false);
sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId });
- sql.OrderByDescending(x => x.VersionDate);
+ sql.OrderByDescending(x => x.VersionDate, SqlSyntax);
var dto = Database.Fetch(sql).FirstOrDefault();
@@ -238,10 +283,10 @@ namespace Umbraco.Core.Persistence.Repositories
{
var sql = new Sql()
.Select("*")
- .From()
- .InnerJoin().On(left => left.VersionId, right => right.VersionId)
- .Where(x => x.VersionId == versionId)
- .Where(x => x.Newest != true);
+ .From(SqlSyntax)
+ .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.VersionId, right => right.VersionId)
+ .Where(x => x.VersionId == versionId, SqlSyntax)
+ .Where(x => x.Newest != true, SqlSyntax);
var dto = Database.Fetch(sql).FirstOrDefault();
if (dto == null) return;
@@ -617,21 +662,27 @@ namespace Umbraco.Core.Persistence.Repositories
public IEnumerable GetByPublishedVersion(IQuery query)
{
+ Func, Sql> translate = t =>
+ {
+ return t.Translate()
+ .Where(x => x.Published, SqlSyntax)
+ .OrderBy(x => x.Level, SqlSyntax)
+ .OrderBy(x => x.SortOrder, SqlSyntax);
+ };
+
// we WANT to return contents in top-down order, ie parents should come before children
// ideal would be pure xml "document order" which can be achieved with:
// ORDER BY substring(path, 1, len(path) - charindex(',', reverse(path))), sortOrder
// but that's probably an overkill - sorting by level,sortOrder should be enough
- var sqlClause = GetBaseQuery(false);
- var translator = new SqlTranslator(sqlClause, query);
- var sql = translator.Translate()
- .Where(x => x.Published)
- .OrderBy(x => x.Level, SqlSyntax)
- .OrderBy(x => x.SortOrder, SqlSyntax);
+ var sqlFull = GetBaseQuery(BaseQueryType.Full);
+ var translatorFull = new SqlTranslator(sqlFull, query);
+ var sqlIds = GetBaseQuery(BaseQueryType.Ids);
+ var translatorIds = new SqlTranslator(sqlIds, query);
- return ProcessQuery(sql, true);
+ return ProcessQuery(translate(translatorFull), translate(translatorIds), true);
}
-
+
///
/// This builds the Xml document used for the XML cache
///
@@ -663,10 +714,14 @@ namespace Umbraco.Core.Persistence.Repositories
parent.Attributes.Append(pIdAtt);
xmlDoc.AppendChild(parent);
- const string sql = @"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.xml, umbracoNode.level from umbracoNode
+ //Ensure that only nodes that have published versions are selected
+ var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.{0}, umbracoNode.{1} from umbracoNode
inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type
where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1)
-order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
+order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
+ SqlSyntax.GetQuotedColumnName("xml"),
+ SqlSyntax.GetQuotedColumnName("level"),
+ SqlSyntax.GetQuotedColumnName("level"));
XmlElement last = null;
@@ -803,9 +858,9 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
Func> filterCallback = () => new Tuple(filterSql.SQL, filterSql.Arguments);
- return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords,
+ return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords,
new Tuple("cmsDocument", "nodeId"),
- sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField,
+ (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
@@ -836,16 +891,32 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
return base.GetDatabaseFieldNameForOrderBy(orderBy);
}
- private IEnumerable ProcessQuery(Sql sql, bool withCache = false)
+ ///
+ /// This is the underlying method that processes most queries for this repository
+ ///
+ ///
+ /// 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
+ ///
+ ///
+ ///
+ private IEnumerable ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = 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);
if (dtos.Count == 0) return Enumerable.Empty();
var content = new IContent[dtos.Count];
var defs = new List();
var templateIds = new List();
-
+
+ //track the looked up content types, even though the content types are cached
+ // they still need to be deep cloned out of the cache and we don't want to add
+ // the overhead of deep cloning them on every item in this loop
+ var contentTypes = new Dictionary();
+
for (var i = 0; i < dtos.Count; i++)
{
var dto = dtos[i];
@@ -864,9 +935,19 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
// else, need to fetch from the database
// content type repository is full-cache so OK to get each one independently
- var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
- var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId);
- content[i] = factory.BuildEntity(dto);
+
+ IContentType contentType;
+ if (contentTypes.ContainsKey(dto.ContentVersionDto.ContentDto.ContentTypeId))
+ {
+ contentType = contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId];
+ }
+ else
+ {
+ contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
+ contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType;
+ }
+
+ content[i] = ContentFactory.BuildEntity(dto, contentType);
// need template
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
@@ -887,7 +968,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
.ToDictionary(x => x.Id, x => x);
// load all properties for all documents from database in 1 query
- var propertyData = GetPropertyCollection(sql, defs);
+ var propertyData = GetPropertyCollection(sqlIds, defs);
// assign
var dtoIndex = 0;
@@ -907,7 +988,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
- ((Entity) cc).ResetDirtyProperties(false);
+ cc.ResetDirtyProperties(false);
}
return content;
@@ -924,8 +1005,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
{
var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
- var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId);
- var content = factory.BuildEntity(dto);
+ var content = ContentFactory.BuildEntity(dto, contentType);
//Check if template id is set on DocumentDto, and get ITemplate if it is.
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs
index cc13275798..b76abf4db6 100644
--- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs
@@ -62,17 +62,27 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IEnumerable PerformGetAll(params int[] ids)
{
- //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit
- return ids.InGroupsOf(2000).SelectMany(@group =>
+ if (ids.Any())
+ {
+ //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit
+ return ids.InGroupsOf(2000).SelectMany(@group =>
+ {
+ var sql = GetBaseQuery(false)
+ .Where("nodeObjectType=@umbracoObjectTypeId", new {umbracoObjectTypeId = NodeObjectTypeId})
+ .Where(string.Format("{0} IN (@ids)", SqlSyntax.GetQuotedColumnName("id")), new {ids = @group});
+
+ sql.OrderBy(x => x.Level, SqlSyntax);
+
+ return Database.Fetch(sql).Select(CreateEntity);
+ });
+ }
+ else
{
var sql = GetBaseQuery(false)
- .Where("nodeObjectType=@umbracoObjectTypeId", new { umbracoObjectTypeId = NodeObjectTypeId })
- .Where(string.Format("{0} IN (@ids)", SqlSyntax.GetQuotedColumnName("id")), new { ids = @group });
-
+ .Where("nodeObjectType=@umbracoObjectTypeId", new {umbracoObjectTypeId = NodeObjectTypeId});
sql.OrderBy(x => x.Level, SqlSyntax);
-
return Database.Fetch(sql).Select(CreateEntity);
- });
+ }
}
protected override IEnumerable PerformGetByQuery(IQuery query)
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
index e2555995d2..28e4fbf199 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
@@ -96,5 +96,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// An Enumerable list of objects
IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null);
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
index 64989f9269..9e8890a37b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
@@ -38,15 +38,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// An Enumerable list of objects
IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "");
-
- ///
- /// Gets paged media descendants as XML by path
- ///
- /// Path starts with
- /// Page number
- /// Page size
- /// Total records the query would return without paging
- /// A paged enumerable of XML entries of media items
- IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords);
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs
index a24116f0e2..9528fd3d76 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs
@@ -73,6 +73,6 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
void AddOrUpdatePreviewXml(IMember content, Func xml);
-
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs
index 3e05d1feaf..b318223ca7 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs
@@ -67,5 +67,16 @@ namespace Umbraco.Core.Persistence.Repositories
/// Id of the object to delete versions from
/// Latest version date
void DeleteVersions(int id, DateTime versionDate);
+
+ ///
+ /// Gets paged content descendants as XML by path
+ ///
+ /// Path starts with
+ /// Page number
+ /// Page size
+ ///
+ /// Total records the query would return without paging
+ /// A paged enumerable of XML entries of content items
+ IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, string[] orderBy, out long totalRecords);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
index 08c2f7e0be..8cfb037c6c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
@@ -76,7 +76,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator(sqlClause, query);
var sql = translator.Translate()
- .OrderBy(x => x.SortOrder);
+ .OrderBy(x => x.SortOrder, SqlSyntax);
return ProcessQuery(sql);
}
@@ -84,18 +84,23 @@ namespace Umbraco.Core.Persistence.Repositories
#endregion
#region Overrides of PetaPocoRepositoryBase
+
+ protected override Sql GetBaseQuery(BaseQueryType queryType)
+ {
+ var sql = new Sql();
+ sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsContentVersion.contentId" : "*"))
+ .From(SqlSyntax)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId, SqlSyntax)
+ .Where(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
+ return sql;
+ }
protected override Sql GetBaseQuery(bool isCount)
{
- var sql = new Sql();
- sql.Select(isCount ? "COUNT(*)" : "*")
- .From()
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .Where(x => x.NodeObjectType == NodeObjectTypeId);
- return sql;
+ return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.Full);
}
protected override string GetBaseWhereClause()
@@ -148,6 +153,11 @@ namespace Umbraco.Core.Persistence.Repositories
var content = new IMedia[dtos.Count];
var defs = new List();
+ //track the looked up content types, even though the content types are cached
+ // they still need to be deep cloned out of the cache and we don't want to add
+ // the overhead of deep cloning them on every item in this loop
+ var contentTypes = new Dictionary();
+
for (var i = 0; i < dtos.Count; i++)
{
var dto = dtos[i];
@@ -165,9 +175,19 @@ namespace Umbraco.Core.Persistence.Repositories
// else, need to fetch from the database
// content type repository is full-cache so OK to get each one independently
- var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
- var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId);
- content[i] = factory.BuildEntity(dto);
+
+ IMediaType contentType;
+ if (contentTypes.ContainsKey(dto.ContentDto.ContentTypeId))
+ {
+ contentType = contentTypes[dto.ContentDto.ContentTypeId];
+ }
+ else
+ {
+ contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
+ contentTypes[dto.ContentDto.ContentTypeId] = contentType;
+ }
+
+ content[i] = MediaFactory.BuildEntity(dto, contentType);
// need properties
defs.Add(new DocumentDefinition(
@@ -195,7 +215,7 @@ namespace Umbraco.Core.Persistence.Repositories
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
- ((Entity) cc).ResetDirtyProperties(false);
+ cc.ResetDirtyProperties(false);
}
return content;
@@ -205,26 +225,16 @@ namespace Umbraco.Core.Persistence.Repositories
{
var sql = GetBaseQuery(false);
sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId });
- sql.OrderByDescending(x => x.VersionDate);
+ sql.OrderByDescending(x => x.VersionDate, SqlSyntax);
var dto = Database.Fetch(sql).FirstOrDefault();
if (dto == null)
return null;
- var mediaType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
+ var content = CreateMediaFromDto(dto, versionId, sql);
- var factory = new MediaFactory(mediaType, NodeObjectTypeId, dto.NodeId);
- var media = factory.BuildEntity(dto);
-
- var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) });
-
- media.Properties = properties[dto.NodeId];
-
- //on initial construction we don't want to have dirty properties tracked
- // http://issues.umbraco.org/issue/U4-1946
- ((Entity)media).ResetDirtyProperties(false);
- return media;
+ return content;
}
public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null)
@@ -245,7 +255,7 @@ namespace Umbraco.Core.Persistence.Repositories
query = query
.WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
query = query
- .Where(x => x.NodeId > baseId)
+ .Where(x => x.NodeId > baseId, SqlSyntax)
.OrderBy(x => x.NodeId, SqlSyntax);
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize))
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
@@ -495,37 +505,13 @@ namespace Umbraco.Core.Persistence.Repositories
filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray());
}
- return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords,
+ return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords,
new Tuple("cmsContentVersion", "contentId"),
- sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField,
+ (sqlFull, sqlIds) => ProcessQuery(sqlFull), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
- ///
- /// Gets paged media descendants as XML by path
- ///
- /// Path starts with
- /// Page number
- /// Page size
- /// Total records the query would return without paging
- /// A paged enumerable of XML entries of media items
- public IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords)
- {
- Sql query;
- if (path == "-1")
- {
- query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE nodeObjectType = @0)", Guid.Parse(Constants.ObjectTypes.Media)).OrderBy("nodeId");
- }
- else
- {
- query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE path LIKE @0)", path.EnsureEndsWith(",%")).OrderBy("nodeId");
- }
- var pagedResult = Database.Page(pageIndex+1, pageSize, query);
- totalRecords = pagedResult.TotalItems;
- return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml));
- }
-
///
/// Private method to create a media object from a ContentDto
///
@@ -536,9 +522,8 @@ namespace Umbraco.Core.Persistence.Repositories
private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId, Sql docSql)
{
var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
-
- var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId);
- var media = factory.BuildEntity(dto);
+
+ var media = MediaFactory.BuildEntity(dto, contentType);
var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType);
diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
index dcab898685..8b3bf4a471 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
@@ -107,24 +107,29 @@ namespace Umbraco.Core.Persistence.Repositories
#region Overrides of PetaPocoRepositoryBase
- protected override Sql GetBaseQuery(bool isCount)
+ protected override Sql GetBaseQuery(BaseQueryType queryType)
{
var sql = new Sql();
- sql.Select(isCount ? "COUNT(*)" : "*")
- .From()
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
+ sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsMember.nodeId" : "*"))
+ .From(SqlSyntax)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
//We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not?
// the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content
// types by default on the document and media repo's so we can query by content type there too.
- .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .Where(x => x.NodeObjectType == NodeObjectTypeId);
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
+ .Where(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
return sql;
+ }
+ protected override Sql GetBaseQuery(bool isCount)
+ {
+ return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.Full);
}
protected override string GetBaseWhereClause()
@@ -381,7 +386,7 @@ namespace Umbraco.Core.Persistence.Repositories
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, true);
- }
+ }
public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null)
{
@@ -617,9 +622,9 @@ namespace Umbraco.Core.Persistence.Repositories
filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray());
}
- return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords,
+ return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords,
new Tuple("cmsMember", "nodeId"),
- sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField,
+ (sqlFull, sqlIds) => ProcessQuery(sqlFull), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
index 163e0810ad..16382ae462 100644
--- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
@@ -26,8 +28,6 @@ using Umbraco.Core.Media;
namespace Umbraco.Core.Persistence.Repositories
{
- using SqlSyntax;
-
internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase
where TEntity : class, IAggregateRoot
{
@@ -138,6 +138,40 @@ namespace Umbraco.Core.Persistence.Repositories
#endregion
+ ///
+ /// Gets paged document descendants as XML by path
+ ///
+ /// Path starts with
+ /// Page number
+ /// Page size
+ ///
+ /// Total records the query would return without paging
+ /// A paged enumerable of XML entries of content items
+ public virtual IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, string[] orderBy, out long totalRecords)
+ {
+ var query = new Sql().Select(string.Format("umbracoNode.id, cmsContentXml.{0}", SqlSyntax.GetQuotedColumnName("xml")))
+ .From("umbracoNode")
+ .InnerJoin("cmsContentXml").On("cmsContentXml.nodeId = umbracoNode.id");
+
+ if (path == "-1")
+ {
+ query.Where("umbracoNode.nodeObjectType = @type", new { type = NodeObjectTypeId });
+ }
+ else
+ {
+ query.Where(string.Format("umbracoNode.{0} LIKE (@0)", SqlSyntax.GetQuotedColumnName("path")), path.EnsureEndsWith(",%"));
+ }
+
+ //each order by param needs to be in a bracket! see: https://github.com/toptensoftware/PetaPoco/issues/177
+ query.OrderBy(orderBy == null
+ ? "(umbracoNode.id)"
+ : string.Join(",", orderBy.Select(x => string.Format("({0})", SqlSyntax.GetQuotedColumnName(x)))));
+
+ var pagedResult = Database.Page(pageIndex + 1, pageSize, query);
+ totalRecords = pagedResult.TotalItems;
+ return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml));
+ }
+
public int CountDescendants(int parentId, string contentTypeAlias = null)
{
var pathMatch = parentId == -1
@@ -349,12 +383,8 @@ namespace Umbraco.Core.Persistence.Repositories
ON CustomPropData.CustomPropValContentId = umbracoNode.id
", sortedInt, sortedDecimal, sortedDate, sortedString, nodeIdSelect.Item2, nodeIdSelect.Item1, versionQuery, sortedSql.Arguments.Length, newestQuery);
- //insert this just above the first LEFT OUTER JOIN (for cmsDocument) or the last WHERE (everything else)
- string newSql;
- if (nodeIdSelect.Item1 == "cmsDocument")
- newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), outerJoinTempTable);
- else
- newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), outerJoinTempTable);
+ //insert this just above the last WHERE
+ string newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), outerJoinTempTable);
var newArgs = sortedSql.Arguments.ToList();
newArgs.Add(orderBy);
@@ -371,11 +401,14 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
- //no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique.
- // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column
- // is empty for many nodes)
- // see: http://issues.umbraco.org/issue/U4-8831
- sortedSql.OrderBy("umbracoNode.id");
+ if (orderBySystemField && orderBy != "umbracoNode.id")
+ {
+ //no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique.
+ // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column
+ // is empty for many nodes)
+ // see: http://issues.umbraco.org/issue/U4-8831
+ sortedSql.OrderBy("umbracoNode.id");
+ }
return sortedSql;
@@ -385,7 +418,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// A helper method for inheritors to get the paged results by query in a way that minimizes queries
///
/// The type of the d.
- /// The 'true' entity type (i.e. Content, Member, etc...)
/// The query.
/// Index of the page.
/// Size of the page.
@@ -398,34 +430,30 @@ namespace Umbraco.Core.Persistence.Repositories
/// Flag to indicate when ordering by system field
///
/// orderBy
- protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords,
+ protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords,
Tuple nodeIdSelect,
- Func> processQuery,
+ Func> processQuery,
string orderBy,
Direction orderDirection,
bool orderBySystemField,
Func> defaultFilter = null)
- where TContentBase : class, IAggregateRoot, TEntity
{
if (orderBy == null) throw new ArgumentNullException("orderBy");
- // Get base query
- var sqlBase = GetBaseQuery(false);
+ // Get base query for returning IDs
+ var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
+ // Get base query for returning all data
+ var sqlBaseFull = GetBaseQuery(BaseQueryType.Full);
if (query == null) query = new Query();
- var translator = new SqlTranslator(sqlBase, query);
- var sqlQuery = translator.Translate();
-
- // Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery,
- // but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id.
- // So we'll modify the SQL.
- var sqlNodeIds = new Sql(
- sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)),
- sqlQuery.Arguments);
+ var translatorIds = new SqlTranslator(sqlBaseIds, query);
+ var sqlQueryIds = translatorIds.Translate();
+ var translatorFull = new SqlTranslator(sqlBaseFull, query);
+ var sqlQueryFull = translatorFull.Translate();
//get sorted and filtered sql
var sqlNodeIdsWithSort = GetSortedSqlForPagedResults(
- GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter),
+ GetFilteredSqlForPagedResults(sqlQueryIds, defaultFilter),
orderDirection, orderBy, orderBySystemField, nodeIdSelect);
// Get page of results and total count
@@ -441,31 +469,25 @@ namespace Umbraco.Core.Persistence.Repositories
//Crete 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);
+ Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage);
- //if this is for sql server, the sqlPage will start with a SELECT * but we don't want that, we only want to return the nodeId
- sqlStringPage = sqlStringPage
- .Replace("SELECT *",
- //This ensures we only take the field name of the node id select and not the table name - since the resulting select
- // will ony work with the field name.
- "SELECT " + nodeIdSelect.Item2);
-
- //We need to make this an inner join on the paged query
- var splitQuery = sqlQuery.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None);
- var withInnerJoinSql = new Sql(splitQuery[0])
+ //We need to make this FULL query an inner join on the paged ID query
+ var splitQuery = sqlQueryFull.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None);
+ var fullQueryWithPagedInnerJoin = new Sql(splitQuery[0])
.Append("INNER JOIN (")
//join the paged query with the paged query arguments
.Append(sqlStringPage, args)
.Append(") temp ")
.Append(string.Format("ON {0}.{1} = temp.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2))
//add the original where clause back with the original arguments
- .Where(splitQuery[1], sqlQuery.Arguments);
+ .Where(splitQuery[1], sqlQueryIds.Arguments);
//get sorted and filtered sql
var fullQuery = GetSortedSqlForPagedResults(
- GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter),
+ GetFilteredSqlForPagedResults(fullQueryWithPagedInnerJoin, defaultFilter),
orderDirection, orderBy, orderBySystemField, nodeIdSelect);
- return processQuery(fullQuery);
+
+ return processQuery(fullQuery, sqlNodeIdsWithSort);
}
else
{
@@ -477,9 +499,9 @@ namespace Umbraco.Core.Persistence.Repositories
protected IDictionary GetPropertyCollection(
Sql docSql,
- IEnumerable documentDefs)
+ IReadOnlyCollection documentDefs)
{
- if (documentDefs.Any() == false) return new Dictionary();
+ 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
@@ -490,23 +512,12 @@ namespace Umbraco.Core.Persistence.Repositories
parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal));
}
- var propSql = new Sql(@"SELECT cmsPropertyData.*
-FROM cmsPropertyData
-INNER JOIN cmsPropertyType
-ON cmsPropertyData.propertytypeid = cmsPropertyType.id
-INNER JOIN
- (" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData
-ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId
-LEFT OUTER JOIN cmsDataTypePreValues
-ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments);
-
- var allPropertyData = Database.Fetch(propSql);
-
- //This is a lazy access call to get all prevalue data for the data types that make up all of these properties which we use
- // below if any property requires tag support
- var allPreValues = new Lazy>(() =>
- {
- var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId
+ //This retrieves all pre-values for all data types that are referenced for all property types
+ // that exist in the data set.
+ //Benchmarks show that eagerly loading these so that we can lazily read the property data
+ // below (with the use of Query intead of Fetch) go about 30% faster, so we'll eagerly load
+ // this now since we cannot execute another reader inside of reading the property data.
+ var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId
FROM cmsDataTypePreValues a
WHERE EXISTS(
SELECT DISTINCT b.id as preValIdInner
@@ -518,25 +529,85 @@ WHERE EXISTS(
ON cmsPropertyType.contentTypeId = docData.contentType
WHERE a.id = b.id)", docSql.Arguments);
- return Database.Fetch(preValsSql);
- });
+ var allPreValues = Database.Fetch(preValsSql);
+
+ //It's Important with the sort order here! We require this to be sorted by node id,
+ // this is required because this data set can be huge depending on the page size. Due
+ // to it's size we need to be smart about iterating over the property values to build
+ // the document. Before we used to use Linq to get the property data for a given content node
+ // and perform a Distinct() call. This kills performance because that would mean if we had 7000 nodes
+ // and on each iteration we will perform a lookup on potentially 100,000 property rows against the node
+ // id which turns out to be a crazy amount of iterations. Instead we know it's sorted by this value we'll
+ // keep an index stored of the rows being read so we never have to re-iterate the entire data set
+ // on each document iteration.
+ var propSql = new Sql(@"SELECT cmsPropertyData.*
+FROM cmsPropertyData
+INNER JOIN cmsPropertyType
+ON cmsPropertyData.propertytypeid = cmsPropertyType.id
+INNER JOIN
+ (" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData
+ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId
+ORDER BY contentNodeId, propertytypeid
+", docSql.Arguments);
+
+ //This does NOT fetch all data into memory in a list, this will read
+ // over the records as a data reader, this is much better for performance and memory,
+ // but it means that during the reading of this data set, nothing else can be read
+ // from SQL server otherwise we'll get an exception.
+ var allPropertyData = Database.Query(propSql);
var result = new Dictionary();
-
var propertiesWithTagSupport = new Dictionary();
+ //used to track the resolved composition property types per content type so we don't have to re-resolve (ToArray) the list every time
+ var resolvedCompositionProperties = new Dictionary();
- //iterate each definition grouped by it's content type - this will mean less property type iterations while building
- // up the property collections
- foreach (var compositionGroup in documentDefs.GroupBy(x => x.Composition))
+ //keep track of the current property data item being enumerated
+ var propertyDataSetEnumerator = allPropertyData.GetEnumerator();
+
+ try
{
- var compositionProperties = compositionGroup.Key.CompositionPropertyTypes.ToArray();
-
- foreach (var def in compositionGroup)
+ //This must be sorted by node id because this is how we are sorting the query to lookup property types above,
+ // which allows us to more efficiently iterate over the large data set of property values
+ foreach (var def in documentDefs.OrderBy(x => x.Id))
{
- var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct();
+ //get the resolved proeprties from our local cache, or resolve them and put them in cache
+ PropertyType[] compositionProperties;
+ if (resolvedCompositionProperties.ContainsKey(def.Composition.Id))
+ {
+ compositionProperties = resolvedCompositionProperties[def.Composition.Id];
+ }
+ else
+ {
+ compositionProperties = def.Composition.CompositionPropertyTypes.ToArray();
+ resolvedCompositionProperties[def.Composition.Id] = compositionProperties;
+ }
- var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate);
- var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray();
+ var propertyDataDtos = new List();
+
+ //Check if there is a current enumerated item and check if we match and add it
+ if (propertyDataSetEnumerator.Current != null)
+ {
+ if (propertyDataSetEnumerator.Current.NodeId == def.Id)
+ {
+ propertyDataDtos.Add(propertyDataSetEnumerator.Current);
+ }
+ }
+ //Move to the next position, see if we match and add it, if not exit
+ while (propertyDataSetEnumerator.MoveNext())
+ {
+ if (propertyDataSetEnumerator.Current.NodeId == def.Id)
+ {
+ propertyDataDtos.Add(propertyDataSetEnumerator.Current);
+ }
+ else
+ {
+ //the node id has changed so we need to exit the loop,
+ //the enumerator position will be maintained
+ break;
+ }
+ }
+
+ var properties = PropertyFactory.BuildEntity(propertyDataDtos, compositionProperties, def.CreateDate, def.VersionDate).ToArray();
foreach (var property in properties)
{
@@ -553,7 +624,7 @@ WHERE EXISTS(
propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport;
//this property has tags, so we need to extract them and for that we need the prevals which we've already looked up
- var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId)
+ var preValData = allPreValues.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId)
.Distinct()
.ToArray();
@@ -574,8 +645,13 @@ WHERE EXISTS(
result[def.Id] = new PropertyCollection(properties);
}
}
+ finally
+ {
+ propertyDataSetEnumerator.Dispose();
+ }
return result;
+
}
public class DocumentDefinition
@@ -687,5 +763,12 @@ WHERE EXISTS(
return allsuccess;
}
+
+ ///
+ /// For Paging, repositories must support returning different query for the query type specified
+ ///
+ ///
+ ///
+ protected abstract Sql GetBaseQuery(BaseQueryType queryType);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs
index dbc1ab6c93..a74ebb8fec 100644
--- a/src/Umbraco.Core/Properties/AssemblyInfo.cs
+++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs
@@ -29,7 +29,7 @@ using System.Security.Permissions;
[assembly: InternalsVisibleTo("umbraco.webservices")]
[assembly: InternalsVisibleTo("umbraco.datalayer")]
[assembly: InternalsVisibleTo("umbraco.MacroEngines")]
-
+[assembly: InternalsVisibleTo("umbraco.providers")]
[assembly: InternalsVisibleTo("umbraco.editorControls")]
[assembly: InternalsVisibleTo("Umbraco.Tests")]
[assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")]
@@ -42,7 +42,10 @@ using System.Security.Permissions;
[assembly: InternalsVisibleTo("Umbraco.VisualStudio")]
[assembly: InternalsVisibleTo("Umbraco.Courier.Core")]
[assembly: InternalsVisibleTo("Umbraco.Courier.Persistence")]
-[assembly: InternalsVisibleTo("umbraco.providers")]
+
+[assembly: InternalsVisibleTo("Umbraco.Deploy")]
+[assembly: InternalsVisibleTo("Umbraco.Deploy.UI")]
+[assembly: InternalsVisibleTo("Umbraco.Deploy.Cloud")]
//allow this to be mocked in our unit tests
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
\ No newline at end of file
diff --git a/src/Umbraco.Core/Serialization/StreamResultExtensions.cs b/src/Umbraco.Core/Serialization/StreamResultExtensions.cs
new file mode 100644
index 0000000000..96490a933c
--- /dev/null
+++ b/src/Umbraco.Core/Serialization/StreamResultExtensions.cs
@@ -0,0 +1,22 @@
+using System.IO;
+using System.Text;
+using System.Xml.Linq;
+
+namespace Umbraco.Core.Serialization
+{
+ public static class StreamResultExtensions
+ {
+ public static string ToJsonString(this Stream stream)
+ {
+ byte[] bytes = new byte[stream.Length];
+ stream.Position = 0;
+ stream.Read(bytes, 0, (int)stream.Length);
+ return Encoding.UTF8.GetString(bytes);
+ }
+
+ public static XDocument ToXDoc(this Stream stream)
+ {
+ return XDocument.Load(stream);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Serialization/StreamedResult.cs b/src/Umbraco.Core/Serialization/StreamedResult.cs
index 9b73fd06bf..0a50751229 100644
--- a/src/Umbraco.Core/Serialization/StreamedResult.cs
+++ b/src/Umbraco.Core/Serialization/StreamedResult.cs
@@ -1,6 +1,4 @@
using System.IO;
-using System.Text;
-using System.Xml.Linq;
namespace Umbraco.Core.Serialization
{
@@ -20,20 +18,4 @@ namespace Umbraco.Core.Serialization
#endregion
}
-
- public static class StreamResultExtensions
- {
- public static string ToJsonString(this Stream stream)
- {
- byte[] bytes = new byte[stream.Length];
- stream.Position = 0;
- stream.Read(bytes, 0, (int)stream.Length);
- return Encoding.UTF8.GetString(bytes);
- }
-
- public static XDocument ToXDoc(this Stream stream)
- {
- return XDocument.Load(stream);
- }
- }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Serialization/UdiJsonConverter.cs b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs
new file mode 100644
index 0000000000..ff62535825
--- /dev/null
+++ b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs
@@ -0,0 +1,27 @@
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Umbraco.Core.Serialization
+{
+
+ public class UdiJsonConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(Udi).IsAssignableFrom(objectType);
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ writer.WriteValue(value.ToString());
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var jo = JToken.ReadFrom(reader);
+ var val = jo.ToObject();
+ return val == null ? null : Udi.Parse(val);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Serialization/UdiRangeJsonConverter.cs b/src/Umbraco.Core/Serialization/UdiRangeJsonConverter.cs
new file mode 100644
index 0000000000..099c46f29d
--- /dev/null
+++ b/src/Umbraco.Core/Serialization/UdiRangeJsonConverter.cs
@@ -0,0 +1,26 @@
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Umbraco.Core.Serialization
+{
+ public class UdiRangeJsonConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(UdiRange).IsAssignableFrom(objectType);
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ writer.WriteValue(value.ToString());
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var jo = JToken.ReadFrom(reader);
+ var val = jo.ToObject();
+ return val == null ? null : UdiRange.Parse(val);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index e0edaa38c4..32665910ad 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -585,7 +585,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
- public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
+ public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
long total;
var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter);
@@ -604,7 +604,7 @@ namespace Umbraco.Core.Services
/// Direction to order by
/// Search text filter
/// An Enumerable list of objects
- public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
+ public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter);
}
@@ -1764,6 +1764,31 @@ namespace Umbraco.Core.Services
return true;
}
+ ///
+ /// Gets paged content descendants as XML by path
+ ///
+ /// Path starts with
+ /// Page number
+ /// Page size
+ /// Total records the query would return without paging
+ /// A paged enumerable of XML entries of content items
+ public IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords)
+ {
+ Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
+ Mandate.ParameterCondition(pageSize > 0, "pageSize");
+
+ var uow = UowProvider.GetUnitOfWork();
+ using (var repository = RepositoryFactory.CreateContentRepository(uow))
+ {
+ var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize,
+ //This order by is VERY important! This allows us to figure out what is implicitly not published, see ContentRepository.BuildXmlCache and
+ // UmbracoContentIndexer.PerformIndexAll which uses the logic based on this sort order
+ new[] {"level", "parentID", "sortOrder"},
+ out totalRecords);
+ return contents;
+ }
+ }
+
///
/// This builds the Xml document used for the XML cache
///
@@ -2192,6 +2217,11 @@ namespace Umbraco.Core.Services
}
}
+ if (string.IsNullOrWhiteSpace(content.Name))
+ {
+ throw new ArgumentException("Cannot save content with empty name.");
+ }
+
using (new WriteLock(Locker))
{
var uow = UowProvider.GetUnitOfWork();
diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs
index 31e86cd128..9f625c27b8 100644
--- a/src/Umbraco.Core/Services/ContentTypeService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeService.cs
@@ -719,8 +719,13 @@ namespace Umbraco.Core.Services
/// Optional id of the user saving the ContentType
public void Save(IContentType contentType, int userId = 0)
{
- if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs(contentType), this))
- return;
+ if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs(contentType), this))
+ return;
+
+ if (string.IsNullOrWhiteSpace(contentType.Name))
+ {
+ throw new ArgumentException("Cannot save content type with empty name.");
+ }
using (new WriteLock(Locker))
{
diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs
index 2cdcfc76d5..b3fca10798 100644
--- a/src/Umbraco.Core/Services/DataTypeService.cs
+++ b/src/Umbraco.Core/Services/DataTypeService.cs
@@ -361,8 +361,13 @@ namespace Umbraco.Core.Services
/// Id of the user issueing the save
public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0)
{
- if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this))
- return;
+ if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this))
+ return;
+
+ if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name))
+ {
+ throw new ArgumentException("Cannot save datatype with empty name.");
+ }
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow))
diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs
index ec5db8c4fb..ff515169d9 100644
--- a/src/Umbraco.Core/Services/IContentService.cs
+++ b/src/Umbraco.Core/Services/IContentService.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml;
+using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -95,6 +96,19 @@ namespace Umbraco.Core.Services
///
public interface IContentService : IService
{
+ ///
+ /// Gets all XML entries found in the cmsContentXml table based on the given path
+ ///
+ /// Path starts with
+ /// Page number
+ /// Page size
+ /// Total records the query would return without paging
+ /// A paged enumerable of XML entries of content items
+ ///
+ /// If -1 is passed, then this will return all content xml entries, otherwise will return all descendents from the path
+ ///
+ IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords);
+
///
/// This builds the Xml document used for the XML cache
///
@@ -245,7 +259,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords,
- string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
+ string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
///
/// Gets a collection of objects by Parent Id
@@ -259,7 +273,7 @@ namespace Umbraco.Core.Services
/// Search text filter
/// An Enumerable list of objects
IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
- string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
+ string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
///
/// Gets a collection of objects by Parent Id
diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs
index 510a1e26ca..375981853e 100644
--- a/src/Umbraco.Core/Services/IMediaService.cs
+++ b/src/Umbraco.Core/Services/IMediaService.cs
@@ -170,7 +170,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords,
- string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
+ string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
///
/// Gets a collection of objects by Parent Id
@@ -184,7 +184,7 @@ namespace Umbraco.Core.Services
/// Search text filter
/// An Enumerable list of objects
IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
- string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
+ string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
///
/// Gets a collection of objects by Parent Id
diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs
index a893d89feb..38f53f2d68 100644
--- a/src/Umbraco.Core/Services/IMemberService.cs
+++ b/src/Umbraco.Core/Services/IMemberService.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -13,6 +14,15 @@ namespace Umbraco.Core.Services
///
public interface IMemberService : IMembershipMemberService
{
+ ///
+ /// Gets all XML entries found in the cmsContentXml table
+ ///
+ /// Page number
+ /// Page size
+ /// Total records the query would return without paging
+ /// A paged enumerable of XML entries of content items
+ IEnumerable GetPagedXmlEntries(long pageIndex, int pageSize, out long totalRecords);
+
///
/// Rebuilds all xml content in the cmsContentXml table for all documents
///
diff --git a/src/Umbraco.Core/Services/MacroService.cs b/src/Umbraco.Core/Services/MacroService.cs
index a6de12ed32..76515a71df 100644
--- a/src/Umbraco.Core/Services/MacroService.cs
+++ b/src/Umbraco.Core/Services/MacroService.cs
@@ -161,19 +161,24 @@ namespace Umbraco.Core.Services
/// Optional Id of the user deleting the macro
public void Save(IMacro macro, int userId = 0)
{
- if (Saving.IsRaisedEventCancelled(new SaveEventArgs(macro), this))
- return;
-
- var uow = UowProvider.GetUnitOfWork();
- using (var repository = RepositoryFactory.CreateMacroRepository(uow))
- {
- repository.AddOrUpdate(macro);
- uow.Commit();
+ if (Saving.IsRaisedEventCancelled(new SaveEventArgs(macro), this))
+ return;
- Saved.RaiseEvent(new SaveEventArgs(macro, false), this);
- }
+ if (string.IsNullOrWhiteSpace(macro.Name))
+ {
+ throw new ArgumentException("Cannot save macro with empty name.");
+ }
- Audit(AuditType.Save, "Save Macro performed by user", userId, -1);
+ var uow = UowProvider.GetUnitOfWork();
+ using (var repository = RepositoryFactory.CreateMacroRepository(uow))
+ {
+ repository.AddOrUpdate(macro);
+ uow.Commit();
+
+ Saved.RaiseEvent(new SaveEventArgs(macro, false), this);
+ }
+
+ Audit(AuditType.Save, "Save Macro performed by user", userId, -1);
}
/////
diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs
index 7c583dfe28..63d3388103 100644
--- a/src/Umbraco.Core/Services/MediaService.cs
+++ b/src/Umbraco.Core/Services/MediaService.cs
@@ -451,7 +451,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
- public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
+ public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
long total;
var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter);
@@ -470,7 +470,7 @@ namespace Umbraco.Core.Services
/// Direction to order by
/// Search text filter
/// An Enumerable list of objects
- public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
+ public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter);
}
@@ -837,7 +837,11 @@ namespace Umbraco.Core.Services
{
return OperationStatus.Cancelled(evtMsgs);
}
+ }
+ if (string.IsNullOrWhiteSpace(media.Name))
+ {
+ throw new ArgumentException("Cannot save media with empty name.");
}
var uow = UowProvider.GetUnitOfWork();
@@ -1224,7 +1228,7 @@ namespace Umbraco.Core.Services
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMediaRepository(uow))
{
- var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, out totalRecords);
+ var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, null, out totalRecords);
return contents;
}
}
diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs
index 094539d66e..70d45926fc 100644
--- a/src/Umbraco.Core/Services/MemberService.cs
+++ b/src/Umbraco.Core/Services/MemberService.cs
@@ -612,6 +612,26 @@ namespace Umbraco.Core.Services
Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1);
}
+ ///
+ /// Gets paged member descendants as XML by path
+ ///
+ /// Page number
+ /// Page size
+ /// Total records the query would return without paging
+ /// A paged enumerable of XML entries of member items
+ public IEnumerable GetPagedXmlEntries(long pageIndex, int pageSize, out long totalRecords)
+ {
+ Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
+ Mandate.ParameterCondition(pageSize > 0, "pageSize");
+
+ var uow = UowProvider.GetUnitOfWork();
+ using (var repository = RepositoryFactory.CreateMemberRepository(uow))
+ {
+ var contents = repository.GetPagedXmlEntriesByPath("-1", pageIndex, pageSize, null, out totalRecords);
+ return contents;
+ }
+ }
+
#endregion
#region IMembershipMemberService Implementation
@@ -978,6 +998,11 @@ namespace Umbraco.Core.Services
}
}
+ if (string.IsNullOrWhiteSpace(entity.Name))
+ {
+ throw new ArgumentException("Cannot save member with empty name.");
+ }
+
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMemberRepository(uow))
{
diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs
index c6bc9dd24a..164bbc8243 100644
--- a/src/Umbraco.Core/Services/MemberTypeService.cs
+++ b/src/Umbraco.Core/Services/MemberTypeService.cs
@@ -78,6 +78,11 @@ namespace Umbraco.Core.Services
if (Saving.IsRaisedEventCancelled(new SaveEventArgs(memberType), this))
return;
+ if (string.IsNullOrWhiteSpace(memberType.Name))
+ {
+ throw new ArgumentException("Cannot save MemberType with empty name.");
+ }
+
using (new WriteLock(Locker))
{
var uow = UowProvider.GetUnitOfWork();
diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs
index 8e984d1e5d..e36e454987 100644
--- a/src/Umbraco.Core/Services/UserService.cs
+++ b/src/Umbraco.Core/Services/UserService.cs
@@ -125,6 +125,11 @@ namespace Umbraco.Core.Services
{
if (userType == null) throw new ArgumentNullException("userType");
+ if (string.IsNullOrWhiteSpace(username))
+ {
+ throw new ArgumentException("Cannot create user with empty username.");
+ }
+
//TODO: PUT lock here!!
var uow = UowProvider.GetUnitOfWork();
@@ -312,6 +317,15 @@ namespace Umbraco.Core.Services
return;
}
+ if (string.IsNullOrWhiteSpace(entity.Username))
+ {
+ throw new ArgumentException("Cannot save user with empty username.");
+ }
+ if (string.IsNullOrWhiteSpace(entity.Name))
+ {
+ throw new ArgumentException("Cannot save user with empty name.");
+ }
+
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateUserRepository(uow))
{
@@ -353,6 +367,14 @@ namespace Umbraco.Core.Services
{
foreach (var member in entities)
{
+ if (string.IsNullOrWhiteSpace(member.Username))
+ {
+ throw new ArgumentException("Cannot save user with empty username.");
+ }
+ if (string.IsNullOrWhiteSpace(member.Name))
+ {
+ throw new ArgumentException("Cannot save user with empty name.");
+ }
repository.AddOrUpdate(member);
}
//commit the whole lot in one go
diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Core/StringUdi.cs
new file mode 100644
index 0000000000..2fcb53f263
--- /dev/null
+++ b/src/Umbraco.Core/StringUdi.cs
@@ -0,0 +1,71 @@
+using System;
+
+namespace Umbraco.Core
+{
+ ///
+ /// Represents a string-based entity identifier.
+ ///
+ public class StringUdi : Udi
+ {
+ ///
+ /// The string part of the identifier.
+ ///
+ public string Id { get; private set; }
+
+ ///
+ /// Initializes a new instance of the StringUdi class with an entity type and a string id.
+ ///
+ /// The entity type part of the udi.
+ /// The string id part of the udi.
+ public StringUdi(string entityType, string id)
+ : base(entityType, "umb://" + entityType + "/" + id)
+ {
+ Id = id;
+ }
+
+ ///
+ /// Initializes a new instance of the StringUdi class with a uri value.
+ ///
+ /// The uri value of the udi.
+ public StringUdi(Uri uriValue)
+ : base(uriValue)
+ {
+ Id = uriValue.AbsolutePath.TrimStart('/');
+ }
+
+ ///
+ /// Converts the string representation of an entity identifier into the equivalent StringUdi instance.
+ ///
+ /// The string to convert.
+ /// A StringUdi instance that contains the value that was parsed.
+ public new static StringUdi Parse(string s)
+ {
+ var udi = Udi.Parse(s);
+ if (!(udi is StringUdi))
+ throw new FormatException("String \"" + s + "\" is not a string entity id.");
+ return (StringUdi)udi;
+ }
+
+ public static bool TryParse(string s, out StringUdi udi)
+ {
+ udi = null;
+ Udi tmp;
+ if (!TryParse(s, out tmp) || !(tmp is StringUdi)) return false;
+ udi = (StringUdi)tmp;
+ return true;
+ }
+
+ ///
+ public override bool IsRoot
+ {
+ get { return Id == string.Empty; }
+ }
+
+ ///
+ public StringUdi EnsureClosed()
+ {
+ base.EnsureNotRoot();
+ return this;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs
new file mode 100644
index 0000000000..a7298c6f89
--- /dev/null
+++ b/src/Umbraco.Core/Udi.cs
@@ -0,0 +1,267 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core.Deploy;
+
+namespace Umbraco.Core
+{
+ ///
+ /// Represents an entity identifier.
+ ///
+ /// 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 ConcurrentDictionary RootUdis = new ConcurrentDictionary();
+ internal readonly Uri UriValue; // internal for UdiRange
+
+ ///
+ /// Initializes a new instance of the Udi class.
+ ///
+ /// The entity type part of the identifier.
+ /// The string value of the identifier.
+ protected Udi(string entityType, string stringValue)
+ {
+ EntityType = entityType;
+ UriValue = new Uri(stringValue);
+ }
+
+ ///
+ /// Initializes a new instance of the Udi class.
+ ///
+ /// The uri value of the identifier.
+ protected Udi(Uri uriValue)
+ {
+ EntityType = uriValue.Host;
+ UriValue = uriValue;
+ }
+
+ static Udi()
+ {
+ // for tests etc.
+ UdiTypes[Constants.DeployEntityType.AnyGuid] = UdiType.GuidUdi;
+ UdiTypes[Constants.DeployEntityType.AnyString] = UdiType.StringUdi;
+
+ // we don't have connectors for these...
+ UdiTypes[Constants.DeployEntityType.Member] = UdiType.GuidUdi;
+ UdiTypes[Constants.DeployEntityType.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)
+ {
+ var attrs = connector.GetCustomAttributes(false);
+ foreach (var attr in attrs)
+ {
+ 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;
+ }
+ }
+ }
+
+ ///
+ /// Gets the entity type part of the identifier.
+ ///
+ public string EntityType { get; private set; }
+
+ public int CompareTo(Udi other)
+ {
+ return string.Compare(UriValue.ToString(), other.UriValue.ToString(), StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ public override string ToString()
+ {
+ // UriValue is created in the ctor and is never null
+ return UriValue.ToString();
+ }
+
+ ///
+ /// Converts the string representation of an entity identifier into the equivalent Udi instance.
+ ///
+ /// The string to convert.
+ /// An Udi instance that contains the value that was parsed.
+ public static Udi Parse(string s)
+ {
+ Udi udi;
+ ParseInternal(s, false, out udi);
+ return udi;
+ }
+
+ public static bool TryParse(string s, out Udi udi)
+ {
+ return ParseInternal(s, true, out udi);
+ }
+
+ private static bool ParseInternal(string s, bool tryParse, out Udi udi)
+ {
+ udi = null;
+ Uri uri;
+
+ if (!Uri.IsWellFormedUriString(s, UriKind.Absolute)
+ || !Uri.TryCreate(s, UriKind.Absolute, out uri))
+ {
+ if (tryParse) return false;
+ throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s));
+ }
+
+ var entityType = uri.Host;
+ UdiType udiType;
+ if (!UdiTypes.TryGetValue(entityType, out udiType))
+ {
+ if (tryParse) return false;
+ throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType));
+ }
+ var path = uri.AbsolutePath.TrimStart('/');
+ if (udiType == UdiType.GuidUdi)
+ {
+ if (path == string.Empty)
+ {
+ udi = GetRootUdi(uri.Host);
+ return true;
+ }
+ Guid guid;
+ if (!Guid.TryParse(path, out guid))
+ {
+ if (tryParse) return false;
+ throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s));
+ }
+ udi = new GuidUdi(uri.Host, guid);
+ return true;
+ }
+ if (udiType == UdiType.StringUdi)
+ {
+ udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, path);
+ return true;
+ }
+ if (tryParse) return false;
+ throw new InvalidOperationException("Internal error.");
+ }
+
+ private static Udi GetRootUdi(string entityType)
+ {
+ return RootUdis.GetOrAdd(entityType, x =>
+ {
+ UdiType udiType;
+ if (!UdiTypes.TryGetValue(x, out udiType))
+ throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType));
+ return udiType == UdiType.StringUdi
+ ? (Udi)new StringUdi(entityType, string.Empty)
+ : new GuidUdi(entityType, Guid.Empty);
+ });
+ }
+
+ ///
+ /// Creates a root Udi for an entity type.
+ ///
+ /// The entity type.
+ /// The root Udi for the entity type.
+ public static Udi Create(string entityType)
+ {
+ return GetRootUdi(entityType);
+ }
+
+ ///
+ /// Creates a string Udi.
+ ///
+ /// The entity type.
+ /// The identifier.
+ /// The string Udi for the entity type and identifier.
+ public static Udi Create(string entityType, string id)
+ {
+ UdiType udiType;
+ if (!UdiTypes.TryGetValue(entityType, out udiType))
+ 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");
+ if (udiType != UdiType.StringUdi)
+ throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have string udis.", entityType));
+
+ return new StringUdi(entityType, id);
+ }
+
+ ///
+ /// Creates a Guid Udi.
+ ///
+ /// The entity type.
+ /// The identifier.
+ /// The Guid Udi for the entity type and identifier.
+ public static Udi Create(string entityType, Guid id)
+ {
+ UdiType udiType;
+ if (!UdiTypes.TryGetValue(entityType, out udiType))
+ 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));
+ if (id == default(Guid))
+ throw new ArgumentException("Cannot be an empty guid.", "id");
+ return new GuidUdi(entityType, id);
+ }
+
+ internal static Udi Create(Uri uri)
+ {
+ UdiType udiType;
+ if (!UdiTypes.TryGetValue(uri.Host, out udiType))
+ throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri");
+ if (udiType == UdiType.GuidUdi)
+ return new GuidUdi(uri);
+ if (udiType == UdiType.GuidUdi)
+ return new StringUdi(uri);
+ throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri));
+ }
+
+ public void EnsureType(params string[] validTypes)
+ {
+ if (!validTypes.Contains(EntityType))
+ throw new Exception(string.Format("Unexpected entity type \"{0}\".", EntityType));
+ }
+
+ ///
+ /// Gets a value indicating whether this Udi is a root Udi.
+ ///
+ /// A root Udi points to the "root of all things" for a given entity type, e.g. the content tree root.
+ public abstract bool IsRoot { get; }
+
+ ///
+ /// Ensures that this Udi is not a root Udi.
+ ///
+ /// This Udi.
+ /// When this Udi is a Root Udi.
+ public Udi EnsureNotRoot()
+ {
+ if (IsRoot) throw new Exception("Root Udi.");
+ return this;
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as Udi;
+ return other != null && GetType() == other.GetType() && UriValue == other.UriValue;
+ }
+
+ public override int GetHashCode()
+ {
+ return UriValue.GetHashCode();
+ }
+
+ public static bool operator ==(Udi udi1, Udi udi2)
+ {
+ if (ReferenceEquals(udi1, udi2)) return true;
+ if ((object)udi1 == null || (object)udi2 == null) return false;
+ return udi1.Equals(udi2);
+ }
+
+ public static bool operator !=(Udi udi1, Udi udi2)
+ {
+ return !(udi1 == udi2);
+ }
+ }
+
+}
diff --git a/src/Umbraco.Core/UdiDefinitionAttribute.cs b/src/Umbraco.Core/UdiDefinitionAttribute.cs
new file mode 100644
index 0000000000..cc52bb6039
--- /dev/null
+++ b/src/Umbraco.Core/UdiDefinitionAttribute.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Umbraco.Core
+{
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
+ public sealed class UdiDefinitionAttribute : Attribute
+ {
+ public UdiDefinitionAttribute(string entityType, UdiType udiType)
+ {
+ if (string.IsNullOrWhiteSpace(entityType)) throw new ArgumentNullException("entityType");
+ if (udiType != UdiType.GuidUdi && udiType != UdiType.StringUdi) throw new ArgumentException("Invalid value.", "udiType");
+ EntityType = entityType;
+ UdiType = udiType;
+ }
+
+ public string EntityType { get; private set; }
+
+ public UdiType UdiType { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/UdiGetterExtensions.cs b/src/Umbraco.Core/UdiGetterExtensions.cs
new file mode 100644
index 0000000000..1bd018a754
--- /dev/null
+++ b/src/Umbraco.Core/UdiGetterExtensions.cs
@@ -0,0 +1,303 @@
+using System;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core
+{
+ ///
+ /// Provides extension methods that return udis for Umbraco entities.
+ ///
+ public static class UdiGetterExtensions
+ {
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this ITemplate entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.Template, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IContentType entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.DocumentType, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IMediaType entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.MediaType, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IMemberType entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.MemberType, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IMemberGroup entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.MemberGroup, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IContentTypeComposition entity)
+ {
+ 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;
+ else throw new NotSupportedException(string.Format("Composition type {0} is not supported.", entity.GetType().FullName));
+ return new GuidUdi(type, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IDataTypeDefinition entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.DataType, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this Umbraco.Core.Models.EntityContainer entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+
+ string entityType;
+ if (entity.ContainedObjectType == Constants.ObjectTypes.DataTypeGuid)
+ entityType = Constants.DeployEntityType.DataTypeContainer;
+ else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentTypeGuid)
+ entityType = Constants.DeployEntityType.DocumentTypeContainer;
+ else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaTypeGuid)
+ entityType = Constants.DeployEntityType.MediaTypeContainer;
+ else
+ throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", entity.ContainedObjectType));
+ return new GuidUdi(entityType, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IMedia entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.Media, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IContent entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.Document, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IMember entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.Member, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static StringUdi GetUdi(this Stylesheet entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new StringUdi(Constants.DeployEntityType.Stylesheet, entity.Path.TrimStart('/')).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static StringUdi GetUdi(this Script entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new StringUdi(Constants.DeployEntityType.Script, entity.Path.TrimStart('/')).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IDictionaryItem entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.DictionaryItem, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IMacro entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.Macro, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static StringUdi GetUdi(this IPartialView entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new StringUdi(Constants.DeployEntityType.PartialView, entity.Path.TrimStart('/')).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static StringUdi GetUdi(this IXsltFile entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new StringUdi(Constants.DeployEntityType.Xslt, entity.Path.TrimStart('/')).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IContentBase entity)
+ {
+ 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;
+ else throw new NotSupportedException(string.Format("ContentBase type {0} is not supported.", entity.GetType().FullName));
+ return new GuidUdi(type, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static GuidUdi GetUdi(this IRelationType entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ return new GuidUdi(Constants.DeployEntityType.RelationType, entity.Key).EnsureClosed();
+ }
+
+ ///
+ /// Gets the entity identifier of the entity.
+ ///
+ /// The entity.
+ /// The entity identifier of the entity.
+ public static Udi GetUdi(this IEntity entity)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+
+ // entity could eg be anything implementing IThing
+ // so we have to go through casts here
+
+ var template = entity as ITemplate;
+ if (template != null) return template.GetUdi();
+
+ var contentType = entity as IContentType;
+ if (contentType != null) return contentType.GetUdi();
+
+ var mediaType = entity as IMediaType;
+ if (mediaType != null) return mediaType.GetUdi();
+
+ var memberType = entity as IMemberType;
+ if (memberType != null) return memberType.GetUdi();
+
+ var memberGroup = entity as IMemberGroup;
+ if (memberGroup != null) return memberGroup.GetUdi();
+
+ var contentTypeComposition = entity as IContentTypeComposition;
+ if (contentTypeComposition != null) return contentTypeComposition.GetUdi();
+
+ var dataTypeComposition = entity as IDataTypeDefinition;
+ if (dataTypeComposition != null) return dataTypeComposition.GetUdi();
+
+ var container = entity as Umbraco.Core.Models.EntityContainer;
+ if (container != null) return container.GetUdi();
+
+ var media = entity as IMedia;
+ if (media != null) return media.GetUdi();
+
+ var content = entity as IContent;
+ if (content != null) return content.GetUdi();
+
+ var member = entity as IMember;
+ if (member != null) return member.GetUdi();
+
+ var contentBase = entity as IContentBase;
+ if (contentBase != null) return contentBase.GetUdi();
+
+ var macro = entity as IMacro;
+ if (macro != null) return macro.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));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/UdiRange.cs b/src/Umbraco.Core/UdiRange.cs
new file mode 100644
index 0000000000..a708220066
--- /dev/null
+++ b/src/Umbraco.Core/UdiRange.cs
@@ -0,0 +1,104 @@
+using System;
+
+namespace Umbraco.Core
+{
+ ///
+ /// Represents a range.
+ ///
+ ///
+ /// A Udi range is composed of a which represents the base of the range,
+ /// plus a selector that can be "." (the Udi), ".*" (the Udi and its children), ".**" (the udi and
+ /// its descendants, "*" (the children of the Udi), and "**" (the descendants of the Udi).
+ /// The Udi here can be a closed entity, or an open entity.
+ public class UdiRange
+ {
+ private readonly Uri _uriValue;
+
+ ///
+ /// Initializes a new instance of the class with a and an optional selector.
+ ///
+ /// A .
+ /// An optional selector.
+ public UdiRange(Udi udi, string selector = Constants.DeploySelector.This)
+ {
+ Udi = udi;
+ switch (selector)
+ {
+ case Constants.DeploySelector.This:
+ Selector = selector;
+ _uriValue = udi.UriValue;
+ break;
+ case Constants.DeploySelector.ChildrenOfThis:
+ case Constants.DeploySelector.DescendantsOfThis:
+ case Constants.DeploySelector.ThisAndChildren:
+ case Constants.DeploySelector.ThisAndDescendants:
+ Selector = selector;
+ _uriValue = new Uri(Udi + "?" + selector);
+ break;
+ default:
+ throw new ArgumentException(string.Format("Invalid selector \"{0}\".", selector));
+ }
+ }
+
+ ///
+ /// Gets the for this range.
+ ///
+ public Udi Udi { get; private set; }
+
+ ///
+ /// Gets or sets the selector for this range.
+ ///
+ public string Selector { get; private set; }
+
+ ///
+ /// Gets the entity type of the for this range.
+ ///
+ public string EntityType
+ {
+ get { return Udi.EntityType; }
+ }
+
+ public static UdiRange Parse(string s)
+ {
+ Uri uri;
+
+ if (!Uri.IsWellFormedUriString(s, UriKind.Absolute)
+ || !Uri.TryCreate(s, UriKind.Absolute, out uri))
+ {
+ //if (tryParse) return false;
+ throw new FormatException(string.Format("String \"{0}\" is not a valid udi range.", s));
+ }
+
+ var udiUri = uri.Query == string.Empty ? uri : new UriBuilder(uri) { Query = string.Empty }.Uri;
+ return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart('?'));
+ }
+
+ public override string ToString()
+ {
+ return _uriValue.ToString();
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as UdiRange;
+ return other != null && GetType() == other.GetType() && _uriValue == other._uriValue;
+ }
+
+ public override int GetHashCode()
+ {
+ return _uriValue.GetHashCode();
+ }
+
+ public static bool operator ==(UdiRange range1, UdiRange range2)
+ {
+ if (ReferenceEquals(range1, range2)) return true;
+ if ((object)range1 == null || (object)range2 == null) return false;
+ return range1.Equals(range2);
+ }
+
+ public static bool operator !=(UdiRange range1, UdiRange range2)
+ {
+ return !(range1 == range2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/UdiType.cs b/src/Umbraco.Core/UdiType.cs
new file mode 100644
index 0000000000..d15987033d
--- /dev/null
+++ b/src/Umbraco.Core/UdiType.cs
@@ -0,0 +1,12 @@
+namespace Umbraco.Core
+{
+ ///
+ /// Defines Udi types.
+ ///
+ public enum UdiType
+ {
+ Unknown,
+ GuidUdi,
+ StringUdi
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 950a44502f..3a53f273f4 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -290,15 +290,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -340,6 +367,7 @@
TrueFiles.resx
+
@@ -410,6 +438,7 @@
+
@@ -482,6 +511,7 @@
+
@@ -527,6 +557,9 @@
+
+
+
@@ -1326,6 +1359,7 @@
+
@@ -1370,6 +1404,11 @@
+
+
+
+
+
diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
index 84cdef73e1..699e563407 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
@@ -172,6 +172,38 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
+ [Test]
+ public void Can_Get_All_Containers()
+ {
+ var provider = new PetaPocoUnitOfWorkProvider(Logger);
+ var unitOfWork = provider.GetUnitOfWork();
+ EntityContainer container1, container2, container3;
+ using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid))
+ {
+ container1 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container1" };
+ containerRepository.AddOrUpdate(container1);
+ container2 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container2" };
+ containerRepository.AddOrUpdate(container2);
+ container3 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container3" };
+ containerRepository.AddOrUpdate(container3);
+ unitOfWork.Commit();
+ Assert.That(container1.Id, Is.GreaterThan(0));
+ Assert.That(container2.Id, Is.GreaterThan(0));
+ Assert.That(container3.Id, Is.GreaterThan(0));
+ }
+ using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid))
+ {
+ var found1 = containerRepository.Get(container1.Id);
+ Assert.IsNotNull(found1);
+ var found2 = containerRepository.Get(container2.Id);
+ Assert.IsNotNull(found2);
+ var found3 = containerRepository.Get(container3.Id);
+ Assert.IsNotNull(found3);
+ var allContainers = containerRepository.GetAll();
+ Assert.AreEqual(3, allContainers.Count());
+ }
+ }
+
[Test]
public void Can_Delete_Container()
{
diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs
index c8372e70ac..062ab164b0 100644
--- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs
+++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs
@@ -367,5 +367,17 @@ namespace Umbraco.Tests.PropertyEditors
var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, height: 200);
Assert.AreEqual(mediaPath + "?anchor=center&mode=crop&height=200", urlString);
}
+
+ ///
+ /// Test to check result when using a background color with padding
+ ///
+ [Test]
+ public void GetCropUrl_BackgroundColorParameter()
+ {
+ var cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"" + mediaPath + "\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}";
+
+ var urlString = mediaPath.GetCropUrl(400, 400, cropperJson, imageCropMode: ImageCropMode.Pad, backgroundColor: "fff");
+ Assert.AreEqual(mediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString);
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs
index 5bf6a4edc5..f48887498e 100644
--- a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs
@@ -1,6 +1,8 @@
using System;
using System.Linq;
+using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
+using Lucene.Net.Index;
using Lucene.Net.Store;
using Moq;
using NUnit.Framework;
@@ -31,12 +33,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ensure_Children_Are_Sorted()
{
- using (var luceneDir = new RAMDirectory())
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
- var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
+ //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
- var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
+ //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var result = searcher.Search(searcher.CreateSearchCriteria().Id(1111).Compile());
Assert.IsNotNull(result);
Assert.AreEqual(1, result.TotalItemCount);
@@ -60,12 +65,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ensure_Result_Has_All_Values()
{
- using (var luceneDir = new RAMDirectory())
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
- var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
+ //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
- var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
+ //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var result = searcher.Search(searcher.CreateSearchCriteria().Id(1111).Compile());
Assert.IsNotNull(result);
Assert.AreEqual(1, result.TotalItemCount);
diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs
index 6ec347cc0f..eadc08d834 100644
--- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs
@@ -27,6 +27,7 @@ using UmbracoExamine;
using UmbracoExamine.DataServices;
using umbraco.BusinessLogic;
using System.Linq;
+using Lucene.Net.Index;
namespace Umbraco.Tests.PublishedContent
{
@@ -106,11 +107,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ensure_Children_Sorted_With_Examine()
{
- using (var luceneDir = new RAMDirectory())
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
- var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
+ //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
- var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
+ //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -135,11 +139,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Do_Not_Find_In_Recycle_Bin()
{
- using (var luceneDir = new RAMDirectory())
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
- var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
+ //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
- var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
+ //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -175,11 +182,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Children_With_Examine()
{
- using (var luceneDir = new RAMDirectory())
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
- var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
+ //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
- var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
+ //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -197,11 +207,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Descendants_With_Examine()
{
- using (var luceneDir = new RAMDirectory())
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
- var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
+ //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
- var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
+ //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -219,11 +232,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void DescendantsOrSelf_With_Examine()
{
- using (var luceneDir = new RAMDirectory())
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
- var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
+ //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
- var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
+ //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -241,12 +257,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ancestors_With_Examine()
{
- using (var luceneDir = new RAMDirectory())
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
- var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
+ //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var ctx = GetUmbracoContext("/test", 1234);
- var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
+ //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
//we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace
@@ -260,12 +279,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void AncestorsOrSelf_With_Examine()
{
- using (var luceneDir = new RAMDirectory())
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
- var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
+ //var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var ctx = GetUmbracoContext("/test", 1234);
- var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
+ //var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
//we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace
diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs
index 72a58861e2..0078d3e83e 100644
--- a/src/Umbraco.Tests/Services/ContentServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs
@@ -668,6 +668,17 @@ namespace Umbraco.Tests.Services
Assert.Throws(() => contentService.CreateContent("Test", -1, "umbAliasDoesntExist"));
}
+ [Test]
+ public void Cannot_Save_Content_With_Empty_Name()
+ {
+ // Arrange
+ var contentService = ServiceContext.ContentService;
+ var content = new Content(string.Empty, -1, ServiceContext.ContentTypeService.GetContentType("umbTextpage"));
+
+ // Act & Assert
+ Assert.Throws(() => contentService.Save(content));
+ }
+
[Test]
public void Can_Get_Content_By_Id()
{
diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
index bc9ec8a9ad..78f4a5dad9 100644
--- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
@@ -304,6 +304,42 @@ namespace Umbraco.Tests.Services
Assert.IsNull(deletedContentType);
}
+ [Test]
+ public void Can_Create_Container()
+ {
+ // Arrange
+ var cts = ServiceContext.ContentTypeService;
+
+ // Act
+ var container = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid);
+ container.Name = "container1";
+ cts.SaveContentTypeContainer(container);
+
+ // Assert
+ var createdContainer = cts.GetContentTypeContainer(container.Id);
+ Assert.IsNotNull(createdContainer);
+ }
+
+ [Test]
+ public void Can_Get_All_Containers()
+ {
+ // Arrange
+ var cts = ServiceContext.ContentTypeService;
+
+ // Act
+ var container1 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid);
+ container1.Name = "container1";
+ cts.SaveContentTypeContainer(container1);
+
+ var container2 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid);
+ container2.Name = "container2";
+ cts.SaveContentTypeContainer(container2);
+
+ // Assert
+ var containers = cts.GetContentTypeContainers(new int[0]);
+ Assert.AreEqual(2, containers.Count());
+ }
+
[Test]
public void Deleting_ContentType_Sends_Correct_Number_Of_DeletedEntities_In_Events()
{
@@ -717,6 +753,16 @@ namespace Umbraco.Tests.Services
Assert.DoesNotThrow(() => service.GetContentType("advancedPage"));
}
+ [Test]
+ public void Cannot_Save_ContentType_With_Empty_Name()
+ {
+ // Arrange
+ var contentType = MockedContentTypes.CreateSimpleContentType("contentType", string.Empty);
+
+ // Act & Assert
+ Assert.Throws(() => ServiceContext.ContentTypeService.Save(contentType));
+ }
+
[Test]
public void Cannot_Rename_PropertyType_Alias_On_Composition_Which_Would_Cause_Conflict_In_Other_Composition()
{
diff --git a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs
index 073d7af406..ed9fc9a7e7 100644
--- a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs
+++ b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs
@@ -221,5 +221,18 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("preVal1", preVals.PreValuesAsArray.First().Value);
Assert.AreEqual("preVal2", preVals.PreValuesAsArray.Last().Value);
}
+
+ [Test]
+ public void Cannot_Save_DataType_With_Empty_Name()
+ {
+ // Arrange
+ var dataTypeService = ServiceContext.DataTypeService;
+
+ // Act
+ var dataTypeDefinition = new DataTypeDefinition(-1, "Test.TestEditor") { Name = string.Empty, DatabaseType = DataTypeDatabaseType.Ntext };
+
+ // Act & Assert
+ Assert.Throws(() => dataTypeService.Save(dataTypeDefinition));
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Services/MacroServiceTests.cs b/src/Umbraco.Tests/Services/MacroServiceTests.cs
index a12ba731a4..a4d6e3d5ef 100644
--- a/src/Umbraco.Tests/Services/MacroServiceTests.cs
+++ b/src/Umbraco.Tests/Services/MacroServiceTests.cs
@@ -251,6 +251,17 @@ namespace Umbraco.Tests.Services
}
+ [Test]
+ public void Cannot_Save_Macro_With_Empty_Name()
+ {
+ // Arrange
+ var macroService = ServiceContext.MacroService;
+ var macro = new Macro("test", string.Empty, scriptPath: "~/Views/MacroPartials/Test.cshtml", cacheDuration: 1234);
+
+ // Act & Assert
+ Assert.Throws(() => macroService.Save(macro));
+ }
+
//[Test]
//public void Can_Get_Many_By_Alias()
//{
diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs
index a53257b26a..8193df911c 100644
--- a/src/Umbraco.Tests/Services/MediaServiceTests.cs
+++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs
@@ -81,6 +81,19 @@ namespace Umbraco.Tests.Services
Assert.That(mediaChild.Trashed, Is.False);
}
+ [Test]
+ public void Cannot_Save_Media_With_Empty_Name()
+ {
+ // Arrange
+ var mediaService = ServiceContext.MediaService;
+ var mediaType = MockedContentTypes.CreateVideoMediaType();
+ ServiceContext.ContentTypeService.Save(mediaType);
+ var media = mediaService.CreateMedia(string.Empty, -1, "video");
+
+ // Act & Assert
+ Assert.Throws(() => mediaService.Save(media));
+ }
+
[Test]
public void Ensure_Content_Xml_Created()
{
diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs
index c9cf4e6d5b..522ecc4a8c 100644
--- a/src/Umbraco.Tests/Services/MemberServiceTests.cs
+++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs
@@ -185,6 +185,18 @@ namespace Umbraco.Tests.Services
Assert.AreEqual(2, membersInRole.Count());
}
+ [Test]
+ public void Cannot_Save_Member_With_Empty_Name()
+ {
+ IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
+ ServiceContext.MemberTypeService.Save(memberType);
+ IMember member = MockedMember.CreateSimpleMember(memberType, string.Empty, "test@test.com", "pass", "test");
+
+ // Act & Assert
+ Assert.Throws(() => ServiceContext.MemberService.Save(member));
+
+ }
+
[TestCase("MyTestRole1", "test1", StringPropertyMatchType.StartsWith, 1)]
[TestCase("MyTestRole1", "test", StringPropertyMatchType.StartsWith, 3)]
[TestCase("MyTestRole1", "test1", StringPropertyMatchType.Exact, 1)]
diff --git a/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs b/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs
index c81d8c3f4e..8dd18b8b50 100644
--- a/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs
+++ b/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs
@@ -173,6 +173,16 @@ namespace Umbraco.Tests.Services
}
}
+ [Test]
+ public void Cannot_Save_MemberType_With_Empty_Name()
+ {
+ // Arrange
+ IMemberType memberType = MockedContentTypes.CreateSimpleMemberType("memberTypeAlias", string.Empty);
+
+ // Act & Assert
+ Assert.Throws(() => ServiceContext.MemberTypeService.Save(memberType));
+ }
+
//[Test]
//public void Can_Save_MemberType_Structure_And_Create_A_Member_Based_On_It()
//{
diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs
index c7002ce79d..833ed1771b 100644
--- a/src/Umbraco.Tests/Services/UserServiceTests.cs
+++ b/src/Umbraco.Tests/Services/UserServiceTests.cs
@@ -490,6 +490,43 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(result4.AllowedSections.Contains("test"));
}
+ [Test]
+ public void Cannot_Create_User_With_Empty_Username()
+ {
+ // Arrange
+ var userService = ServiceContext.UserService;
+ var userType = userService.GetUserTypeByAlias("admin");
+
+ // Act & Assert
+ Assert.Throws(() => userService.CreateUserWithIdentity(string.Empty, "john@umbraco.io", userType));
+ }
+
+ [Test]
+ public void Cannot_Save_User_With_Empty_Username()
+ {
+ // Arrange
+ var userService = ServiceContext.UserService;
+ var userType = userService.GetUserTypeByAlias("admin");
+ var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io", userType);
+ user.Username = string.Empty;
+
+ // Act & Assert
+ Assert.Throws(() => userService.Save(user));
+ }
+
+ [Test]
+ public void Cannot_Save_User_With_Empty_Name()
+ {
+ // Arrange
+ var userService = ServiceContext.UserService;
+ var userType = userService.GetUserTypeByAlias("admin");
+ var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io", userType);
+ user.Name = string.Empty;
+
+ // Act & Assert
+ Assert.Throws(() => userService.Save(user));
+ }
+
[Test]
public void Get_By_Profile_Username()
{
diff --git a/src/Umbraco.Tests/UdiTests.cs b/src/Umbraco.Tests/UdiTests.cs
new file mode 100644
index 0000000000..36242bab13
--- /dev/null
+++ b/src/Umbraco.Tests/UdiTests.cs
@@ -0,0 +1,155 @@
+using System;
+using System.Linq;
+using Newtonsoft.Json;
+using NUnit.Framework;
+using Umbraco.Core;
+using Umbraco.Core.Serialization;
+
+namespace Umbraco.Tests
+{
+ [TestFixture]
+ public class UdiTests
+ {
+ [Test]
+ public void StringEntityCtorTest()
+ {
+ var udi = new StringUdi(Constants.DeployEntityType.AnyString, "test-id");
+ Assert.AreEqual(Constants.DeployEntityType.AnyString, udi.EntityType);
+ Assert.AreEqual("test-id", udi.Id);
+ Assert.AreEqual("umb://" + Constants.DeployEntityType.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);
+ 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());
+ }
+
+ [Test]
+ public void GuidEntityCtorTest()
+ {
+ var guid = Guid.NewGuid();
+ var udi = new GuidUdi(Constants.DeployEntityType.AnyGuid, guid);
+ Assert.AreEqual(Constants.DeployEntityType.AnyGuid, udi.EntityType);
+ Assert.AreEqual(guid, udi.Guid);
+ Assert.AreEqual("umb://" + Constants.DeployEntityType.AnyGuid + "/" + guid.ToString("N"), udi.ToString());
+ }
+
+ [Test]
+ public void GuidEntityParseTest()
+ {
+ var guid = Guid.NewGuid();
+ var s = "umb://" + Constants.DeployEntityType.AnyGuid + "/" + guid.ToString("N");
+ var udi = Udi.Parse(s);
+ Assert.AreEqual(Constants.DeployEntityType.AnyGuid, udi.EntityType);
+ Assert.IsInstanceOf(udi);
+ var gudi = udi as GuidUdi;
+ Assert.IsNotNull(gudi);
+ Assert.AreEqual(guid, gudi.Guid);
+ Assert.AreEqual(s, udi.ToString());
+ }
+
+ [Test]
+ public void EqualityTest()
+ {
+ var guid1 = Guid.NewGuid();
+ var guid2 = Guid.NewGuid();
+
+ Assert.IsTrue(new GuidUdi("type", guid1).Equals(new GuidUdi("type", guid1)));
+ Assert.IsTrue(new GuidUdi("type", guid1) == new GuidUdi("type", guid1));
+
+ Assert.IsTrue(((Udi)new GuidUdi("type", guid1)).Equals((Udi)new GuidUdi("type", guid1)));
+ Assert.IsTrue((Udi)new GuidUdi("type", guid1) == (Udi)new GuidUdi("type", guid1));
+
+ Assert.IsFalse(new GuidUdi("type", guid1).Equals(new GuidUdi("typex", guid1)));
+ Assert.IsFalse(new GuidUdi("type", guid1) == new GuidUdi("typex", guid1));
+ Assert.IsFalse(new GuidUdi("type", guid1).Equals(new GuidUdi("type", guid2)));
+ Assert.IsFalse(new GuidUdi("type", guid1) == new GuidUdi("type", guid2));
+
+ Assert.IsTrue(new GuidUdi("type", guid1).ToString() == new StringUdi("type", guid1.ToString("N")).ToString());
+ Assert.IsFalse(new GuidUdi("type", guid1) == new StringUdi("type", guid1.ToString("N")));
+ }
+
+ [Test]
+ public void DistinctTest()
+ {
+ 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),
+ };
+ Assert.AreEqual(1, entities.Distinct().Count());
+ }
+
+ [Test]
+ public void CreateTest()
+ {
+ var guid = Guid.NewGuid();
+ var udi = Udi.Create(Constants.DeployEntityType.AnyGuid, guid);
+ Assert.AreEqual(Constants.DeployEntityType.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("barf", "foo"));
+ }
+
+ [Test]
+ public void RangeTest()
+ {
+ // can parse open string udi
+ var stringUdiString = "umb://" + Constants.DeployEntityType.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;
+ Udi guidUdi;
+ Assert.IsTrue(Udi.TryParse(guidUdiString, out guidUdi));
+ Assert.AreEqual(Guid.Empty, ((GuidUdi)guidUdi).Guid);
+
+ // can create a range
+ var range = new UdiRange(stringUdi, Constants.DeploySelector.ChildrenOfThis);
+
+ // cannot create invalid ranges
+ Assert.Throws(() => new UdiRange(guidUdi, "x"));
+ }
+
+ [Test]
+ public void SerializationTest()
+ {
+ var settings = new JsonSerializerSettings
+ {
+ Converters = new JsonConverter[] { new UdiJsonConverter(), new UdiRangeJsonConverter() }
+ };
+
+
+ var guid = Guid.NewGuid();
+ var udi = new GuidUdi(Constants.DeployEntityType.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(guid, ((GuidUdi)dudi).Guid);
+
+ var range = new UdiRange(udi, Constants.DeploySelector.ChildrenOfThis);
+ json = JsonConvert.SerializeObject(range, settings);
+ Assert.AreEqual(string.Format("\"umb://any-guid/{0:N}?children\"", guid), json);
+
+ var drange = JsonConvert.DeserializeObject(json, settings);
+ Assert.AreEqual(udi, drange.Udi);
+ Assert.AreEqual(string.Format("umb://any-guid/{0:N}", guid), drange.Udi.UriValue.ToString());
+ Assert.AreEqual(Constants.DeploySelector.ChildrenOfThis, drange.Selector);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index 1f5e324142..9bb433f59b 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -58,8 +58,9 @@
..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll
-
- ..\packages\Examine.0.1.70.0\lib\Examine.dll
+
+ ..\packages\Examine.0.1.80\lib\net45\Examine.dll
+ True..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll
@@ -170,6 +171,9 @@
+
+
+
diff --git a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs
index 3e7377f3b6..34c7a1f6ac 100644
--- a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs
+++ b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs
@@ -1,6 +1,8 @@
using System;
using System.Linq;
using Examine;
+using Lucene.Net.Analysis.Standard;
+using Lucene.Net.Index;
using Lucene.Net.Store;
using NUnit.Framework;
using Umbraco.Tests.TestHelpers;
@@ -15,53 +17,39 @@ namespace Umbraco.Tests.UmbracoExamine
[Test]
public void Events_Ignoring_Node()
{
- //change the parent id so that they are all ignored
- var existingCriteria = _indexer.IndexerData;
- _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
- 999); //change to 999
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
+ {
+ //change the parent id so that they are all ignored
+ var existingCriteria = indexer.IndexerData;
+ indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
+ 999); //change to 999
- var isIgnored = false;
+ var isIgnored = false;
- EventHandler ignoringNode = (s, e) =>
- {
- isIgnored = true;
- };
+ EventHandler ignoringNode = (s, e) =>
+ {
+ isIgnored = true;
+ };
- _indexer.IgnoringNode += ignoringNode;
+ indexer.IgnoringNode += ignoringNode;
- //get a node from the data repo
- var node = _contentService.GetPublishedContentByXPath("//*[string-length(@id)>0 and number(@id)>0]")
- .Root
- .Elements()
- .First();
+ //get a node from the data repo
+ var node = _contentService.GetPublishedContentByXPath("//*[string-length(@id)>0 and number(@id)>0]")
+ .Root
+ .Elements()
+ .First();
- _indexer.ReIndexNode(node, IndexTypes.Content);
+ indexer.ReIndexNode(node, IndexTypes.Content);
- Assert.IsTrue(isIgnored);
-
+ Assert.IsTrue(isIgnored);
+ }
}
private readonly TestContentService _contentService = new TestContentService();
- private static UmbracoExamineSearcher _searcher;
- private static UmbracoContentIndexer _indexer;
- private Lucene.Net.Store.Directory _luceneDir;
-
- public override void Initialize()
- {
- base.Initialize();
-
- _luceneDir = new RAMDirectory();
- _indexer = IndexInitializer.GetUmbracoIndexer(_luceneDir);
- _indexer.RebuildIndex();
- _searcher = IndexInitializer.GetUmbracoSearcher(_luceneDir);
- }
-
- public override void TearDown()
- {
- base.TearDown();
- _luceneDir.Dispose();
- }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs
index 00e94ced63..c7922b9d58 100644
--- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs
+++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs
@@ -7,6 +7,7 @@ using Examine.LuceneEngine.Config;
using Examine.LuceneEngine.Providers;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
+using Lucene.Net.Index;
using Lucene.Net.Store;
using Moq;
using Umbraco.Core;
@@ -33,7 +34,7 @@ namespace Umbraco.Tests.UmbracoExamine
internal static class IndexInitializer
{
public static UmbracoContentIndexer GetUmbracoIndexer(
- Directory luceneDir,
+ IndexWriter writer,
Analyzer analyzer = null,
IDataService dataService = null,
IContentService contentService = null,
@@ -41,7 +42,8 @@ namespace Umbraco.Tests.UmbracoExamine
IDataTypeService dataTypeService = null,
IMemberService memberService = null,
IUserService userService = null,
- IContentTypeService contentTypeService = null)
+ IContentTypeService contentTypeService = null,
+ bool supportUnpublishedContent = false)
{
if (dataService == null)
{
@@ -177,15 +179,17 @@ namespace Umbraco.Tests.UmbracoExamine
var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies);
var i = new UmbracoContentIndexer(indexCriteria,
- luceneDir, //custom lucene directory
- dataService,
- contentService,
- mediaService,
- dataTypeService,
- userService,
- contentTypeService,
- analyzer,
- false);
+ writer,
+ dataService,
+ contentService,
+ mediaService,
+ dataTypeService,
+ userService,
+ contentTypeService,
+ false)
+ {
+ SupportUnpublishedContent = supportUnpublishedContent
+ };
//i.IndexSecondsInterval = 1;
@@ -193,13 +197,14 @@ namespace Umbraco.Tests.UmbracoExamine
return i;
}
- public static UmbracoExamineSearcher GetUmbracoSearcher(Directory luceneDir, Analyzer analyzer = null)
+
+ public static UmbracoExamineSearcher GetUmbracoSearcher(IndexWriter writer, Analyzer analyzer = null)
{
if (analyzer == null)
{
analyzer = new StandardAnalyzer(Version.LUCENE_29);
}
- return new UmbracoExamineSearcher(luceneDir, analyzer);
+ return new UmbracoExamineSearcher(writer, analyzer);
}
public static LuceneSearcher GetLuceneSearcher(Directory luceneDir)
diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs
index 5d6812f94a..3f324bb2ed 100644
--- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs
+++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs
@@ -1,11 +1,11 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
using Examine;
using Examine.LuceneEngine;
using Examine.LuceneEngine.Providers;
using Examine.LuceneEngine.SearchCriteria;
using Examine.SearchCriteria;
+using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
@@ -15,218 +15,230 @@ using UmbracoExamine;
namespace Umbraco.Tests.UmbracoExamine
{
-
///
/// Tests the standard indexing capabilities
///
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
[TestFixture, RequiresSTA]
public class IndexTest : ExamineBaseTest
- {
+ {
+ ///
+ /// Check that the node signalled as protected in the content service is not present in the index.
+ ///
+ [Test]
+ public void Index_Protected_Content_Not_Indexed()
+ {
- /////
- ///
- /// Check that the node signalled as protected in the content service is not present in the index.
- ///
- [Test]
- public void Index_Protected_Content_Not_Indexed()
- {
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
+ {
+ indexer.RebuildIndex();
- var protectedQuery = new BooleanQuery();
- protectedQuery.Add(
- new BooleanClause(
- new TermQuery(new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content)),
- BooleanClause.Occur.MUST));
+ var protectedQuery = new BooleanQuery();
+ protectedQuery.Add(
+ new BooleanClause(
+ new TermQuery(new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content)),
+ BooleanClause.Occur.MUST));
- protectedQuery.Add(
- new BooleanClause(
- new TermQuery(new Term(LuceneIndexer.IndexNodeIdFieldName, TestContentService.ProtectedNode.ToString())),
- BooleanClause.Occur.MUST));
+ protectedQuery.Add(
+ new BooleanClause(
+ new TermQuery(new Term(LuceneIndexer.IndexNodeIdFieldName, TestContentService.ProtectedNode.ToString())),
+ BooleanClause.Occur.MUST));
- var collector = new AllHitsCollector(false, true);
- var s = _searcher.GetSearcher();
- s.Search(protectedQuery, collector);
+ var collector = new AllHitsCollector(false, true);
+ var s = searcher.GetSearcher();
+ s.Search(protectedQuery, collector);
- Assert.AreEqual(0, collector.Count, "Protected node should not be indexed");
+ Assert.AreEqual(0, collector.Count, "Protected node should not be indexed");
+ }
- }
+ }
- [Test]
- public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID()
- {
- //change parent id to 1116
- var existingCriteria = _indexer.IndexerData;
- _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
- 1116);
-
- //rebuild so it excludes children unless they are under 1116
- _indexer.RebuildIndex();
+ [Test]
+ public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID()
+ {
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
+ {
+ indexer.RebuildIndex();
- //ensure that node 2112 doesn't exist
- var results = _searcher.Search(_searcher.CreateSearchCriteria().Id(2112).Compile());
- Assert.AreEqual(0, results.Count());
+ var mediaService = new TestMediaService();
- //get a node from the data repo (this one exists underneath 2222)
- var node = _mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]")
- .Root
- .Elements()
- .Where(x => (int)x.Attribute("id") == 2112)
- .First();
+ //change parent id to 1116
+ var existingCriteria = indexer.IndexerData;
+ indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
+ 1116);
- var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112
- Assert.AreEqual("-1,1111,2222,2112", currPath);
+ //rebuild so it excludes children unless they are under 1116
+ indexer.RebuildIndex();
- //now mimic moving 2112 to 1116
- //node.SetAttributeValue("path", currPath.Replace("2222", "1116"));
- node.SetAttributeValue("path", "-1,1116,2112");
- node.SetAttributeValue("parentID", "1116");
+ //ensure that node 2112 doesn't exist
+ var results = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile());
+ Assert.AreEqual(0, results.TotalItemCount);
- //now reindex the node, this should first delete it and then WILL add it because of the parent id constraint
- _indexer.ReIndexNode(node, IndexTypes.Media);
+ //get a node from the data repo (this one exists underneath 2222)
+ var node = mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]")
+ .Root
+ .Elements()
+ .First(x => (int)x.Attribute("id") == 2112);
- //RESET the parent id
- existingCriteria = ((IndexCriteria)_indexer.IndexerData);
- _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
- null);
+ var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112
+ Assert.AreEqual("-1,1111,2222,2112", currPath);
- //now ensure it's deleted
- var newResults = _searcher.Search(_searcher.CreateSearchCriteria().Id(2112).Compile());
- Assert.AreEqual(1, newResults.Count());
- }
+ //now mimic moving 2112 to 1116
+ //node.SetAttributeValue("path", currPath.Replace("2222", "1116"));
+ node.SetAttributeValue("path", "-1,1116,2112");
+ node.SetAttributeValue("parentID", "1116");
- [Test]
- [Ignore]
- public void Index_Move_Media_To_Non_Indexable_ParentID()
- {
- //get a node from the data repo (this one exists underneath 2222)
- var node = _mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]")
- .Root
- .Elements()
- .Where(x => (int)x.Attribute("id") == 2112)
- .First();
+ //now reindex the node, this should first delete it and then WILL add it because of the parent id constraint
+ indexer.ReIndexNode(node, IndexTypes.Media);
- var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112
- Assert.AreEqual("-1,1111,2222,2112", currPath);
+ //RESET the parent id
+ existingCriteria = ((IndexCriteria)indexer.IndexerData);
+ indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
+ null);
- //ensure it's indexed
- _indexer.ReIndexNode(node, IndexTypes.Media);
+ //now ensure it's deleted
+ var newResults = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile());
+ Assert.AreEqual(1, newResults.TotalItemCount);
+ }
- //change the parent node id to be the one it used to exist under
- var existingCriteria = _indexer.IndexerData;
- _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
- 2222);
+
+ }
- //now mimic moving the node underneath 1116 instead of 2222
- node.SetAttributeValue("path", currPath.Replace("2222", "1116"));
- node.SetAttributeValue("parentID", "1116");
+ [Test]
+ public void Index_Move_Media_To_Non_Indexable_ParentID()
+ {
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
+ {
+ indexer.RebuildIndex();
- //now reindex the node, this should first delete it and then NOT add it because of the parent id constraint
- _indexer.ReIndexNode(node, IndexTypes.Media);
+ var mediaService = new TestMediaService();
- //RESET the parent id
- existingCriteria = ((IndexCriteria)_indexer.IndexerData);
- _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
- null);
+ //get a node from the data repo (this one exists underneath 2222)
+ var node = mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]")
+ .Root
+ .Elements()
+ .First(x => (int)x.Attribute("id") == 2112);
- //now ensure it's deleted
- var results = _searcher.Search(_searcher.CreateSearchCriteria().Id(2112).Compile());
- Assert.AreEqual(0, results.Count());
+ var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112
+ Assert.AreEqual("-1,1111,2222,2112", currPath);
- }
+ //ensure it's indexed
+ indexer.ReIndexNode(node, IndexTypes.Media);
+
+ //change the parent node id to be the one it used to exist under
+ var existingCriteria = indexer.IndexerData;
+ indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
+ 2222);
+
+ //now mimic moving the node underneath 1116 instead of 2222
+ node.SetAttributeValue("path", currPath.Replace("2222", "1116"));
+ node.SetAttributeValue("parentID", "1116");
+
+ //now reindex the node, this should first delete it and then NOT add it because of the parent id constraint
+ indexer.ReIndexNode(node, IndexTypes.Media);
+
+ //RESET the parent id
+ existingCriteria = ((IndexCriteria)indexer.IndexerData);
+ indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
+ null);
+
+ //now ensure it's deleted
+ var results = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile());
+ Assert.AreEqual(0, results.TotalItemCount);
+ }
+ }
- ///
- /// This will ensure that all 'Content' (not media) is cleared from the index using the Lucene API directly.
- /// We then call the Examine method to re-index Content and do some comparisons to ensure that it worked correctly.
- ///
- [Test]
- public void Index_Reindex_Content()
- {
- var s = (IndexSearcher)_searcher.GetSearcher();
+ ///
+ /// This will ensure that all 'Content' (not media) is cleared from the index using the Lucene API directly.
+ /// We then call the Examine method to re-index Content and do some comparisons to ensure that it worked correctly.
+ ///
+ [Test]
+ public void Index_Reindex_Content()
+ {
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer, supportUnpublishedContent: true))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
+ {
+ indexer.RebuildIndex();
- //first delete all 'Content' (not media). This is done by directly manipulating the index with the Lucene API, not examine!
-
- var contentTerm = new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content);
- var writer = _indexer.GetIndexWriter();
- writer.DeleteDocuments(contentTerm);
- writer.Commit();
-
+ var s = (IndexSearcher)searcher.GetSearcher();
- //make sure the content is gone. This is done with lucene APIs, not examine!
- var collector = new AllHitsCollector(false, true);
- var query = new TermQuery(contentTerm);
- s = (IndexSearcher)_searcher.GetSearcher(); //make sure the searcher is up do date.
- s.Search(query, collector);
- Assert.AreEqual(0, collector.Count);
+ //first delete all 'Content' (not media). This is done by directly manipulating the index with the Lucene API, not examine!
- //call our indexing methods
- _indexer.IndexAll(IndexTypes.Content);
+ var contentTerm = new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content);
+ writer.DeleteDocuments(contentTerm);
+ writer.Commit();
- collector = new AllHitsCollector(false, true);
- s = (IndexSearcher)_searcher.GetSearcher(); //make sure the searcher is up do date.
- s.Search(query, collector);
- //var ids = new List();
- //for (var i = 0; i < collector.Count;i++)
- //{
- // ids.Add(s.Doc(collector.GetDocId(i)).GetValues("__NodeId")[0]);
- //}
- Assert.AreEqual(20, collector.Count);
- }
+ //make sure the content is gone. This is done with lucene APIs, not examine!
+ var collector = new AllHitsCollector(false, true);
+ var query = new TermQuery(contentTerm);
+ s = (IndexSearcher)searcher.GetSearcher(); //make sure the searcher is up do date.
+ s.Search(query, collector);
+ Assert.AreEqual(0, collector.Count);
- ///
- /// This will delete an item from the index and ensure that all children of the node are deleted too!
- ///
- [Test]
- [Ignore]
- public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed()
- {
+ //call our indexing methods
+ indexer.IndexAll(IndexTypes.Content);
- //now delete a node that has children
+ collector = new AllHitsCollector(false, true);
+ s = (IndexSearcher)searcher.GetSearcher(); //make sure the searcher is up do date.
+ s.Search(query, collector);
+ //var ids = new List();
+ //for (var i = 0; i < collector.Count;i++)
+ //{
+ // ids.Add(s.Doc(collector.GetDocId(i)).GetValues("__NodeId")[0]);
+ //}
+ Assert.AreEqual(21, collector.Count);
+ }
+ }
- _indexer.DeleteFromIndex(1140.ToString());
- //this node had children: 1141 & 1142, let's ensure they are also removed
+ ///
+ /// This will delete an item from the index and ensure that all children of the node are deleted too!
+ ///
+ [Test]
+ public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed()
+ {
- var results = _searcher.Search(_searcher.CreateSearchCriteria().Id(1141).Compile());
- Assert.AreEqual(0, results.Count());
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
+ {
+ indexer.RebuildIndex();
- results = _searcher.Search(_searcher.CreateSearchCriteria().Id(1142).Compile());
- Assert.AreEqual(0, results.Count());
+ //now delete a node that has children
- }
+ indexer.DeleteFromIndex(1140.ToString());
+ //this node had children: 1141 & 1142, let's ensure they are also removed
- #region Private methods and properties
+ var results = searcher.Search(searcher.CreateSearchCriteria().Id(1141).Compile());
+ Assert.AreEqual(0, results.TotalItemCount);
- private readonly TestContentService _contentService = new TestContentService();
- private readonly TestMediaService _mediaService = new TestMediaService();
-
- private static UmbracoExamineSearcher _searcher;
- private static UmbracoContentIndexer _indexer;
-
- #endregion
-
- #region Initialize and Cleanup
-
- private Lucene.Net.Store.Directory _luceneDir;
-
- public override void TearDown()
- {
- base.TearDown();
- _luceneDir.Dispose();
+ results = searcher.Search(searcher.CreateSearchCriteria().Id(1142).Compile());
+ Assert.AreEqual(0, results.TotalItemCount);
+ }
+ }
+
+ #region Initialize and Cleanup
+
+ public override void TearDown()
+ {
+ base.TearDown();
+
UmbracoExamineSearcher.DisableInitializationCheck = null;
BaseUmbracoIndexer.DisableInitializationCheck = null;
}
-
- public override void Initialize()
- {
- base.Initialize();
- _luceneDir = new RAMDirectory();
- _indexer = IndexInitializer.GetUmbracoIndexer(_luceneDir);
- _indexer.RebuildIndex();
- _searcher = IndexInitializer.GetUmbracoSearcher(_luceneDir);
- }
-
-
- #endregion
- }
+ #endregion
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/UmbracoExamine/RandomIdRAMDirectory.cs b/src/Umbraco.Tests/UmbracoExamine/RandomIdRAMDirectory.cs
new file mode 100644
index 0000000000..34b69274c8
--- /dev/null
+++ b/src/Umbraco.Tests/UmbracoExamine/RandomIdRAMDirectory.cs
@@ -0,0 +1,14 @@
+using System;
+using Lucene.Net.Store;
+
+namespace Umbraco.Tests.UmbracoExamine
+{
+ public class RandomIdRAMDirectory : RAMDirectory
+ {
+ private readonly string _lockId = Guid.NewGuid().ToString();
+ public override string GetLockID()
+ {
+ return _lockId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs
index 9b8b3d50d7..f739286f98 100644
--- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs
+++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs
@@ -8,6 +8,8 @@ using Examine.LuceneEngine.Providers;
using Lucene.Net.Store;
using NUnit.Framework;
using Examine.LuceneEngine.SearchCriteria;
+using Lucene.Net.Analysis.Standard;
+using Lucene.Net.Index;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.UmbracoExamine
@@ -20,19 +22,17 @@ namespace Umbraco.Tests.UmbracoExamine
[Test]
public void Test_Sort_Order_Sorting()
{
- //var newIndexFolder = new DirectoryInfo(Path.Combine("App_Data\\SearchTests", Guid.NewGuid().ToString()));
- //System.IO.Directory.CreateDirectory(newIndexFolder.FullName);
-
- using (var luceneDir = new RAMDirectory())
- //using (var luceneDir = new SimpleFSDirectory(newIndexFolder))
- {
- var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir, null,
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
+ using (var indexer = IndexInitializer.GetUmbracoIndexer(writer, null,
new TestDataService()
- {
- ContentService = new TestContentService(TestFiles.umbraco_sort)
- });
- indexer.RebuildIndex();
- var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
+ {
+ ContentService = new TestContentService(TestFiles.umbraco_sort)
+ },
+ supportUnpublishedContent: true))
+ using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
+ {
+ indexer.RebuildIndex();
var s = (LuceneSearcher)searcher;
var luceneSearcher = s.GetSearcher();
@@ -69,25 +69,7 @@ namespace Umbraco.Tests.UmbracoExamine
currentSort = sort;
}
return true;
- }
-
- //[Test]
- //public void Test_Index_Type_With_German_Analyzer()
- //{
- // using (var luceneDir = new RAMDirectory())
- // {
- // var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir,
- // new GermanAnalyzer());
- // indexer.RebuildIndex();
- // var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
- // }
- //}
-
- //private readonly TestContentService _contentService = new TestContentService();
- //private readonly TestMediaService _mediaService = new TestMediaService();
- //private static UmbracoExamineSearcher _searcher;
- //private static UmbracoContentIndexer _indexer;
- //private Lucene.Net.Store.Directory _luceneDir;
+ }
}
}
diff --git a/src/Umbraco.Tests/UmbracoExamine/UmbracoContentIndexerTests.cs b/src/Umbraco.Tests/UmbracoExamine/UmbracoContentIndexerTests.cs
new file mode 100644
index 0000000000..3278130022
--- /dev/null
+++ b/src/Umbraco.Tests/UmbracoExamine/UmbracoContentIndexerTests.cs
@@ -0,0 +1,107 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core;
+using Umbraco.Core.Models;
+using UmbracoExamine;
+
+namespace Umbraco.Tests.UmbracoExamine
+{
+ [TestFixture]
+ public class UmbracoContentIndexerTests : ExamineBaseTest
+ {
+ [Test]
+ public void Get_Serialized_Content_No_Published_Content()
+ {
+ var contentSet = new List
+ {
+ Mock.Of(c => c.Id == 1 && c.Path == "-1,1" && c.Published && c.Level == 1),
+ Mock.Of(c => c.Id == 2 && c.Path == "-1,2" && c.Published && c.Level == 1),
+ Mock.Of(c => c.Id == 3 && c.Path == "-1,3" && c.Published == false && c.Level == 1), // no
+ Mock.Of(c => c.Id == 4 && c.Path == "-1,4" && c.Published == false && c.Level == 1), // no
+
+ Mock.Of(c => c.Id == 5 && c.Path == "-1,1,5" && c.Published && c.Level == 2),
+ Mock.Of(c => c.Id == 6 && c.Path == "-1,2,6" && c.Published == false && c.Level == 2), // no
+ Mock.Of(c => c.Id == 7 && c.Path == "-1,3,7" && c.Published && c.Level == 2), // no
+ Mock.Of(c => c.Id == 8 && c.Path == "-1,4,8" && c.Published && c.Level == 2), // no
+ Mock.Of(c => c.Id == 9 && c.Path == "-1,4,9" && c.Published && c.Level == 2), // no
+
+ Mock.Of(c => c.Id == 10 && c.Path == "-1,1,5,10" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 15 && c.Path == "-1,1,5,15" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 11 && c.Path == "-1,2,6,11" && c.Published && c.Level == 3), // no
+ Mock.Of(c => c.Id == 16 && c.Path == "-1,2,6,16" && c.Published && c.Level == 3), // no
+ Mock.Of(c => c.Id == 12 && c.Path == "-1,3,7,12" && c.Published && c.Level == 3), // no
+ Mock.Of(c => c.Id == 17 && c.Path == "-1,3,7,17" && c.Published && c.Level == 3), // no
+ Mock.Of(c => c.Id == 13 && c.Path == "-1,4,8,13" && c.Published && c.Level == 3), // no
+ Mock.Of(c => c.Id == 18 && c.Path == "-1,4,8,18" && c.Published && c.Level == 3), // no
+ Mock.Of(c => c.Id == 14 && c.Path == "-1,4,9,14" && c.Published && c.Level == 3), // no
+ Mock.Of(c => c.Id == 19 && c.Path == "-1,4,9,19" && c.Published && c.Level == 3), // no
+ };
+
+ //ensure the rest of the required values are populted
+ foreach (var content in contentSet)
+ {
+ var mock = Mock.Get(content);
+ mock.Setup(x => x.ContentType).Returns(Mock.Of(type => type.Icon == "hello"));
+ }
+
+ contentSet.Sort((a, b) => Comparer.Default.Compare(a.Level, b.Level));
+
+ var published = new HashSet();
+
+ var result = UmbracoContentIndexer.GetSerializedContent(false, content => new XElement("test"), contentSet, published)
+ .WhereNotNull()
+ .ToArray();
+
+ Assert.AreEqual(5, result.Length);
+ }
+
+ [Test]
+ public void Get_Serialized_Content_With_Published_Content()
+ {
+ var contentSet = new List
+ {
+ Mock.Of(c => c.Id == 1 && c.Path == "-1,1" && c.Published && c.Level == 1),
+ Mock.Of(c => c.Id == 2 && c.Path == "-1,2" && c.Published && c.Level == 1),
+ Mock.Of(c => c.Id == 3 && c.Path == "-1,3" && c.Published == false && c.Level == 1),
+ Mock.Of(c => c.Id == 4 && c.Path == "-1,4" && c.Published == false && c.Level == 1),
+
+ Mock.Of(c => c.Id == 5 && c.Path == "-1,1,5" && c.Published && c.Level == 2),
+ Mock.Of(c => c.Id == 6 && c.Path == "-1,2,6" && c.Published == false && c.Level == 2),
+ Mock.Of(c => c.Id == 7 && c.Path == "-1,3,7" && c.Published && c.Level == 2),
+ Mock.Of(c => c.Id == 8 && c.Path == "-1,4,8" && c.Published && c.Level == 2),
+ Mock.Of(c => c.Id == 9 && c.Path == "-1,4,9" && c.Published && c.Level == 2),
+
+ Mock.Of(c => c.Id == 10 && c.Path == "-1,1,5,10" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 15 && c.Path == "-1,1,5,15" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 11 && c.Path == "-1,2,6,11" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 16 && c.Path == "-1,2,6,16" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 12 && c.Path == "-1,3,7,12" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 17 && c.Path == "-1,3,7,17" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 13 && c.Path == "-1,4,8,13" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 18 && c.Path == "-1,4,8,18" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 14 && c.Path == "-1,4,9,14" && c.Published && c.Level == 3),
+ Mock.Of(c => c.Id == 19 && c.Path == "-1,4,9,19" && c.Published && c.Level == 3),
+ };
+
+ //ensure the rest of the required values are populted
+ foreach (var content in contentSet)
+ {
+ var mock = Mock.Get(content);
+ mock.Setup(x => x.ContentType).Returns(Mock.Of(type => type.Icon == "hello"));
+ }
+
+ contentSet.Sort((a, b) => Comparer.Default.Compare(a.Level, b.Level));
+
+ var published = new HashSet();
+
+ var result = UmbracoContentIndexer.GetSerializedContent(true, content => new XElement("test"), contentSet, published)
+ .WhereNotNull()
+ .ToArray();
+
+ Assert.AreEqual(19, result.Length);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config
index 6428eebadf..b71ba7a170 100644
--- a/src/Umbraco.Tests/packages.config
+++ b/src/Umbraco.Tests/packages.config
@@ -2,7 +2,7 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js
index 890acf3d6f..50c4775ef9 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js
@@ -43,7 +43,8 @@
templateUrl: "views/components/tabs/umb-tabs-nav.html",
scope: {
model: "=",
- tabdrop: "="
+ tabdrop: "=",
+ idSuffix: "@"
},
link: link
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblaunchminieditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblaunchminieditor.directive.js
index 91212a82f8..11b934ce96 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblaunchminieditor.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblaunchminieditor.directive.js
@@ -7,7 +7,7 @@
* Used on a button to launch a mini content editor editor dialog
**/
angular.module("umbraco.directives")
- .directive('umbLaunchMiniEditor', function (dialogService, editorState, fileManager, contentEditingHelper) {
+ .directive('umbLaunchMiniEditor', function (miniEditorHelper) {
return {
restrict: 'A',
replace: false,
@@ -16,69 +16,8 @@ angular.module("umbraco.directives")
},
link: function(scope, element, attrs) {
- var launched = false;
-
element.click(function() {
-
- if (launched === true) {
- return;
- }
-
- launched = true;
-
- //We need to store the current files selected in the file manager locally because the fileManager
- // is a singleton and is shared globally. The mini dialog will also be referencing the fileManager
- // and we don't want it to be sharing the same files as the main editor. So we'll store the current files locally here,
- // clear them out and then launch the dialog. When the dialog closes, we'll reset the fileManager to it's previous state.
- var currFiles = _.groupBy(fileManager.getFiles(), "alias");
- fileManager.clearFiles();
-
- //We need to store the original editorState entity because it will need to change when the mini editor is loaded so that
- // any property editors that are working with editorState get given the correct entity, otherwise strange things will
- // start happening.
- var currEditorState = editorState.getCurrent();
-
- dialogService.open({
- template: "views/common/dialogs/content/edit.html",
- id: scope.node.id,
- closeOnSave: true,
- tabFilter: ["Generic properties"],
- callback: function (data) {
-
- //set the node name back
- scope.node.name = data.name;
-
- //reset the fileManager to what it was
- fileManager.clearFiles();
- _.each(currFiles, function (val, key) {
- fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; }));
- });
-
- //reset the editor state
- editorState.set(currEditorState);
-
- //Now we need to check if the content item that was edited was actually the same content item
- // as the main content editor and if so, update all property data
- if (data.id === currEditorState.id) {
- var changed = contentEditingHelper.reBindChangedProperties(currEditorState, data);
- }
-
- launched = false;
- },
- closeCallback: function () {
- //reset the fileManager to what it was
- fileManager.clearFiles();
- _.each(currFiles, function (val, key) {
- fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; }));
- });
-
- //reset the editor state
- editorState.set(currEditorState);
-
- launched = false;
- }
- });
-
+ miniEditorHelper.launchMiniEditor(scope.node);
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js
new file mode 100644
index 0000000000..77f6f06a30
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js
@@ -0,0 +1,122 @@
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbNodePreview
+@restrict E
+@scope
+
+@description
+Added in Umbraco v. 7.6: Use this directive to render a node preview.
+
+
Markup example
+
+
+
+
+
+
+
+
+
+
+
+
Controller example
+
+ (function () {
+ "use strict";
+
+ function Controller() {
+
+ var vm = this;
+
+ vm.allowRemove = true;
+ vm.allowOpen = true;
+ vm.sortable = true;
+
+ vm.nodes = [
+ {
+ "icon": "icon-document",
+ "name": "My node 1",
+ "published": true,
+ "description": "A short description of my node"
+ },
+ {
+ "icon": "icon-document",
+ "name": "My node 2",
+ "published": true,
+ "description": "A short description of my node"
+ }
+ ];
+
+ vm.remove = remove;
+ vm.open = open;
+
+ function remove(index, nodes) {
+ alert("remove node");
+ }
+
+ function open(node) {
+ alert("open node");
+ }
+
+ }
+
+ angular.module("umbraco").controller("My.NodePreviewController", Controller);
+
+ })();
+
+
+@param {string} icon (binding): The node icon.
+@param {string} name (binding): The node name.
+@param {boolean} published (binding): The node pusblished state.
+@param {string} description (binding): A short description.
+@param {boolean} sortable (binding): Will add a move cursor on the node preview. Can used in combination with ui-sortable.
+@param {boolean} allowRemove (binding): Show/Hide the remove button.
+@param {boolean} allowOpen (binding): Show/Hide the open button.
+@param {function} onRemove (expression): Callback function when the remove button is clicked.
+@param {function} onOpen (expression): Callback function when the open button is clicked.
+**/
+
+(function () {
+ 'use strict';
+
+ function NodePreviewDirective() {
+
+ function link(scope, el, attr, ctrl) {
+
+ }
+
+ var directive = {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/umb-node-preview.html',
+ scope: {
+ icon: "=?",
+ name: "=",
+ description: "=?",
+ published: "=?",
+ sortable: "=?",
+ allowOpen: "=?",
+ allowRemove: "=?",
+ onOpen: "&?",
+ onRemove: "&?"
+ },
+ link: link
+ };
+
+ return directive;
+
+ }
+
+ angular.module('umbraco.directives').directive('umbNodePreview', NodePreviewDirective);
+
+})();
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js
index fc60781375..8c03679183 100644
--- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js
+++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js
@@ -33,6 +33,15 @@ angular.module('umbraco.mocks').
return [200, nodes, null];
}
+ function returnEntityUrl() {
+
+ if (!mocksUtils.checkAuth()) {
+ return [401, null, null];
+ }
+
+ return [200, "url", null];
+
+ }
return {
register: function () {
@@ -48,6 +57,10 @@ angular.module('umbraco.mocks').
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetById?'))
.respond(returnEntitybyId);
+
+ $httpBackend
+ .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetUrl?'))
+ .respond(returnEntityUrl);
}
};
}]);
\ 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 914b601249..5e0f5deada 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
@@ -56,7 +56,7 @@ function entityResource($q, $http, umbRequestHelper) {
*
* ##usage
*
- * entityResource.getPath(id)
+ * entityResource.getPath(id, type)
* .then(function(pathArray) {
* alert('its here!');
* });
@@ -77,6 +77,37 @@ function entityResource($q, $http, umbRequestHelper) {
'Failed to retrieve path for id:' + id);
},
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.entityResource#getUrl
+ * @methodOf umbraco.resources.entityResource
+ *
+ * @description
+ * Returns a url, given a node ID and type
+ *
+ * ##usage
+ *
- Renders the contents of a child template, by inserting a
- @RenderBody() placeholder.
+
-
+
-
Render a named section
+
- Renders a named area of a child template, by insert a @RenderSection(name) placeholder.
- This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition.
+
-
+
-
- Set the name of the section to render in this area of the template
-
+
- If mandatory, the child template must contain a @section definition, otherwise an error is shown.
+
-
+
-
Define a named section
+
- Defines a part of your template as a named section by wrapping it in
- a @section { ... }. This can be rendered in a
- specific area of the master of this template, by using @RenderSection.
+
-
+
-
- Give the section a name
-
-
-
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html
index 8522bc3b6a..808822e138 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html
@@ -1,5 +1,5 @@
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js
index e34bc48ecd..9ce2506e76 100644
--- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js
@@ -34,18 +34,24 @@
fields: {},
file: file
}).progress(function (evt) {
+
+ // hack: in some browsers the progress event is called after success
+ // this prevents the UI from going back to a uploading state
+ if(vm.zipFile.uploadStatus !== "done" && vm.zipFile.uploadStatus !== "error") {
- // set view state to uploading
- vm.state = 'uploading';
+ // set view state to uploading
+ vm.state = 'uploading';
- // calculate progress in percentage
- var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10);
+ // calculate progress in percentage
+ var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10);
- // set percentage property on file
- vm.zipFile.uploadProgress = progressPercentage;
+ // set percentage property on file
+ vm.zipFile.uploadProgress = progressPercentage;
- // set uploading status on file
- vm.zipFile.uploadStatus = "uploading";
+ // set uploading status on file
+ vm.zipFile.uploadStatus = "uploading";
+
+ }
}).success(function (data, status, headers, config) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html
index 5da3c33a76..a41a7e72c0 100644
--- a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html
+++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html
@@ -12,8 +12,8 @@
-
-
+
+
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 667dfd0f22..947603794a 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
@@ -1,7 +1,7 @@
//this controller simply tells the dialogs service to open a mediaPicker window
//with a specified callback, this callback will receive an object with a selection on it
-function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location) {
+function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper) {
function trim(str, chr) {
var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
@@ -39,6 +39,9 @@ function contentPickerController($scope, dialogService, entityResource, editorSt
else {
$scope.contentPickerForm.maxCount.$setValidity("maxCount", true);
}
+
+ setSortingState($scope.renderModel);
+
});
}
@@ -59,6 +62,14 @@ function contentPickerController($scope, dialogService, entityResource, editorSt
}
};
+ // sortable options
+ $scope.sortableOptions = {
+ distance: 10,
+ tolerance: "pointer",
+ scroll: true,
+ zIndex: 6000
+ };
+
if ($scope.model.config) {
//merge the server config on top of the default config, then set the server config to use the result
$scope.model.config = angular.extend(defaultConfig, $scope.model.config);
@@ -75,8 +86,9 @@ function contentPickerController($scope, dialogService, entityResource, editorSt
: $scope.model.config.startNode.type === "media"
? "Media"
: "Document";
- $scope.allowOpenButton = entityType === "Document" || entityType === "Media";
+ $scope.allowOpenButton = entityType === "Document";
$scope.allowEditButton = entityType === "Document";
+ $scope.allowRemoveButton = true;
//the dialog options for the picker
var dialogOptions = {
@@ -192,14 +204,26 @@ function contentPickerController($scope, dialogService, entityResource, editorSt
});
if (currIds.indexOf(item.id) < 0) {
- item.icon = iconHelper.convertFromLegacyIcon(item.icon);
- $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, path: item.path });
+ setEntityUrl(item);
}
};
$scope.clear = function () {
$scope.renderModel = [];
};
+
+ $scope.openMiniEditor = function(node) {
+ miniEditorHelper.launchMiniEditor(node).then(function(updatedNode){
+ // update the node
+ node.name = updatedNode.name;
+ node.published = updatedNode.hasPublishedVersion;
+ if(entityType !== "Member") {
+ entityResource.getUrl(updatedNode.id, entityType).then(function(data){
+ node.url = data;
+ });
+ }
+ });
+ };
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
var currIds = _.map($scope.renderModel, function (i) {
@@ -215,26 +239,89 @@ function contentPickerController($scope, dialogService, entityResource, editorSt
//load current data
var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
+
entityResource.getByIds(modelIds, entityType).then(function (data) {
- //Ensure we populate the render model in the same order that the ids were stored!
_.each(modelIds, function (id, i) {
var entity = _.find(data, function (d) {
return d.id == id;
});
-
+
if (entity) {
- entity.icon = iconHelper.convertFromLegacyIcon(entity.icon);
- $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: entity.path });
+ setEntityUrl(entity);
}
-
- });
+ });
//everything is loaded, start the watch on the model
startWatch();
});
+
+ function setEntityUrl(entity) {
+
+ // get url for content and media items
+ if(entityType !== "Member") {
+ entityResource.getUrl(entity.id, entityType).then(function(data){
+ // update url
+ angular.forEach($scope.renderModel, function(item){
+ if(item.id === entity.id) {
+ item.url = data;
+ }
+ });
+ });
+ }
+
+ // add the selected item to the renderModel
+ // if it needs to show a url the item will get
+ // updated when the url comes back from server
+ addSelectedItem(entity);
+
+ }
+
+ function addSelectedItem(item) {
+
+ // set icon
+ if(item.icon) {
+ item.icon = iconHelper.convertFromLegacyIcon(item.icon);
+ }
+
+ // set default icon
+ if (!item.icon) {
+ switch (entityType) {
+ case "Document":
+ item.icon = "icon-document";
+ break;
+ case "Media":
+ item.icon = "icon-picture";
+ break;
+ case "Member":
+ item.icon = "icon-user";
+ break;
+ }
+ }
+
+ $scope.renderModel.push({
+ "name": item.name,
+ "id": item.id,
+ "icon": item.icon,
+ "path": item.path,
+ "url": item.url,
+ "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true
+ // only content supports published/unpublished content so we set everything else to published so the UI looks correct
+ });
+
+ }
+
+ function setSortingState(items) {
+ // disable sorting if the list only consist of one item
+ if(items.length > 1) {
+ $scope.sortableOptions.disabled = false;
+ } else {
+ $scope.sortableOptions.disabled = true;
+ }
+ }
+
}
angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController);
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html
index 9f5f3fb60f..900cf6b416 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html
@@ -2,32 +2,28 @@
-
diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js
index e6d1312109..d5e3a7a496 100644
--- a/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js
+++ b/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js
@@ -17,7 +17,11 @@ describe('Content picker controller tests', function () {
value:"1233,1231,23121",
label: "My content picker",
description: "desc",
- config: {}
+ config: {
+ startNode: {
+ type: "content"
+ }
+ }
};
//this controller requires an angular form controller applied to it
@@ -47,6 +51,12 @@ describe('Content picker controller tests', function () {
}));
describe('content edit controller save and publish', function () {
+
+ var item = {
+ name: "meh",
+ id: 666,
+ icon: "woop"
+ };
it('should define the default properties on construction', function () {
expect(scope.model.value).toNotBe(undefined);
@@ -65,7 +75,6 @@ describe('Content picker controller tests', function () {
});
it("Removing an item should update renderModel, ids and model.value", function(){
-
scope.remove(1);
scope.$apply();
expect(scope.renderModel.length).toBe(2);
@@ -73,24 +82,29 @@ describe('Content picker controller tests', function () {
});
it("Adding an item should update renderModel, ids and model.value", function(){
-
- scope.add({name: "meh", id: 666, icon: "woop"});
+ scope.add(item);
scope.$apply();
- expect(scope.renderModel.length).toBe(4);
- expect(scope.model.value).toBe("1233,1231,23121,666");
+ setTimeout(function(){
+ expect(scope.renderModel.length).toBe(4);
+ expect(scope.model.value).toBe("1233,1231,23121,666");
+ }, 1000);
});
- it("Adding a dublicate item should note update renderModel, ids and model.value", function(){
-
- scope.add({ name: "meh", id: 666, icon: "woop" });
+ it("Adding a duplicate item should note update renderModel, ids and model.value", function(){
+ scope.add(item);
scope.$apply();
- expect(scope.renderModel.length).toBe(4);
- expect(scope.model.value).toBe("1233,1231,23121,666");
+ setTimeout(function(){
+ expect(scope.renderModel.length).toBe(4);
+ expect(scope.model.value).toBe("1233,1231,23121,666");
+ }, 1000);
- scope.add({ name: "meh 2", id: 666, icon: "woop 2" });
+ scope.add(item);
scope.$apply();
- expect(scope.renderModel.length).toBe(4);
- expect(scope.model.value).toBe("1233,1231,23121,666");
+ setTimeout(function(){
+ expect(scope.renderModel.length).toBe(4);
+ expect(scope.model.value).toBe("1233,1231,23121,666");
+ }, 1000);
+
});
});
});
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index 2c4cd4859d..540f008fc8 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -127,8 +127,8 @@
False..\packages\dotless.1.4.1.0\lib\dotless.Core.dll
-
- ..\packages\Examine.0.1.70.0\lib\Examine.dll
+
+ ..\packages\Examine.0.1.80\lib\net45\Examine.dllTrue
@@ -138,8 +138,9 @@
..\packages\ImageProcessor.2.5.1\lib\net45\ImageProcessor.dll
-
- ..\packages\ImageProcessor.Web.4.7.2\lib\net45\ImageProcessor.Web.dll
+
+ ..\packages\ImageProcessor.Web.4.8.0\lib\net45\ImageProcessor.Web.dll
+ TrueFalse
diff --git a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx
index 59a96e9a37..7728d281c3 100644
--- a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx
+++ b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx
@@ -13,6 +13,7 @@
+
diff --git a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs
index 19ac019a8f..628e82a767 100644
--- a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs
+++ b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs
@@ -48,6 +48,15 @@ namespace Umbraco.Web.UI.Umbraco.Dashboard {
///
protected global::ClientDependency.Core.Controls.JsInclude JsInclude3;
+ ///
+ /// JsInclude4 control.
+ ///
+ ///
+ /// Auto-generated field.
+ /// To modify move field declaration from designer file to code-behind file.
+ ///
+ protected global::ClientDependency.Core.Controls.JsInclude JsInclude4;
+
///
/// JsInclude6 control.
///
diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config
index 6919e10f64..629438762f 100644
--- a/src/Umbraco.Web.UI/packages.config
+++ b/src/Umbraco.Web.UI/packages.config
@@ -4,9 +4,9 @@
-
+
-
+
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
index b0452fb631..fb032a7f22 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
@@ -291,6 +291,7 @@
Vælg medlemVælg medlemsgruppeDer er ingen parametre for denne makro
+ Der er ikke tilføjet nogen makroerLink ditFjern link fra ditkonto
@@ -1004,17 +1005,117 @@ Mange hilsner fra Umbraco robotten
Vis prøveStyles
+
Rediger skabelon
+
+ SektionerIndsæt indholdsområde
- Indsæt indholdsområdemarkering
- Indsæt ordbogselement
- Indsæt makro
- Indsæt Umbraco sidefelt
+ Indsæt pladsholder for indholdsområde
+
+ Indsæt
+ Hvad vil du indsætte ?
+
+ Oversættelse
+ Indsætter en oversætbar tekst, som skifter efter det sprog, som websitet vises i.
+
+ Makro
+
+ En makro er et element, som kan have forskellige indstillinger, når det indsættes.
+ Brug det som en genbrugelig del af dit design såsom gallerier, formularer og lister.
+
+
+ Sideværdi
+
+ Viser værdien af et felt fra den nuværende side. Kan indstilles til at bruge rekursive værdier eller
+ vise en standardværdi i tilfælde af, at feltet er tomt.
+
+
+ Partial view
+
+ Et Partial View er et skabelonelement, som kan indsættes i andre skabeloner og derved
+ genbruges og deles på tværs af sideskabelonerne.
+
+
Master skabelon
- Lynguide til Umbracos skabelontags
+ Ingen masterskabelon
+ Ingen master
+
+ Indsæt en underliggende skabelon
+
+ @RenderBody() element.
+ ]]>
+
+
+
+ Definer en sektion
+
+ @section { ... }. Herefter kan denne sektion flettes ind i
+ overliggende skabelon ved at indsætte et @RenderSection element.
+ ]]>
+
+
+ Indsæt en sektion
+
+ @RenderSection(name) element. Den underliggende skabelon skal have
+ defineret en sektion via et @section [name]{ ... } element.
+ ]]>
+
+
+ Sektionsnavn
+ Sektionen er obligatorisk
+
+
+ Hvis obligatorisk, skal underskabelonen indeholde en @section -definition.
+
+
+
+ Query builder
+ sider returneret, på
+
+ Returner
+ alt indhold
+ indhold af typen "%0%"
+
+ fra
+ mit website
+ hvor
+ og
+
+ er
+ ikke er
+ er før
+ er før (inkl. valgte dato)
+ er efter
+ er efter (inkl. valgte dato)
+ er
+ ikke er
+ indeholder
+ ikke indeholder
+ er større end
+ er større end eller det samme som
+ er mindre end
+ er mindre end eller det samme som
+
+ Id
+ Navn
+ Oprettelsesdato
+ Sidste opdatering
+
+ Sortér efter
+ stigende rækkefølge
+ faldende rækkefølge
+
Skabelon
+
+
Alternativt feltAlternativ tekst
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
index 2021be1d9d..444ecc2e3d 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
@@ -305,6 +305,7 @@
Select member groupNo icons were foundThere are no parameters for this macro
+ There are no macros available to insertExternal login providersException DetailsStacktrace
@@ -1032,6 +1033,9 @@ To manage your website, simply open the Umbraco back office and start adding con
SectionsInsert content areaInsert content area placeholder
+
+ Insert
+ Choose what to insert into your templateDictionary itemA dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites.
@@ -1053,18 +1057,79 @@ To manage your website, simply open the Umbraco back office and start adding con
Master template
+ No master template
+ No master
+
+ Render child template
+
+ @RenderBody() placeholder.
+ ]]>
+
+
+
+ Define a named section
+
+ @section { ... }. This can be rendered in a
+ specific area of the parent of this template, by using @RenderSection.
+ ]]>
+
+
+ Render a named section
+
+ @RenderSection(name) placeholder.
+ This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition.
+ ]]>
+
+
+ Section Name
+ Section is mandatory
+
+ If mandatory, the child template must contain a @section definition, otherwise an error is shown.
+
+
Query builderitems returned, in
- I want
+ I want
+ all content
+ content of type "%0%"from
+ my websitewhereand
- everything
+ is
+ is not
+ before
+ before (including selected date)
+ after
+ after (including selected date)
+ equals
+ does not equal
+ contains
+ does not contain
+ greater than
+ greater than or equal to
+ less than
+ less than or equal to
+
+ Id
+ Name
+ Created Date
+ Last Updated Date
+
+ order by
+ ascending
+ descendingTemplate
+
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
index 173da6cc9b..aa9f33695d 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
@@ -1027,13 +1027,105 @@ To manage your website, simply open the Umbraco back office and start adding con
Edit template
+
+ SectionsInsert content areaInsert content area placeholder
- Insert dictionary item
- Insert Macro
- Insert Umbraco page field
+
+ Insert
+ Choose what to insert into your template
+
+ Dictionary item
+ A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites.
+
+ Macro
+
+ A Macro is a configurable component which is great for
+ reusable parts of your design, where you need the option to provide parameters,
+ such as galleries, forms and lists.
+
+
+ Value
+ Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values.
+
+ Partial view
+
+ A partial view is a separate template file which can be rendered inside another
+ template, it's great for reusing markup or for separating complex templates into separate files.
+
+
Master template
- Quick Guide to Umbraco template tags
+ No master template
+ No master
+
+ Render child template
+
+ @RenderBody() placeholder.
+ ]]>
+
+
+
+ Define a named section
+
+ @section { ... }. This can be rendered in a
+ specific area of the parent of this template, by using @RenderSection.
+ ]]>
+
+
+ Render a named section
+
+ @RenderSection(name) placeholder.
+ This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition.
+ ]]>
+
+
+ Section Name
+ Section is mandatory
+
+ If mandatory, the child template must contain a @section definition, otherwise an error is shown.
+
+
+
+ Query builder
+ items returned, in
+
+ I want
+ all content
+ content of type "%0%"
+ from
+ my website
+ where
+ and
+
+ is
+ is not
+ before
+ before (including selected date)
+ after
+ after (including selected date)
+ equals
+ does not equal
+ contains
+ does not contain
+ greater than
+ greater than or equal to
+ less than
+ less than or equal to
+
+ Id
+ Name
+ Created Date
+ Last Updated Date
+
+ order by
+ ascending
+ descending
+
Template
@@ -1142,7 +1234,7 @@ To manage your website, simply open the Umbraco back office and start adding con
Building models
- this can take abit of time, don't worry
+ this can take a bit of time, don't worryModels generatedModels could not be generatedModels generation has failed, see exception in U log
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml
index 255bf62d5d..c6f94949d2 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml
@@ -27,6 +27,9 @@
ОпубликоватьОбновить узлыОпубликовать весь сайт
+ Установить разрешения для страницы '%0%'
+ Выберите, куда переместить
+ В структуре документов нижеВосстановитьРазрешенияОткатить
@@ -189,7 +192,7 @@
Роль участникаТип участникаДата не указана
- Заголовок страницы
+ Заголовок ссылкиНе является членом групп(ы)СвойстваЭтот документ опубликован, но скрыт, потому что его родительский документ '%0%' не опубликован
@@ -341,9 +344,10 @@
Просмотр элемента кэшаСоздать папку...Связать с оригиналом
+ Включая все дочерниеСамое дружелюбное сообществоСсылка на страницу
- Открывает документ по ссылке в новом окне или вкладке браузера
+ Открывать ссылку в новом окне или вкладке браузераСсылка на медиа-файлВыбрать медиаВыбрать значок
diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs
index 246571d479..1340545621 100644
--- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs
+++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs
@@ -131,9 +131,7 @@ namespace Umbraco.Web.Cache
ClearAllIsolatedCacheByEntityType();
ClearAllIsolatedCacheByEntityType();
ClearAllIsolatedCacheByEntityType();
-
- //all property type cache
- ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.PropertyTypeCacheKey);
+
//all content type property cache
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.ContentTypePropertiesCacheKey);
//all content type cache
@@ -266,12 +264,6 @@ namespace Umbraco.Web.Cache
///
private static void ClearContentTypeCache(JsonPayload payload)
{
- //clears the cache for each property type associated with the content type
- foreach (var pid in payload.PropertyTypeIds)
- {
- ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.PropertyTypeCacheKey + pid);
- }
-
//clears the cache associated with the Content type itself
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(string.Format("{0}{1}", CacheKeys.ContentTypeCacheKey, payload.Id));
//clears the cache associated with the content type properties collection
diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs
index 376606ff5c..d7b2cc3994 100644
--- a/src/Umbraco.Web/Editors/BackOfficeController.cs
+++ b/src/Umbraco.Web/Editors/BackOfficeController.cs
@@ -262,7 +262,7 @@ namespace Umbraco.Web.Editors
},
{
"mediaTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl(
- controller => controller.GetAllowedChildren(0))
+ controller => controller.GetAllowedChildren("0"))
},
{
"macroApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl(
diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs
index 596e27e3a5..104c451273 100644
--- a/src/Umbraco.Web/Editors/EntityController.cs
+++ b/src/Umbraco.Web/Editors/EntityController.cs
@@ -17,6 +17,7 @@ using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using System.Linq;
+using System.Net.Http;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models;
using Umbraco.Web.WebApi.Filters;
@@ -144,6 +145,47 @@ namespace Umbraco.Web.Editors
return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse);
}
+
+
+ ///
+ /// Gets the url of an entity
+ ///
+ /// Int id of the entity to fetch URL for
+ /// The tpye of entity such as Document, Media, Member
+ /// The URL or path to the item
+ public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type)
+ {
+ var returnUrl = string.Empty;
+
+ if (type == UmbracoEntityTypes.Document)
+ {
+ var foundUrl = Umbraco.Url(id);
+ if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#")
+ {
+ returnUrl = foundUrl;
+
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(returnUrl)
+ };
+ }
+ }
+
+ var ancestors = GetAncestors(id, type);
+
+ //if content, skip the first node for replicating NiceUrl defaults
+ if(type == UmbracoEntityTypes.Document) {
+ ancestors = ancestors.Skip(1);
+ }
+
+ returnUrl = "/" + string.Join("/", ancestors.Select(x => x.Name));
+
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(returnUrl)
+ };
+ }
+
///
/// Gets an entity by it's unique id if the entity supports that
///
@@ -614,7 +656,7 @@ namespace Umbraco.Web.Editors
.Select(Mapper.Map);
// entities are in "some" order, put them back in order
- var xref = entities.ToDictionary(x => x.Id);
+ var xref = entities.ToDictionary(x => x.Key);
var result = keysArray.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null);
return result;
diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs
index fb17d9462e..2ed01c0192 100644
--- a/src/Umbraco.Web/Editors/MediaController.cs
+++ b/src/Umbraco.Web/Editors/MediaController.cs
@@ -36,6 +36,7 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Persistence.FaultHandling;
using Umbraco.Web.UI;
using Notification = Umbraco.Web.Models.ContentEditing.Notification;
+using Umbraco.Core.Persistence;
namespace Umbraco.Web.Editors
{
@@ -175,7 +176,37 @@ namespace Umbraco.Web.Editors
/// Returns the child media objects
///
[FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")]
- public PagedResult> GetChildren(int id,
+ public PagedResult> GetChildren(string id,
+ int pageNumber = 0,
+ int pageSize = 0,
+ string orderBy = "SortOrder",
+ Direction orderDirection = Direction.Ascending,
+ bool orderBySystemField = true,
+ string filter = "")
+ {
+ int idInt; Guid idGuid;
+
+ if (Guid.TryParse(id, out idGuid))
+ {
+ var entity = Services.EntityService.GetByKey(idGuid);
+ if (entity != null)
+ {
+ return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
+ }
+ else
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+ }
+ else if (int.TryParse(id, out idInt))
+ {
+ return GetChildren(idInt, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
+ }
+
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ private PagedResult> GetChildren(int id,
int pageNumber = 0,
int pageSize = 0,
string orderBy = "SortOrder",
@@ -448,12 +479,37 @@ namespace Umbraco.Web.Editors
}
//get the string json from the request
- int parentId;
- if (int.TryParse(result.FormData["currentFolder"], out parentId) == false)
+ int parentId; bool entityFound;
+ string currentFolderId = result.FormData["currentFolder"];
+ if (int.TryParse(currentFolderId, out parentId) == false)
{
- return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer");
+ // if a guid then try to look up the entity
+ Guid idGuid;
+ if (Guid.TryParse(currentFolderId, out idGuid))
+ {
+ var entity = Services.EntityService.GetByKey(idGuid);
+ if (entity != null)
+ {
+ entityFound = true;
+ parentId = entity.Id;
+ }
+ else
+ {
+ throw new EntityNotFoundException(currentFolderId, "The passed id doesn't exist");
+ }
+ }
+ else
+ {
+ return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer or Guid");
+ }
+
+ if (entityFound == false)
+ {
+ return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer or Guid");
+ }
}
+
//ensure the user has access to this folder by parent id!
if (CheckPermissions(
new Dictionary(),
diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs
index 0f83ad6d03..67b8ae6d1f 100644
--- a/src/Umbraco.Web/Editors/MediaTypeController.cs
+++ b/src/Umbraco.Web/Editors/MediaTypeController.cs
@@ -11,6 +11,8 @@ using System.Net;
using System.Net.Http;
using Umbraco.Web.WebApi;
using Umbraco.Core.Services;
+using Umbraco.Core.Models.EntityBase;
+using System;
namespace Umbraco.Web.Editors
{
@@ -174,7 +176,22 @@ namespace Umbraco.Web.Editors
///
///
[UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)]
- public IEnumerable GetAllowedChildren(int contentId)
+ public IEnumerable GetAllowedChildren(string contentId)
+ {
+ Guid idGuid = Guid.Empty;
+ int idInt;
+ if (Guid.TryParse(contentId, out idGuid)) {
+ var entity = ApplicationContext.Services.EntityService.GetByKey(idGuid);
+ return GetAllowedChildrenInternal(entity.Id);
+ } else if (int.TryParse(contentId, out idInt))
+ {
+ return GetAllowedChildrenInternal(idInt);
+ }
+
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ private IEnumerable GetAllowedChildrenInternal(int contentId)
{
if (contentId == Constants.System.RecycleBinContent)
return Enumerable.Empty();
diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs
index e3c63dd22e..cd004fe926 100644
--- a/src/Umbraco.Web/Editors/TemplateQueryController.cs
+++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs
@@ -9,6 +9,7 @@ using System;
using System.Diagnostics;
using Umbraco.Web.Dynamics;
using Umbraco.Web.Models.TemplateQuery;
+using Umbraco.Core.Services;
namespace Umbraco.Web.Editors
{
@@ -29,33 +30,44 @@ namespace Umbraco.Web.Editors
{ }
- private static readonly IEnumerable Terms = new List()
+ private IEnumerable Terms
+ {
+ get
{
- new OperathorTerm("is", Operathor.Equals, new [] {"string"}),
- new OperathorTerm("is not", Operathor.NotEquals, new [] {"string"}),
- new OperathorTerm("before", Operathor.LessThan, new [] {"datetime"}),
- new OperathorTerm("before (including selected date)", Operathor.LessThanEqualTo, new [] {"datetime"}),
- new OperathorTerm("after", Operathor.GreaterThan, new [] {"datetime"}),
- new OperathorTerm("after (including selected date)", Operathor.GreaterThanEqualTo, new [] {"datetime"}),
- new OperathorTerm("equals", Operathor.Equals, new [] {"int"}),
- new OperathorTerm("does not equal", Operathor.NotEquals, new [] {"int"}),
- new OperathorTerm("contains", Operathor.Contains, new [] {"string"}),
- new OperathorTerm("does not contain", Operathor.NotContains, new [] {"string"}),
- new OperathorTerm("greater than", Operathor.GreaterThan, new [] {"int"}),
- new OperathorTerm("greater than or equal to", Operathor.GreaterThanEqualTo, new [] {"int"}),
- new OperathorTerm("less than", Operathor.LessThan, new [] {"int"}),
- new OperathorTerm("less than or equal to", Operathor.LessThanEqualTo, new [] {"int"})
- };
+ return new List()
+ {
+ new OperathorTerm(Services.TextService.Localize("template/is"), Operathor.Equals, new [] {"string"}),
+ new OperathorTerm(Services.TextService.Localize("template/isNot"), Operathor.NotEquals, new [] {"string"}),
+ new OperathorTerm(Services.TextService.Localize("template/before"), Operathor.LessThan, new [] {"datetime"}),
+ new OperathorTerm(Services.TextService.Localize("template/beforeIncDate"), Operathor.LessThanEqualTo, new [] {"datetime"}),
+ new OperathorTerm(Services.TextService.Localize("template/after"), Operathor.GreaterThan, new [] {"datetime"}),
+ new OperathorTerm(Services.TextService.Localize("template/afterIncDate"), Operathor.GreaterThanEqualTo, new [] {"datetime"}),
+ new OperathorTerm(Services.TextService.Localize("template/equals"), Operathor.Equals, new [] {"int"}),
+ new OperathorTerm(Services.TextService.Localize("template/doesNotEqual"), Operathor.NotEquals, new [] {"int"}),
+ new OperathorTerm(Services.TextService.Localize("template/contains"), Operathor.Contains, new [] {"string"}),
+ new OperathorTerm(Services.TextService.Localize("template/doesNotContain"), Operathor.NotContains, new [] {"string"}),
+ new OperathorTerm(Services.TextService.Localize("template/greaterThan"), Operathor.GreaterThan, new [] {"int"}),
+ new OperathorTerm(Services.TextService.Localize("template/greaterThanEqual"), Operathor.GreaterThanEqualTo, new [] {"int"}),
+ new OperathorTerm(Services.TextService.Localize("template/lessThan"), Operathor.LessThan, new [] {"int"}),
+ new OperathorTerm(Services.TextService.Localize("template/lessThanEqual"), Operathor.LessThanEqualTo, new [] {"int"})
+ };
+ }
+ }
- private static readonly IEnumerable Properties = new List()
+ private IEnumerable Properties
+ {
+ get
{
- new PropertyModel() { Name = "Id", Alias = "Id", Type = "int" },
- new PropertyModel() { Name = "Name", Alias = "Name", Type = "string" },
- //new PropertyModel() { Name = "Url", Alias = "url", Type = "string" },
- new PropertyModel() { Name = "Created Date", Alias = "CreateDate", Type = "datetime" },
- new PropertyModel() { Name = "Last Updated Date", Alias = "UpdateDate", Type = "datetime" }
-
- };
+ return new List()
+ {
+ new PropertyModel() {Name = Services.TextService.Localize("template/id"), Alias = "Id", Type = "int"},
+ new PropertyModel() {Name = Services.TextService.Localize("template/name"), Alias = "Name", Type = "string"},
+ //new PropertyModel() { Name = "Url", Alias = "url", Type = "string" },
+ new PropertyModel() {Name = Services.TextService.Localize("template/createdDate"), Alias = "CreateDate", Type = "datetime"},
+ new PropertyModel() {Name = Services.TextService.Localize("template/lastUpdatedDate"), Alias = "UpdateDate", Type = "datetime"}
+ };
+ }
+ }
public QueryResultModel PostTemplateQuery(QueryModel model)
{
@@ -297,9 +309,10 @@ namespace Umbraco.Web.Editors
{
var contentTypes =
ApplicationContext.Services.ContentTypeService.GetAllContentTypes()
- .Select(x => new ContentTypeModel() { Alias = x.Alias, Name = x.Name })
+ .Select(x => new ContentTypeModel() { Alias = x.Alias, Name = Services.TextService.Localize("template/contentOfType", tokens: new string[] { x.Name } ) })
.OrderBy(x => x.Name).ToList();
- contentTypes.Insert(0, new ContentTypeModel() { Alias = string.Empty, Name = "Everything" });
+
+ contentTypes.Insert(0, new ContentTypeModel() { Alias = string.Empty, Name = Services.TextService.Localize("template/allContent") });
return contentTypes;
}
diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs
index 3062613b6b..2802e4fe80 100644
--- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs
+++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs
@@ -228,12 +228,13 @@ namespace Umbraco.Web
bool cacheBuster = true,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
- bool upScale = true)
+ bool upScale = true,
+ string backgroundColor = null)
{
return
new HtmlString(mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode,
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode,
- upScale));
+ upScale, backgroundColor));
}
[Obsolete("Use the UrlHelper.GetCropUrl extension instead")]
@@ -252,12 +253,13 @@ namespace Umbraco.Web
string cacheBusterValue = null,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
- bool upScale = true)
+ bool upScale = true,
+ string backgroundColor = null)
{
return
new HtmlString(imageUrl.GetCropUrl(width, height, imageCropperValue, cropAlias, quality, imageCropMode,
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode,
- upScale));
+ upScale, backgroundColor));
}
#endregion
diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs
index e518c5e246..08781076dc 100644
--- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs
+++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs
@@ -81,7 +81,7 @@ namespace Umbraco.Web
/// Use focal point, to generate an output image using the focal point instead of the predefined crop
///
///
- /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters>.
+ /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters.
///
///
/// Add a serialised date of the last edit of the item to ensure client cache refresh when updated
@@ -91,10 +91,13 @@ namespace Umbraco.Web
///
///
/// Use a dimension as a ratio
- ///
+ ///
///
/// If the image should be upscaled to requested dimensions
- ///
+ ///
+ ///
+ /// Changes the background color of the image. Used when adding a background when resizing image formats without an alpha channel.
+ ///
///
/// The .
///
@@ -112,7 +115,8 @@ namespace Umbraco.Web
bool cacheBuster = true,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
- bool upScale = true)
+ bool upScale = true,
+ string backgroundColor = null)
{
if (mediaItem == null) throw new ArgumentNullException("mediaItem");
@@ -132,7 +136,7 @@ namespace Umbraco.Web
mediaItemUrl = stronglyTyped.Src;
return GetCropUrl(
mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions,
- cacheBusterValue, furtherOptions, ratioMode, upScale);
+ cacheBusterValue, furtherOptions, ratioMode, upScale, backgroundColor);
}
//this shouldn't be the case but we'll check
@@ -143,14 +147,14 @@ namespace Umbraco.Web
mediaItemUrl = stronglyTyped.Src;
return GetCropUrl(
mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions,
- cacheBusterValue, furtherOptions, ratioMode, upScale);
+ cacheBusterValue, furtherOptions, ratioMode, upScale, backgroundColor);
}
//it's a single string
mediaItemUrl = cropperValue.ToString();
return GetCropUrl(
mediaItemUrl, width, height, mediaItemUrl, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions,
- cacheBusterValue, furtherOptions, ratioMode, upScale);
+ cacheBusterValue, furtherOptions, ratioMode, upScale, backgroundColor);
}
///
@@ -198,6 +202,9 @@ namespace Umbraco.Web
///
/// If the image should be upscaled to requested dimensions
///
+ ///
+ /// Changes the background color of the image. Used when adding a background when resizing image formats without an alpha channel.
+ ///
///
/// The .
///
@@ -215,7 +222,8 @@ namespace Umbraco.Web
string cacheBusterValue = null,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
- bool upScale = true)
+ bool upScale = true,
+ string backgroundColor = null)
{
if (string.IsNullOrEmpty(imageUrl)) return string.Empty;
@@ -226,7 +234,7 @@ namespace Umbraco.Web
}
return GetCropUrl(
imageUrl, cropDataSet, width, height, cropAlias, quality, imageCropMode,
- imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale);
+ imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale, backgroundColor);
}
public static string GetCropUrl(
@@ -243,7 +251,8 @@ namespace Umbraco.Web
string cacheBusterValue = null,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
- bool upScale = true)
+ bool upScale = true,
+ string backgroundColor = null)
{
if (string.IsNullOrEmpty(imageUrl) == false)
{
@@ -350,6 +359,11 @@ namespace Umbraco.Web
imageProcessorUrl.Append("&upscale=false");
}
+ if (backgroundColor != null)
+ {
+ imageProcessorUrl.Append("&bgcolor=" + backgroundColor);
+ }
+
if (furtherOptions != null)
{
imageProcessorUrl.Append(furtherOptions);
diff --git a/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs
index 8e813bd3d3..91c1aefdb0 100644
--- a/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs
+++ b/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs
@@ -36,6 +36,9 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "masterTemplateAlias")]
public string MasterTemplateAlias { get; set; }
+ [DataMember(Name = "isMasterTemplate")]
+ public bool IsMasterTemplate { get; set; }
+
///
/// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes.
///
diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs
index 89499b833e..7b30b1f4b3 100644
--- a/src/Umbraco.Web/Properties/AssemblyInfo.cs
+++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs
@@ -34,6 +34,9 @@ using System.Security;
[assembly: InternalsVisibleTo("Concorde.Sync")]
[assembly: InternalsVisibleTo("Umbraco.Courier.Core")]
[assembly: InternalsVisibleTo("Umbraco.Courier.Persistence")]
+[assembly: InternalsVisibleTo("Umbraco.Deploy")]
+[assembly: InternalsVisibleTo("Umbraco.Deploy.UI")]
+[assembly: InternalsVisibleTo("Umbraco.Deploy.Cloud")]
[assembly: InternalsVisibleTo("Umbraco.VisualStudio")]
[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder")]
[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.AspNet")]
diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs
index 4d58060164..be10cd83e2 100644
--- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs
@@ -33,17 +33,12 @@ namespace Umbraco.Web.PropertyEditors
internal class ContentPickerPreValueEditor : PreValueEditor
{
- [PreValueField("showOpenButton", "Show open button", "boolean")]
+ [PreValueField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = " Opens the node in a dialog")]
public string ShowOpenButton { get; set; }
-
- [PreValueField("showEditButton", "Show edit button (this feature is in preview!)", "boolean")]
- public string ShowEditButton { get; set; }
[PreValueField("startNodeId", "Start node", "treepicker")]
public int StartNodeId { get; set; }
- [PreValueField("showPathOnHover", "Show path when hovering items", "boolean")]
- public bool ShowPathOnHover { get; set; }
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
index b40cf3bbf3..af30b4ceeb 100644
--- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
@@ -31,6 +31,8 @@ namespace Umbraco.Web.PropertyEditors
{
try
{
+ //TODO: We should deserialize this to Umbraco.Core.Models.GridValue instead of doing the below
+
var json = JsonConvert.DeserializeObject(e.Fields[field.Name]);
//check if this is formatted for grid json
@@ -92,7 +94,12 @@ namespace Umbraco.Web.PropertyEditors
//swallow...on purpose, there's a chance that this isn't json and we don't want that to affect
// the website.
}
+ catch (ArgumentException)
+ {
+ //swallow on purpose to prevent this error:
+ // Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject.
+ }
}
}
}
diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs
index b3d3f02448..82daf80e34 100644
--- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs
@@ -45,16 +45,9 @@ namespace Umbraco.Web.PropertyEditors
[PreValueField("maxNumber", "Maximum number of items", "number")]
public string MaxNumber { get; set; }
-
- [PreValueField("showOpenButton", "Show open button", "boolean")]
+ [PreValueField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = " Opens the node in a dialog")]
public string ShowOpenButton { get; set; }
- [PreValueField("showEditButton", "Show edit button (this feature is in preview!)", "boolean")]
- public string ShowEditButton { get; set; }
-
- [PreValueField("showPathOnHover", "Show path when hovering items", "boolean")]
- public bool ShowPathOnHover { get; set; }
-
///
/// This ensures the multiPicker pre-val is set based on the maxNumber of nodes set
///
diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs
index 9f33a44ea9..9a5f90ff89 100644
--- a/src/Umbraco.Web/Trees/ContentTreeController.cs
+++ b/src/Umbraco.Web/Trees/ContentTreeController.cs
@@ -202,11 +202,13 @@ namespace Umbraco.Web.Trees
///
protected override bool HasPathAccess(string id, FormDataCollection queryStrings)
{
- var content = Services.ContentService.GetById(int.Parse(id));
- if (content == null)
+ var entity = GetEntityFromId(id);
+ if (entity == null)
{
return false;
}
+
+ IContent content = Services.ContentService.GetById(entity.Id);
return Security.CurrentUser.HasPathAccess(content);
}
diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
index 3708c6fd4f..027e4e488c 100644
--- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
+++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
@@ -96,7 +96,7 @@ namespace Umbraco.Web.Trees
LogHelper.Warn("The user " + Security.CurrentUser.Username + " does not have access to the tree node " + id);
return new TreeNodeCollection();
}
-
+
// So there's an alt id specified, it's not the root node and the user has access to it, great! But there's one thing we
// need to consider:
// If the tree is being rendered in a dialog view we want to render only the children of the specified id, but
@@ -110,7 +110,7 @@ namespace Umbraco.Web.Trees
id = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
}
}
-
+
var entities = GetChildEntities(id);
nodes.AddRange(entities.Select(entity => GetSingleTreeNode(entity, id, queryStrings)).Where(node => node != null));
return nodes;
@@ -122,11 +122,24 @@ namespace Umbraco.Web.Trees
protected IEnumerable GetChildEntities(string id)
{
+ // use helper method to ensure we support both integer and guid lookups
int iid;
- if (int.TryParse(id, out iid) == false)
+
+ // if it's the root node, we won't use the look up
+ if (id != "-1")
{
- throw new InvalidCastException("The id for the media tree must be an integer");
+ var idEntity = GetEntityFromId(id);
+ if (idEntity == null)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+ iid = idEntity.Id;
}
+ else
+ {
+ iid = int.Parse(id);
+ }
+
//if a request is made for the root node data but the user's start node is not the default, then
// we need to return their start node data
@@ -134,11 +147,12 @@ namespace Umbraco.Web.Trees
{
//just return their single start node, it will show up under the 'Content' label
var startNode = Services.EntityService.Get(UserStartNode, UmbracoObjectType);
- if (startNode == null)
+ if (startNode != null)
+ return new[] { startNode };
+ else
{
throw new EntityNotFoundException(UserStartNode, "User's start content node could not be found");
}
- return new[] { startNode };
}
return Services.EntityService.GetChildren(iid, UmbracoObjectType).ToArray();
@@ -176,9 +190,9 @@ namespace Umbraco.Web.Trees
{
id = altStartId;
}
-
+
var nodes = GetTreeNodesInternal(id, queryStrings);
-
+
//only render the recycle bin if we are not in dialog and the start id id still the root
if (IsDialog(queryStrings) == false && id == Constants.System.Root.ToInvariantString())
{
@@ -210,8 +224,9 @@ namespace Umbraco.Web.Trees
///
private TreeNodeCollection GetTreeNodesInternal(string id, FormDataCollection queryStrings)
{
+ IUmbracoEntity current = GetEntityFromId(id);
+
//before we get the children we need to see if this is a container node
- var current = Services.EntityService.Get(int.Parse(id), UmbracoObjectType);
//test if the parent is a listview / container
if (current != null && current.IsContainer())
@@ -286,5 +301,32 @@ namespace Umbraco.Web.Trees
{
return allowedUserOptions.Select(x => x.Action).OfType().Any();
}
+
+ ///
+ /// Get an entity via an id that can be either an integer or a Guid
+ ///
+ ///
+ ///
+ internal IUmbracoEntity GetEntityFromId(string id)
+ {
+ IUmbracoEntity entity;
+ Guid idGuid = Guid.Empty;
+ int idInt;
+ 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
+ {
+ return null;
+ }
+
+ return entity;
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 2160297c71..e4bb3c9e88 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -112,8 +112,9 @@
..\packages\dotless.1.4.1.0\lib\dotless.Core.dll
-
- ..\packages\Examine.0.1.70.0\lib\Examine.dll
+
+ ..\packages\Examine.0.1.80\lib\net45\Examine.dll
+ True..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll
diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs
index 629d69ed4a..76a000aded 100644
--- a/src/Umbraco.Web/UrlHelperRenderExtensions.cs
+++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs
@@ -114,6 +114,9 @@ namespace Umbraco.Web
/// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be
/// set to false if using the result of this method for CSS.
///
+ ///
+ /// Changes the background color of the image. Used when adding a background when resizing image formats without an alpha channel.
+ ///
///
/// The .
///
@@ -132,11 +135,12 @@ namespace Umbraco.Web
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true,
- bool htmlEncode = true)
+ bool htmlEncode = true,
+ string backgroundColor = null)
{
var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode,
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode,
- upScale);
+ upScale, backgroundColor);
return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url);
}
@@ -190,6 +194,9 @@ namespace Umbraco.Web
/// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be
/// set to false if using the result of this method for CSS.
///
+ ///
+ /// Changes the background color of the image. Used when adding a background when resizing image formats without an alpha channel.
+ ///
///
/// The .
///
@@ -208,11 +215,12 @@ namespace Umbraco.Web
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true,
- bool htmlEncode = true)
+ bool htmlEncode = true,
+ string backgroundColor = null)
{
var url = imageUrl.GetCropUrl(width, height, imageCropperValue, cropAlias, quality, imageCropMode,
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode,
- upScale);
+ upScale, backgroundColor);
return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url);
}
@@ -333,7 +341,5 @@ namespace Umbraco.Web
{
return url.SurfaceAction(action, typeof (T), additionalRouteVals);
}
-
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs
index 9c50468465..79e7fe8f53 100644
--- a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs
+++ b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs
@@ -166,6 +166,8 @@ namespace Umbraco.Web.WebServices
var msg = ValidateLuceneIndexer(indexerName, out indexer);
if (msg.IsSuccessStatusCode)
{
+ LogHelper.Info(string.Format("Rebuilding index '{0}'", indexerName));
+
//remove it in case there's a handler there alraedy
indexer.IndexOperationComplete -= Indexer_IndexOperationComplete;
//now add a single handler
@@ -201,6 +203,8 @@ namespace Umbraco.Web.WebServices
//ensure it's not listening anymore
indexer.IndexOperationComplete -= Indexer_IndexOperationComplete;
+ LogHelper.Info(string.Format("Rebuilding index '{0}' done, {1} items committed (can differ from the number of items in the index)", indexer.Name, indexer.CommitCount));
+
var cacheKey = "temp_indexing_op_" + indexer.Name;
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(cacheKey);
}
@@ -266,7 +270,10 @@ namespace Umbraco.Web.WebServices
var val = p.GetValue(indexer, null);
if (val == null)
{
- LogHelper.Warn("Property value was null when setting up property on indexer: " + indexer.Name + " property: " + p.Name);
+ // Do not warn for new new attribute that is optional
+ if(string.Equals(p.Name, "DirectoryFactory", StringComparison.InvariantCultureIgnoreCase) == false)
+ LogHelper.Warn("Property value was null when setting up property on indexer: " + indexer.Name + " property: " + p.Name);
+
val = string.Empty;
}
indexerModel.ProviderProperties.Add(p.Name, val.ToString());
diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config
index 520a6e4f76..1382e9ceea 100644
--- a/src/Umbraco.Web/packages.config
+++ b/src/Umbraco.Web/packages.config
@@ -3,7 +3,7 @@
-
+
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControl.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControl.cs
index 5a8b0b616e..c912db701d 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControl.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControl.cs
@@ -2,6 +2,7 @@ using System;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Collections;
+using System.ComponentModel;
using System.IO;
using Umbraco.Core.IO;
using System.Linq;
@@ -11,6 +12,8 @@ namespace umbraco.controls
///
/// Summary description for ContentTypeControl.
///
+ [Obsolete("No longer used, will be removed in v8")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public class ContentTypeControl : uicontrols.TabView
{
public event System.EventHandler OnSave;
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs
index 9734401d95..91a8677c81 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Configuration.Provider;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
@@ -320,13 +321,14 @@ namespace umbraco.cms.presentation.user
}
// Populate dropdowns
- foreach (DocumentType dt in DocumentType.GetAllAsList())
- cDocumentType.Items.Add(
- new ListItem(dt.Text, dt.Alias)
- );
+ var allContentTypes = Services.ContentTypeService.GetAllContentTypes().ToList();
+ foreach (var dt in allContentTypes)
+ {
+ cDocumentType.Items.Add(new ListItem(dt.Name, dt.Alias));
+ }
// populate fields
- ArrayList fields = new ArrayList();
+ var fields = new ArrayList();
cDescription.ID = "cDescription";
cCategories.ID = "cCategories";
cExcerpt.ID = "cExcerpt";
@@ -334,9 +336,9 @@ namespace umbraco.cms.presentation.user
cCategories.Items.Add(new ListItem(ui.Text("choose"), ""));
cExcerpt.Items.Add(new ListItem(ui.Text("choose"), ""));
- foreach (PropertyType pt in PropertyType.GetAll())
+ foreach (var pt in allContentTypes.SelectMany(x => x.PropertyTypes).OrderBy(x => x.Name))
{
- if (!fields.Contains(pt.Alias))
+ if (fields.Contains(pt.Alias) == false)
{
cDescription.Items.Add(new ListItem(string.Format("{0} ({1})", pt.Name, pt.Alias), pt.Alias));
cCategories.Items.Add(new ListItem(string.Format("{0} ({1})", pt.Name, pt.Alias), pt.Alias));
diff --git a/src/UmbracoExamine/BaseUmbracoIndexer.cs b/src/UmbracoExamine/BaseUmbracoIndexer.cs
index d05a47099d..1d87b0dfff 100644
--- a/src/UmbracoExamine/BaseUmbracoIndexer.cs
+++ b/src/UmbracoExamine/BaseUmbracoIndexer.cs
@@ -55,6 +55,7 @@ namespace UmbracoExamine
///
///
///
+ ///
protected BaseUmbracoIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async)
: base(indexerData, indexPath, analyzer, async)
{
@@ -67,6 +68,19 @@ namespace UmbracoExamine
DataService = dataService;
}
+ ///
+ /// Creates an NRT indexer
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected BaseUmbracoIndexer(IIndexCriteria indexerData, IndexWriter writer, IDataService dataService, bool async)
+ : base(indexerData, writer, async)
+ {
+ DataService = dataService;
+ }
+
#endregion
///
@@ -101,7 +115,7 @@ namespace UmbracoExamine
/// Determines if the manager will call the indexing methods when content is saved or deleted as
/// opposed to cache being updated.
///
- public bool SupportUnpublishedContent { get; protected set; }
+ public bool SupportUnpublishedContent { get; protected internal set; }
///
/// The data service used for retreiving and submitting data to the cms
diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs
index efc3e4a214..e7df641122 100644
--- a/src/UmbracoExamine/UmbracoContentIndexer.cs
+++ b/src/UmbracoExamine/UmbracoContentIndexer.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Xml;
using System.Xml.Linq;
using Examine;
using Lucene.Net.Documents;
@@ -15,6 +17,7 @@ using Examine.LuceneEngine;
using Examine.LuceneEngine.Config;
using UmbracoExamine.Config;
using Lucene.Net.Analysis;
+using Lucene.Net.Index;
using Umbraco.Core.Persistence.Querying;
using IContentService = Umbraco.Core.Services.IContentService;
using IMediaService = Umbraco.Core.Services.IMediaService;
@@ -144,6 +147,34 @@ namespace UmbracoExamine
_contentTypeService = contentTypeService;
}
+ ///
+ /// Creates an NRT indexer
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public UmbracoContentIndexer(IIndexCriteria indexerData, IndexWriter writer, IDataService dataService,
+ IContentService contentService,
+ IMediaService mediaService,
+ IDataTypeService dataTypeService,
+ IUserService userService,
+ IContentTypeService contentTypeService,
+ bool async)
+ : base(indexerData, writer, dataService, async)
+ {
+ _contentService = contentService;
+ _mediaService = mediaService;
+ _dataTypeService = dataTypeService;
+ _userService = userService;
+ _contentTypeService = contentTypeService;
+ }
+
#endregion
#region Constants & Fields
@@ -229,7 +260,13 @@ namespace UmbracoExamine
SupportProtectedContent = supportProtected;
else
SupportProtectedContent = false;
-
+
+ bool disableXmlDocLookup;
+ if (config["disableXmlDocLookup"] != null && bool.TryParse(config["disableXmlDocLookup"], out disableXmlDocLookup))
+ DisableXmlDocumentLookup = disableXmlDocLookup;
+ else
+ DisableXmlDocumentLookup = false;
+
base.Initialize(name, config);
}
@@ -237,6 +274,11 @@ namespace UmbracoExamine
#region Properties
+ ///
+ /// Whether to use the cmsContentXml data to re-index when possible (i.e. for published content, media and members)
+ ///
+ public bool DisableXmlDocumentLookup { get; private set; }
+
///
/// By default this is false, if set to true then the indexer will include indexing content that is flagged as publicly protected.
/// This property is ignored if SupportUnpublishedContent is set to true.
@@ -359,131 +401,250 @@ namespace UmbracoExamine
}
#endregion
- #region Protected
-
- ///
- /// This is a static query, it's parameters don't change so store statically
- ///
- private IQuery _publishedQuery;
+ #region Protected
protected override void PerformIndexAll(string type)
{
+ if (SupportedTypes.Contains(type) == false)
+ return;
+
const int pageSize = 10000;
var pageIndex = 0;
- switch (type)
+ DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - Start data queries - {0}", type));
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
+
+ try
{
- case IndexTypes.Content:
- var contentParentId = -1;
- if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0)
- {
- contentParentId = IndexerData.ParentNodeId.Value;
- }
- IContent[] content;
-
- //used to track non-published entities so we can determine what items are implicitly not published
- var notPublished = new HashSet();
-
- do
- {
- long total;
-
- IEnumerable descendants;
- if (SupportUnpublishedContent)
+ switch (type)
+ {
+ case IndexTypes.Content:
+ var contentParentId = -1;
+ if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0)
{
- descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total);
- }
- else
- {
- if (_publishedQuery == null)
- {
- _publishedQuery = Query.Builder.Where(x => x.Published == true);
- }
-
- //get all paged records but order by level ascending, we need to do this because we need to track which nodes are not published so that we can determine
- // which descendent nodes are implicitly not published
- descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "level", Direction.Ascending, true, (string)null);
- }
-
- //if specific types are declared we need to post filter them
- //TODO: Update the service layer to join the cmsContentType table so we can query by content type too
- if (IndexerData.IncludeNodeTypes.Any())
- {
- content = descendants.Where(x => IndexerData.IncludeNodeTypes.Contains(x.ContentType.Alias)).ToArray();
- }
- else
- {
- content = descendants.ToArray();
- }
- AddNodesToIndex(GetSerializedContent(content, notPublished).WhereNotNull(), type);
- pageIndex++;
- } while (content.Length == pageSize);
-
- notPublished.Clear();
-
- break;
- case IndexTypes.Media:
- var mediaParentId = -1;
-
- if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0)
- {
- mediaParentId = IndexerData.ParentNodeId.Value;
- }
-
- XElement[] mediaXElements;
-
- var mediaTypes = _contentTypeService.GetAllMediaTypes().ToArray();
- var icons = mediaTypes.ToDictionary(x => x.Id, y => y.Icon);
-
- do
- {
- long total;
- if (mediaParentId == -1)
- {
- mediaXElements = _mediaService.GetPagedXmlEntries("-1", pageIndex, pageSize, out total).ToArray();
- }
- else
- {
- //Get the parent
- var parent = _mediaService.GetById(mediaParentId);
- if (parent == null)
- mediaXElements = new XElement[0];
- else
- mediaXElements = _mediaService.GetPagedXmlEntries(parent.Path, pageIndex, pageSize, out total).ToArray();
- }
-
- //if specific types are declared we need to post filter them
- //TODO: Update the service layer to join the cmsContentType table so we can query by content type too
- if (IndexerData.IncludeNodeTypes.Any())
- {
- var includeNodeTypeIds = mediaTypes.Where(x => IndexerData.IncludeNodeTypes.Contains(x.Alias)).Select(x => x.Id);
- mediaXElements = mediaXElements.Where(elm => includeNodeTypeIds.Contains(elm.AttributeValue("nodeType"))).ToArray();
+ contentParentId = IndexerData.ParentNodeId.Value;
}
- foreach (var element in mediaXElements)
+ if (SupportUnpublishedContent == false && DisableXmlDocumentLookup == false)
{
- element.Add(new XAttribute("icon", icons[element.AttributeValue("nodeType")]));
+ //get all node Ids that have a published version - this is a fail safe check, in theory
+ // only document nodes that have a published version would exist in the cmsContentXml table
+ var allNodesWithPublishedVersions = ApplicationContext.Current.DatabaseContext.Database.Fetch(
+ "select DISTINCT cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1");
+
+ XElement last = null;
+ var trackedIds = new HashSet();
+
+ ReindexWithXmlEntries(type, contentParentId,
+ () => _contentTypeService.GetAllContentTypes().ToArray(),
+ (path, pIndex, pSize) =>
+ {
+ long totalContent;
+
+ //sorted by: umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder
+ var result = _contentService.GetPagedXmlEntries(path, pIndex, pSize, out totalContent).ToArray();
+
+ //then like we do in the ContentRepository.BuildXmlCache we need to track what Parents have been processed
+ // already so that we can then exclude implicitly unpublished content items
+ var filtered = new List();
+
+ foreach (var xml in result)
+ {
+ var id = xml.AttributeValue("id");
+
+ //don't include this if it doesn't have a published version
+ if (allNodesWithPublishedVersions.Contains(id) == false)
+ continue;
+
+ var parentId = xml.AttributeValue("parentID");
+
+ if (parentId == null) continue; //this shouldn't happen
+
+ //if the parentid is changing
+ if (last != null && last.AttributeValue("parentID") != parentId)
+ {
+ var found = trackedIds.Contains(parentId);
+ if (found == false)
+ {
+ //Need to short circuit here, if the parent is not there it means that the parent is unpublished
+ // and therefore the child is not published either so cannot be included in the xml cache
+ continue;
+ }
+ }
+
+ last = xml;
+ trackedIds.Add(xml.AttributeValue("id"));
+
+ filtered.Add(xml);
+ }
+
+ return new Tuple(totalContent, filtered.ToArray());
+ },
+ i => _contentService.GetById(i));
+ }
+ else
+ {
+ //used to track non-published entities so we can determine what items are implicitly not published
+ //currently this is not in use apart form in tests
+ var notPublished = new HashSet();
+
+ int currentPageSize;
+ do
+ {
+ long total;
+
+ IContent[] descendants;
+ if (SupportUnpublishedContent)
+ {
+ descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "umbracoNode.id").ToArray();
+ }
+ else
+ {
+ //get all paged records but order by level ascending, we need to do this because we need to track which nodes are not published so that we can determine
+ // which descendent nodes are implicitly not published
+ descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "level", Direction.Ascending, true, (string)null).ToArray();
+ }
+
+ // need to store decendants count before filtering, in order for loop to work correctly
+ currentPageSize = descendants.Length;
+
+ //if specific types are declared we need to post filter them
+ //TODO: Update the service layer to join the cmsContentType table so we can query by content type too
+ IEnumerable content;
+ if (IndexerData.IncludeNodeTypes.Any())
+ {
+ content = descendants.Where(x => IndexerData.IncludeNodeTypes.Contains(x.ContentType.Alias));
+ }
+ else
+ {
+ content = descendants;
+ }
+
+ AddNodesToIndex(GetSerializedContent(
+ SupportUnpublishedContent,
+ c => _serializer.Serialize(_contentService, _dataTypeService, _userService, c),
+ content, notPublished).WhereNotNull(), type);
+
+ pageIndex++;
+ } while (currentPageSize == pageSize);
}
- AddNodesToIndex(mediaXElements, type);
- pageIndex++;
- } while (mediaXElements.Length == pageSize);
+ break;
+ case IndexTypes.Media:
+ var mediaParentId = -1;
- break;
+ if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0)
+ {
+ mediaParentId = IndexerData.ParentNodeId.Value;
+ }
+
+ ReindexWithXmlEntries(type, mediaParentId,
+ () => _contentTypeService.GetAllMediaTypes().ToArray(),
+ (path, pIndex, pSize) =>
+ {
+ long totalMedia;
+ var result = _mediaService.GetPagedXmlEntries(path, pIndex, pSize, out totalMedia).ToArray();
+ return new Tuple(totalMedia, result);
+ },
+ i => _mediaService.GetById(i));
+
+ break;
+ }
}
+ finally
+ {
+ stopwatch.Stop();
+ }
+
+ DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}, took {1}ms", type, stopwatch.ElapsedMilliseconds));
}
- private IEnumerable GetSerializedContent(IEnumerable content, ISet notPublished)
+ ///
+ /// Performs a reindex of a type based on looking up entries from the cmsContentXml table - but using callbacks to get this data since
+ /// we don't have a common underlying service interface for the media/content stuff
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal void ReindexWithXmlEntries(
+ string type,
+ int parentId,
+ Func getContentTypes,
+ Func> getPagedXmlEntries,
+ Func getContent)
+ where TContentType: IContentTypeComposition
+ {
+ const int pageSize = 10000;
+ var pageIndex = 0;
+
+ XElement[] xElements;
+
+ var contentTypes = getContentTypes();
+ var icons = contentTypes.ToDictionary(x => x.Id, y => y.Icon);
+
+ do
+ {
+ long total;
+ if (parentId == -1)
+ {
+ var pagedElements = getPagedXmlEntries("-1", pageIndex, pageSize);
+ total = pagedElements.Item1;
+ xElements = pagedElements.Item2;
+ }
+ else
+ {
+ //Get the parent
+ var parent = getContent(parentId);
+ if (parent == null)
+ xElements = new XElement[0];
+ else
+ {
+ var pagedElements = getPagedXmlEntries(parent.Path, pageIndex, pageSize);
+ total = pagedElements.Item1;
+ xElements = pagedElements.Item2;
+ }
+ }
+
+ //if specific types are declared we need to post filter them
+ //TODO: Update the service layer to join the cmsContentType table so we can query by content type too
+ if (IndexerData.IncludeNodeTypes.Any())
+ {
+ var includeNodeTypeIds = contentTypes.Where(x => IndexerData.IncludeNodeTypes.Contains(x.Alias)).Select(x => x.Id);
+ xElements = xElements.Where(elm => includeNodeTypeIds.Contains(elm.AttributeValue("nodeType"))).ToArray();
+ }
+
+ foreach (var element in xElements)
+ {
+ if (element.Attribute("icon") == null)
+ {
+ element.Add(new XAttribute("icon", icons[element.AttributeValue("nodeType")]));
+ }
+ }
+
+ AddNodesToIndex(xElements, type);
+ pageIndex++;
+ } while (xElements.Length == pageSize);
+ }
+
+ internal static IEnumerable GetSerializedContent(
+ bool supportUnpublishdContent,
+ Func serializer,
+ IEnumerable content,
+ ISet notPublished)
{
foreach (var c in content)
{
- if (SupportUnpublishedContent == false)
+ if (supportUnpublishdContent == false)
{
//if we don't support published content and this is not published then track it and return null
if (c.Published == false)
{
notPublished.Add(c.Path);
yield return null;
+ continue;
}
//if we don't support published content, check if this content item exists underneath any already tracked
@@ -491,14 +652,11 @@ namespace UmbracoExamine
if (notPublished.Any(path => c.Path.StartsWith(string.Format("{0},", path))))
{
yield return null;
+ continue;
}
- }
+ }
- var xml = _serializer.Serialize(
- _contentService,
- _dataTypeService,
- _userService,
- c);
+ var xml = serializer(c);
//add a custom 'icon' attribute
xml.Add(new XAttribute("icon", c.ContentType.Icon));
@@ -520,7 +678,7 @@ namespace UmbracoExamine
public override void RebuildIndex()
{
- DataService.LogService.AddVerboseLog(-1, "Rebuilding index");
+ DataService.LogService.AddInfoLog(-1, "Rebuilding index");
base.RebuildIndex();
}
diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj
index 1bc438edd7..8720849744 100644
--- a/src/UmbracoExamine/UmbracoExamine.csproj
+++ b/src/UmbracoExamine/UmbracoExamine.csproj
@@ -82,8 +82,9 @@
..\Solution Items\TheFARM-Public.snk
-
- ..\packages\Examine.0.1.70.0\lib\Examine.dll
+
+ ..\packages\Examine.0.1.80\lib\net45\Examine.dll
+ True..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll
diff --git a/src/UmbracoExamine/UmbracoExamineSearcher.cs b/src/UmbracoExamine/UmbracoExamineSearcher.cs
index 7a2a80c3fe..26b02c904e 100644
--- a/src/UmbracoExamine/UmbracoExamineSearcher.cs
+++ b/src/UmbracoExamine/UmbracoExamineSearcher.cs
@@ -51,12 +51,41 @@ namespace UmbracoExamine
///
public override string Name
{
- get
- {
- return _name;
- }
+ get { return _name; }
+ }
+
+ ///
+ /// Constructor to allow for creating an indexer at runtime
+ ///
+ ///
+ ///
+
+ public UmbracoExamineSearcher(DirectoryInfo indexPath, Analyzer analyzer)
+ : base(indexPath, analyzer)
+ {
}
+ ///
+ /// Constructor to allow for creating an indexer at runtime
+ ///
+ ///
+ ///
+ public UmbracoExamineSearcher(Lucene.Net.Store.Directory luceneDirectory, Analyzer analyzer)
+ : base(luceneDirectory, analyzer)
+ {
+ }
+
+ ///
+ /// Creates an NRT searcher
+ ///
+ ///
+ ///
+ public UmbracoExamineSearcher(IndexWriter writer, Analyzer analyzer)
+ : base(writer, analyzer)
+ {
+ }
+
+ #endregion
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
@@ -115,30 +144,6 @@ namespace UmbracoExamine
}
}
- ///
- /// Constructor to allow for creating an indexer at runtime
- ///
- ///
- ///
-
- public UmbracoExamineSearcher(DirectoryInfo indexPath, Analyzer analyzer)
- : base(indexPath, analyzer)
- {
- }
-
- ///
- /// Constructor to allow for creating an indexer at runtime
- ///
- ///
- ///
-
- public UmbracoExamineSearcher(Lucene.Net.Store.Directory luceneDirectory, Analyzer analyzer)
- : base(luceneDirectory, analyzer)
- {
- }
-
- #endregion
-
///
/// Used for unit tests
///
diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs
index 64a574822f..2e48dff64e 100644
--- a/src/UmbracoExamine/UmbracoMemberIndexer.cs
+++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs
@@ -9,6 +9,8 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Services;
using UmbracoExamine.Config;
using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
using Examine;
using System.IO;
using UmbracoExamine.DataServices;
@@ -24,6 +26,7 @@ namespace UmbracoExamine
{
private readonly IMemberService _memberService;
+ private readonly IMemberTypeService _memberTypeService;
private readonly IDataTypeService _dataTypeService;
///
@@ -33,6 +36,7 @@ namespace UmbracoExamine
{
_dataTypeService = ApplicationContext.Current.Services.DataTypeService;
_memberService = ApplicationContext.Current.Services.MemberService;
+ _memberTypeService = ApplicationContext.Current.Services.MemberTypeService;
}
///
@@ -48,6 +52,7 @@ namespace UmbracoExamine
{
_dataTypeService = ApplicationContext.Current.Services.DataTypeService;
_memberService = ApplicationContext.Current.Services.MemberService;
+ _memberTypeService = ApplicationContext.Current.Services.MemberTypeService;
}
///
@@ -60,6 +65,8 @@ namespace UmbracoExamine
///
///
///
+ [Obsolete("Use the ctor specifying all dependencies instead")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService,
IDataTypeService dataTypeService,
IMemberService memberService,
@@ -68,9 +75,31 @@ namespace UmbracoExamine
{
_dataTypeService = dataTypeService;
_memberService = memberService;
+ _memberTypeService = ApplicationContext.Current.Services.MemberTypeService;
}
-
+ ///
+ /// Constructor to allow for creating an indexer at runtime
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService,
+ IDataTypeService dataTypeService,
+ IMemberService memberService,
+ IMemberTypeService memberTypeService,
+ Analyzer analyzer, bool async)
+ : base(indexerData, indexPath, dataService, analyzer, async)
+ {
+ _dataTypeService = dataTypeService;
+ _memberService = memberService;
+ _memberTypeService = memberTypeService;
+ }
///
/// Ensures that the'_searchEmail' is added to the user fields so that it is indexed - without having to modify the config
@@ -129,40 +158,68 @@ namespace UmbracoExamine
if (SupportedTypes.Contains(type) == false)
return;
- const int pageSize = 1000;
- var pageIndex = 0;
+ DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - Start data queries - {0}", type));
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
- IMember[] members;
-
- if (IndexerData.IncludeNodeTypes.Any())
+ try
{
- //if there are specific node types then just index those
- foreach (var nodeType in IndexerData.IncludeNodeTypes)
+ if (DisableXmlDocumentLookup == false)
{
- do
+ ReindexWithXmlEntries(type, -1,
+ () => _memberTypeService.GetAll().ToArray(),
+ (path, pIndex, pSize) =>
+ {
+ long totalContent;
+ var result = _memberService.GetPagedXmlEntries(pIndex, pSize, out totalContent).ToArray();
+ return new Tuple(totalContent, result);
+ },
+ i => _memberService.GetById(i));
+ }
+ else
+ {
+ const int pageSize = 1000;
+ var pageIndex = 0;
+
+ IMember[] members;
+
+ if (IndexerData.IncludeNodeTypes.Any())
{
- long total;
- members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, true, null, nodeType).ToArray();
+ //if there are specific node types then just index those
+ foreach (var nodeType in IndexerData.IncludeNodeTypes)
+ {
+ do
+ {
+ long total;
+ members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, true, null, nodeType).ToArray();
- AddNodesToIndex(GetSerializedMembers(members), type);
+ AddNodesToIndex(GetSerializedMembers(members), type);
- pageIndex++;
- } while (members.Length == pageSize);
+ pageIndex++;
+ } while (members.Length == pageSize);
+ }
+ }
+ else
+ {
+ //no node types specified, do all members
+ do
+ {
+ int total;
+ members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray();
+
+ AddNodesToIndex(GetSerializedMembers(members), type);
+
+ pageIndex++;
+ } while (members.Length == pageSize);
+ }
}
}
- else
+ finally
{
- //no node types specified, do all members
- do
- {
- int total;
- members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray();
-
- AddNodesToIndex(GetSerializedMembers(members), type);
-
- pageIndex++;
- } while (members.Length == pageSize);
+ stopwatch.Stop();
}
+
+ DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}, took {1}ms", type, stopwatch.ElapsedMilliseconds));
}
private IEnumerable GetSerializedMembers(IEnumerable members)
@@ -181,7 +238,9 @@ namespace UmbracoExamine
var fields = base.GetSpecialFieldsToIndex(allValuesForIndexing);
//adds the special path property to the index
- fields.Add("__key", allValuesForIndexing["__key"]);
+ string valuesForIndexing;
+ if (allValuesForIndexing.TryGetValue("__key", out valuesForIndexing))
+ fields.Add("__key", valuesForIndexing);
return fields;
diff --git a/src/UmbracoExamine/packages.config b/src/UmbracoExamine/packages.config
index 04734b9fb8..0c85a9c3ca 100644
--- a/src/UmbracoExamine/packages.config
+++ b/src/UmbracoExamine/packages.config
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/src/umbraco.MacroEngines/packages.config b/src/umbraco.MacroEngines/packages.config
index 9a785e35c3..930b7adbb0 100644
--- a/src/umbraco.MacroEngines/packages.config
+++ b/src/umbraco.MacroEngines/packages.config
@@ -1,6 +1,6 @@
-
+
diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj
index c9ee70e52c..198867a5d6 100644
--- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj
+++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj
@@ -45,8 +45,9 @@
false
-
- ..\packages\Examine.0.1.70.0\lib\Examine.dll
+
+ ..\packages\Examine.0.1.80\lib\net45\Examine.dll
+ True..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll
diff --git a/src/umbraco.cms/businesslogic/Tags/Tag.cs b/src/umbraco.cms/businesslogic/Tags/Tag.cs
index 8534d02d26..d4e0c90d00 100644
--- a/src/umbraco.cms/businesslogic/Tags/Tag.cs
+++ b/src/umbraco.cms/businesslogic/Tags/Tag.cs
@@ -351,7 +351,7 @@ namespace umbraco.cms.businesslogic.Tags
{
Document cnode = new Document(rr.GetInt("nodeid"));
- if (cnode != null && cnode.Published)
+ if (cnode.Published)
docs.Add(cnode);
}
}
diff --git a/src/umbraco.cms/businesslogic/propertytype/propertytype.cs b/src/umbraco.cms/businesslogic/propertytype/propertytype.cs
index b9622d7c91..e041306ba3 100644
--- a/src/umbraco.cms/businesslogic/propertytype/propertytype.cs
+++ b/src/umbraco.cms/businesslogic/propertytype/propertytype.cs
@@ -54,32 +54,31 @@ namespace umbraco.cms.businesslogic.propertytype
public PropertyType(int id)
{
- using (var sqlHelper = Application.SqlHelper)
- using (IRecordsReader dr = sqlHelper.ExecuteReader(
- "Select mandatory, DataTypeId, propertyTypeGroupId, ContentTypeId, sortOrder, alias, name, validationRegExp, description from cmsPropertyType where id=@id",
- sqlHelper.CreateParameter("@id", id)))
+ var found = ApplicationContext.Current.DatabaseContext.Database
+ .SingleOrDefault(
+ "Select mandatory, DataTypeId, propertyTypeGroupId, contentTypeId, sortOrder, alias, name, validationRegExp, description from cmsPropertyType where id=@id",
+ new {id = id});
+
+ if (found == null)
+ throw new ArgumentException("Propertytype with id: " + id + " doesnt exist!");
+
+ _mandatory = found.mandatory;
+ _id = id;
+
+ if (found.propertyTypeGroupId != null)
{
- if (!dr.Read())
- throw new ArgumentException("Propertytype with id: " + id + " doesnt exist!");
-
- _mandatory = dr.GetBoolean("mandatory");
- _id = id;
-
- if (!dr.IsNull("propertyTypeGroupId"))
- {
- _propertyTypeGroup = dr.GetInt("propertyTypeGroupId");
- //TODO: Remove after refactoring!
- _tabId = _propertyTypeGroup;
- }
-
- _sortOrder = dr.GetInt("sortOrder");
- _alias = dr.GetString("alias");
- _name = dr.GetString("Name");
- _validationRegExp = dr.GetString("validationRegExp");
- _DataTypeId = dr.GetInt("DataTypeId");
- _contenttypeid = dr.GetInt("contentTypeId");
- _description = dr.GetString("description");
+ _propertyTypeGroup = found.propertyTypeGroupId;
+ //TODO: Remove after refactoring!
+ _tabId = _propertyTypeGroup;
}
+
+ _sortOrder = found.sortOrder;
+ _alias = found.alias;
+ _name = found.name;
+ _validationRegExp = found.validationRegExp;
+ _DataTypeId = found.DataTypeId;
+ _contenttypeid = found.contentTypeId;
+ _description = found.description;
}
#endregion
@@ -92,7 +91,6 @@ namespace umbraco.cms.businesslogic.propertytype
set
{
_DataTypeId = value.Id;
- InvalidateCache();
using (var sqlHelper = Application.SqlHelper)
sqlHelper.ExecuteNonQuery(
"Update cmsPropertyType set DataTypeId = " + value.Id + " where id=" + Id);
@@ -119,7 +117,6 @@ namespace umbraco.cms.businesslogic.propertytype
{
_tabId = value;
PropertyTypeGroup = value;
- InvalidateCache();
}
}
@@ -148,7 +145,6 @@ namespace umbraco.cms.businesslogic.propertytype
set
{
_mandatory = value;
- InvalidateCache();
using (var sqlHelper = Application.SqlHelper)
sqlHelper.ExecuteNonQuery("Update cmsPropertyType set mandatory = @mandatory where id = @id",
sqlHelper.CreateParameter("@mandatory", value),
@@ -162,7 +158,6 @@ namespace umbraco.cms.businesslogic.propertytype
set
{
_validationRegExp = value;
- InvalidateCache();
using (var sqlHelper = Application.SqlHelper)
sqlHelper.ExecuteNonQuery("Update cmsPropertyType set validationRegExp = @validationRegExp where id = @id",
sqlHelper.CreateParameter("@validationRegExp", value), sqlHelper.CreateParameter("@id", Id));
@@ -199,7 +194,6 @@ namespace umbraco.cms.businesslogic.propertytype
set
{
_description = value;
- InvalidateCache();
using (var sqlHelper = Application.SqlHelper)
sqlHelper.ExecuteNonQuery("Update cmsPropertyType set description = @description where id = @id",
sqlHelper.CreateParameter("@description", value),
@@ -213,7 +207,6 @@ namespace umbraco.cms.businesslogic.propertytype
set
{
_sortOrder = value;
- InvalidateCache();
using (var sqlHelper = Application.SqlHelper)
sqlHelper.ExecuteNonQuery("Update cmsPropertyType set sortOrder = @sortOrder where id = @id",
sqlHelper.CreateParameter("@sortOrder", value),
@@ -227,7 +220,6 @@ namespace umbraco.cms.businesslogic.propertytype
set
{
_alias = value;
- InvalidateCache();
using (var sqlHelper = Application.SqlHelper)
sqlHelper.ExecuteNonQuery("Update cmsPropertyType set alias = @alias where id= @id",
sqlHelper.CreateParameter("@alias", Casing.SafeAliasWithForcingCheck(_alias)),
@@ -264,7 +256,6 @@ namespace umbraco.cms.businesslogic.propertytype
set
{
_name = value;
- InvalidateCache();
using (var sqlHelper = Application.SqlHelper)
sqlHelper.ExecuteNonQuery(
"UPDATE cmsPropertyType SET name=@name WHERE id=@id",
@@ -331,17 +322,17 @@ namespace umbraco.cms.businesslogic.propertytype
public static IEnumerable GetPropertyTypes()
{
var result = new List();
- using (var sqlHelper = Application.SqlHelper)
- using (IRecordsReader dr =
- sqlHelper.ExecuteReader("select id from cmsPropertyType order by Name"))
+
+ var propertyTypeIds = ApplicationContext.Current.DatabaseContext.Database.Fetch(
+ "select id from cmsPropertyType order by Name");
+
+ foreach (var propertyTypeId in propertyTypeIds)
{
- while (dr.Read())
- {
- PropertyType pt = GetPropertyType(dr.GetInt("id"));
- if (pt != null)
- result.Add(pt);
- }
+ PropertyType pt = GetPropertyType(propertyTypeId);
+ if (pt != null)
+ result.Add(pt);
}
+
return result;
}
@@ -353,18 +344,17 @@ namespace umbraco.cms.businesslogic.propertytype
public static IEnumerable GetPropertyTypesByGroup(int groupId)
{
var result = new List();
- using (var sqlHelper = Application.SqlHelper)
- using (IRecordsReader dr =
- sqlHelper.ExecuteReader("SELECT id FROM cmsPropertyType WHERE propertyTypeGroupId = @groupId order by SortOrder",
- sqlHelper.CreateParameter("@groupId", groupId)))
+
+ var propertyTypeIds = ApplicationContext.Current.DatabaseContext.Database.Fetch(
+ "SELECT id FROM cmsPropertyType WHERE propertyTypeGroupId = @groupId order by SortOrder", new {groupId = groupId});
+
+ foreach (var propertyTypeId in propertyTypeIds)
{
- while (dr.Read())
- {
- PropertyType pt = GetPropertyType(dr.GetInt("id"));
- if (pt != null)
- result.Add(pt);
- }
+ PropertyType pt = GetPropertyType(propertyTypeId);
+ if (pt != null)
+ result.Add(pt);
}
+
return result;
}
@@ -376,20 +366,18 @@ namespace umbraco.cms.businesslogic.propertytype
public static IEnumerable GetByDataTypeDefinition(int dataTypeDefId)
{
var result = new List();
- using (var sqlHelper = Application.SqlHelper)
- using (IRecordsReader dr =
- sqlHelper.ExecuteReader(
- "select id, Name from cmsPropertyType where dataTypeId=@dataTypeId order by Name",
- sqlHelper.CreateParameter("@dataTypeId", dataTypeDefId)))
+
+ var propertyTypeIds = ApplicationContext.Current.DatabaseContext.Database.Fetch(
+ "select id from cmsPropertyType where dataTypeId=@dataTypeId order by Name", new {dataTypeId = dataTypeDefId});
+
+ foreach (var propertyTypeId in propertyTypeIds)
{
- while (dr.Read())
- {
- PropertyType pt = GetPropertyType(dr.GetInt("id"));
- if (pt != null)
- result.Add(pt);
- }
+ PropertyType pt = GetPropertyType(propertyTypeId);
+ if (pt != null)
+ result.Add(pt);
}
- return result.ToList();
+
+ return result;
}
public void delete()
@@ -411,7 +399,6 @@ namespace umbraco.cms.businesslogic.propertytype
// delete cache from either master (via tabid) or current contentype
FlushCacheBasedOnTab();
- InvalidateCache();
}
public void FlushCacheBasedOnTab()
@@ -478,8 +465,6 @@ namespace umbraco.cms.businesslogic.propertytype
protected virtual void FlushCache()
{
- // clear local cache
- ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(GetCacheKey(Id));
// clear cache in contentype
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ContentTypePropertiesCacheKey + _contenttypeid);
@@ -496,31 +481,9 @@ namespace umbraco.cms.businesslogic.propertytype
public static PropertyType GetPropertyType(int id)
{
- return ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem(
- GetCacheKey(id),
- timeout: TimeSpan.FromMinutes(30),
- getCacheItem: () =>
- {
- try
- {
- return new PropertyType(id);
- }
- catch
- {
- return null;
- }
- });
- }
-
- private void InvalidateCache()
- {
- ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(GetCacheKey(Id));
- }
-
- private static string GetCacheKey(int id)
- {
- return CacheKeys.PropertyTypeCacheKey + id;
+ return new PropertyType(id);
}
+
#endregion
}
diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs
index 70775f5c71..bd21517d80 100644
--- a/src/umbraco.cms/businesslogic/web/Document.cs
+++ b/src/umbraco.cms/businesslogic/web/Document.cs
@@ -433,21 +433,20 @@ namespace umbraco.cms.businesslogic.web
{
XmlDocument xd = new XmlDocument();
- using (var sqlHelper = Application.SqlHelper)
- using (IRecordsReader dr = sqlHelper.ExecuteReader("select nodeId from cmsDocument"))
+ var nodeIds = ApplicationContext.Current.DatabaseContext.Database.Fetch(
+ "select nodeId from cmsDocument");
+
+ foreach (var nodeId in nodeIds)
{
- while (dr.Read())
+ try
{
- try
- {
- new Document(dr.GetInt("nodeId")).SaveXmlPreview(xd);
- }
- catch (Exception ee)
- {
- LogHelper.Error("Error generating preview xml", ee);
- }
+ new Document(nodeId).SaveXmlPreview(xd);
}
- }
+ catch (Exception ee)
+ {
+ LogHelper.Error("Error generating preview xml", ee);
+ }
+ }
}
///
diff --git a/src/umbraco.editorControls/PickerRelations/PickerRelationsEventHandler.cs b/src/umbraco.editorControls/PickerRelations/PickerRelationsEventHandler.cs
index 13f786359b..844d9920e0 100644
--- a/src/umbraco.editorControls/PickerRelations/PickerRelationsEventHandler.cs
+++ b/src/umbraco.editorControls/PickerRelations/PickerRelationsEventHandler.cs
@@ -2,8 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
-
-using umbraco.BusinessLogic; // ApplicationBase
+// ApplicationBase
using umbraco.businesslogic;
using umbraco.cms.businesslogic; // SaveEventArgs
using umbraco.cms.businesslogic.media; // Media
@@ -12,6 +11,8 @@ using umbraco.cms.businesslogic.web; // Documentusing umbraco.cms.businesslogic.
using umbraco.cms.businesslogic.property;
using umbraco.cms.businesslogic.relation;
using umbraco.DataLayer;
+using Umbraco.Core;
+using Application = umbraco.BusinessLogic.Application;
namespace umbraco.editorControls.PickerRelations
{
@@ -212,7 +213,7 @@ namespace umbraco.editorControls.PickerRelations
private static void DeleteRelations(RelationType relationType, int contentNodeId, bool reverseIndexing, string instanceIdentifier)
{
//if relationType is bi-directional or a reverse index then we can't get at the relations via the API, so using SQL
- string getRelationsSql = "SELECT id FROM umbracoRelation WHERE relType = " + relationType.Id.ToString() + " AND ";
+ string getRelationsSql = "SELECT id FROM umbracoRelation WHERE relType = " + relationType.Id + " AND ";
if (reverseIndexing || relationType.Dual)
{
@@ -229,19 +230,16 @@ namespace umbraco.editorControls.PickerRelations
getRelationsSql += " AND comment = '" + instanceIdentifier + "'";
- using (var sqlHelper = Application.SqlHelper)
- using (IRecordsReader relations = sqlHelper.ExecuteReader(getRelationsSql))
- {
- //clear data
- Relation relation;
- while (relations.Read())
- {
- relation = new Relation(relations.GetInt("id"));
+ var relationIds = ApplicationContext.Current.DatabaseContext.Database.Fetch(
+ getRelationsSql);
+ foreach (var relationId in relationIds)
+ {
+ var relation = new Relation(relationId);
- // TODO: [HR] check to see if an instance identifier is used
- relation.Delete();
- }
- }
+ // TODO: [HR] check to see if an instance identifier is used
+ relation.Delete();
+ }
+
}
///