diff --git a/README.md b/README.md index 3e9be6cb5f..1d42543eb3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Umbraco CMS =========== -The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 350,000 websites worldwide: [https://umbraco.com](https://umbraco.com) +The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 390,000 websites worldwide: [https://umbraco.com](https://umbraco.com) [![ScreenShot](vimeo.png)](https://vimeo.com/172382998/) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index f442b92551..ce837f7189 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -15,6 +15,8 @@ en-US umbraco + + @@ -28,52 +30,51 @@ - - + - - - - - + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -104,4 +105,4 @@ - \ No newline at end of file + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 41d47df7e9..4a587e8a07 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -16,9 +16,10 @@ umbraco - + + @@ -44,6 +45,8 @@ + + diff --git a/build/NuSpecs/tools/cache.config.install.xdt b/build/NuSpecs/tools/cache.config.install.xdt new file mode 100644 index 0000000000..746e3c3298 --- /dev/null +++ b/build/NuSpecs/tools/cache.config.install.xdt @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1 index c4f213ba01..e2230e0c32 100644 --- a/build/NuSpecs/tools/install.core.ps1 +++ b/build/NuSpecs/tools/install.core.ps1 @@ -71,6 +71,7 @@ if ($project) { if(Test-Path $umbracoBinFolder\ImageProcessor.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\ImageProcessor.Web.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.Web.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false } + if(Test-Path $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll) { Remove-Item $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll -Force -Confirm:$false } diff --git a/build/NuSpecs/tools/processing.config.install.xdt b/build/NuSpecs/tools/processing.config.install.xdt new file mode 100644 index 0000000000..0bef321533 --- /dev/null +++ b/build/NuSpecs/tools/processing.config.install.xdt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index df03fe8381..a5c5d3f363 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,2 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.5.5 \ No newline at end of file +7.6.0 +alpha068 diff --git a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj index 86796e1dd7..2904410748 100644 --- a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj +++ b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,7 +10,7 @@ Properties SQLCE4Umbraco SQLCE4Umbraco - v4.5 + v4.6.2 512 @@ -49,13 +49,9 @@ ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll - False - False ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll - False - False diff --git a/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs b/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs index fd778bbfb3..2dd0f26e90 100644 --- a/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs +++ b/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs @@ -240,7 +240,7 @@ namespace SqlCE4Umbraco { var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx); AttachParameters(cmd, commandParameters); - return cmd.ExecuteReader(CommandBehavior.CloseConnection); + return cmd.ExecuteReader(); } catch { diff --git a/src/SQLCE4Umbraco/app.config b/src/SQLCE4Umbraco/app.config index 1f5a6442ad..6d9f461c6f 100644 --- a/src/SQLCE4Umbraco/app.config +++ b/src/SQLCE4Umbraco/app.config @@ -4,7 +4,7 @@ - + @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index f6a14611df..05460ef463 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -2,7 +2,7 @@ using System.Resources; [assembly: AssemblyCompany("Umbraco")] -[assembly: AssemblyCopyright("Copyright © Umbraco 2016")] +[assembly: AssemblyCopyright("Copyright © Umbraco 2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-alpha032")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.6.0-alpha068")] \ No newline at end of file diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index e47ef04650..5b2caf409f 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Profiling; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Sync; @@ -162,6 +163,9 @@ namespace Umbraco.Core /// public static ApplicationContext Current { get; internal set; } + // fixme + internal IScopeProvider ScopeProvider { get { return _databaseContext == null ? null : _databaseContext.ScopeProvider; } } + /// /// Returns the application wide cache accessor /// @@ -296,7 +300,7 @@ namespace Umbraco.Core // if we have a db context available, if we don't then we are not installed anyways if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.CanConnect) { - var found = Services.MigrationEntryService.FindEntry(GlobalSettings.UmbracoMigrationName, UmbracoVersion.GetSemanticVersion()); + var found = Services.MigrationEntryService.FindEntry(Constants.System.UmbracoMigrationName, UmbracoVersion.GetSemanticVersion()); if (found == null) { //we haven't executed this migration in this environment, so even though the config versions match, @@ -418,10 +422,17 @@ namespace Umbraco.Core this.ApplicationCache = null; if (_databaseContext != null) //need to check the internal field here { + if (_databaseContext.ScopeProvider.AmbientScope != null) + { + var scope = _databaseContext.ScopeProvider.AmbientScope; + scope.Dispose(); + } + /* if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.Database != null) { DatabaseContext.Database.Dispose(); - } + } + */ } this.DatabaseContext = null; this.Services = null; diff --git a/src/Umbraco.Core/BindingRedirects.cs b/src/Umbraco.Core/BindingRedirects.cs new file mode 100644 index 0000000000..2ee54a369b --- /dev/null +++ b/src/Umbraco.Core/BindingRedirects.cs @@ -0,0 +1,48 @@ +using System; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Web; +using Umbraco.Core; + +namespace Umbraco.Core +{ + /// + /// Manages any assembly binding redirects that cannot be done via config + /// + internal class BindingRedirects + { + public static void Initialize() + { + // this only gets called when an assembly can't be resolved + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + } + + private static readonly Regex Log4NetAssemblyPattern = new Regex("log4net, Version=([\\d\\.]+?), Culture=neutral, PublicKeyToken=null", RegexOptions.Compiled); + private const string Log4NetReplacement = "log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a"; + + /// + /// This is used to do an assembly binding redirect via code - normally required due to signature changes in assemblies + /// + /// + /// + /// + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + //log4net: + // Use regex to match and replace + if (Log4NetAssemblyPattern.IsMatch(args.Name) && args.Name != Log4NetReplacement) + { + return Assembly.Load(Log4NetAssemblyPattern.Replace(args.Name, Log4NetReplacement)); + } + + //AutoMapper: + // ensure the assembly is indeed AutoMapper and that the PublicKeyToken is null before trying to Load again + // do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stackoverflow + if (args.Name.StartsWith("AutoMapper") && args.Name.EndsWith("PublicKeyToken=null")) + return Assembly.Load(args.Name.Replace(", PublicKeyToken=null", ", PublicKeyToken=be96cd2c38ef1005")); + + return null; + + } + } +} \ 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/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 1f51fc3ccc..fc98473c4c 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -1,23 +1,26 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { /// - /// The default cache policy for retrieving a single entity + /// Represents the default cache policy. /// - /// - /// + /// The type of the entity. + /// The type of the identifier. /// - /// This cache policy uses sliding expiration and caches instances for 5 minutes. However if allow zero count is true, then we use the - /// default policy with no expiry. + /// The default cache policy caches entities with a 5 minutes sliding expiration. + /// Each entity is cached individually. + /// If options.GetAllCacheAllowZeroCount then a 'zero-count' array is cached when GetAll finds nothing. + /// If options.GetAllCacheValidateCount then we check against the db when getting many entities. /// internal class DefaultRepositoryCachePolicy : RepositoryCachePolicyBase where TEntity : class, IAggregateRoot { + private static readonly TEntity[] EmptyEntities = new TEntity[0]; // const private readonly RepositoryCachePolicyOptions _options; public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) @@ -27,242 +30,223 @@ namespace Umbraco.Core.Cache _options = options; } - protected string GetCacheIdKey(object id) + public override IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) { - if (id == null) throw new ArgumentNullException("id"); - - return string.Format("{0}{1}", GetCacheTypeKey(), id); + return new ScopedRepositoryCachePolicy(this, runtimeCache, scope); } - protected string GetCacheTypeKey() + protected string GetEntityCacheKey(object id) + { + if (id == null) throw new ArgumentNullException("id"); + return GetEntityTypeCacheKey() + id; + } + + protected string GetEntityTypeCacheKey() { return string.Format("uRepo_{0}_", typeof(TEntity).Name); } - public override void CreateOrUpdate(TEntity entity, Action persistMethod) + protected virtual void InsertEntity(string cacheKey, TEntity entity) + { + Cache.InsertCacheItem(cacheKey, () => entity, TimeSpan.FromMinutes(5), true); + } + + protected virtual void InsertEntities(TId[] ids, TEntity[] entities) + { + if (ids.Length == 0 && entities.Length == 0 && _options.GetAllCacheAllowZeroCount) + { + // getting all of them, and finding nothing. + // if we can cache a zero count, cache an empty array, + // for as long as the cache is not cleared (no expiration) + Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => EmptyEntities); + } + else + { + // individually cache each item + foreach (var entity in entities) + { + var capture = entity; + Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => capture, TimeSpan.FromMinutes(5), true); + } + } + } + + /// + public override void Create(TEntity entity, Action persistNew) { if (entity == null) throw new ArgumentNullException("entity"); - if (persistMethod == null) throw new ArgumentNullException("persistMethod"); try { - persistMethod(entity); + persistNew(entity); - //set the disposal action - SetCacheAction(() => + // just to be safe, we cannot cache an item without an identity + if (entity.HasIdentity) { - //just to be safe, we cannot cache an item without an identity - if (entity.HasIdentity) - { - Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity, - timeout: TimeSpan.FromMinutes(5), - isSliding: true); - } - - //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared - Cache.ClearCacheItem(GetCacheTypeKey()); - }); - + Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true); + } + + // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared + Cache.ClearCacheItem(GetEntityTypeCacheKey()); } catch { - //set the disposal action - SetCacheAction(() => - { - //if an exception is thrown we need to remove the entry from cache, this is ONLY a work around because of the way - // that we cache entities: http://issues.umbraco.org/issue/U4-4259 - Cache.ClearCacheItem(GetCacheIdKey(entity.Id)); + // if an exception is thrown we need to remove the entry from cache, + // this is ONLY a work around because of the way + // that we cache entities: http://issues.umbraco.org/issue/U4-4259 + Cache.ClearCacheItem(GetEntityCacheKey(entity.Id)); + + // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared + Cache.ClearCacheItem(GetEntityTypeCacheKey()); - //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared - Cache.ClearCacheItem(GetCacheTypeKey()); - }); - throw; } } - public override void Remove(TEntity entity, Action persistMethod) + /// + public override void Update(TEntity entity, Action persistUpdated) { if (entity == null) throw new ArgumentNullException("entity"); - if (persistMethod == null) throw new ArgumentNullException("persistMethod"); try { - persistMethod(entity); - } - finally - { - //set the disposal action - var cacheKey = GetCacheIdKey(entity.Id); - SetCacheAction(() => + persistUpdated(entity); + + // just to be safe, we cannot cache an item without an identity + if (entity.HasIdentity) { - Cache.ClearCacheItem(cacheKey); - //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared - Cache.ClearCacheItem(GetCacheTypeKey()); - }); + Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true); + } + + // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared + Cache.ClearCacheItem(GetEntityTypeCacheKey()); + } + catch + { + // if an exception is thrown we need to remove the entry from cache, + // this is ONLY a work around because of the way + // that we cache entities: http://issues.umbraco.org/issue/U4-4259 + Cache.ClearCacheItem(GetEntityCacheKey(entity.Id)); + + // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared + Cache.ClearCacheItem(GetEntityTypeCacheKey()); + + throw; } } - public override TEntity Get(TId id, Func getFromRepo) + /// + public override void Delete(TEntity entity, Action persistDeleted) { - if (getFromRepo == null) throw new ArgumentNullException("getFromRepo"); + if (entity == null) throw new ArgumentNullException("entity"); - var cacheKey = GetCacheIdKey(id); + try + { + persistDeleted(entity); + } + finally + { + // whatever happens, clear the cache + var cacheKey = GetEntityCacheKey(entity.Id); + Cache.ClearCacheItem(cacheKey); + // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared + Cache.ClearCacheItem(GetEntityTypeCacheKey()); + } + } + + /// + public override TEntity Get(TId id, Func performGet, Func> performGetAll) + { + var cacheKey = GetEntityCacheKey(id); var fromCache = Cache.GetCacheItem(cacheKey); + + // if found in cache then return else fetch and cache if (fromCache != null) return fromCache; - - var entity = getFromRepo(id); + var entity = performGet(id); - //set the disposal action - SetCacheAction(cacheKey, entity); + if (entity != null && entity.HasIdentity) + InsertEntity(cacheKey, entity); return entity; } - public override TEntity Get(TId id) + /// + public override TEntity GetCached(TId id) { - var cacheKey = GetCacheIdKey(id); + var cacheKey = GetEntityCacheKey(id); return Cache.GetCacheItem(cacheKey); } - public override bool Exists(TId id, Func getFromRepo) + /// + public override bool Exists(TId id, Func performExists, Func> performGetAll) { - if (getFromRepo == null) throw new ArgumentNullException("getFromRepo"); - - var cacheKey = GetCacheIdKey(id); + // if found in cache the return else check + var cacheKey = GetEntityCacheKey(id); var fromCache = Cache.GetCacheItem(cacheKey); - return fromCache != null || getFromRepo(id); + return fromCache != null || performExists(id); } - public override TEntity[] GetAll(TId[] ids, Func> getFromRepo) + /// + public override TEntity[] GetAll(TId[] ids, Func> performGetAll) { - if (getFromRepo == null) throw new ArgumentNullException("getFromRepo"); - - if (ids.Any()) + if (ids.Length > 0) { - var entities = ids.Select(Get).ToArray(); - if (ids.Length.Equals(entities.Length) && entities.Any(x => x == null) == false) - return entities; + // try to get each entity from the cache + // if we can find all of them, return + var entities = ids.Select(GetCached).WhereNotNull().ToArray(); + if (ids.Length.Equals(entities.Length)) + return entities; // no need for null checks, we are not caching nulls } else { - var allEntities = GetAllFromCache(); - if (allEntities.Any()) + // get everything we have + var entities = Cache.GetCacheItemsByKeySearch(GetEntityTypeCacheKey()) + .ToArray(); // no need for null checks, we are not caching nulls + + if (entities.Length > 0) { + // if some of them were in the cache... if (_options.GetAllCacheValidateCount) { - //Get count of all entities of current type (TEntity) to ensure cached result is correct + // need to validate the count, get the actual count and return if ok var totalCount = _options.PerformCount(); - if (allEntities.Length == totalCount) - return allEntities; + if (entities.Length == totalCount) + return entities; } else { - return allEntities; + // no need to validate, just return what we have and assume it's all there is + return entities; } } else if (_options.GetAllCacheAllowZeroCount) { - //if the repository allows caching a zero count, then check the zero count cache - if (HasZeroCountCache()) - { - //there is a zero count cache so return an empty list - return new TEntity[] {}; - } + // if none of them were in the cache + // and we allow zero count - check for the special (empty) entry + var empty = Cache.GetCacheItem(GetEntityTypeCacheKey()); + if (empty != null) return empty; } } - //we need to do the lookup from the repo - var entityCollection = getFromRepo(ids) - //ensure we don't include any null refs in the returned collection! - .WhereNotNull() + // cache failed, get from repo and cache + var repoEntities = performGetAll(ids) + .WhereNotNull() // exclude nulls! + .Where(x => x.HasIdentity) // be safe, though would be weird... .ToArray(); - //set the disposal action - SetCacheAction(ids, entityCollection); + // note: if empty & allow zero count, will cache a special (empty) entry + InsertEntities(ids, repoEntities); - return entityCollection; + return repoEntities; } - /// - /// Looks up the zero count cache, must return null if it doesn't exist - /// - /// - protected bool HasZeroCountCache() + /// + public override void ClearAll() { - var zeroCount = Cache.GetCacheItem(GetCacheTypeKey()); - return (zeroCount != null && zeroCount.Any() == false); + // fixme the cache should NOT contain anything else so we can clean all, can't we? + Cache.ClearAllCache(); + //Cache.ClearCacheByKeySearch(GetEntityTypeCacheKey()); } - - /// - /// Performs the lookup for all entities of this type from the cache - /// - /// - protected TEntity[] GetAllFromCache() - { - var allEntities = Cache.GetCacheItemsByKeySearch(GetCacheTypeKey()) - .WhereNotNull() - .ToArray(); - return allEntities.Any() ? allEntities : new TEntity[] {}; - } - - /// - /// Sets the action to execute on disposal for a single entity - /// - /// - /// - protected virtual void SetCacheAction(string cacheKey, TEntity entity) - { - if (entity == null) return; - - SetCacheAction(() => - { - //just to be safe, we cannot cache an item without an identity - if (entity.HasIdentity) - { - Cache.InsertCacheItem(cacheKey, () => entity, - timeout: TimeSpan.FromMinutes(5), - isSliding: true); - } - }); - } - - /// - /// Sets the action to execute on disposal for an entity collection - /// - /// - /// - protected virtual void SetCacheAction(TId[] ids, TEntity[] entityCollection) - { - SetCacheAction(() => - { - //This option cannot execute if we are looking up specific Ids - if (ids.Any() == false && entityCollection.Length == 0 && _options.GetAllCacheAllowZeroCount) - { - //there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache - // to signify that there is a zero count cache - //NOTE: Don't set expiry/sliding for a zero count - Cache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {}); - } - else - { - //This is the default behavior, we'll individually cache each item so that if/when these items are resolved - // by id, they are returned from the already existing cache. - foreach (var entity in entityCollection.WhereNotNull()) - { - var localCopy = entity; - //just to be safe, we cannot cache an item without an identity - if (localCopy.HasIdentity) - { - Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy, - timeout: TimeSpan.FromMinutes(5), - isSliding: true); - } - } - } - }); - } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs deleted file mode 100644 index 5c02e41a48..0000000000 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Cache -{ - /// - /// Creates cache policies - /// - /// - /// - internal class DefaultRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory - where TEntity : class, IAggregateRoot - { - private readonly IRuntimeCacheProvider _runtimeCache; - private readonly RepositoryCachePolicyOptions _options; - - public DefaultRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, RepositoryCachePolicyOptions options) - { - _runtimeCache = runtimeCache; - _options = options; - } - - public virtual IRepositoryCachePolicy CreatePolicy() - { - return new DefaultRepositoryCachePolicy(_runtimeCache, _options); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index 9b37d1861f..41c0249877 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -3,227 +3,178 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Collections; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { /// - /// A caching policy that caches an entire dataset as a single collection + /// Represents a caching policy that caches the entire entities set as a single collection. /// - /// - /// + /// The type of the entity. + /// The type of the identifier. + /// + /// Caches the entire set of entities as a single collection. + /// Used by Content-, Media- and MemberTypeRepository, DataTypeRepository, DomainRepository, + /// LanguageRepository, PublicAccessRepository, TemplateRepository... things that make sense to + /// keep as a whole in memory. + /// internal class FullDataSetRepositoryCachePolicy : RepositoryCachePolicyBase where TEntity : class, IAggregateRoot { - private readonly Func _getEntityId; - private readonly Func> _getAllFromRepo; + private readonly Func _entityGetId; private readonly bool _expires; - public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func getEntityId, Func> getAllFromRepo, bool expires) + public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func entityGetId, bool expires) : base(cache) { - _getEntityId = getEntityId; - _getAllFromRepo = getAllFromRepo; + _entityGetId = entityGetId; _expires = expires; } - private bool? _hasZeroCountCache; + public override IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) + { + return new ScopedRepositoryCachePolicy(this, runtimeCache, scope); + } + protected static readonly TId[] EmptyIds = new TId[0]; // const - protected string GetCacheTypeKey() + protected string GetEntityTypeCacheKey() { return string.Format("uRepo_{0}_", typeof(TEntity).Name); } - public override void CreateOrUpdate(TEntity entity, Action persistMethod) + protected void InsertEntities(TEntity[] entities) { - if (entity == null) throw new ArgumentNullException("entity"); - if (persistMethod == null) throw new ArgumentNullException("persistMethod"); + // cache is expected to be a deep-cloning cache ie it deep-clones whatever is + // IDeepCloneable when it goes in, and out. it also resets dirty properties, + // making sure that no 'dirty' entity is cached. + // + // this policy is caching the entire list of entities. to ensure that entities + // are properly deep-clones when cached, it uses a DeepCloneableList. however, + // we don't want to deep-clone *each* entity in the list when fetching it from + // cache as that would not be efficient for Get(id). so the DeepCloneableList is + // set to ListCloneBehavior.CloneOnce ie it will clone *once* when inserting, + // and then will *not* clone when retrieving. - try + if (_expires) { - persistMethod(entity); - - //set the disposal action - SetCacheAction(() => - { - //Clear all - Cache.ClearCacheItem(GetCacheTypeKey()); - }); + Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => new DeepCloneableList(entities), TimeSpan.FromMinutes(5), true); } - catch + else { - //set the disposal action - SetCacheAction(() => - { - //Clear all - Cache.ClearCacheItem(GetCacheTypeKey()); - }); - throw; + Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => new DeepCloneableList(entities)); } } - public override void Remove(TEntity entity, Action persistMethod) + /// + public override void Create(TEntity entity, Action persistNew) { if (entity == null) throw new ArgumentNullException("entity"); - if (persistMethod == null) throw new ArgumentNullException("persistMethod"); try { - persistMethod(entity); + persistNew(entity); } finally { - //set the disposal action - SetCacheAction(() => - { - //Clear all - Cache.ClearCacheItem(GetCacheTypeKey()); - }); + ClearAll(); } } - public override TEntity Get(TId id, Func getFromRepo) + /// + public override void Update(TEntity entity, Action persistUpdated) { - //Force get all with cache - var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull()); + if (entity == null) throw new ArgumentNullException("entity"); - //we don't have anything in cache (this should never happen), just return from the repo - if (found == null) return getFromRepo(id); - var entity = found.FirstOrDefault(x => _getEntityId(x).Equals(id)); - if (entity == null) return null; - - //We must ensure to deep clone each one out manually since the deep clone list only clones one way - return (TEntity)entity.DeepClone(); - } - - public override TEntity Get(TId id) - { - //Force get all with cache - var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull()); - - //we don't have anything in cache (this should never happen), just return null - if (found == null) return null; - var entity = found.FirstOrDefault(x => _getEntityId(x).Equals(id)); - if (entity == null) return null; - - //We must ensure to deep clone each one out manually since the deep clone list only clones one way - return (TEntity)entity.DeepClone(); - } - - public override bool Exists(TId id, Func getFromRepo) - { - //Force get all with cache - var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull()); - - //we don't have anything in cache (this should never happen), just return from the repo - return found == null - ? getFromRepo(id) - : found.Any(x => _getEntityId(x).Equals(id)); - } - - public override TEntity[] GetAll(TId[] ids, Func> getFromRepo) - { - //process getting all including setting the cache callback - var result = PerformGetAll(getFromRepo); - - //now that the base result has been calculated, they will all be cached. - // Now we can just filter by ids if they have been supplied - - return (ids.Any() - ? result.Where(x => ids.Contains(_getEntityId(x))).ToArray() - : result) - //We must ensure to deep clone each one out manually since the deep clone list only clones one way - .Select(x => (TEntity)x.DeepClone()) - .ToArray(); - } - - private TEntity[] PerformGetAll(Func> getFromRepo) - { - var allEntities = GetAllFromCache(); - if (allEntities.Any()) + try { - return allEntities; + persistUpdated(entity); } - - //check the zero count cache - if (HasZeroCountCache()) + finally { - //there is a zero count cache so return an empty list - return new TEntity[] { }; + ClearAll(); } - - //we need to do the lookup from the repo - var entityCollection = getFromRepo(new TId[] { }) - //ensure we don't include any null refs in the returned collection! - .WhereNotNull() - .ToArray(); - - //set the disposal action - SetCacheAction(entityCollection); - - return entityCollection; } - /// - /// For this type of caching policy, we don't cache individual items - /// - /// - /// - protected void SetCacheAction(string cacheKey, TEntity entity) + /// + public override void Delete(TEntity entity, Action persistDeleted) { - //No-op - } + if (entity == null) throw new ArgumentNullException("entity"); - /// - /// Sets the action to execute on disposal for an entity collection - /// - /// - protected void SetCacheAction(TEntity[] entityCollection) - { - //set the disposal action - SetCacheAction(() => + try { - //We want to cache the result as a single collection - - if (_expires) - { - Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection), - timeout: TimeSpan.FromMinutes(5), - isSliding: true); - } - else - { - Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection)); - } - }); + persistDeleted(entity); + } + finally + { + ClearAll(); + } } - /// - /// Looks up the zero count cache, must return null if it doesn't exist - /// - /// - protected bool HasZeroCountCache() + /// + public override TEntity Get(TId id, Func performGet, Func> performGetAll) { - if (_hasZeroCountCache.HasValue) - return _hasZeroCountCache.Value; + // get all from the cache, then look for the entity + var all = GetAllCached(performGetAll); + var entity = all.FirstOrDefault(x => _entityGetId(x).Equals(id)); - _hasZeroCountCache = Cache.GetCacheItem>(GetCacheTypeKey()) != null; - return _hasZeroCountCache.Value; + // see note in InsertEntities - what we get here is the original + // cached entity, not a clone, so we need to manually ensure it is deep-cloned. + return entity == null ? null : (TEntity) entity.DeepClone(); } - /// - /// This policy will cache the full data set as a single collection - /// - /// - protected TEntity[] GetAllFromCache() + /// + public override TEntity GetCached(TId id) { - var found = Cache.GetCacheItem>(GetCacheTypeKey()); + // get all from the cache -- and only the cache, then look for the entity + var all = Cache.GetCacheItem>(GetEntityTypeCacheKey()); + var entity = all == null ? null : all.FirstOrDefault(x => _entityGetId(x).Equals(id)); - //This method will get called before checking for zero count cache, so we'll just set the flag here - _hasZeroCountCache = found != null; - - return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray(); + // see note in InsertEntities - what we get here is the original + // cached entity, not a clone, so we need to manually ensure it is deep-cloned. + return entity == null ? null : (TEntity)entity.DeepClone(); } + /// + public override bool Exists(TId id, Func performExits, Func> performGetAll) + { + // get all as one set, then look for the entity + var all = GetAllCached(performGetAll); + return all.Any(x => _entityGetId(x).Equals(id)); + } + + /// + public override TEntity[] GetAll(TId[] ids, Func> performGetAll) + { + // get all as one set, from cache if possible, else repo + var all = GetAllCached(performGetAll); + + // if ids have been specified, filter + if (ids.Length > 0) all = all.Where(x => ids.Contains(_entityGetId(x))); + + // and return + // see note in SetCacheActionToInsertEntities - what we get here is the original + // cached entities, not clones, so we need to manually ensure they are deep-cloned. + return all.Select(x => (TEntity) x.DeepClone()).ToArray(); + } + + // does NOT clone anything, so be nice with the returned values + private IEnumerable GetAllCached(Func> performGetAll) + { + // try the cache first + var all = Cache.GetCacheItem>(GetEntityTypeCacheKey()); + if (all != null) return all.ToArray(); + + // else get from repo and cache + var entities = performGetAll(EmptyIds).WhereNotNull().ToArray(); + InsertEntities(entities); // may be an empty array... + return entities; + } + + /// + public override void ClearAll() + { + Cache.ClearCacheItem(GetEntityTypeCacheKey()); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs deleted file mode 100644 index e4addcf355..0000000000 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Cache -{ - /// - /// Creates cache policies - /// - /// - /// - internal class FullDataSetRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory - where TEntity : class, IAggregateRoot - { - private readonly IRuntimeCacheProvider _runtimeCache; - private readonly Func _getEntityId; - private readonly Func> _getAllFromRepo; - private readonly bool _expires; - - public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, Func getEntityId, Func> getAllFromRepo, bool expires) - { - _runtimeCache = runtimeCache; - _getEntityId = getEntityId; - _getAllFromRepo = getAllFromRepo; - _expires = expires; - } - - public virtual IRepositoryCachePolicy CreatePolicy() - { - return new FullDataSetRepositoryCachePolicy(_runtimeCache, _getEntityId, _getAllFromRepo, _expires); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs index 215487c3be..cd37ac1e60 100644 --- a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs @@ -1,18 +1,90 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { - internal interface IRepositoryCachePolicy : IDisposable + internal interface IRepositoryCachePolicy where TEntity : class, IAggregateRoot { - TEntity Get(TId id, Func getFromRepo); - TEntity Get(TId id); - bool Exists(TId id, Func getFromRepo); - - void CreateOrUpdate(TEntity entity, Action persistMethod); - void Remove(TEntity entity, Action persistMethod); - TEntity[] GetAll(TId[] ids, Func> getFromRepo); + // note: + // at the moment each repository instance creates its corresponding cache policy instance + // we could reduce allocations by using static cache policy instances but then we would need + // to modify all methods here to pass the repository and cache eg: + // + // TEntity Get(TRepository repository, IRuntimeCacheProvider cache, TId id); + // + // it is not *that* complicated but then RepositoryBase needs to have a TRepository generic + // type parameter and it all becomes convoluted - keeping it simple for the time being. + + // fixme explain + IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope); + + /// + /// Gets an entity from the cache, else from the repository. + /// + /// The identifier. + /// The repository PerformGet method. + /// The repository PerformGetAll method. + /// The entity with the specified identifier, if it exits, else null. + /// First considers the cache then the repository. + TEntity Get(TId id, Func performGet, Func> performGetAll); + + /// + /// Gets an entity from the cache. + /// + /// The identifier. + /// The entity with the specified identifier, if it is in the cache already, else null. + /// Does not consider the repository at all. + TEntity GetCached(TId id); + + /// + /// Gets a value indicating whether an entity with a specified identifier exists. + /// + /// The identifier. + /// The repository PerformExists method. + /// The repository PerformGetAll method. + /// A value indicating whether an entity with the specified identifier exists. + /// First considers the cache then the repository. + bool Exists(TId id, Func performExists, Func> performGetAll); + + /// + /// Creates an entity. + /// + /// The entity. + /// The repository PersistNewItem method. + /// Creates the entity in the repository, and updates the cache accordingly. + void Create(TEntity entity, Action persistNew); + + /// + /// Updates an entity. + /// + /// The entity. + /// The reopsitory PersistUpdatedItem method. + /// Updates the entity in the repository, and updates the cache accordingly. + void Update(TEntity entity, Action persistUpdated); + + /// + /// Removes an entity. + /// + /// The entity. + /// The repository PersistDeletedItem method. + /// Removes the entity from the repository and clears the cache. + void Delete(TEntity entity, Action persistDeleted); + + /// + /// Gets entities. + /// + /// The identifiers. + /// The repository PerformGetAll method. + /// If is empty, all entities, else the entities with the specified identifiers. + /// Get all the entities. Either from the cache or the repository depending on the implementation. + TEntity[] GetAll(TId[] ids, Func> performGetAll); + + /// + /// Clears the entire cache. + /// + void ClearAll(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs deleted file mode 100644 index 2d69704b63..0000000000 --- a/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Cache -{ - internal interface IRepositoryCachePolicyFactory where TEntity : class, IAggregateRoot - { - IRepositoryCachePolicy CreatePolicy(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/NoRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/NoRepositoryCachePolicy.cs new file mode 100644 index 0000000000..54891af48b --- /dev/null +++ b/src/Umbraco.Core/Cache/NoRepositoryCachePolicy.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Cache +{ + internal class NoRepositoryCachePolicy : IRepositoryCachePolicy + where TEntity : class, IAggregateRoot + { + private static readonly NoRepositoryCachePolicy StaticInstance = new NoRepositoryCachePolicy(); + + private NoRepositoryCachePolicy() + { } + + public static NoRepositoryCachePolicy Instance { get { return StaticInstance; } } + + public IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) + { + throw new NotImplementedException(); + } + + public TEntity Get(TId id, Func performGet, Func> performGetAll) + { + return performGet(id); + } + + public TEntity GetCached(TId id) + { + return null; + } + + public bool Exists(TId id, Func performExists, Func> performGetAll) + { + return performExists(id); + } + + public void Create(TEntity entity, Action persistNew) + { + persistNew(entity); + } + + public void Update(TEntity entity, Action persistUpdated) + { + persistUpdated(entity); + } + + public void Delete(TEntity entity, Action persistDeleted) + { + persistDeleted(entity); + } + + public TEntity[] GetAll(TId[] ids, Func> performGetAll) + { + return performGetAll(ids).ToArray(); + } + + public void ClearAll() + { } + } +} diff --git a/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs deleted file mode 100644 index b24838bc3b..0000000000 --- a/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Cache -{ - /// - /// Creates cache policies - /// - /// - /// - internal class OnlySingleItemsRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory - where TEntity : class, IAggregateRoot - { - private readonly IRuntimeCacheProvider _runtimeCache; - private readonly RepositoryCachePolicyOptions _options; - - public OnlySingleItemsRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, RepositoryCachePolicyOptions options) - { - _runtimeCache = runtimeCache; - _options = options; - } - - public virtual IRepositoryCachePolicy CreatePolicy() - { - return new SingleItemsOnlyRepositoryCachePolicy(_runtimeCache, _options); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs index b939cd14e6..0b5d2b15c5 100644 --- a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs @@ -1,48 +1,51 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { - internal abstract class RepositoryCachePolicyBase : DisposableObject, IRepositoryCachePolicy + /// + /// A base class for repository cache policies. + /// + /// The type of the entity. + /// The type of the identifier. + internal abstract class RepositoryCachePolicyBase : IRepositoryCachePolicy where TEntity : class, IAggregateRoot { - private Action _action; - protected RepositoryCachePolicyBase(IRuntimeCacheProvider cache) { - if (cache == null) throw new ArgumentNullException("cache"); - + if (cache == null) throw new ArgumentNullException("cache"); Cache = cache; } + public abstract IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope); + protected IRuntimeCacheProvider Cache { get; private set; } - /// - /// The disposal performs the caching - /// - protected override void DisposeResources() - { - if (_action != null) - { - _action(); - } - } + /// + public abstract TEntity Get(TId id, Func performGet, Func> performGetAll); - /// - /// Sets the action to execute on disposal - /// - /// - protected void SetCacheAction(Action action) - { - _action = action; - } + /// + public abstract TEntity GetCached(TId id); + + /// + public abstract bool Exists(TId id, Func performExists, Func> performGetAll); + + /// + public abstract void Create(TEntity entity, Action persistNew); + + /// + public abstract void Update(TEntity entity, Action persistUpdated); + + /// + public abstract void Delete(TEntity entity, Action persistDeleted); + + /// + public abstract TEntity[] GetAll(TId[] ids, Func> performGetAll); + + /// + public abstract void ClearAll(); - public abstract TEntity Get(TId id, Func getFromRepo); - public abstract TEntity Get(TId id); - public abstract bool Exists(TId id, Func getFromRepo); - public abstract void CreateOrUpdate(TEntity entity, Action persistMethod); - public abstract void Remove(TEntity entity, Action persistMethod); - public abstract TEntity[] GetAll(TId[] ids, Func> getFromRepo); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs index e8c6ac02b0..14cef76db6 100644 --- a/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs @@ -2,6 +2,9 @@ using System; namespace Umbraco.Core.Cache { + /// + /// Specifies how a repository cache policy should cache entities. + /// internal class RepositoryCachePolicyOptions { /// diff --git a/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs new file mode 100644 index 0000000000..f4244a49a1 --- /dev/null +++ b/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Cache +{ + internal class ScopedRepositoryCachePolicy : IRepositoryCachePolicy + where TEntity : class, IAggregateRoot + { + private readonly IRepositoryCachePolicy _cachePolicy; + private readonly IRuntimeCacheProvider _globalIsolatedCache; + private readonly IScope _scope; + + public ScopedRepositoryCachePolicy(IRepositoryCachePolicy cachePolicy, IRuntimeCacheProvider globalIsolatedCache, IScope scope) + { + _cachePolicy = cachePolicy; + _globalIsolatedCache = globalIsolatedCache; + _scope = scope; + } + + public IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) + { + throw new InvalidOperationException(); // obviously + } + + public TEntity Get(TId id, Func performGet, Func> performGetAll) + { + // loads into the local cache only, ok for now + return _cachePolicy.Get(id, performGet, performGetAll); + } + + public TEntity GetCached(TId id) + { + // loads into the local cache only, ok for now + return _cachePolicy.GetCached(id); + } + + public bool Exists(TId id, Func performExists, Func> performGetAll) + { + // loads into the local cache only, ok for now + return _cachePolicy.Exists(id, performExists, performGetAll); + } + + public void Create(TEntity entity, Action persistNew) + { + // writes into the local cache + _cachePolicy.Create(entity, persistNew); + } + + public void Update(TEntity entity, Action persistUpdated) + { + // writes into the local cache + _cachePolicy.Update(entity, persistUpdated); + } + + public void Delete(TEntity entity, Action persistDeleted) + { + // deletes the local cache + _cachePolicy.Delete(entity, persistDeleted); + } + + public TEntity[] GetAll(TId[] ids, Func> performGetAll) + { + // loads into the local cache only, ok for now + return _cachePolicy.GetAll(ids, performGetAll); + } + + public void ClearAll() + { + // clears the local cache + _cachePolicy.ClearAll(); + } + } +} diff --git a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs index 28ac4ee2d1..7ba7d445fe 100644 --- a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs @@ -1,24 +1,27 @@ -using System.Linq; -using Umbraco.Core.Collections; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Cache { /// - /// A caching policy that ignores all caches for GetAll - it will only cache calls for individual items + /// Represents a special policy that does not cache the result of GetAll. /// - /// - /// + /// The type of the entity. + /// The type of the identifier. + /// + /// Overrides the default repository cache policy and does not writes the result of GetAll + /// to cache, but only the result of individual Gets. It does read the cache for GetAll, though. + /// Used by DictionaryRepository. + /// internal class SingleItemsOnlyRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IAggregateRoot { - public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) : base(cache, options) + public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) + : base(cache, options) + { } + + protected override void InsertEntities(TId[] ids, TEntity[] entities) { - } - - protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection) - { - //no-op + // nop } } } \ No newline at end of file diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs index 0dc5f5b00f..dbf3f89714 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/CacheHelper.cs @@ -21,6 +21,10 @@ namespace Umbraco.Core private static readonly ICacheProvider NullRequestCache = new NullCacheProvider(); private static readonly ICacheProvider NullStaticCache = new NullCacheProvider(); private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider(); + private static readonly IsolatedRuntimeCache NullIsolatedCache = new IsolatedRuntimeCache(_ => NullRuntimeCache); + private static readonly CacheHelper NullCache = new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, NullIsolatedCache); + + public static CacheHelper NoCache { get { return NullCache; } } /// /// Creates a cache helper with disabled caches @@ -31,7 +35,10 @@ namespace Umbraco.Core /// public static CacheHelper CreateDisabledCacheHelper() { - return new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, new IsolatedRuntimeCache(t => NullRuntimeCache)); + // do *not* return NoCache + // NoCache is a special instance that is detected by RepositoryBase and disables all cache policies + // CreateDisabledCacheHelper is used in tests to use no cache, *but* keep all cache policies + return new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, NullIsolatedCache); } /// diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs new file mode 100644 index 0000000000..0bb9de6c86 --- /dev/null +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Umbraco.Core.CodeAnnotations +{ + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] + internal class UmbracoUdiTypeAttribute : Attribute + { + public string UdiType { get; private set; } + + public UmbracoUdiTypeAttribute(string udiType) + { + UdiType = udiType; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 525bff2999..c28f398333 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -212,7 +212,7 @@ namespace Umbraco.Core.Configuration { get { - var settings = ConfigurationManager.ConnectionStrings[UmbracoConnectionName]; + var settings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; var connectionString = string.Empty; if (settings != null) @@ -241,10 +241,7 @@ namespace Umbraco.Core.Configuration } } } - - //TODO: Move these to constants! - public const string UmbracoConnectionName = "umbracoDbDSN"; - public const string UmbracoMigrationName = "Umbraco"; + /// /// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance. diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 7169a3a80b..04e57c5e1d 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -4,8 +4,7 @@ using System.Configuration; namespace Umbraco.Core.Configuration.UmbracoSettings { - - internal class ContentElement : ConfigurationElement, IContentSection + internal class ContentElement : UmbracoConfigurationElement, IContentSection { [ConfigurationProperty("imaging")] internal ContentImagingElement Imaging @@ -22,25 +21,13 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("ResolveUrlsFromTextString")] internal InnerTextConfigurationElement ResolveUrlsFromTextString { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["ResolveUrlsFromTextString"], - //set the default - false); - } + get { return GetOptionalTextElement("ResolveUrlsFromTextString", false); } } [ConfigurationProperty("UploadAllowDirectories")] internal InnerTextConfigurationElement UploadAllowDirectories { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["UploadAllowDirectories"], - //set the default - true); - } + get { return GetOptionalTextElement("UploadAllowDirectories", true); } } public IEnumerable Error404Collection @@ -63,121 +50,61 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("ensureUniqueNaming")] internal InnerTextConfigurationElement EnsureUniqueNaming { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["ensureUniqueNaming"], - //set the default - true); - } + get { return GetOptionalTextElement("ensureUniqueNaming", true); } } [ConfigurationProperty("TidyEditorContent")] internal InnerTextConfigurationElement TidyEditorContent { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["TidyEditorContent"], - //set the default - false); - } + get { return GetOptionalTextElement("TidyEditorContent", false); } } [ConfigurationProperty("TidyCharEncoding")] internal InnerTextConfigurationElement TidyCharEncoding { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["TidyCharEncoding"], - //set the default - "UTF8"); - } + get { return GetOptionalTextElement("TidyCharEncoding", "UTF8"); } } [ConfigurationProperty("XmlCacheEnabled")] internal InnerTextConfigurationElement XmlCacheEnabled { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["XmlCacheEnabled"], - //set the default - true); - } + get { return GetOptionalTextElement("XmlCacheEnabled", true); } } [ConfigurationProperty("ContinouslyUpdateXmlDiskCache")] internal InnerTextConfigurationElement ContinouslyUpdateXmlDiskCache { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["ContinouslyUpdateXmlDiskCache"], - //set the default - true); - } + get { return GetOptionalTextElement("ContinouslyUpdateXmlDiskCache", true); } } [ConfigurationProperty("XmlContentCheckForDiskChanges")] internal InnerTextConfigurationElement XmlContentCheckForDiskChanges { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["XmlContentCheckForDiskChanges"], - //set the default - false); - } + get { return GetOptionalTextElement("XmlContentCheckForDiskChanges", false); } } [ConfigurationProperty("EnableSplashWhileLoading")] internal InnerTextConfigurationElement EnableSplashWhileLoading { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["EnableSplashWhileLoading"], - //set the default - false); - } + get { return GetOptionalTextElement("EnableSplashWhileLoading", false); } } [ConfigurationProperty("PropertyContextHelpOption")] internal InnerTextConfigurationElement PropertyContextHelpOption { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["PropertyContextHelpOption"], - //set the default - "text"); - } + get { return GetOptionalTextElement("PropertyContextHelpOption", "text"); } } [ConfigurationProperty("UseLegacyXmlSchema")] internal InnerTextConfigurationElement UseLegacyXmlSchema { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["UseLegacyXmlSchema"], - //set the default - false); - } + get { return GetOptionalTextElement("UseLegacyXmlSchema", false); } } [ConfigurationProperty("ForceSafeAliases")] internal InnerTextConfigurationElement ForceSafeAliases { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["ForceSafeAliases"], - //set the default - true); - } + get { return GetOptionalTextElement("ForceSafeAliases", true); } } [ConfigurationProperty("PreviewBadge")] @@ -185,123 +112,69 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { get { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["PreviewBadge"], - //set the default - @"In Preview Mode - click to end"); + return GetOptionalTextElement("PreviewBadge", @"In Preview Mode - click to end"); } } [ConfigurationProperty("UmbracoLibraryCacheDuration")] internal InnerTextConfigurationElement UmbracoLibraryCacheDuration { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["UmbracoLibraryCacheDuration"], - //set the default - 1800); - - } + get { return GetOptionalTextElement("UmbracoLibraryCacheDuration", 1800); } } [ConfigurationProperty("MacroErrors")] internal InnerTextConfigurationElement MacroErrors { - get - { - - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["MacroErrors"], - //set the default - MacroErrorBehaviour.Inline); - } + get { return GetOptionalTextElement("MacroErrors", MacroErrorBehaviour.Inline); } } [Obsolete("This is here so that if this config element exists we won't get a YSOD, it is not used whatsoever and will be removed in future versions")] [ConfigurationProperty("DocumentTypeIconList")] internal InnerTextConfigurationElement DocumentTypeIconList { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["DocumentTypeIconList"], - //set the default - IconPickerBehaviour.HideFileDuplicates); - } + get { return GetOptionalTextElement("DocumentTypeIconList", IconPickerBehaviour.HideFileDuplicates); } } [ConfigurationProperty("disallowedUploadFiles")] internal CommaDelimitedConfigurationElement DisallowedUploadFiles { - get - { - return new OptionalCommaDelimitedConfigurationElement( - (CommaDelimitedConfigurationElement)this["disallowedUploadFiles"], - //set the default - new[] { "ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd" }); - - } + get { return GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); } } [ConfigurationProperty("cloneXmlContent")] internal InnerTextConfigurationElement CloneXmlContent { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["cloneXmlContent"], - //set the default - true); - } + get { return GetOptionalTextElement("cloneXmlContent", true); } } [ConfigurationProperty("GlobalPreviewStorageEnabled")] internal InnerTextConfigurationElement GlobalPreviewStorageEnabled { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["GlobalPreviewStorageEnabled"], - //set the default - false); - } + get { return GetOptionalTextElement("GlobalPreviewStorageEnabled", false); } } [ConfigurationProperty("defaultDocumentTypeProperty")] internal InnerTextConfigurationElement DefaultDocumentTypeProperty { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["defaultDocumentTypeProperty"], - //set the default - "Textstring"); - } + get { return GetOptionalTextElement("defaultDocumentTypeProperty", "Textstring"); } + } + + [ConfigurationProperty("showDeprecatedPropertyEditors")] + internal InnerTextConfigurationElement ShowDeprecatedPropertyEditors + { + get { return GetOptionalTextElement("showDeprecatedPropertyEditors", false); } } [ConfigurationProperty("EnableInheritedDocumentTypes")] internal InnerTextConfigurationElement EnableInheritedDocumentTypes { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement) this["EnableInheritedDocumentTypes"], - //set the default - true); - } + get { return GetOptionalTextElement("EnableInheritedDocumentTypes", true); } } [ConfigurationProperty("EnableInheritedMediaTypes")] internal InnerTextConfigurationElement EnableInheritedMediaTypes { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["EnableInheritedMediaTypes"], - //set the default - true); - } + get { return GetOptionalTextElement("EnableInheritedMediaTypes", true); } } [ConfigurationProperty("EnablePropertyValueConverters")] @@ -451,6 +324,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return DefaultDocumentTypeProperty; } } + bool IContentSection.ShowDeprecatedPropertyEditors + { + get { return ShowDeprecatedPropertyEditors; } + } + bool IContentSection.EnableInheritedDocumentTypes { get { return EnableInheritedDocumentTypes; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs index 0d14609caa..f60e964a04 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs @@ -3,42 +3,24 @@ using System.Configuration; namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class ContentScriptEditorElement : ConfigurationElement + internal class ContentScriptEditorElement : UmbracoConfigurationElement { [ConfigurationProperty("scriptFolderPath")] internal InnerTextConfigurationElement ScriptFolderPath { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["scriptFolderPath"], - //set the default - "/scripts"); - } + get { return GetOptionalTextElement("scriptFolderPath", "/scripts"); } } [ConfigurationProperty("scriptFileTypes")] internal OptionalCommaDelimitedConfigurationElement ScriptFileTypes { - get - { - return new OptionalCommaDelimitedConfigurationElement( - (OptionalCommaDelimitedConfigurationElement)this["scriptFileTypes"], - //set the default - new[] { "js", "xml" }); - } + get { return GetOptionalDelimitedElement("scriptFileTypes", new[] {"js", "xml"}); } } [ConfigurationProperty("scriptDisableEditor")] internal InnerTextConfigurationElement ScriptEditorDisable { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement) this["scriptDisableEditor"], - //set the default - false); - } + get { return GetOptionalTextElement("scriptDisableEditor", false); } } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index 4b9c0304ce..5dbf9f1a33 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -60,6 +60,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string DefaultDocumentTypeProperty { get; } + /// + /// The default for this is false but if you would like deprecated property editors displayed + /// in the data type editor you can enable this + /// + bool ShowDeprecatedPropertyEditors { get; } + bool EnableInheritedDocumentTypes { get; } bool EnableInheritedMediaTypes { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs index 0dfc4afc00..eafe43817d 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class ImagingAutoFillUploadFieldElement : ConfigurationElement, IImagingAutoFillUploadField + internal class ImagingAutoFillUploadFieldElement : UmbracoConfigurationElement, IImagingAutoFillUploadField { /// /// Allow setting internally so we can create a default @@ -17,49 +17,25 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("widthFieldAlias")] internal InnerTextConfigurationElement WidthFieldAlias { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["widthFieldAlias"], - //set the default - "umbracoWidth"); - } + get { return GetOptionalTextElement("widthFieldAlias", "umbracoWidth"); } } [ConfigurationProperty("heightFieldAlias")] internal InnerTextConfigurationElement HeightFieldAlias { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["heightFieldAlias"], - //set the default - "umbracoHeight"); - } + get { return GetOptionalTextElement("heightFieldAlias", "umbracoHeight"); } } [ConfigurationProperty("lengthFieldAlias")] internal InnerTextConfigurationElement LengthFieldAlias { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["lengthFieldAlias"], - //set the default - "umbracoBytes"); - } + get { return GetOptionalTextElement("lengthFieldAlias", "umbracoBytes"); } } [ConfigurationProperty("extensionFieldAlias")] internal InnerTextConfigurationElement ExtensionFieldAlias { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["extensionFieldAlias"], - //set the default - "umbracoExtension"); - } + get { return GetOptionalTextElement("extensionFieldAlias", "umbracoExtension"); } } string IImagingAutoFillUploadField.Alias diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs index f95a9c7e76..39e6327b3a 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs @@ -3,67 +3,37 @@ using System.Configuration; namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class LoggingElement : ConfigurationElement, ILoggingSection + internal class LoggingElement : UmbracoConfigurationElement, ILoggingSection { [ConfigurationProperty("autoCleanLogs")] internal InnerTextConfigurationElement AutoCleanLogs { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["autoCleanLogs"], - //set the default - false); - } + get { return GetOptionalTextElement("autoCleanLogs", false); } } [ConfigurationProperty("enableLogging")] internal InnerTextConfigurationElement EnableLogging { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["enableLogging"], - //set the default - true); - } + get { return GetOptionalTextElement("enableLogging", true); } } [ConfigurationProperty("enableAsyncLogging")] internal InnerTextConfigurationElement EnableAsyncLogging { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["enableAsyncLogging"], - //set the default - true); - } + get { return GetOptionalTextElement("enableAsyncLogging", true); } } [ConfigurationProperty("cleaningMiliseconds")] internal InnerTextConfigurationElement CleaningMiliseconds { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["cleaningMiliseconds"], - //set the default - -1); - } + get { return GetOptionalTextElement("cleaningMiliseconds", -1); } } [ConfigurationProperty("maxLogAge")] internal InnerTextConfigurationElement MaxLogAge { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["maxLogAge"], - //set the default - -1); - } + get { return GetOptionalTextElement("maxLogAge", -1); } } [ConfigurationCollection(typeof(DisabledLogTypesCollection), AddItemName = "logTypeAlias")] diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs index 16eb943887..89e3f447ee 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class NotificationsElement : ConfigurationElement + internal class NotificationsElement : UmbracoConfigurationElement { [ConfigurationProperty("email")] internal InnerTextConfigurationElement NotificationEmailAddress @@ -13,13 +13,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("disableHtmlEmail")] internal InnerTextConfigurationElement DisableHtmlEmail { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement) this["disableHtmlEmail"], - //set the default - false); - } + get { return GetOptionalTextElement("disableHtmlEmail", false); } } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs index 9dc4a94824..779d33c8b8 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs @@ -5,30 +5,18 @@ using System.Collections.Generic; namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class RequestHandlerElement : ConfigurationElement, IRequestHandlerSection + internal class RequestHandlerElement : UmbracoConfigurationElement, IRequestHandlerSection { [ConfigurationProperty("useDomainPrefixes")] public InnerTextConfigurationElement UseDomainPrefixes { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["useDomainPrefixes"], - //set the default - false); - } + get { return GetOptionalTextElement("useDomainPrefixes", false); } } [ConfigurationProperty("addTrailingSlash")] public InnerTextConfigurationElement AddTrailingSlash { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["addTrailingSlash"], - //set the default - true); - } + get { return GetOptionalTextElement("addTrailingSlash", true); } } private UrlReplacingElement _defaultUrlReplacing; diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs index f280b3e20c..ddb168ddbd 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs @@ -2,66 +2,36 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class SecurityElement : ConfigurationElement, ISecuritySection + internal class SecurityElement : UmbracoConfigurationElement, ISecuritySection { [ConfigurationProperty("keepUserLoggedIn")] internal InnerTextConfigurationElement KeepUserLoggedIn { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["keepUserLoggedIn"], - //set the default - true); - } + get { return GetOptionalTextElement("keepUserLoggedIn", true); } } [ConfigurationProperty("hideDisabledUsersInBackoffice")] internal InnerTextConfigurationElement HideDisabledUsersInBackoffice { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["hideDisabledUsersInBackoffice"], - //set the default - false); - } + get { return GetOptionalTextElement("hideDisabledUsersInBackoffice", false); } } [ConfigurationProperty("allowPasswordReset")] internal InnerTextConfigurationElement AllowPasswordReset { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["allowPasswordReset"], - //set the default - true); - } + get { return GetOptionalTextElement("allowPasswordReset", true); } } [ConfigurationProperty("authCookieName")] internal InnerTextConfigurationElement AuthCookieName { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["authCookieName"], - //set the default - Constants.Web.AuthCookieName); - } + get { return GetOptionalTextElement("authCookieName", Constants.Web.AuthCookieName); } } [ConfigurationProperty("authCookieDomain")] internal InnerTextConfigurationElement AuthCookieDomain { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["authCookieDomain"], - //set the default - null); - } + get { return GetOptionalTextElement("authCookieDomain", null); } } bool ISecuritySection.KeepUserLoggedIn diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs index c7eb659766..4e249df3a2 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs @@ -3,55 +3,31 @@ using System.Configuration; namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class TemplatesElement : ConfigurationElement, ITemplatesSection + internal class TemplatesElement : UmbracoConfigurationElement, ITemplatesSection { [ConfigurationProperty("useAspNetMasterPages")] internal InnerTextConfigurationElement UseAspNetMasterPages { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["useAspNetMasterPages"], - //set the default - true); - } + get { return GetOptionalTextElement("useAspNetMasterPages", true); } } [ConfigurationProperty("enableSkinSupport")] internal InnerTextConfigurationElement EnableSkinSupport { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["enableSkinSupport"], - //set the default - true); - } + get { return GetOptionalTextElement("enableSkinSupport", true); } } [ConfigurationProperty("defaultRenderingEngine", IsRequired = true)] internal InnerTextConfigurationElement DefaultRenderingEngine { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["defaultRenderingEngine"], - //set the default - RenderingEngine.Mvc); - } + get { return GetOptionalTextElement("defaultRenderingEngine", RenderingEngine.Mvc); } } [Obsolete("This has no affect and will be removed in future versions")] [ConfigurationProperty("enableTemplateFolders")] internal InnerTextConfigurationElement EnableTemplateFolders { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["enableTemplateFolders"], - //set the default - false); - } + get { return GetOptionalTextElement("enableTemplateFolders", false); } } bool ITemplatesSection.UseAspNetMasterPages diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoConfigurationElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoConfigurationElement.cs new file mode 100644 index 0000000000..063b5324d8 --- /dev/null +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoConfigurationElement.cs @@ -0,0 +1,36 @@ +using System.Collections.Concurrent; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + /// + /// Base class with shared helper methods + /// + internal class UmbracoConfigurationElement : ConfigurationElement + { + /// + /// Used so the RawElement types are not re-created every time they are accessed + /// + private readonly ConcurrentDictionary _rawElements = new ConcurrentDictionary(); + + protected OptionalInnerTextConfigurationElement GetOptionalTextElement(string name, T defaultVal) + { + return (OptionalInnerTextConfigurationElement) _rawElements.GetOrAdd( + name, + s => new OptionalInnerTextConfigurationElement( + (InnerTextConfigurationElement) this[s], + //set the default + defaultVal)); + } + + protected OptionalCommaDelimitedConfigurationElement GetOptionalDelimitedElement(string name, string[] defaultVal) + { + return (OptionalCommaDelimitedConfigurationElement) _rawElements.GetOrAdd( + name, + s => new OptionalCommaDelimitedConfigurationElement( + (CommaDelimitedConfigurationElement) this[name], + //set the default + defaultVal)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 9d2bf7ddae..c39b0ebf16 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 "alpha032"; } } + public static string CurrentComment { get { return "alpha068"; } } // 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-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index fb1fb42044..fd9476a8ab 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -112,9 +112,15 @@ public const string Languages = "languages"; + public const string PartialViews = "partialViews"; + + public const string PartialViewMacros = "partialViewMacros"; + + public const string Scripts = "scripts"; + //TODO: Fill in the rest! } - } + } } \ No newline at end of file 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-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-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 3f9974166c..229bd1b7d7 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -109,6 +109,11 @@ namespace Umbraco.Core /// public const string Member = "39EB0F98-B348-42A1-8662-E7EB18487560"; + /// + /// Guid for a Media Type object. + /// + public static readonly Guid MemberGuid = new Guid(Member); + /// /// Guid for a Member Group object. /// @@ -152,6 +157,46 @@ namespace Umbraco.Core /// Guid for a Lock object. /// public static readonly Guid LockObjectGuid = new Guid(LockObject); + + /// + /// Guid for a relation type. + /// + public const string RelationType = "B1988FAD-8675-4F47-915A-B3A602BC5D8D"; + + /// + /// Guid for a relation type. + /// + public static readonly Guid RelationTypeGuid = new Guid(RelationType); + + /// + /// Guid for a Forms Form. + /// + public const string FormsForm = "F5A9F787-6593-46F0-B8FF-BFD9BCA9F6BB"; + + /// + /// Guid for a Forms Form. + /// + public static readonly Guid FormsFormGuid = new Guid(FormsForm); + + /// + /// Guid for a Forms PreValue Source. + /// + public const string FormsPreValue = "42D7BF9B-A362-4FEE-B45A-674D5C064B70"; + + /// + /// Guid for a Forms PreValue Source. + /// + public static readonly Guid FormsPreValueGuid = new Guid(FormsPreValue); + + /// + /// Guid for a Forms DataSource. + /// + public const string FormsDataSource = "CFED6CE4-9359-443E-9977-9956FEB1D867"; + + /// + /// Guid for a Forms DataSource. + /// + public static readonly Guid FormsDataSourceGuid = new Guid(FormsDataSource); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 80f118b58e..189d11d3a1 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -42,10 +42,14 @@ namespace Umbraco.Core [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string ContentPicker = "158AA029-24ED-4948-939E-C3DA209E5FBA"; + + [Obsolete("This is an obsoleted content picker, use ContentPicker2Alias instead")] + public const string ContentPickerAlias = "Umbraco.ContentPickerAlias"; + /// /// Alias for the Content Picker datatype. /// - public const string ContentPickerAlias = "Umbraco.ContentPickerAlias"; + public const string ContentPicker2Alias = "Umbraco.ContentPicker2"; /// /// Guid for the Date datatype. @@ -192,11 +196,15 @@ namespace Umbraco.Core [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string MediaPicker = "EAD69342-F06D-4253-83AC-28000225583B"; + [Obsolete("This is an obsoleted picker, use MediaPicker2Alias instead")] + public const string MediaPickerAlias = "Umbraco.MediaPicker"; + /// /// Alias for the Media Picker datatype. /// - public const string MediaPickerAlias = "Umbraco.MediaPicker"; + public const string MediaPicker2Alias = "Umbraco.MediaPicker2"; + [Obsolete("This is an obsoleted picker, use MediaPicker2Alias instead")] public const string MultipleMediaPickerAlias = "Umbraco.MultipleMediaPicker"; /// @@ -205,26 +213,32 @@ namespace Umbraco.Core [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string MemberPicker = "39F533E4-0551-4505-A64B-E0425C5CE775"; + [Obsolete("This is an obsoleted picker, use MemberPicker2Alias instead")] + public const string MemberPickerAlias = "Umbraco.MemberPicker"; + /// /// Alias for the Member Picker datatype. /// - public const string MemberPickerAlias = "Umbraco.MemberPicker"; + public const string MemberPicker2Alias = "Umbraco.MemberPicker2"; /// /// Alias for the Member Group Picker datatype. /// public const string MemberGroupPickerAlias = "Umbraco.MemberGroupPicker"; - + /// /// Guid for the Multi-Node Tree Picker datatype /// [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string MultiNodeTreePicker = "7E062C13-7C41-4AD9-B389-41D88AEEF87C"; + [Obsolete("This is an obsoleted picker, use MultiNodeTreePicker2Alias instead")] + public const string MultiNodeTreePickerAlias = "Umbraco.MultiNodeTreePicker"; + /// /// Alias for the Multi-Node Tree Picker datatype /// - public const string MultiNodeTreePickerAlias = "Umbraco.MultiNodeTreePicker"; + public const string MultiNodeTreePicker2Alias = "Umbraco.MultiNodeTreePicker2"; /// /// Guid for the Multiple Textstring datatype. diff --git a/src/Umbraco.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 bc86d1717f..b38417c39b 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -25,13 +25,10 @@ public const int DefaultContentListViewDataTypeId = -95; public const int DefaultMediaListViewDataTypeId = -96; public const int DefaultMembersListViewDataTypeId = -97; - } - 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"; + public const string UmbracoConnectionName = "umbracoDbDSN"; + public const string UmbracoMigrationName = "Umbraco"; } + } } \ 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 4ef08bd02f..b4897bab7e 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -28,6 +28,7 @@ using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Publishing; using Umbraco.Core.Macros; using Umbraco.Core.Manifest; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Core.Strings; @@ -105,11 +106,14 @@ namespace Umbraco.Core LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors(); //create database and service contexts for the app context - var dbFactory = new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, ProfilingLogger.Logger); + var dbFactory = new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, ProfilingLogger.Logger); Database.Mapper = new PetaPocoMapper(); + var scopeProvider = new ScopeProvider(dbFactory); + dbFactory.ScopeProvider = scopeProvider; + var dbContext = new DatabaseContext( - dbFactory, + scopeProvider, ProfilingLogger.Logger, SqlSyntaxProviders.CreateDefault(ProfilingLogger.Logger)); @@ -117,7 +121,7 @@ namespace Umbraco.Core dbContext.Initialize(); //get the service context - var serviceContext = CreateServiceContext(dbContext, dbFactory); + var serviceContext = CreateServiceContext(dbContext, scopeProvider); //set property and singleton from response ApplicationContext.Current = ApplicationContext = CreateApplicationContext(dbContext, serviceContext); @@ -160,17 +164,16 @@ namespace Umbraco.Core /// Creates and returns the service context for the app /// /// - /// + /// /// - protected virtual ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory) + protected virtual ServiceContext CreateServiceContext(DatabaseContext dbContext, IScopeProvider scopeProvider) { //default transient factory var msgFactory = new TransientMessagesFactory(); return new ServiceContext( new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), - new PetaPocoUnitOfWorkProvider(dbFactory), - new FileUnitOfWorkProvider(), - new PublishingStrategy(msgFactory, ProfilingLogger.Logger), + new PetaPocoUnitOfWorkProvider(scopeProvider), + new FileUnitOfWorkProvider(scopeProvider), ApplicationCache, ProfilingLogger.Logger, msgFactory); @@ -415,7 +418,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 1c8b3dabee..73780cae04 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; using Umbraco.Core.Persistence.Migrations.Initial; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; namespace Umbraco.Core @@ -26,13 +27,32 @@ namespace Umbraco.Core /// public class DatabaseContext { - private readonly IDatabaseFactory _factory; + internal readonly IScopeProviderInternal ScopeProvider; private readonly ILogger _logger; private readonly SqlSyntaxProviders _syntaxProviders; private bool _configured; private string _connectionString; private string _providerName; private DatabaseSchemaResult _result; + private DateTime? _connectionLastChecked = null; + + /// + /// The number of minutes to throttle the checks to CanConnect + /// + private const int ConnectionCheckMinutes = 1; + + #region Compatibility with 7.5 + + // note: the ctors accepting IDatabaseFactory are here only for backward compatibility purpose + // + // problem: IDatabaseFactory2 adds the CreateNewDatabase() method which creates a new database + // 'cos IDatabaseFactory CreateDatabase() is supposed to also manage the ambient thing. We + // want to keep these ctors for backward compatibility reasons (in case ppl use them in tests) + // so we need to create a scope provider (else nothing would work) and so we need a IDatabaseFactory2, + // so...? + // solution: wrap IDatabaseFactory and pretend we have a IDatabaseFactory2, it *should* work in most + // cases but really, it depends on what ppl are doing in their tests... yet, cannot really see any + // other way to do it? [Obsolete("Use the constructor specifying all dependencies instead")] public DatabaseContext(IDatabaseFactory factory) @@ -42,8 +62,7 @@ namespace Umbraco.Core new SqlCeSyntaxProvider(), new SqlServerSyntaxProvider() })) - { - } + { } /// /// Default constructor @@ -57,7 +76,11 @@ namespace Umbraco.Core if (logger == null) throw new ArgumentNullException("logger"); if (syntaxProviders == null) throw new ArgumentNullException("syntaxProviders"); - _factory = factory; + var asDbFactory2 = factory as IDatabaseFactory2; + ScopeProvider = asDbFactory2 == null + ? new ScopeProvider(new DatabaseFactoryWrapper(factory)) + : new ScopeProvider(asDbFactory2); + _logger = logger; _syntaxProviders = syntaxProviders; } @@ -74,22 +97,86 @@ namespace Umbraco.Core _providerName = providerName; SqlSyntax = sqlSyntax; SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax; - _factory = factory; + + var asDbFactory2 = factory as IDatabaseFactory2; + ScopeProvider = asDbFactory2 == null + ? new ScopeProvider(new DatabaseFactoryWrapper(factory)) + : new ScopeProvider(asDbFactory2); + _logger = logger; _configured = true; } -#if DEBUG_DATABASES - public List Databases + private class DatabaseFactoryWrapper : IDatabaseFactory2 { - get + private readonly IDatabaseFactory _factory; + + public DatabaseFactoryWrapper(IDatabaseFactory factory) { - var factory = _factory as DefaultDatabaseFactory; - if (factory == null) throw new NotSupportedException(); - return factory.Databases; + _factory = factory; + } + + public UmbracoDatabase CreateDatabase() + { + return _factory.CreateDatabase(); + } + + public UmbracoDatabase CreateNewDatabase() + { + return CreateDatabase(); + } + + public void Dispose() + { + _factory.Dispose(); } } -#endif + + #endregion + + [Obsolete("Use the constructor specifying all dependencies instead")] + internal DatabaseContext(IScopeProviderInternal scopeProvider) + : this(scopeProvider, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[] + { + new MySqlSyntaxProvider(LoggerResolver.Current.Logger), + new SqlCeSyntaxProvider(), + new SqlServerSyntaxProvider() + })) + { } + + /// + /// Default constructor + /// + /// + /// + /// + internal DatabaseContext(IScopeProviderInternal scopeProvider, ILogger logger, SqlSyntaxProviders syntaxProviders) + { + if (scopeProvider == null) throw new ArgumentNullException("scopeProvider"); + if (logger == null) throw new ArgumentNullException("logger"); + if (syntaxProviders == null) throw new ArgumentNullException("syntaxProviders"); + + ScopeProvider = scopeProvider; + _logger = logger; + _syntaxProviders = syntaxProviders; + } + + /// + /// Create a configured DatabaseContext + /// + /// + /// + /// + /// + internal DatabaseContext(IScopeProviderInternal scopeProvider, ILogger logger, ISqlSyntaxProvider sqlSyntax, string providerName) + { + _providerName = providerName; + SqlSyntax = sqlSyntax; + SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax; + ScopeProvider = scopeProvider; + _logger = logger; + _configured = true; + } public ISqlSyntaxProvider SqlSyntax { get; private set; } @@ -104,7 +191,17 @@ namespace Umbraco.Core /// public virtual UmbracoDatabase Database { - get { return _factory.CreateDatabase(); } + get + { + if (IsDatabaseConfigured == false) + { + throw new InvalidOperationException("Cannot create a database instance, there is no available connection string"); + } + + return ScopeProvider.GetAmbientOrNoScope().Database; + //var scope = ScopeProvider.AmbientScope; + //return scope != null ? scope.Database : ScopeProvider.CreateNoScope().Database; + } } /// @@ -117,6 +214,8 @@ namespace Umbraco.Core /// will be properly removed from call context and does not interfere with anything else. In most case /// it is not replacing anything, just temporarily installing a database in context. /// + // fixme - this should just entirely be replaced by Scope? + /* public virtual IDisposable UseSafeDatabase(bool force = false) { var factory = _factory as DefaultDatabaseFactory; @@ -132,6 +231,7 @@ namespace Umbraco.Core // create a new, temp, database (will be disposed with UsingDatabase) return new UsingDatabase(null, factory.CreateDatabase()); } + */ /// /// Boolean indicating whether the database has been configured @@ -148,13 +248,28 @@ namespace Umbraco.Core { get { - if (IsDatabaseConfigured == false) return false; - var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider); - LogHelper.Info("CanConnect = " + canConnect); - return canConnect; + if (IsDatabaseConfigured == false) + return false; + + //Don't check again if the timeout period hasn't elapsed + //this ensures we don't keep checking the connection too many times in a row like during startup. + //Do check if the _connectionLastChecked is null which means we're just initializing or it could + //not connect last time it was checked. + if ((_connectionLastChecked.HasValue && (DateTime.Now - _connectionLastChecked.Value).TotalMinutes > ConnectionCheckMinutes) + || _connectionLastChecked.HasValue == false) + { + var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider); + LogHelper.Info("CanConnect = " + canConnect); + + _connectionLastChecked = canConnect == false ? null : (DateTime?) DateTime.Now; + return canConnect; + } + + return _connectionLastChecked.HasValue; } } + /// /// Gets the configured umbraco db connection string. /// @@ -174,14 +289,14 @@ namespace Umbraco.Core return _providerName; _providerName = Constants.DatabaseProviders.SqlServer; - if (ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName] != null) + if (ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName] != null) { - if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName) == false) - _providerName = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName; + if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName) == false) + _providerName = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName; } else { - throw new InvalidOperationException("Can't find a connection string with the name '" + GlobalSettings.UmbracoConnectionName + "'"); + throw new NullReferenceException("Can't find a connection string with the name '" + Constants.System.UmbracoConnectionName + "'"); } return _providerName; } @@ -220,15 +335,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); @@ -367,9 +477,9 @@ namespace Umbraco.Core { //Set the connection string for the new datalayer var connectionStringSettings = string.IsNullOrEmpty(providerName) - ? new ConnectionStringSettings(GlobalSettings.UmbracoConnectionName, + ? new ConnectionStringSettings(Constants.System.UmbracoConnectionName, connectionString) - : new ConnectionStringSettings(GlobalSettings.UmbracoConnectionName, + : new ConnectionStringSettings(Constants.System.UmbracoConnectionName, connectionString, providerName); _connectionString = connectionString; @@ -380,10 +490,10 @@ namespace Umbraco.Core var connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single(); // Update connectionString if it exists, or else create a new appSetting for the given key and value - var setting = connectionstrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == GlobalSettings.UmbracoConnectionName); + var setting = connectionstrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == Constants.System.UmbracoConnectionName); if (setting == null) connectionstrings.Add(new XElement("add", - new XAttribute("name", GlobalSettings.UmbracoConnectionName), + new XAttribute("name", Constants.System.UmbracoConnectionName), new XAttribute("connectionString", connectionStringSettings), new XAttribute("providerName", providerName))); else @@ -408,23 +518,23 @@ namespace Umbraco.Core /// internal void Initialize() { - var databaseSettings = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName]; + var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; if (databaseSettings != null && string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) == false && string.IsNullOrWhiteSpace(databaseSettings.ProviderName) == false) { var providerName = Constants.DatabaseProviders.SqlServer; string connString = null; - if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName)) + if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName)) { - providerName = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName; - connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString; + providerName = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName; + connString = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ConnectionString; } Initialize(providerName, connString); } - else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false) + else if (ConfigurationManager.AppSettings.ContainsKey(Constants.System.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[Constants.System.UmbracoConnectionName]) == false) { //A valid connectionstring does not exist, but the legacy appSettings key was found, so we'll reconfigure the conn.string. - var legacyConnString = ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]; + var legacyConnString = ConfigurationManager.AppSettings[Constants.System.UmbracoConnectionName]; if (legacyConnString.ToLowerInvariant().Contains("sqlce4umbraco")) { ConfigureEmbeddedDatabaseConnection(); @@ -455,7 +565,7 @@ namespace Umbraco.Core } //Remove the legacy connection string, so we don't end up in a loop if something goes wrong. - GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName); + GlobalSettings.RemoveSetting(Constants.System.UmbracoConnectionName); } else @@ -651,7 +761,7 @@ namespace Umbraco.Core //DO the upgrade! - var runner = new MigrationRunner(migrationEntryService, _logger, currentInstalledVersion, UmbracoVersion.GetSemanticVersion(), GlobalSettings.UmbracoMigrationName); + var runner = new MigrationRunner(migrationEntryService, _logger, currentInstalledVersion, UmbracoVersion.GetSemanticVersion(), Constants.System.UmbracoMigrationName); var upgraded = runner.Execute(database, true); @@ -793,6 +903,7 @@ namespace Umbraco.Core return true; } + /* private class UsingDatabase : IDisposable { private readonly UmbracoDatabase _orig; @@ -815,5 +926,6 @@ namespace Umbraco.Core GC.SuppressFinalize(this); } } + */ } } \ No newline at end of file diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs new file mode 100644 index 0000000000..a3fd160a49 --- /dev/null +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -0,0 +1,56 @@ +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; + Name = Udi.ToString(); + + 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 + + public string Name { get; set; } + public string Alias { get; set; } + } +} \ 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/DeploySupportAttribute.cs b/src/Umbraco.Core/Deploy/DeploySupportAttribute.cs deleted file mode 100644 index f7f32b52d5..0000000000 --- a/src/Umbraco.Core/Deploy/DeploySupportAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using Semver; - -namespace Umbraco.Core.Deploy -{ - [AttributeUsage(AttributeTargets.Assembly)] - public class DeploySupportAttribute : Attribute - { - public DeploySupportAttribute(string version) - { - Version = version; - } - - public SemVersion Version { get; private set; } - } -} 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..653e6386bb --- /dev/null +++ b/src/Umbraco.Core/Deploy/IArtifact.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Core.Deploy +{ + /// + /// Represents an artifact ie an object that can be transfered between environments. + /// + public interface IArtifact : IArtifactSignature + { + string Name { get; } + string Alias { get; } + } +} \ 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..7d13e0fbde --- /dev/null +++ b/src/Umbraco.Core/Deploy/IFileSource.cs @@ -0,0 +1,91 @@ +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); + + /// + /// Gets files and store them using a file store. + /// + /// The udis of the files to get. + /// A collection of file types which can store the files. + void GetFiles(IEnumerable udis, IFileTypeCollection fileTypes); + + /// + /// Gets files and store them using a file store. + /// + /// The udis of the files to get. + /// A collection of file types which can store the files. + /// A cancellation token. + Task GetFilesAsync(IEnumerable udis, IFileTypeCollection fileTypes, 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/IFileType.cs b/src/Umbraco.Core/Deploy/IFileType.cs new file mode 100644 index 0000000000..f7ab22ffae --- /dev/null +++ b/src/Umbraco.Core/Deploy/IFileType.cs @@ -0,0 +1,32 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Core.Deploy +{ + public interface IFileType + { + Stream GetStream(StringUdi udi); + + Task GetStreamAsync(StringUdi udi, CancellationToken token); + + Stream GetChecksumStream(StringUdi udi); + + long GetLength(StringUdi udi); + + void SetStream(StringUdi udi, Stream stream); + + Task SetStreamAsync(StringUdi udi, Stream stream, CancellationToken token); + + bool CanSetPhysical { get; } + + void Set(StringUdi udi, string physicalPath, bool copy = false); + + // this is not pretty as *everywhere* in Deploy we take care of ignoring + // the physical path and always rely on Core's virtual IFileSystem but + // Cloud wants to add some of these files to Git and needs the path... + string GetPhysicalPath(StringUdi udi); + + string GetVirtualPath(StringUdi udi); + } +} diff --git a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs new file mode 100644 index 0000000000..9e2ab137d1 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Deploy +{ + public interface IFileTypeCollection + { + IFileType this[string entityType] { get; } + + bool Contains(string entityType); + } +} diff --git a/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs new file mode 100644 index 0000000000..a4c5e7aeab --- /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 view. + /// + /// The grid editor view. It needs to be the view instead of the alias as the view is really what identifies what kind of connector should be used. Alias can be anything and you can have multiple different aliases using the same kind of view. + /// A value indicating whether the connector supports the grid editor view. + /// Note that can be string.Empty to indicate the "default" connector. + bool IsConnector(string view); + + /// + /// 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/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/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index 83eb080049..177e82bbf1 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; @@ -67,22 +68,15 @@ namespace Umbraco.Core } } - /// The for each. - /// The items. - /// The func. - /// item type - /// Result type - /// the Results + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use a normal foreach loop instead, this adds more allocations than necessary")] public static TResult[] ForEach(this IEnumerable items, Func func) { return items.Select(func).ToArray(); } - /// The for each. - /// The items. - /// The action. - /// Item type - /// list of TItem + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use a normal foreach loop instead, this adds more allocations than necessary")] public static IEnumerable ForEach(this IEnumerable items, Action action) { if (items != null) @@ -101,6 +95,7 @@ namespace Umbraco.Core /// The select child. /// Item type /// list of TItem + [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Do not use, use SelectRecursive instead which has far less potential of re-iterating an iterator which may cause significantly more SQL queries")] public static IEnumerable FlattenList(this IEnumerable e, Func> f) { diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs index 72cef19f7a..a102ea66ef 100644 --- a/src/Umbraco.Core/Events/CancellableEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs @@ -10,8 +10,8 @@ namespace Umbraco.Core.Events /// Event args for that can support cancellation /// [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public class CancellableEventArgs : EventArgs - { + public class CancellableEventArgs : EventArgs, IEquatable + { private bool _cancel; public CancellableEventArgs(bool canCancel, EventMessages messages, IDictionary additionalData) @@ -98,6 +98,36 @@ namespace Umbraco.Core.Events /// This allows for a bit of flexibility in our event raising - it's not pretty but we need to maintain backwards compatibility /// so we cannot change the strongly typed nature for some events. /// - public ReadOnlyDictionary AdditionalData { get; private set; } - } + public ReadOnlyDictionary AdditionalData { get; private set; } + + public bool Equals(CancellableEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(AdditionalData, other.AdditionalData); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CancellableEventArgs) obj); + } + + public override int GetHashCode() + { + return (AdditionalData != null ? AdditionalData.GetHashCode() : 0); + } + + public static bool operator ==(CancellableEventArgs left, CancellableEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) + { + return !Equals(left, right); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs index a05f09ece5..7815747f18 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Security.Permissions; using Umbraco.Core.Models; @@ -10,7 +11,7 @@ namespace Umbraco.Core.Events /// /// [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public class CancellableObjectEventArgs : CancellableEventArgs + public class CancellableObjectEventArgs : CancellableEventArgs, IEquatable> { public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) : base(canCancel, messages, additionalData) @@ -48,5 +49,37 @@ namespace Umbraco.Core.Events /// protected T EventObject { get; set; } + public bool Equals(CancellableObjectEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CancellableObjectEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject); + } + } + + public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/CopyEventArgs.cs b/src/Umbraco.Core/Events/CopyEventArgs.cs index 16f4fae982..65dc54cc88 100644 --- a/src/Umbraco.Core/Events/CopyEventArgs.cs +++ b/src/Umbraco.Core/Events/CopyEventArgs.cs @@ -1,6 +1,9 @@ +using System; +using System.Collections.Generic; + namespace Umbraco.Core.Events { - public class CopyEventArgs : CancellableObjectEventArgs + public class CopyEventArgs : CancellableObjectEventArgs, IEquatable> { public CopyEventArgs(TEntity original, TEntity copy, bool canCancel, int parentId) : base(original, canCancel) @@ -43,5 +46,42 @@ namespace Umbraco.Core.Events public int ParentId { get; private set; } public bool RelateToOriginal { get; set; } + + public bool Equals(CopyEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && EqualityComparer.Default.Equals(Copy, other.Copy) && ParentId == other.ParentId && RelateToOriginal == other.RelateToOriginal; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CopyEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Copy); + hashCode = (hashCode * 397) ^ ParentId; + hashCode = (hashCode * 397) ^ RelateToOriginal.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(CopyEventArgs left, CopyEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(CopyEventArgs left, CopyEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index 1025066bcc..df13363b95 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -1,8 +1,9 @@ +using System; using System.Collections.Generic; namespace Umbraco.Core.Events { - public class DeleteEventArgs : CancellableObjectEventArgs> + public class DeleteEventArgs : CancellableObjectEventArgs>, IEquatable>, IDeletingMediaFilesEventArgs { /// /// Constructor accepting multiple entities that are used in the delete operation @@ -99,10 +100,43 @@ namespace Umbraco.Core.Events /// /// A list of media files that can be added to during a deleted operation for which Umbraco will ensure are removed /// - public List MediaFilesToDelete { get; private set; } + public List MediaFilesToDelete { get; private set; } + + public bool Equals(DeleteEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && MediaFilesToDelete.Equals(other.MediaFilesToDelete); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DeleteEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ MediaFilesToDelete.GetHashCode(); + } + } + + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) + { + return !Equals(left, right); + } } - public class DeleteEventArgs : CancellableEventArgs + public class DeleteEventArgs : CancellableEventArgs, IEquatable { public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages) : base(canCancel, eventMessages) @@ -125,5 +159,38 @@ namespace Umbraco.Core.Events /// Gets the Id of the object being deleted. /// public int Id { get; private set; } + + public bool Equals(DeleteEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && Id == other.Id; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DeleteEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ Id; + } + } + + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs b/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs index 4d53270608..1db1296640 100644 --- a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs @@ -2,7 +2,7 @@ using System; namespace Umbraco.Core.Events { - public class DeleteRevisionsEventArgs : DeleteEventArgs + public class DeleteRevisionsEventArgs : DeleteEventArgs, IEquatable { public DeleteRevisionsEventArgs(int id, bool canCancel, Guid specificVersion = default(Guid), bool deletePriorVersions = false, DateTime dateToRetain = default(DateTime)) : base(id, canCancel) @@ -31,5 +31,42 @@ namespace Umbraco.Core.Events { get { return SpecificVersion != default(Guid); } } + + public bool Equals(DeleteRevisionsEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && DateToRetain.Equals(other.DateToRetain) && DeletePriorVersions == other.DeletePriorVersions && SpecificVersion.Equals(other.SpecificVersion); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DeleteRevisionsEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ DateToRetain.GetHashCode(); + hashCode = (hashCode * 397) ^ DeletePriorVersions.GetHashCode(); + hashCode = (hashCode * 397) ^ SpecificVersion.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/EventDefinition.cs b/src/Umbraco.Core/Events/EventDefinition.cs new file mode 100644 index 0000000000..e861e9496e --- /dev/null +++ b/src/Umbraco.Core/Events/EventDefinition.cs @@ -0,0 +1,73 @@ +using System; + +namespace Umbraco.Core.Events +{ + internal class EventDefinition : EventDefinitionBase + { + private readonly EventHandler _trackedEvent; + private readonly object _sender; + private readonly EventArgs _args; + + public EventDefinition(EventHandler trackedEvent, object sender, EventArgs args, string eventName = null) + : base(sender, args, eventName) + { + _trackedEvent = trackedEvent; + _sender = sender; + _args = args; + } + + public override void RaiseEvent() + { + if (_trackedEvent != null) + { + _trackedEvent(_sender, _args); + } + } + } + + internal class EventDefinition : EventDefinitionBase + { + private readonly EventHandler _trackedEvent; + private readonly object _sender; + private readonly TEventArgs _args; + + public EventDefinition(EventHandler trackedEvent, object sender, TEventArgs args, string eventName = null) + : base(sender, args, eventName) + { + _trackedEvent = trackedEvent; + _sender = sender; + _args = args; + } + + public override void RaiseEvent() + { + if (_trackedEvent != null) + { + _trackedEvent(_sender, _args); + } + } + } + + internal class EventDefinition : EventDefinitionBase + { + private readonly TypedEventHandler _trackedEvent; + private readonly TSender _sender; + private readonly TEventArgs _args; + + public EventDefinition(TypedEventHandler trackedEvent, TSender sender, TEventArgs args, string eventName = null) + : base(sender, args, eventName) + { + _trackedEvent = trackedEvent; + _sender = sender; + _args = args; + } + + public override void RaiseEvent() + { + if (_trackedEvent != null) + { + _trackedEvent(_sender, _args); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/EventDefinitionBase.cs b/src/Umbraco.Core/Events/EventDefinitionBase.cs new file mode 100644 index 0000000000..c0a061f80f --- /dev/null +++ b/src/Umbraco.Core/Events/EventDefinitionBase.cs @@ -0,0 +1,70 @@ +using System; +using System.Reflection; + +namespace Umbraco.Core.Events +{ + public abstract class EventDefinitionBase : IEventDefinition, IEquatable + { + protected EventDefinitionBase(object sender, object args, string eventName = null) + { + if (sender == null) throw new ArgumentNullException("sender"); + if (args == null) throw new ArgumentNullException("args"); + Sender = sender; + Args = args; + EventName = eventName; + + if (EventName.IsNullOrWhiteSpace()) + { + var findResult = EventNameExtractor.FindEvent(sender, args, + //don't match "Ing" suffixed names + exclude:EventNameExtractor.MatchIngNames); + + if (findResult.Success == false) + throw new AmbiguousMatchException("Could not automatically find the event name, the event name will need to be explicitly registered for this event definition. Error: " + findResult.Result.Error); + EventName = findResult.Result.Name; + } + } + + public object Sender { get; private set; } + public object Args { get; private set; } + public string EventName { get; private set; } + + public abstract void RaiseEvent(); + + public bool Equals(EventDefinitionBase other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Args.Equals(other.Args) && string.Equals(EventName, other.EventName) && Sender.Equals(other.Sender); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((EventDefinitionBase) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Args.GetHashCode(); + hashCode = (hashCode * 397) ^ EventName.GetHashCode(); + hashCode = (hashCode * 397) ^ Sender.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(EventDefinitionBase left, EventDefinitionBase right) + { + return Equals(left, right); + } + + public static bool operator !=(EventDefinitionBase left, EventDefinitionBase right) + { + return Equals(left, right) == false; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/EventDefinitionFilter.cs b/src/Umbraco.Core/Events/EventDefinitionFilter.cs new file mode 100644 index 0000000000..4bbe75d10b --- /dev/null +++ b/src/Umbraco.Core/Events/EventDefinitionFilter.cs @@ -0,0 +1,24 @@ +namespace Umbraco.Core.Events +{ + /// + /// The filter used in the GetEvents method which determines + /// how the result list is filtered + /// + public enum EventDefinitionFilter + { + /// + /// Returns all events tracked + /// + All, + + /// + /// Deduplicates events and only returns the first duplicate instance tracked + /// + FirstIn, + + /// + /// Deduplicates events and only returns the last duplicate instance tracked + /// + LastIn + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/EventExtensions.cs b/src/Umbraco.Core/Events/EventExtensions.cs index bd66b605fd..a366ad0cce 100644 --- a/src/Umbraco.Core/Events/EventExtensions.cs +++ b/src/Umbraco.Core/Events/EventExtensions.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; +using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Events { @@ -10,73 +12,40 @@ namespace Umbraco.Core.Events /// public static class EventExtensions { - /// - /// Raises the event and returns a boolean value indicating if the event was cancelled - /// - /// - /// - /// - /// - /// - /// - public static bool IsRaisedEventCancelled( - this TypedEventHandler eventHandler, - TArgs args, - TSender sender) - where TArgs : CancellableEventArgs - { - if (eventHandler != null) - eventHandler(sender, args); + // keep these two for backward compatibility reasons but understand that + // they are *not* part of any scope / event dispatcher / anything... + /// + /// Raises a cancelable event and returns a value indicating whether the event should be cancelled. + /// + /// The type of the event source. + /// The type of the event data. + /// The event handler. + /// The event source. + /// The event data. + /// A value indicating whether the cancelable event should be cancelled + /// A cancelable event is raised by a component when it is about to perform an action that can be canceled. + public static bool IsRaisedEventCancelled(this TypedEventHandler eventHandler, TArgs args, TSender sender) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) return args.Cancel; + eventHandler(sender, args); return args.Cancel; } - + /// - /// Raises the event + /// Raises an event. /// - /// - /// - /// - /// - /// - public static void RaiseEvent( - this TypedEventHandler eventHandler, - TArgs args, - TSender sender) - where TArgs : EventArgs - { - if (eventHandler != null) - eventHandler(sender, args); - } - - // moves the last handler that was added to an instance event, to first position - public static void PromoteLastHandler(object sender, string eventName) - { - var fieldInfo = sender.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic); - if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + "."); - PromoteLastHandler(sender, fieldInfo); - } - - // moves the last handler that was added to a static event, to first position - public static void PromoteLastHandler(string eventName) + /// The type of the event source. + /// The type of the event data. + /// The event handler. + /// The event source. + /// The event data. + public static void RaiseEvent(this TypedEventHandler eventHandler, TArgs args, TSender sender) + where TArgs : EventArgs { - var fieldInfo = typeof(TSender).GetField(eventName, BindingFlags.Static | BindingFlags.NonPublic); - if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + "."); - PromoteLastHandler(null, fieldInfo); - } - - private static void PromoteLastHandler(object sender, FieldInfo fieldInfo) - { - var d = fieldInfo.GetValue(sender) as Delegate; - if (d == null) return; - - var l = d.GetInvocationList(); - var x = l[l.Length - 1]; - for (var i = l.Length - 1; i > 0; i--) - l[i] = l[i - 1]; - l[0] = x; - - fieldInfo.SetValue(sender, Delegate.Combine(l)); + if (eventHandler == null) return; + eventHandler(sender, args); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MessageType.cs b/src/Umbraco.Core/Events/EventMessageType.cs similarity index 100% rename from src/Umbraco.Core/Events/MessageType.cs rename to src/Umbraco.Core/Events/EventMessageType.cs diff --git a/src/Umbraco.Core/Events/EventNameExtractor.cs b/src/Umbraco.Core/Events/EventNameExtractor.cs new file mode 100644 index 0000000000..33137770d4 --- /dev/null +++ b/src/Umbraco.Core/Events/EventNameExtractor.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace Umbraco.Core.Events +{ + /// + /// There is actually no way to discover an event name in c# at the time of raising the event. It is possible + /// to get the event name from the handler that is being executed based on the event being raised, however that is not + /// what we want in this case. We need to find the event name before it is being raised - you would think that it's possible + /// with reflection or anything but that is not the case, the delegate that defines an event has no info attached to it, it + /// is literally just an event. + /// + /// So what this does is take the sender and event args objects, looks up all public/static events on the sender that have + /// a generic event handler with generic arguments (but only) one, then we match the type of event arguments with the ones + /// being passed in. As it turns out, in our services this will work for the majority of our events! In some cases it may not + /// work and we'll have to supply a string but hopefully this saves a bit of magic strings. + /// + /// We can also write tests to validate these are all working correctly for all services. + /// + internal class EventNameExtractor + { + + /// + /// Finds the event name on the sender that matches the args type + /// + /// + /// + /// + /// A filter to exclude matched event names, this filter should return true to exclude the event name from being matched + /// + /// + /// null if not found or an ambiguous match + /// + public static Attempt FindEvent(Type senderType, Type argsType, Func exclude) + { + var found = MatchedEventNames.GetOrAdd(new Tuple(senderType, argsType), tuple => + { + var events = CandidateEvents.GetOrAdd(senderType, t => + { + return t.GetEvents(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + //we can only look for events handlers with generic types because that is the only + // way that we can try to find a matching event based on the arg type passed in + .Where(x => x.EventHandlerType.IsGenericType) + .Select(x => new EventInfoArgs(x, x.EventHandlerType.GetGenericArguments())) + //we are only looking for event handlers that have more than one generic argument + .Where(x => + { + if (x.GenericArgs.Length == 1) return true; + + //special case for our own TypedEventHandler + if (x.EventInfo.EventHandlerType.GetGenericTypeDefinition() == typeof(TypedEventHandler<,>) && x.GenericArgs.Length == 2) + { + return true; + } + + return false; + }) + .ToArray(); + }); + + return events.Where(x => + { + if (x.GenericArgs.Length == 1 && x.GenericArgs[0] == tuple.Item2) + return true; + + //special case for our own TypedEventHandler + if (x.EventInfo.EventHandlerType.GetGenericTypeDefinition() == typeof(TypedEventHandler<,>) + && x.GenericArgs.Length == 2 + && x.GenericArgs[1] == tuple.Item2) + { + return true; + } + + return false; + }).Select(x => x.EventInfo.Name).ToArray(); + }); + + var filtered = found.Where(x => exclude(x) == false).ToArray(); + + if (filtered.Length == 0) + return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.NoneFound)); + + if (filtered.Length == 1) + return Attempt.Succeed(new EventNameExtractorResult(filtered[0])); + + //there's more than one left so it's ambiguous! + return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.Ambiguous)); + } + + /// + /// Finds the event name on the sender that matches the args type + /// + /// + /// + /// + /// A filter to exclude matched event names, this filter should return true to exclude the event name from being matched + /// + /// + /// null if not found or an ambiguous match + /// + public static Attempt FindEvent(object sender, object args, Func exclude) + { + return FindEvent(sender.GetType(), args.GetType(), exclude); + } + + /// + /// Return true if the event is named with an ING name such as "Saving" or "RollingBack" + /// + /// + /// + internal static bool MatchIngNames(string eventName) + { + var splitter = new Regex(@"(? + /// Return true if the event is not named with an ING name such as "Saving" or "RollingBack" + /// + /// + /// + internal static bool MatchNonIngNames(string eventName) + { + var splitter = new Regex(@"(? + /// Used to cache all candidate events for a given type so we don't re-look them up + /// + private static readonly ConcurrentDictionary CandidateEvents = new ConcurrentDictionary(); + + /// + /// Used to cache all matched event names by (sender type + arg type) so we don't re-look them up + /// + private static readonly ConcurrentDictionary, string[]> MatchedEventNames = new ConcurrentDictionary, string[]>(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/EventNameExtractorError.cs b/src/Umbraco.Core/Events/EventNameExtractorError.cs new file mode 100644 index 0000000000..3b27db2a1a --- /dev/null +++ b/src/Umbraco.Core/Events/EventNameExtractorError.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Events +{ + internal enum EventNameExtractorError + { + NoneFound, + Ambiguous + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/EventNameExtractorResult.cs b/src/Umbraco.Core/Events/EventNameExtractorResult.cs new file mode 100644 index 0000000000..6213882d1f --- /dev/null +++ b/src/Umbraco.Core/Events/EventNameExtractorResult.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Events +{ + internal class EventNameExtractorResult + { + public EventNameExtractorError? Error { get; private set; } + public string Name { get; private set; } + + public EventNameExtractorResult(string name) + { + Name = name; + } + + public EventNameExtractorResult(EventNameExtractorError error) + { + Error = error; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ExportEventArgs.cs b/src/Umbraco.Core/Events/ExportEventArgs.cs index 161a073615..acbf920636 100644 --- a/src/Umbraco.Core/Events/ExportEventArgs.cs +++ b/src/Umbraco.Core/Events/ExportEventArgs.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Xml.Linq; namespace Umbraco.Core.Events { - public class ExportEventArgs : CancellableObjectEventArgs> + public class ExportEventArgs : CancellableObjectEventArgs>, IEquatable> { /// /// Constructor accepting a single entity instance @@ -48,5 +49,38 @@ namespace Umbraco.Core.Events /// Returns the xml relating to the export event /// public XElement Xml { get; private set; } + + public bool Equals(ExportEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && Equals(Xml, other.Xml); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ExportEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (Xml != null ? Xml.GetHashCode() : 0); + } + } + + public static bool operator ==(ExportEventArgs left, ExportEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(ExportEventArgs left, ExportEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs new file mode 100644 index 0000000000..45681042ba --- /dev/null +++ b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + internal interface IDeletingMediaFilesEventArgs + { + List MediaFilesToDelete { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/IEventDefinition.cs b/src/Umbraco.Core/Events/IEventDefinition.cs new file mode 100644 index 0000000000..eb4fe65eb8 --- /dev/null +++ b/src/Umbraco.Core/Events/IEventDefinition.cs @@ -0,0 +1,13 @@ +using System; + +namespace Umbraco.Core.Events +{ + public interface IEventDefinition + { + object Sender { get; } + object Args { get; } + string EventName { get; } + + void RaiseEvent(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/IEventDispatcher.cs b/src/Umbraco.Core/Events/IEventDispatcher.cs new file mode 100644 index 0000000000..78f8ed3a4a --- /dev/null +++ b/src/Umbraco.Core/Events/IEventDispatcher.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + /// + /// Dispatches events from within a scope. + /// + /// + /// The name of the event is auto-magically discovered by matching the sender type, args type, and + /// eventHandler type. If the match is not unique, then the name parameter must be used to specify the + /// name in an explicit way. + /// What happens when an event is dispatched depends on the scope settings. It can be anything from + /// "trigger immediately" to "just ignore". Refer to the scope documentation for more details. + /// + public interface IEventDispatcher + { + // not sure about the Dispatch & DispatchCancelable signatures at all for now + // nor about the event name thing, etc - but let's keep it like this + + /// + /// Dispatches a cancelable event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// A value indicating whether the cancelable event was cancelled. + /// See general remarks on the interface. + bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string name = null); + + /// + /// Dispatches a cancelable event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// A value indicating whether the cancelable event was cancelled. + /// See general remarks on the interface. + bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string name = null) + where TArgs : CancellableEventArgs; + + /// + /// Dispatches a cancelable event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// A value indicating whether the cancelable event was cancelled. + /// See general remarks on the interface. + bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string name = null) + where TArgs : CancellableEventArgs; + + /// + /// Dispatches an event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// See general remarks on the interface. + void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string name = null); + + /// + /// Dispatches an event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// See general remarks on the interface. + void Dispatch(EventHandler eventHandler, object sender, TArgs args, string name = null); + + /// + /// Dispatches an event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// See general remarks on the interface. + void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string name = null); + + /// + /// Notifies the dispatcher that the scope is exiting. + /// + /// A value indicating whether the scope completed. + void ScopeExit(bool completed); + + /// + /// Gets the collected events. + /// + /// The collected events. + IEnumerable GetEvents(EventDefinitionFilter filter); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ImportEventArgs.cs b/src/Umbraco.Core/Events/ImportEventArgs.cs index 3bdd6d6fcf..dcecf5c36b 100644 --- a/src/Umbraco.Core/Events/ImportEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportEventArgs.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Xml.Linq; namespace Umbraco.Core.Events { - public class ImportEventArgs : CancellableObjectEventArgs> + public class ImportEventArgs : CancellableObjectEventArgs>, IEquatable> { /// /// Constructor accepting an XElement with the xml being imported @@ -46,5 +47,38 @@ namespace Umbraco.Core.Events /// Returns the xml relating to the import event /// public XElement Xml { get; private set; } + + public bool Equals(ImportEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && Equals(Xml, other.Xml); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ImportEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (Xml != null ? Xml.GetHashCode() : 0); + } + } + + public static bool operator ==(ImportEventArgs left, ImportEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(ImportEventArgs left, ImportEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs index 8596629731..231e58c07e 100644 --- a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Packaging.Models; namespace Umbraco.Core.Events { - internal class ImportPackageEventArgs : CancellableObjectEventArgs> + internal class ImportPackageEventArgs : CancellableObjectEventArgs>, IEquatable> { private readonly MetaData _packageMetaData; @@ -21,6 +22,44 @@ namespace Umbraco.Core.Events public MetaData PackageMetaData { get { return _packageMetaData; } + } + + public IEnumerable InstallationSummary + { + get { return EventObject; } + } + + public bool Equals(ImportPackageEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && _packageMetaData.Equals(other._packageMetaData); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ImportPackageEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ _packageMetaData.GetHashCode(); + } + } + + public static bool operator ==(ImportPackageEventArgs left, ImportPackageEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(ImportPackageEventArgs left, ImportPackageEventArgs right) + { + return !Equals(left, right); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MigrationEventArgs.cs b/src/Umbraco.Core/Events/MigrationEventArgs.cs index 89dfe56294..8b8898e7d4 100644 --- a/src/Umbraco.Core/Events/MigrationEventArgs.cs +++ b/src/Umbraco.Core/Events/MigrationEventArgs.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Persistence.Migrations; namespace Umbraco.Core.Events { - public class MigrationEventArgs : CancellableObjectEventArgs> + public class MigrationEventArgs : CancellableObjectEventArgs>, IEquatable { /// /// Constructor accepting multiple migrations that are used in the migration runner @@ -31,13 +31,13 @@ namespace Umbraco.Core.Events [Obsolete("Use constructor accepting a product name instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel) - : this(eventObject, null, configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName, canCancel) + : this(eventObject, null, configuredVersion, targetVersion, Constants.System.UmbracoMigrationName, canCancel) { } [Obsolete("Use constructor accepting SemVersion instances and a product name instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public MigrationEventArgs(IList eventObject, Version configuredVersion, Version targetVersion, bool canCancel) - : this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), GlobalSettings.UmbracoMigrationName, canCancel) + : this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), Constants.System.UmbracoMigrationName, canCancel) { } /// @@ -74,7 +74,7 @@ namespace Umbraco.Core.Events MigrationContext = migrationContext; ConfiguredSemVersion = configuredVersion; TargetSemVersion = targetVersion; - ProductName = GlobalSettings.UmbracoMigrationName; + ProductName = Constants.System.UmbracoMigrationName; } /// @@ -97,13 +97,13 @@ namespace Umbraco.Core.Events [Obsolete("Use constructor accepting a product name instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion) - : this(eventObject, null, configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName, false) + : this(eventObject, null, configuredVersion, targetVersion, Constants.System.UmbracoMigrationName, false) { } [Obsolete("Use constructor accepting SemVersion instances and a product name instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public MigrationEventArgs(IList eventObject, Version configuredVersion, Version targetVersion) - : this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), GlobalSettings.UmbracoMigrationName, false) + : this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), Constants.System.UmbracoMigrationName, false) { } /// @@ -141,5 +141,43 @@ namespace Umbraco.Core.Events public string ProductName { get; private set; } internal MigrationContext MigrationContext { get; private set; } + + public bool Equals(MigrationEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && ConfiguredSemVersion.Equals(other.ConfiguredSemVersion) && MigrationContext.Equals(other.MigrationContext) && string.Equals(ProductName, other.ProductName) && TargetSemVersion.Equals(other.TargetSemVersion); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((MigrationEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ ConfiguredSemVersion.GetHashCode(); + hashCode = (hashCode * 397) ^ MigrationContext.GetHashCode(); + hashCode = (hashCode * 397) ^ ProductName.GetHashCode(); + hashCode = (hashCode * 397) ^ TargetSemVersion.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(MigrationEventArgs left, MigrationEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(MigrationEventArgs left, MigrationEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs index 0f0a5183a9..228e1ca2f7 100644 --- a/src/Umbraco.Core/Events/MoveEventArgs.cs +++ b/src/Umbraco.Core/Events/MoveEventArgs.cs @@ -4,7 +4,7 @@ using System.Linq; namespace Umbraco.Core.Events { - public class MoveEventArgs : CancellableObjectEventArgs + public class MoveEventArgs : CancellableObjectEventArgs, IEquatable> { /// /// Constructor accepting a collection of MoveEventInfo objects @@ -123,5 +123,38 @@ namespace Umbraco.Core.Events /// [Obsolete("Retrieve the ParentId from the MoveInfoCollection property instead")] public int ParentId { get; private set; } + + public bool Equals(MoveEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && MoveInfoCollection.Equals(other.MoveInfoCollection); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((MoveEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ MoveInfoCollection.GetHashCode(); + } + } + + public static bool operator ==(MoveEventArgs left, MoveEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(MoveEventArgs left, MoveEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MoveEventInfo.cs b/src/Umbraco.Core/Events/MoveEventInfo.cs index a74db7f36e..9e77971837 100644 --- a/src/Umbraco.Core/Events/MoveEventInfo.cs +++ b/src/Umbraco.Core/Events/MoveEventInfo.cs @@ -1,6 +1,9 @@ +using System; +using System.Collections.Generic; + namespace Umbraco.Core.Events { - public class MoveEventInfo + public class MoveEventInfo : IEquatable> { public MoveEventInfo(TEntity entity, string originalPath, int newParentId) { @@ -12,5 +15,41 @@ namespace Umbraco.Core.Events public TEntity Entity { get; set; } public string OriginalPath { get; set; } public int NewParentId { get; set; } + + public bool Equals(MoveEventInfo other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return EqualityComparer.Default.Equals(Entity, other.Entity) && NewParentId == other.NewParentId && string.Equals(OriginalPath, other.OriginalPath); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((MoveEventInfo) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = EqualityComparer.Default.GetHashCode(Entity); + hashCode = (hashCode * 397) ^ NewParentId; + hashCode = (hashCode * 397) ^ OriginalPath.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(MoveEventInfo left, MoveEventInfo right) + { + return Equals(left, right); + } + + public static bool operator !=(MoveEventInfo left, MoveEventInfo right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/NewEventArgs.cs b/src/Umbraco.Core/Events/NewEventArgs.cs index acfd64e60d..415d82b90e 100644 --- a/src/Umbraco.Core/Events/NewEventArgs.cs +++ b/src/Umbraco.Core/Events/NewEventArgs.cs @@ -1,8 +1,10 @@ +using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Events { - public class NewEventArgs : CancellableObjectEventArgs + public class NewEventArgs : CancellableObjectEventArgs, IEquatable> { @@ -84,5 +86,42 @@ namespace Umbraco.Core.Events /// Gets or Sets the parent IContent object. /// public TEntity Parent { get; private set; } + + public bool Equals(NewEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && string.Equals(Alias, other.Alias) && EqualityComparer.Default.Equals(Parent, other.Parent) && ParentId == other.ParentId; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((NewEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ Alias.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Parent); + hashCode = (hashCode * 397) ^ ParentId; + return hashCode; + } + } + + public static bool operator ==(NewEventArgs left, NewEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(NewEventArgs left, NewEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs new file mode 100644 index 0000000000..1e1a9bc6d1 --- /dev/null +++ b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Events +{ + /// + /// This event manager supports event cancellation and will raise the events as soon as they are tracked, it does not store tracked events + /// + internal class PassThroughEventDispatcher : IEventDispatcher + { + public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null) + { + if (eventHandler == null) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null) + { + if (eventHandler == null) return; + eventHandler(sender, args); + } + + public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string eventName = null) + { + if (eventHandler == null) return; + eventHandler(sender, args); + } + + public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) + { + if (eventHandler == null) return; + eventHandler(sender, args); + } + + public IEnumerable GetEvents(EventDefinitionFilter filter) + { + return Enumerable.Empty(); + } + + public void ScopeExit(bool completed) + { } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs index a791781617..1aa7c2308c 100644 --- a/src/Umbraco.Core/Events/PublishEventArgs.cs +++ b/src/Umbraco.Core/Events/PublishEventArgs.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Events { - public class PublishEventArgs : CancellableObjectEventArgs> + public class PublishEventArgs : CancellableObjectEventArgs>, IEquatable> { /// /// Constructor accepting multiple entities that are used in the publish operation @@ -101,5 +102,38 @@ namespace Umbraco.Core.Events } public bool IsAllRepublished { get; private set; } + + public bool Equals(PublishEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && IsAllRepublished == other.IsAllRepublished; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((PublishEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ IsAllRepublished.GetHashCode(); + } + } + + public static bool operator ==(PublishEventArgs left, PublishEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(PublishEventArgs left, PublishEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index ca4bbd2719..c6d7c659b7 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Events { - public class RecycleBinEventArgs : CancellableEventArgs + public class RecycleBinEventArgs : CancellableEventArgs, IEquatable, IDeletingMediaFilesEventArgs { public RecycleBinEventArgs(Guid nodeObjectType, Dictionary> allPropertyData, bool emptiedSuccessfully) : base(false) @@ -97,6 +97,8 @@ namespace Umbraco.Core.Events /// public List Files { get; private set; } + public List MediaFilesToDelete { get { return Files; } } + /// /// Gets the list of all property data associated with a content id /// @@ -122,5 +124,44 @@ namespace Umbraco.Core.Events { get { return NodeObjectType == new Guid(Constants.ObjectTypes.Media); } } + + public bool Equals(RecycleBinEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && AllPropertyData.Equals(other.AllPropertyData) && Files.Equals(other.Files) && Ids.Equals(other.Ids) && NodeObjectType.Equals(other.NodeObjectType) && RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((RecycleBinEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ AllPropertyData.GetHashCode(); + hashCode = (hashCode * 397) ^ Files.GetHashCode(); + hashCode = (hashCode * 397) ^ Ids.GetHashCode(); + hashCode = (hashCode * 397) ^ NodeObjectType.GetHashCode(); + hashCode = (hashCode * 397) ^ RecycleBinEmptiedSuccessfully.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(RecycleBinEventArgs left, RecycleBinEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(RecycleBinEventArgs left, RecycleBinEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/RollbackEventArgs.cs b/src/Umbraco.Core/Events/RollbackEventArgs.cs index db9dded08c..cf2189e962 100644 --- a/src/Umbraco.Core/Events/RollbackEventArgs.cs +++ b/src/Umbraco.Core/Events/RollbackEventArgs.cs @@ -17,5 +17,7 @@ namespace Umbraco.Core.Events { get { return EventObject; } } + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs index dafd326e1c..e816a8f8bd 100644 --- a/src/Umbraco.Core/Events/SaveEventArgs.cs +++ b/src/Umbraco.Core/Events/SaveEventArgs.cs @@ -116,5 +116,7 @@ namespace Umbraco.Core.Events { get { return EventObject; } } + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs new file mode 100644 index 0000000000..93315a9946 --- /dev/null +++ b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs @@ -0,0 +1,48 @@ +using Umbraco.Core.IO; + +namespace Umbraco.Core.Events +{ + /// + /// This event manager is created for each scope and is aware of if it is nested in an outer scope + /// + /// + /// The outer scope is the only scope that can raise events, the inner scope's will defer to the outer scope + /// + internal class ScopeEventDispatcher : ScopeEventDispatcherBase + { + public ScopeEventDispatcher() + : base(true) + { } + + protected override void ScopeExitCompleted() + { + // fixme - we'd need to de-duplicate events somehow, etc - and the deduplication should be last in wins + + foreach (var e in GetEvents(EventDefinitionFilter.All)) + { + e.RaiseEvent(); + + // fixme - not sure I like doing it here - but then where? how? + var delete = e.Args as IDeletingMediaFilesEventArgs; + if (delete != null && delete.MediaFilesToDelete.Count > 0) + MediaFileSystem.DeleteMediaFiles(delete.MediaFilesToDelete); + } + } + + private MediaFileSystem _mediaFileSystem; + + private MediaFileSystem MediaFileSystem + { + get + { + if (_mediaFileSystem != null) return _mediaFileSystem; + + // fixme - insane! reading config goes cross AppDomain and serializes context? + using (new SafeCallContext()) + { + return _mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem; + } + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs new file mode 100644 index 0000000000..d8462d18b3 --- /dev/null +++ b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Events +{ + public abstract class ScopeEventDispatcherBase : IEventDispatcher + { + private List _events; + private readonly bool _raiseCancelable; + + protected ScopeEventDispatcherBase(bool raiseCancelable) + { + _raiseCancelable = raiseCancelable; + } + + private List Events { get { return _events ?? (_events = new List()); } } + + public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null) + { + if (eventHandler == null) return args.Cancel; + if (_raiseCancelable == false) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) return args.Cancel; + if (_raiseCancelable == false) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) return args.Cancel; + if (_raiseCancelable == false) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null) + { + if (eventHandler == null) return; + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string eventName = null) + { + if (eventHandler == null) return; + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) + { + if (eventHandler == null) return; + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public IEnumerable GetEvents(EventDefinitionFilter filter) + { + if (_events == null) + return Enumerable.Empty(); + + switch (filter) + { + case EventDefinitionFilter.All: + return _events; + case EventDefinitionFilter.FirstIn: + var l1 = new OrderedHashSet(); + foreach (var e in _events) + { + l1.Add(e); + } + return l1; + case EventDefinitionFilter.LastIn: + var l2 = new OrderedHashSet(keepOldest: false); + foreach (var e in _events) + { + l2.Add(e); + } + return l2; + default: + throw new ArgumentOutOfRangeException("filter", filter, null); + } + } + + public void ScopeExit(bool completed) + { + if (_events == null) return; + if (completed) + ScopeExitCompleted(); + _events.Clear(); + } + + protected abstract void ScopeExitCompleted(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ScopeLifespanMessagesFactory.cs b/src/Umbraco.Core/Events/ScopeLifespanMessagesFactory.cs new file mode 100644 index 0000000000..710ee8a129 --- /dev/null +++ b/src/Umbraco.Core/Events/ScopeLifespanMessagesFactory.cs @@ -0,0 +1,58 @@ +using System; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Events +{ + /// + /// Stores the instance of EventMessages in the current scope. + /// + internal class ScopeLifespanMessagesFactory : IEventMessagesFactory + { + public const string ContextKey = "Umbraco.Core.Events.ScopeLifespanMessagesFactory"; + + private readonly IHttpContextAccessor _contextAccessor; + private readonly IScopeProviderInternal _scopeProvider; + + public static ScopeLifespanMessagesFactory Current { get; private set; } + + public ScopeLifespanMessagesFactory(IHttpContextAccessor contextAccesor, IScopeProvider scopeProvider) + { + if (contextAccesor == null) throw new ArgumentNullException("contextAccesor"); + if (scopeProvider == null) throw new ArgumentNullException("scopeProvider"); + if (scopeProvider is IScopeProviderInternal == false) throw new ArgumentException("Not IScopeProviderInternal.", "scopeProvider"); + _contextAccessor = contextAccesor; + _scopeProvider = (IScopeProviderInternal) scopeProvider; + Current = this; + } + + public EventMessages Get() + { + var messages = GetFromHttpContext(); + if (messages != null) return messages; + + var scope = _scopeProvider.GetAmbientOrNoScope(); + return scope.Messages; + } + + public EventMessages GetFromHttpContext() + { + if (_contextAccessor == null || _contextAccessor.Value == null) return null; + return (EventMessages)_contextAccessor.Value.Items[ContextKey]; + } + + public EventMessages TryGet() + { + var messages = GetFromHttpContext(); + if (messages != null) return messages; + + var scope = _scopeProvider.AmbientScope; + return scope == null ? null : scope.MessagesOrNull; + } + + public void Set(EventMessages messages) + { + if (_contextAccessor.Value == null) return; + _contextAccessor.Value.Items[ContextKey] = messages; + } + } +} diff --git a/src/Umbraco.Core/Events/TypedEventHandler.cs b/src/Umbraco.Core/Events/TypedEventHandler.cs index a8170190d4..113bb82f7e 100644 --- a/src/Umbraco.Core/Events/TypedEventHandler.cs +++ b/src/Umbraco.Core/Events/TypedEventHandler.cs @@ -2,6 +2,6 @@ using System; namespace Umbraco.Core.Events { - [Serializable] + [Serializable] public delegate void TypedEventHandler(TSender sender, TEventArgs e); } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs b/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs new file mode 100644 index 0000000000..324867a8f7 --- /dev/null +++ b/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Umbraco.Core.Packaging.Models; + +namespace Umbraco.Core.Events +{ + internal class UninstallPackageEventArgs : CancellableObjectEventArgs> + { + private readonly MetaData _packageMetaData; + + public UninstallPackageEventArgs(TEntity eventObject, bool canCancel) + : base(new[] { eventObject }, canCancel) + { + } + + public UninstallPackageEventArgs(TEntity eventObject, MetaData packageMetaData) + : base(new[] { eventObject }) + { + _packageMetaData = packageMetaData; + } + + public MetaData PackageMetaData + { + get { return _packageMetaData; } + } + + public IEnumerable UninstallationSummary + { + get { return EventObject; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/FileResources/Files.Designer.cs b/src/Umbraco.Core/FileResources/Files.Designer.cs index 456dae221f..500f9bf36c 100644 --- a/src/Umbraco.Core/FileResources/Files.Designer.cs +++ b/src/Umbraco.Core/FileResources/Files.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.0 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. 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/IHttpContextAccessor.cs b/src/Umbraco.Core/IHttpContextAccessor.cs new file mode 100644 index 0000000000..a0873c78a9 --- /dev/null +++ b/src/Umbraco.Core/IHttpContextAccessor.cs @@ -0,0 +1,9 @@ +using System.Web; + +namespace Umbraco.Core +{ + public interface IHttpContextAccessor + { + HttpContextBase Value { get; } + } +} diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index aeb6f9a42a..be09f1e310 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -38,13 +38,12 @@ namespace Umbraco.Core.IO } // GetSize has been added to IFileSystem2 but not IFileSystem - // this is implementing GetSize for IFileSystem, the old way public static long GetSize(this IFileSystem fs, string path) { - // if we reach this point, fs is *not* IFileSystem2 - // so it's not FileSystemWrapper nor shadow nor anything we know - // so... fall back to the old & inefficient method + var fs2 = fs as IFileSystem2; + if (fs2 != null) return fs2.GetSize(path); + // this is implementing GetSize for IFileSystem, the old way using (var file = fs.OpenFile(path)) { return file.Length; diff --git a/src/Umbraco.Core/IO/FileSystemProviderManager.cs b/src/Umbraco.Core/IO/FileSystemProviderManager.cs index d807832bcb..5f0e014012 100644 --- a/src/Umbraco.Core/IO/FileSystemProviderManager.cs +++ b/src/Umbraco.Core/IO/FileSystemProviderManager.cs @@ -5,9 +5,10 @@ using System.Configuration; using System.Linq; using System.Reflection; using Umbraco.Core.Configuration; +using Umbraco.Core.Scoping; namespace Umbraco.Core.IO -{ +{ public class FileSystemProviderManager { private readonly FileSystemProvidersSection _config; @@ -40,6 +41,12 @@ namespace Umbraco.Core.IO get { return Instance; } } + private IScopeProviderInternal ScopeProvider + { + // fixme - 'course this is bad, but enough for now + get { return ApplicationContext.Current == null ? null : ApplicationContext.Current.ScopeProvider as IScopeProviderInternal; } + } + internal FileSystemProviderManager() { _config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders"); @@ -52,13 +59,13 @@ namespace Umbraco.Core.IO _masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); _mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); - _macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper(_macroPartialFileSystem, "Views/MacroPartials"); - _partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper(_partialViewsFileSystem, "Views/Partials"); - _stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper(_stylesheetsFileSystem, "css"); - _scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper(_scriptsFileSystem, "scripts"); - _xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper(_xsltFileSystem, "xslt"); - _masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper(_masterPagesFileSystem, "masterpages"); - _mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper(_mvcViewsFileSystem, "Views"); + _macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper(_macroPartialFileSystem, "Views/MacroPartials", ScopeProvider); + _partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper(_partialViewsFileSystem, "Views/Partials", ScopeProvider); + _stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper(_stylesheetsFileSystem, "css", ScopeProvider); + _scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper(_scriptsFileSystem, "scripts", ScopeProvider); + _xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper(_xsltFileSystem, "xslt", ScopeProvider); + _masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper(_masterPagesFileSystem, "masterpages", ScopeProvider); + _mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper(_mvcViewsFileSystem, "Views", ScopeProvider); // filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again MediaFileSystem = GetFileSystemProvider(); @@ -92,7 +99,7 @@ namespace Umbraco.Core.IO } private readonly ConcurrentDictionary _providerLookup = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _aliases = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _aliases = new ConcurrentDictionary(); /// /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. @@ -121,7 +128,7 @@ namespace Umbraco.Core.IO // find a ctor matching the config parameters var paramCount = providerConfig.Parameters != null ? providerConfig.Parameters.Count : 0; - var constructor = providerType.GetConstructors().SingleOrDefault(x + var constructor = providerType.GetConstructors().SingleOrDefault(x => x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name))); if (constructor == null) throw new InvalidOperationException(string.Format("Type {0} has no ctor matching the {1} configuration parameter(s).", providerType.FullName, paramCount)); @@ -129,7 +136,7 @@ namespace Umbraco.Core.IO var parameters = new object[paramCount]; if (providerConfig.Parameters != null) // keeps ReSharper happy for (var i = 0; i < paramCount; i++) - parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value; + parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value; return new ProviderConstructionInfo { @@ -159,7 +166,7 @@ namespace Umbraco.Core.IO var alias = _aliases.GetOrAdd(typeof (TFileSystem), fsType => { // validate the ctor - var constructor = fsType.GetConstructors().SingleOrDefault(x + var constructor = fsType.GetConstructors().SingleOrDefault(x => x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom(x.GetParameters().Single().ParameterType)); if (constructor == null) throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + "."); @@ -176,8 +183,8 @@ namespace Umbraco.Core.IO // so we are double-wrapping here // could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe var innerFs = GetUnderlyingFileSystemProvider(alias); - var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias); - var fs = (TFileSystem) Activator.CreateInstance(typeof (TFileSystem), innerFs); + var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias, ScopeProvider); + var fs = (TFileSystem) Activator.CreateInstance(typeof (TFileSystem), shadowWrapper); _wrappers.Add(shadowWrapper); // keeping a weak reference to the wrapper return fs; } @@ -186,25 +193,7 @@ namespace Umbraco.Core.IO #region Shadow - // note - // shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one - // global shadow for the entire application, so great care should be taken to ensure that the - // application is *not* doing anything else when using a shadow. - // shadow applies to well-known filesystems *only* - at the moment, any other filesystem that would - // be created directly (via ctor) or via GetFileSystemProvider is *not* shadowed. - - // shadow must be enabled in an app event handler before anything else ie before any filesystem - // is actually created and used - after, it is too late - enabling shadow has a neglictible perfs - // impact. - // NO! by the time an app event handler is instanciated it is already too late, see note in ctor. - //internal void EnableShadow() - //{ - // if (_mvcViewsFileSystem != null) // test one of the fs... - // throw new InvalidOperationException("Cannot enable shadow once filesystems have been created."); - // _shadowEnabled = true; - //} - - public ICompletable Shadow(Guid id) + internal ICompletable Shadow(Guid id) { var typed = _wrappers.ToArray(); var wrappers = new ShadowWrapper[typed.Length + 7]; @@ -218,7 +207,7 @@ namespace Umbraco.Core.IO wrappers[i++] = _masterPagesFileSystemWrapper; wrappers[i] = _mvcViewsFileSystemWrapper; - return ShadowFileSystemsScope.CreateScope(id, wrappers); + return new ShadowFileSystems(id, wrappers); } #endregion diff --git a/src/Umbraco.Core/IO/FileSystemWrapper.cs b/src/Umbraco.Core/IO/FileSystemWrapper.cs index 27e08330ed..97b8a2f8f6 100644 --- a/src/Umbraco.Core/IO/FileSystemWrapper.cs +++ b/src/Umbraco.Core/IO/FileSystemWrapper.cs @@ -109,5 +109,24 @@ namespace Umbraco.Core.IO var wrapped2 = Wrapped as IFileSystem2; return wrapped2 == null ? Wrapped.GetSize(path) : wrapped2.GetSize(path); } + + // explicitely implementing - not breaking + bool IFileSystem2.CanAddPhysical + { + get + { + var wrapped2 = Wrapped as IFileSystem2; + return wrapped2 != null && wrapped2.CanAddPhysical; + } + } + + // explicitely implementing - not breaking + void IFileSystem2.AddFile(string path, string physicalPath, bool overrideIfExists, bool copy) + { + var wrapped2 = Wrapped as IFileSystem2; + if (wrapped2 == null) + throw new NotSupportedException(); + wrapped2.AddFile(path, physicalPath, overrideIfExists, copy); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index 003b4891f5..e3e0f9e2d2 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -44,6 +44,10 @@ namespace Umbraco.Core.IO { long GetSize(string path); + bool CanAddPhysical { get; } + + void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false); + // TODO: implement these // //void CreateDirectory(string path); diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 286acf0285..2ee0463435 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -4,11 +4,11 @@ using System.Globalization; using System.Reflection; using System.IO; using System.Configuration; +using System.Linq; using System.Web; using System.Text.RegularExpressions; using System.Web.Hosting; using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; namespace Umbraco.Core.IO { @@ -351,7 +351,54 @@ namespace Umbraco.Core.IO writer.Write(contents); } } - - } + + } + + /// + /// Checks if a given path is a full path including drive letter + /// + /// + /// + // From: http://stackoverflow.com/a/35046453/5018 + internal static bool IsFullPath(this string path) + { + return string.IsNullOrWhiteSpace(path) == false + && path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1 + && Path.IsPathRooted(path) + && Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false; + } + + /// + /// Get properly formatted relative path from an existing absolute or relative path + /// + /// + /// + internal static string GetRelativePath(this string path) + { + if (path.IsFullPath()) + { + var rootDirectory = GetRootDirectorySafe(); + var relativePath = path.ToLowerInvariant().Replace(rootDirectory.ToLowerInvariant(), string.Empty); + path = relativePath; + } + + return path.EnsurePathIsApplicationRootPrefixed(); + } + + /// + /// Ensures that a path has `~/` as prefix + /// + /// + /// + internal static string EnsurePathIsApplicationRootPrefixed(this string path) + { + if (path.StartsWith("~/")) + return path; + if (path.StartsWith("/") == false && path.StartsWith("\\") == false) + path = string.Format("/{0}", path); + if (path.StartsWith("~") == false) + path = string.Format("~{0}", path); + return path; + } } } diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index 6f32ef6da0..d9281a7590 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -24,6 +25,7 @@ namespace Umbraco.Core.IO { private readonly IContentSection _contentConfig; private readonly UploadAutoFillProperties _uploadAutoFillProperties; + private readonly ILogger _logger; private readonly object _folderCounterLock = new object(); private long _folderCounter; @@ -42,6 +44,7 @@ namespace Umbraco.Core.IO public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig, ILogger logger) : base(wrapped) { + _logger = logger; _contentConfig = contentConfig; _uploadAutoFillProperties = new UploadAutoFillProperties(this, logger, contentConfig); } @@ -99,7 +102,7 @@ namespace Umbraco.Core.IO } else { - // new scheme: path is "-/" OR "--" + // new scheme: path is "/" where xuid is a combination of cuid and puid // default media filesystem maps to "~/media/" // assumes that cuid and puid keys can be trusted - and that a single property type // for a single content cannot store two different files with the same name @@ -435,6 +438,64 @@ namespace Umbraco.Core.IO } } + public void DeleteMediaFiles(IEnumerable files) + { + files = files.Distinct(); + + Parallel.ForEach(files, file => + { + try + { + if (file.IsNullOrWhiteSpace()) return; + + if (FileExists(file) == false) return; + DeleteFile(file, true); + + if (UseTheNewMediaPathScheme == false) + { + // old scheme: filepath is "/" OR "-" + // remove the directory if any + var dir = Path.GetDirectoryName(file); + if (string.IsNullOrWhiteSpace(dir) == false) + DeleteDirectory(dir, true); + } + else + { + // new scheme: path is "/" where xuid is a combination of cuid and puid + // remove the directory + var dir = Path.GetDirectoryName(file); + DeleteDirectory(dir, true); + } + + // I don't even understand... + /* + + var relativeFilePath = GetRelativePath(file); // fixme - should be relative already + if (FileExists(relativeFilePath) == false) return; + + var parentDirectory = Path.GetDirectoryName(relativeFilePath); + + // don't want to delete the media folder if not using directories. + if (_contentSection.UploadAllowDirectories && parentDirectory != GetRelativePath("/")) + { + //issue U4-771: if there is a parent directory the recursive parameter should be true + DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false); + } + else + { + DeleteFile(file, true); + } + + */ + } + catch (Exception e) + { + _logger.Error("Failed to delete attached file \"" + file + "\".", e); + } + }); + } + + #endregion #region GenerateThumbnails diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 33e4dc71e3..bf18d02e54 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -364,6 +364,29 @@ namespace Umbraco.Core.IO return file.Exists ? file.Length : -1; } + public bool CanAddPhysical { get { return true; } } + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + var fullPath = GetFullPath(path); + + if (File.Exists(fullPath)) + { + if (overrideIfExists == false) + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); + File.Delete(fullPath); + } + + var directory = Path.GetDirectoryName(fullPath); + if (directory == null) throw new InvalidOperationException("Could not get directory."); + Directory.CreateDirectory(directory); // ensure it exists + + if (copy) + File.Copy(physicalPath, fullPath); + else + File.Move(physicalPath, fullPath); + } + #region Helper Methods protected virtual void EnsureDirectory(string path) diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs index 1e5da10bdc..cb2f4e346c 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystem.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; namespace Umbraco.Core.IO { @@ -33,8 +34,16 @@ namespace Umbraco.Core.IO { try { - using (var stream = _sfs.OpenFile(kvp.Key)) - _fs.AddFile(kvp.Key, stream, true); + var fs2 = _fs as IFileSystem2; + if (fs2 != null && fs2.CanAddPhysical) + { + fs2.AddFile(kvp.Key, _sfs.GetFullPath(kvp.Key)); // overwrite, move + } + else + { + using (var stream = _sfs.OpenFile(kvp.Key)) + _fs.AddFile(kvp.Key, stream, true); + } } catch (Exception e) { @@ -201,19 +210,20 @@ namespace Umbraco.Core.IO public IEnumerable GetFiles(string path) { - var normPath = NormPath(path); - var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray(); - var files = _fs.GetFiles(path); - return files - .Except(shadows.Where(kvp => (kvp.Value.IsFile && kvp.Value.IsDelete) || kvp.Value.IsDir) - .Select(kvp => kvp.Key)) - .Union(shadows.Where(kvp => kvp.Value.IsFile && kvp.Value.IsExist).Select(kvp => kvp.Key)) - .Distinct(); + return GetFiles(path, null); } public IEnumerable GetFiles(string path, string filter) { - return _fs.GetFiles(path, filter); + var normPath = NormPath(path); + var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray(); + var files = filter != null ? _fs.GetFiles(path, filter) : _fs.GetFiles(path); + var regexFilter = FilterToRegex(filter); + return files + .Except(shadows.Where(kvp => (kvp.Value.IsFile && kvp.Value.IsDelete) || kvp.Value.IsDir) + .Select(kvp => kvp.Key)) + .Union(shadows.Where(kvp => kvp.Value.IsFile && kvp.Value.IsExist && FilterByRegex(kvp.Key, regexFilter)).Select(kvp => kvp.Key)) + .Distinct(); } public Stream OpenFile(string path) @@ -245,6 +255,9 @@ namespace Umbraco.Core.IO public string GetFullPath(string path) { + ShadowNode sf; + if (Nodes.TryGetValue(NormPath(path), out sf)) + return sf.IsDir || sf.IsDelete ? null : _sfs.GetFullPath(path); return _fs.GetFullPath(path); } @@ -282,5 +295,63 @@ namespace Umbraco.Core.IO if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path."); return _sfs.GetSize(path); } + + public bool CanAddPhysical { get { return true; } } + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + ShadowNode sf; + var normPath = NormPath(path); + if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); + + var parts = normPath.Split('/'); + for (var i = 0; i < parts.Length - 1; i++) + { + var dirPath = string.Join("/", parts.Take(i + 1)); + ShadowNode sd; + if (Nodes.TryGetValue(dirPath, out sd)) + { + if (sd.IsFile) throw new InvalidOperationException("Invalid path."); + if (sd.IsDelete) Nodes[dirPath] = new ShadowNode(false, true); + } + else + { + if (_fs.DirectoryExists(dirPath)) continue; + if (_fs.FileExists(dirPath)) throw new InvalidOperationException("Invalid path."); + Nodes[dirPath] = new ShadowNode(false, true); + } + } + + _sfs.AddFile(path, physicalPath, overrideIfExists, copy); + Nodes[normPath] = new ShadowNode(false, false); + } + + /// + /// Helper function for filtering keys by Regex if a filter is specified. + /// + /// + /// + /// + internal static bool FilterByRegex(string input, string regexFilter) + { + if (regexFilter == null) return true; + return regexFilter != string.Empty && Regex.IsMatch(input, regexFilter); + } + + /// + /// Transforms a filter pattern into a Regex pattern + /// + /// + /// + /// + /// Appending '$' only if not containing wildcard is stupid and broken. + /// It is however what seems to be what they're doing in .NET so we need to match the functionality. + /// + internal static string FilterToRegex(string pattern) + { + if (pattern == null) return null; + return "^" + Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + (pattern.Contains("*") ? string.Empty : "$"); + } } } diff --git a/src/Umbraco.Core/IO/ShadowFileSystems.cs b/src/Umbraco.Core/IO/ShadowFileSystems.cs new file mode 100644 index 0000000000..2fe703c3df --- /dev/null +++ b/src/Umbraco.Core/IO/ShadowFileSystems.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.IO +{ + internal class ShadowFileSystems : ICompletable + { + // note: taking a reference to the _manager instead of using manager.Current + // to avoid using Current everywhere but really, we support only 1 scope at + // a time, not multiple scopes in case of multiple managers (not supported) + + private static readonly object Locker = new object(); + private static Guid _currentId = Guid.Empty; + private readonly Guid _id; + private readonly ShadowWrapper[] _wrappers; + private bool _completed; + + public ShadowFileSystems(Guid id, ShadowWrapper[] wrappers) + { + lock (Locker) + { + if (_currentId != Guid.Empty) + throw new InvalidOperationException("Already shadowing."); + _currentId = id; + + LogHelper.Debug("Shadow " + id + "."); + _id = id; + _wrappers = wrappers; + foreach (var wrapper in _wrappers) + wrapper.Shadow(id); + } + } + + public void Complete() + { + _completed = true; + } + + public void Dispose() + { + lock (Locker) + { + LogHelper.Debug("UnShadow " + _id + " (" + (_completed ? "complete" : "abort") + ")."); + + var exceptions = new List(); + foreach (var wrapper in _wrappers) + { + try + { + // this may throw an AggregateException if some of the changes could not be applied + wrapper.UnShadow(_completed); + } + catch (AggregateException ae) + { + exceptions.Add(ae); + } + } + + _currentId = Guid.Empty; + + if (exceptions.Count > 0) + throw new AggregateException(_completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions); + } + } + + // for tests + internal static void ResetId() + { + _currentId = Guid.Empty; + } + } +} diff --git a/src/Umbraco.Core/IO/ShadowFileSystemsScope.cs b/src/Umbraco.Core/IO/ShadowFileSystemsScope.cs deleted file mode 100644 index 0793946b62..0000000000 --- a/src/Umbraco.Core/IO/ShadowFileSystemsScope.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Remoting.Messaging; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.IO -{ - internal class ShadowFileSystemsScope : ICompletable - { - // note: taking a reference to the _manager instead of using manager.Current - // to avoid using Current everywhere but really, we support only 1 scope at - // a time, not multiple scopes in case of multiple managers (not supported) - - private const string ItemKey = "Umbraco.Core.IO.ShadowFileSystemsScope"; - private static readonly object Locker = new object(); - private readonly Guid _id; - private readonly ShadowWrapper[] _wrappers; - - static ShadowFileSystemsScope() - { - SafeCallContext.Register( - () => - { - var scope = CallContext.LogicalGetData(ItemKey); - CallContext.FreeNamedDataSlot(ItemKey); - return scope; - }, - o => - { - if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException(); - if (o != null) CallContext.LogicalSetData(ItemKey, o); - }); - } - - private ShadowFileSystemsScope(Guid id, ShadowWrapper[] wrappers) - { - LogHelper.Debug("Shadow " + id + "."); - _id = id; - _wrappers = wrappers; - foreach (var wrapper in _wrappers) - wrapper.Shadow(id); - } - - // internal for tests + FileSystemProviderManager - // do NOT use otherwise - internal static ShadowFileSystemsScope CreateScope(Guid id, ShadowWrapper[] wrappers) - { - lock (Locker) - { - if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException("Already shadowing."); - CallContext.LogicalSetData(ItemKey, ItemKey); // value does not matter - } - return new ShadowFileSystemsScope(id, wrappers); - } - - internal static bool InScope - { - get { return NoScope == false; } - } - - internal static bool NoScope - { - get { return CallContext.LogicalGetData(ItemKey) == null; } - } - - public void Complete() - { - lock (Locker) - { - LogHelper.Debug("UnShadow " + _id + " (complete)."); - - var exceptions = new List(); - foreach (var wrapper in _wrappers) - { - try - { - // this may throw an AggregateException if some of the changes could not be applied - wrapper.UnShadow(true); - } - catch (AggregateException ae) - { - exceptions.Add(ae); - } - } - - if (exceptions.Count > 0) - throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions); - - // last, & *only* if successful (otherwise we'll unshadow & cleanup as best as we can) - CallContext.FreeNamedDataSlot(ItemKey); - } - } - - public void Dispose() - { - lock (Locker) - { - if (CallContext.LogicalGetData(ItemKey) == null) return; - - try - { - LogHelper.Debug("UnShadow " + _id + " (abort)"); - foreach (var wrapper in _wrappers) - wrapper.UnShadow(false); // should not throw - } - finally - { - // last, & always - CallContext.FreeNamedDataSlot(ItemKey); - } - } - } - } -} diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index 87d2b8db9f..43e0372d2a 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -2,20 +2,23 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Umbraco.Core.Scoping; namespace Umbraco.Core.IO { internal class ShadowWrapper : IFileSystem2 { + private readonly IScopeProviderInternal _scopeProvider; private readonly IFileSystem _innerFileSystem; private readonly string _shadowPath; private ShadowFileSystem _shadowFileSystem; private string _shadowDir; - public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath) + public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath, IScopeProviderInternal scopeProvider) { _innerFileSystem = innerFileSystem; _shadowPath = shadowPath; + _scopeProvider = scopeProvider; } internal void Shadow(Guid id) @@ -24,7 +27,7 @@ namespace Umbraco.Core.IO // on ShadowFileSystemsScope.None - and if None is false then we should be running // in a single thread anyways - var virt = "~/App_Data/Shadow/" + id + "/" + _shadowPath; + var virt = "~/App_Data/TEMP/ShadowFs/" + id + "/" + _shadowPath; _shadowDir = IOHelper.MapPath(virt); Directory.CreateDirectory(_shadowDir); var tempfs = new PhysicalFileSystem(virt); @@ -62,7 +65,14 @@ namespace Umbraco.Core.IO private IFileSystem FileSystem { - get { return ShadowFileSystemsScope.NoScope ? _innerFileSystem : _shadowFileSystem; } + get + { + var isScoped = _scopeProvider != null && _scopeProvider.AmbientScope != null && _scopeProvider.AmbientScope.ScopedFileSystems; + + return isScoped + ? _shadowFileSystem + : _innerFileSystem; + } } public IEnumerable GetDirectories(string path) @@ -154,5 +164,22 @@ namespace Umbraco.Core.IO var filesystem2 = filesystem as IFileSystem2; return filesystem2 == null ? filesystem.GetSize(path) : filesystem2.GetSize(path); } + + public bool CanAddPhysical + { + get + { + var fileSystem2 = FileSystem as IFileSystem2; + return fileSystem2 != null && fileSystem2.CanAddPhysical; + } + } + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + var fileSystem2 = FileSystem as IFileSystem2; + if (fileSystem2 == null) + throw new NotSupportedException(); + fileSystem2.AddFile(path, physicalPath, overrideIfExists, copy); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/ImageProcessorLogger.cs b/src/Umbraco.Core/Logging/ImageProcessorLogger.cs index 02ca9c2949..015aced101 100644 --- a/src/Umbraco.Core/Logging/ImageProcessorLogger.cs +++ b/src/Umbraco.Core/Logging/ImageProcessorLogger.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Core.Logging -{ - using System; - using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; +using ImageProcessor.Common.Exceptions; - using ImageProcessor.Common.Exceptions; +namespace Umbraco.Core.Logging +{ /// /// A logger for explicitly logging ImageProcessor exceptions. diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 49a6e89226..8423006f4d 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -394,14 +394,13 @@ namespace Umbraco.Core.Models return content.Properties .Where(property => propertyGroup.PropertyTypes .Select(propertyType => propertyType.Id) - .Contains(property.PropertyTypeId)) - .OrderBy(x => x.PropertyType.SortOrder); + .Contains(property.PropertyTypeId)); } /// /// Set property values by alias with an annonymous object /// - public static void PropertyValues(this IContent content, object value) + public static void PropertyValues(this IContentBase content, object value) { if (value == null) throw new Exception("No properties has been passed in"); diff --git a/src/Umbraco.Core/Models/ContentTypeExtensions.cs b/src/Umbraco.Core/Models/ContentTypeExtensions.cs index a47b430979..90488219fc 100644 --- a/src/Umbraco.Core/Models/ContentTypeExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeExtensions.cs @@ -1,32 +1,73 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core.Services; namespace Umbraco.Core.Models { + //fixme: This whole thing needs to go, it's super hacky and doens't need to exist in the first place internal static class ContentTypeExtensions { /// /// Get all descendant content types /// /// + /// /// - public static IEnumerable Descendants(this IContentTypeBase contentType) + public static IEnumerable Descendants(this IContentTypeBase contentType, IContentTypeService contentTypeService) { - var contentTypeService = ApplicationContext.Current.Services.ContentTypeService; - var descendants = contentTypeService.GetContentTypeChildren(contentType.Id) - .SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id)); - return descendants; + if (contentType is IContentType) + { + var descendants = contentTypeService.GetContentTypeChildren(contentType.Id) + .SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id)); + return descendants; + } + + if (contentType is IMediaType) + { + var descendants = contentTypeService.GetMediaTypeChildren(contentType.Id) + .SelectRecursive(type => contentTypeService.GetMediaTypeChildren(type.Id)); + return descendants; + } + + //No other content types have children (i.e. member types) + return Enumerable.Empty(); + } + + /// + /// Get all descendant content types + /// + /// + /// + /// + public static IEnumerable Descendants(this IContentTypeBase contentType, ContentTypeServiceBase contentTypeService) + { + var cService = contentTypeService as IContentTypeService; + + return cService == null ? Enumerable.Empty() : contentType.Descendants(cService); } /// /// Get all descendant and self content types /// /// + /// /// - public static IEnumerable DescendantsAndSelf(this IContentTypeBase contentType) + public static IEnumerable DescendantsAndSelf(this IContentTypeBase contentType, IContentTypeService contentTypeService) { - var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants()); + var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants(contentTypeService)); + return descendantsAndSelf; + } + + /// + /// Get all descendant and self content types + /// + /// + /// + /// + public static IEnumerable DescendantsAndSelf(this IContentTypeBase contentType, ContentTypeServiceBase contentTypeService) + { + var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants(contentTypeService)); return descendantsAndSelf; } diff --git a/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs index 14c8640fc9..3798cb08e0 100644 --- a/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs +++ b/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs @@ -16,6 +16,11 @@ namespace Umbraco.Core.Models.Editors /// public class ContentPropertyData { + public ContentPropertyData(object value, PreValueCollection preValues) + : this(value, preValues, new Dictionary()) + { + } + public ContentPropertyData(object value, PreValueCollection preValues, IDictionary additionalData) { Value = value; @@ -28,6 +33,9 @@ namespace Umbraco.Core.Models.Editors /// public object Value { get; private set; } + /// + /// The pre-value collection for the content property + /// public PreValueCollection PreValues { get; private set; } /// 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/IMacroProperty.cs b/src/Umbraco.Core/Models/IMacroProperty.cs index a6c1d9ca5f..8726f51317 100644 --- a/src/Umbraco.Core/Models/IMacroProperty.cs +++ b/src/Umbraco.Core/Models/IMacroProperty.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; @@ -11,6 +12,9 @@ namespace Umbraco.Core.Models [DataMember] int Id { get; set; } + [DataMember] + Guid Key { get; set; } + /// /// Gets or sets the Alias of the Property /// 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/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index 0dc95a8987..fab34e5f17 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -13,6 +13,7 @@ namespace Umbraco.Core.Models.Identity public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { config.CreateMap() + .ForMember(user => user.LastLoginDateUtc, expression => expression.MapFrom(user => user.LastLoginDate.ToUniversalTime())) .ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email)) .ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id)) .ForMember(user => user.LockoutEndDateUtc, expression => expression.MapFrom(user => user.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null)) diff --git a/src/Umbraco.Core/Models/Identity/IdentityUser.cs b/src/Umbraco.Core/Models/Identity/IdentityUser.cs index cba4fc514a..c867dcf622 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUser.cs @@ -24,12 +24,17 @@ namespace Umbraco.Core.Models.Identity /// /// public IdentityUser() - { + { this.Claims = new List(); this.Roles = new List(); this.Logins = new List(); } + /// + /// Last login date + /// + public virtual DateTime? LastLoginDateUtc { get; set; } + /// /// Email /// diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index ef5479c316..58ee94bdc9 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using System.Text.RegularExpressions; -using Umbraco.Core.IO; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Strings; @@ -27,11 +24,12 @@ namespace Umbraco.Core.Models _addedProperties = new List(); _removedProperties = new List(); } - + /// /// Creates an item with pre-filled properties /// /// + /// /// /// /// @@ -43,10 +41,11 @@ namespace Umbraco.Core.Models /// /// /// - public Macro(int id, bool useInEditor, int cacheDuration, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cacheByMember, bool dontRender, string scriptPath) + public Macro(int id, Guid key, bool useInEditor, int cacheDuration, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cacheByMember, bool dontRender, string scriptPath) : this() { Id = id; + Key = key; UseInEditor = useInEditor; CacheDuration = cacheDuration; Alias = alias.ToCleanString(CleanStringType.Alias); diff --git a/src/Umbraco.Core/Models/MacroProperty.cs b/src/Umbraco.Core/Models/MacroProperty.cs index 0f29c18881..d3d82d10ad 100644 --- a/src/Umbraco.Core/Models/MacroProperty.cs +++ b/src/Umbraco.Core/Models/MacroProperty.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Models { public MacroProperty() { - + _key = Guid.NewGuid(); } /// @@ -30,6 +30,7 @@ namespace Umbraco.Core.Models _alias = alias; _name = name; _sortOrder = sortOrder; + _key = Guid.NewGuid(); //try to get the new mapped parameter editor var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(editorAlias, false); @@ -45,16 +46,18 @@ namespace Umbraco.Core.Models /// Ctor for creating an existing property /// /// + /// /// /// /// /// - internal MacroProperty(int id, string @alias, string name, int sortOrder, string editorAlias) + internal MacroProperty(int id, Guid key, string @alias, string name, int sortOrder, string editorAlias) { _id = id; _alias = alias; _name = name; _sortOrder = sortOrder; + _key = key; //try to get the new mapped parameter editor var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(editorAlias, false); @@ -66,6 +69,7 @@ namespace Umbraco.Core.Models _editorAlias = editorAlias; } + private Guid _key; private string _alias; private string _name; private int _sortOrder; @@ -76,6 +80,7 @@ namespace Umbraco.Core.Models private class PropertySelectors { + public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); @@ -83,6 +88,16 @@ namespace Umbraco.Core.Models public readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.EditorAlias); } + /// + /// Gets or sets the Key of the Property + /// + [DataMember] + public Guid Key + { + get { return _key; } + set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); } + } + /// /// Gets or sets the Alias of the Property /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 1875ae03b3..d586bc155a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -106,12 +106,13 @@ namespace Umbraco.Core.Models.PublishedContent /// The property type alias. /// The datatype definition identifier. /// The property editor alias. + /// Generally used only for testing, in production this will always be true /// /// The new published property type does not belong to a published content type. /// The values of and are /// assumed to be valid and consistent. /// - internal PublishedPropertyType(string propertyTypeAlias, int dataTypeDefinitionId, string propertyEditorAlias) + internal PublishedPropertyType(string propertyTypeAlias, int dataTypeDefinitionId, string propertyEditorAlias, bool initConverters = true) { // ContentType // - in unit tests, to be set by PublishedContentType when creating it @@ -122,7 +123,8 @@ namespace Umbraco.Core.Models.PublishedContent DataTypeId = dataTypeDefinitionId; PropertyEditorAlias = propertyEditorAlias; - InitializeConverters(); + if (initConverters) + InitializeConverters(); } #endregion diff --git a/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs b/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs index 652e63df0b..6e8fc29d95 100644 --- a/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Models.Rdbms internal class DataTypePreValueDto { [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 6)] + [PrimaryKeyColumn(IdentitySeed = 10)] public int Id { get; set; } [Column("datatypeNodeId")] diff --git a/src/Umbraco.Core/Models/Rdbms/MacroDto.cs b/src/Umbraco.Core/Models/Rdbms/MacroDto.cs index 855b4d1f93..ba2cee9356 100644 --- a/src/Umbraco.Core/Models/Rdbms/MacroDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MacroDto.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; @@ -13,6 +14,10 @@ namespace Umbraco.Core.Models.Rdbms [PrimaryKeyColumn] public int Id { get; set; } + [Column("uniqueId")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacro_UniqueId")] + public Guid UniqueId { get; set; } + [Column("macroUseInEditor")] [Constraint(Default = "0")] public bool UseInEditor { get; set; } diff --git a/src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs b/src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs index e2efddc829..7cfe6c3da7 100644 --- a/src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using System; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms @@ -11,7 +12,12 @@ namespace Umbraco.Core.Models.Rdbms [Column("id")] [PrimaryKeyColumn] public int Id { get; set; } - + + // important to use column name != cmsMacro.uniqueId (fix in v8) + [Column("uniquePropertyId")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacroProperty_UniquePropertyId")] + public Guid UniqueId { get; set; } + [Column("editorAlias")] public string EditorAlias { get; set; } diff --git a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs index e5f7b3f17c..cbe9f909f8 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs @@ -22,6 +22,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("LoginName")] [Length(1000)] [Constraint(Default = "''")] + [Index(IndexTypes.NonClustered, Name = "IX_cmsMember_LoginName")] public string LoginName { get; set; } [Column("Password")] diff --git a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs index c5fac092df..c61ece96cf 100644 --- a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs @@ -35,6 +35,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("path")] [Length(150)] + [Index(IndexTypes.NonClustered, Name = "IX_umbracoNodePath")] public string Path { get; set; } [Column("sortOrder")] diff --git a/src/Umbraco.Core/Models/Rdbms/RelationDto.cs b/src/Umbraco.Core/Models/Rdbms/RelationDto.cs index 368904a5cb..d3d741a191 100644 --- a/src/Umbraco.Core/Models/Rdbms/RelationDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RelationDto.cs @@ -16,6 +16,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("parentId")] [ForeignKey(typeof(NodeDto), Name = "FK_umbracoRelation_umbracoNode")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelation_parentChildType", ForColumns = "parentId,childId,relType")] public int ParentId { get; set; } [Column("childId")] diff --git a/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs index 8a6eb5c02a..d13ce33520 100644 --- a/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs @@ -15,6 +15,10 @@ namespace Umbraco.Core.Models.Rdbms [PrimaryKeyColumn(IdentitySeed = NodeIdSeed)] public int Id { get; set; } + [Column("typeUniqueId")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_UniqueId")] + public Guid UniqueId { get; set; } + [Column("dual")] public bool Dual { get; set; } @@ -25,11 +29,13 @@ namespace Umbraco.Core.Models.Rdbms public Guid ChildObjectType { get; set; } [Column("name")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")] public string Name { get; set; } [Column("alias")] [NullSetting(NullSetting = NullSettings.Null)] [Length(100)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")] public string Alias { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UmbracoDeployChecksumDto.cs b/src/Umbraco.Core/Models/Rdbms/UmbracoDeployChecksumDto.cs deleted file mode 100644 index 06d904ee88..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/UmbracoDeployChecksumDto.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoDeployChecksum")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class UmbracoDeployChecksumDto - { - [Column("id")] - [PrimaryKeyColumn(Name = "PK_umbracoDeployChecksum")] - public int Id { get; set; } - - [Column("entityType")] - [Length(32)] - [NullSetting(NullSetting = NullSettings.NotNull)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoDeployChecksum", ForColumns = "entityType,entityGuid,entityPath")] - public string EntityType { get; set; } - - [Column("entityGuid")] - [NullSetting(NullSetting = NullSettings.Null)] - public Guid EntityGuid { get; set; } - - [Column("entityPath")] - [Length(256)] - [NullSetting(NullSetting = NullSettings.Null)] - public string EntityPath { get; set; } - - [Column("localChecksum")] - [NullSetting(NullSetting = NullSettings.NotNull)] - [Length(32)] - public string LocalChecksum { get; set; } - - [Column("compositeChecksum")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(32)] - public string CompositeChecksum { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UmbracoDeployDependencyDto.cs b/src/Umbraco.Core/Models/Rdbms/UmbracoDeployDependencyDto.cs deleted file mode 100644 index 765a32c929..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/UmbracoDeployDependencyDto.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoDeployDependency")] - [ExplicitColumns] - internal class UmbracoDeployDependencyDto - { - [Column("sourceId")] - [PrimaryKeyColumn(AutoIncrement = false, Clustered = true, Name = "PK_umbracoDeployDependency", OnColumns = "sourceId, targetId")] - [ForeignKey(typeof(UmbracoDeployChecksumDto), Name = "FK_umbracoDeployDependency_umbracoDeployChecksum_id1")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public int SourceId { get; set; } - - [Column("targetId")] - [ForeignKey(typeof(UmbracoDeployChecksumDto), Name = "FK_umbracoDeployDependency_umbracoDeployChecksum_id2")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public int TargetId { get; set; } - - [Column("mode")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public int Mode { get; set; } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs b/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs index 1e6662735f..f0d769a21e 100644 --- a/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs @@ -15,6 +15,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("nodeId")] [ForeignKey(typeof(NodeDto))] + [Index(IndexTypes.NonClustered, Name = "IX_umbracoUser2NodePermission_nodeId")] public int NodeId { get; set; } [Column("permission")] diff --git a/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs b/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs index 987be2a276..a27657cbe0 100644 --- a/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs +++ b/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs @@ -1,16 +1,123 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.UI.WebControls; +using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Models { internal static class UmbracoEntityExtensions { + /// + /// Does a quick check on the entity's set path to ensure that it's valid and consistent + /// + /// + /// + public static void ValidatePathWithException(this NodeDto entity) + { + //don't validate if it's empty and it has no id + if (entity.NodeId == default(int) && entity.Path.IsNullOrWhiteSpace()) + return; + if (entity.Path.IsNullOrWhiteSpace()) + throw new InvalidDataException(string.Format("The content item {0} has an empty path: {1} with parentID: {2}", entity.NodeId, entity.Path, entity.ParentId)); + + var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (pathParts.Length < 2) + { + //a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id + throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", entity.NodeId, entity.Path, entity.ParentId)); + } + + if (entity.ParentId != default(int) && pathParts[pathParts.Length - 2] != entity.ParentId.ToInvariantString()) + { + //the 2nd last id in the path must be it's parent id + throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", entity.NodeId, entity.Path, entity.ParentId)); + } + } + + /// + /// Does a quick check on the entity's set path to ensure that it's valid and consistent + /// + /// + /// + public static bool ValidatePath(this IUmbracoEntity entity) + { + //don't validate if it's empty and it has no id + if (entity.HasIdentity == false && entity.Path.IsNullOrWhiteSpace()) + return true; + + if (entity.Path.IsNullOrWhiteSpace()) + return false; + + var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (pathParts.Length < 2) + { + //a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id + return false; + } + + if (entity.ParentId != default(int) && pathParts[pathParts.Length - 2] != entity.ParentId.ToInvariantString()) + { + //the 2nd last id in the path must be it's parent id + return false; + } + + return true; + } + + /// + /// This will validate the entity's path and if it's invalid it will fix it, if fixing is required it will recursively + /// check and fix all ancestors if required. + /// + /// + /// + /// A callback specified to retrieve the parent entity of the entity + /// A callback specified to update a fixed entity + public static void EnsureValidPath(this T entity, + ILogger logger, + Func getParent, + Action update) + where T: IUmbracoEntity + { + if (entity.HasIdentity == false) + throw new InvalidOperationException("Could not ensure the entity path, the entity has not been assigned an identity"); + + if (entity.ValidatePath() == false) + { + logger.Warn(typeof(UmbracoEntityExtensions), string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", entity.Id, entity.Path, entity.ParentId)); + if (entity.ParentId == -1) + { + entity.Path = string.Concat("-1,", entity.Id); + //path changed, update it + update(entity); + } + else + { + var parent = getParent(entity); + if (parent == null) + throw new NullReferenceException("Could not ensure path for entity " + entity.Id + " could not resolve it's parent " + entity.ParentId); + + //the parent must also be valid! + parent.EnsureValidPath(logger, getParent, update); + + entity.Path = string.Concat(parent.Path, ",", entity.Id); + //path changed, update it + update(entity); + } + } + } + + /// + /// When resolved from EntityService this checks if the entity has the HasChildren flag + /// + /// + /// public static bool HasChildren(this IUmbracoEntity entity) { if (entity.AdditionalData.ContainsKey("HasChildren")) @@ -31,6 +138,11 @@ namespace Umbraco.Core.Models return entity.AdditionalData.GetValueIgnoreCase(key, defaultVal); } + /// + /// When resolved from EntityService this checks if the entity has the IsContainer flag + /// + /// + /// public static bool IsContainer(this IUmbracoEntity entity) { if (entity.AdditionalData.ContainsKeyIgnoreCase("IsContainer") == false) return false; diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 2be2010301..0df7a21e57 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -1,4 +1,6 @@ -using Umbraco.Core.CodeAnnotations; +using System; +using System.ComponentModel; +using Umbraco.Core.CodeAnnotations; namespace Umbraco.Core.Models { @@ -17,12 +19,14 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.ContentItemType)] [FriendlyName("Content Item Type")] + [Obsolete("This is not used and will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] ContentItemType, /// /// Root /// - [UmbracoObjectType(Constants.ObjectTypes.SystemRoot)] + [UmbracoObjectType(Constants.ObjectTypes.SystemRoot)] [FriendlyName("Root")] ROOT, @@ -31,6 +35,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Document, typeof(IContent))] [FriendlyName("Document")] + [UmbracoUdiType(Constants.UdiEntityType.Document)] Document, /// @@ -38,6 +43,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Media, typeof(IMedia))] [FriendlyName("Media")] + [UmbracoUdiType(Constants.UdiEntityType.Media)] Media, /// @@ -45,6 +51,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.MemberType, typeof(IMemberType))] [FriendlyName("Member Type")] + [UmbracoUdiType(Constants.UdiEntityType.MemberType)] MemberType, /// @@ -52,6 +59,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Template, typeof(ITemplate))] [FriendlyName("Template")] + [UmbracoUdiType(Constants.UdiEntityType.Template)] Template, /// @@ -59,6 +67,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.MemberGroup)] [FriendlyName("Member Group")] + [UmbracoUdiType(Constants.UdiEntityType.MemberGroup)] MemberGroup, //TODO: What is a 'Content Item' supposed to be??? @@ -67,6 +76,8 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.ContentItem)] [FriendlyName("Content Item")] + [Obsolete("This is not used and will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] ContentItem, /// @@ -74,6 +85,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.MediaType, typeof(IMediaType))] [FriendlyName("Media Type")] + [UmbracoUdiType(Constants.UdiEntityType.MediaType)] MediaType, /// @@ -81,13 +93,14 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.DocumentType, typeof(IContentType))] [FriendlyName("Document Type")] + [UmbracoUdiType(Constants.UdiEntityType.DocumentType)] DocumentType, /// /// Recycle Bin /// [UmbracoObjectType(Constants.ObjectTypes.ContentRecycleBin)] - [FriendlyName("Recycle Bin")] + [FriendlyName("Recycle Bin")] RecycleBin, /// @@ -95,6 +108,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Stylesheet)] [FriendlyName("Stylesheet")] + [UmbracoUdiType(Constants.UdiEntityType.Stylesheet)] Stylesheet, /// @@ -102,6 +116,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Member, typeof(IMember))] [FriendlyName("Member")] + [UmbracoUdiType(Constants.UdiEntityType.Member)] Member, /// @@ -109,6 +124,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.DataType, typeof(IDataTypeDefinition))] [FriendlyName("Data Type")] + [UmbracoUdiType(Constants.UdiEntityType.DataType)] DataType, /// @@ -116,6 +132,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.DocumentTypeContainer)] [FriendlyName("Document Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.DocumentTypeContainer)] DocumentTypeContainer, /// @@ -123,6 +140,7 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.MediaTypeContainer)] [FriendlyName("Media Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.MediaTypeContainer)] MediaTypeContainer, /// @@ -130,8 +148,36 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.DataTypeContainer)] [FriendlyName("Data Type Container")] - DataTypeContainer + [UmbracoUdiType(Constants.UdiEntityType.DataTypeContainer)] + DataTypeContainer, + /// + /// Relation type + /// + [UmbracoObjectType(Constants.ObjectTypes.RelationType)] + [FriendlyName("Relation Type")] + [UmbracoUdiType(Constants.UdiEntityType.RelationType)] + RelationType, + /// + /// Forms Form + /// + [UmbracoObjectType(Constants.ObjectTypes.FormsForm)] + [FriendlyName("Form")] + FormsForm, + + /// + /// Forms PreValue + /// + [UmbracoObjectType(Constants.ObjectTypes.FormsPreValue)] + [FriendlyName("PreValue")] + FormsPreValue, + + /// + /// Forms DataSource + /// + [UmbracoObjectType(Constants.ObjectTypes.FormsDataSource)] + [FriendlyName("DataSource")] + FormsDataSource } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs b/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs index 34ee29b96f..5f92e6425e 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs @@ -12,6 +12,7 @@ namespace Umbraco.Core.Models { //MUST be concurrent to avoid thread collisions! private static readonly ConcurrentDictionary UmbracoObjectTypeCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary UmbracoObjectTypeUdiCache = new ConcurrentDictionary(); /// /// Get an UmbracoObjectTypes value from it's name @@ -43,6 +44,21 @@ namespace Umbraco.Core.Models return umbracoObjectType; } + public static string GetUdiType(Guid guid) + { + var umbracoObjectType = Constants.UdiEntityType.Unknown; + + foreach (var name in Enum.GetNames(typeof(UmbracoObjectTypes))) + { + var objType = GetUmbracoObjectType(name); + if (objType.GetGuid() == guid) + { + umbracoObjectType = GetUdiType(objType); + } + } + return umbracoObjectType; + } + /// /// Extension method for the UmbracoObjectTypes enum to return the enum GUID /// @@ -68,6 +84,26 @@ namespace Umbraco.Core.Models }); } + public static string GetUdiType(this UmbracoObjectTypes umbracoObjectType) + { + return UmbracoObjectTypeUdiCache.GetOrAdd(umbracoObjectType, types => + { + var type = typeof(UmbracoObjectTypes); + var memInfo = type.GetMember(umbracoObjectType.ToString()); + var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoUdiTypeAttribute), + false); + + if (attributes.Length == 0) + return Constants.UdiEntityType.Unknown; + + var attribute = ((UmbracoUdiTypeAttribute)attributes[0]); + if (attribute == null) + return Constants.UdiEntityType.Unknown; + + return attribute.UdiType; + }); + } + /// /// Extension method for the UmbracoObjectTypes enum to return the enum name /// diff --git a/src/Umbraco.Core/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/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 6ed424c232..6a1befaad8 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -468,19 +468,19 @@ namespace Umbraco.Core /// /// /// - internal static IDictionary ToDictionary(this T o, + public static IDictionary ToDictionary(this T o, params Expression>[] ignoreProperties) { return o.ToDictionary(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray()); } - /// - /// Turns object into dictionary - /// - /// - /// Properties to ignore - /// - internal static IDictionary ToDictionary(this object o, params string[] ignoreProperties) + /// + /// Turns object into dictionary + /// + /// + /// Properties to ignore + /// + public static IDictionary ToDictionary(this object o, params string[] ignoreProperties) { if (o != null) { @@ -647,5 +647,10 @@ namespace Umbraco.Core return "[GetPropertyValueException]"; } } + + internal static Guid AsGuid(this object value) + { + return value is Guid ? (Guid) value : Guid.Empty; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/OrderedHashSet.cs b/src/Umbraco.Core/OrderedHashSet.cs new file mode 100644 index 0000000000..2fd545c915 --- /dev/null +++ b/src/Umbraco.Core/OrderedHashSet.cs @@ -0,0 +1,50 @@ +using System.Collections.ObjectModel; + +namespace Umbraco.Core +{ + /// + /// A custom collection similar to HashSet{T} which only contains unique items, however this collection keeps items in order + /// and is customizable to keep the newest or oldest equatable item + /// + /// + internal class OrderedHashSet : KeyedCollection + { + private readonly bool _keepOldest; + + public OrderedHashSet(bool keepOldest = true) + { + _keepOldest = keepOldest; + } + + protected override void InsertItem(int index, T item) + { + if (Dictionary == null) + { + base.InsertItem(index, item); + } + else + { + var exists = Dictionary.ContainsKey(item); + + //if we want to keep the newest, then we need to remove the old item and add the new one + if (exists == false) + { + base.InsertItem(index, item); + } + else if(_keepOldest == false) + { + if (Remove(item)) + { + index--; + } + base.InsertItem(index, item); + } + } + } + + protected override T GetKeyForItem(T item) + { + return item; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs b/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs new file mode 100644 index 0000000000..13bb4bfc77 --- /dev/null +++ b/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Packaging.Models +{ + [Serializable] + [DataContract(IsReference = true)] + internal class UninstallationSummary + { + public MetaData MetaData { get; set; } + public IEnumerable DataTypesUninstalled { get; set; } + public IEnumerable LanguagesUninstalled { get; set; } + public IEnumerable DictionaryItemsUninstalled { get; set; } + public IEnumerable MacrosUninstalled { get; set; } + public IEnumerable FilesUninstalled { get; set; } + public IEnumerable TemplatesUninstalled { get; set; } + public IEnumerable ContentTypesUninstalled { get; set; } + public IEnumerable StylesheetsUninstalled { get; set; } + public IEnumerable ContentUninstalled { get; set; } + public bool PackageUninstalled { get; set; } + } + + internal static class UninstallationSummaryExtentions + { + public static UninstallationSummary InitEmpty(this UninstallationSummary summary) + { + summary.ContentUninstalled = new List(); + summary.ContentTypesUninstalled = new List(); + summary.DataTypesUninstalled = new List(); + summary.DictionaryItemsUninstalled = new List(); + summary.FilesUninstalled = new List(); + summary.LanguagesUninstalled = new List(); + summary.MacrosUninstalled = new List(); + summary.MetaData = new MetaData(); + summary.TemplatesUninstalled = new List(); + summary.PackageUninstalled = false; + return summary; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/DatabaseDebugHelper.cs b/src/Umbraco.Core/Persistence/DatabaseDebugHelper.cs index 06512c2d68..4cb8327f5b 100644 --- a/src/Umbraco.Core/Persistence/DatabaseDebugHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseDebugHelper.cs @@ -42,6 +42,8 @@ namespace Umbraco.Core.Persistence { var prof = con as StackExchange.Profiling.Data.ProfiledDbConnection; if (prof != null) con = prof.InnerConnection; + var ceCon = con as System.Data.SqlServerCe.SqlCeConnection; + if (ceCon != null) return null; // "NotSupported: SqlCE"; var dbCon = con as DbConnection; return dbCon == null ? "NotSupported: " + con.GetType() diff --git a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs index c0c7b2a777..86072c45d5 100644 --- a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs +++ b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs @@ -4,9 +4,11 @@ using System.Configuration; using System.Data; using System.Data.Common; using System.Data.SqlClient; +using System.Data.SqlServerCe; using System.Linq; using System.Text; using System.Threading.Tasks; +using MySql.Data.MySqlClient; using Umbraco.Core.Logging; namespace Umbraco.Core.Persistence @@ -69,6 +71,38 @@ namespace Umbraco.Core.Persistence } } + public static string GetConnStringExSecurityInfo(this IDbConnection connection) + { + try + { + if (connection is SqlConnection) + { + var builder = new SqlConnectionStringBuilder(connection.ConnectionString); + return string.Format("DataSource: {0}, InitialCatalog: {1}", builder.DataSource, builder.InitialCatalog); + } + + if (connection is SqlCeConnection) + { + var builder = new SqlCeConnectionStringBuilder(connection.ConnectionString); + return string.Format("DataSource: {0}", builder.DataSource); + } + + if (connection is MySqlConnection) + { + var builder = new MySqlConnectionStringBuilder(connection.ConnectionString); + return string.Format("Server: {0}, Database: {1}", builder.Server, builder.Database); + } + } + catch (Exception ex) + { + LogHelper.WarnWithException(typeof(DbConnectionExtensions), + "Could not resolve connection string parameters", ex); + return "(Could not resolve)"; + } + + throw new ArgumentException(string.Format("The connection type {0} is not supported", connection.GetType())); + } + public static bool IsAvailable(this IDbConnection connection) { try @@ -79,7 +113,8 @@ namespace Umbraco.Core.Persistence catch (DbException exc) { // Don't swallow this error, the exception is super handy for knowing "why" its not available - LogHelper.WarnWithException("Configured database is reporting as not being available!", exc); + LogHelper.WarnWithException(typeof(DbConnectionExtensions), + "Configured database is reporting as not being available! {0}", exc, connection.GetConnStringExSecurityInfo); return false; } diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index fc981b0280..c27147895c 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; -using System.Runtime.Remoting.Messaging; -using System.Web; using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence { @@ -14,30 +12,17 @@ namespace Umbraco.Core.Persistence /// it will create one per context, otherwise it will be a global singleton object which is NOT thread safe /// since we need (at least) a new instance of the database object per thread. /// - internal class DefaultDatabaseFactory : DisposableObject, IDatabaseFactory + internal class DefaultDatabaseFactory : DisposableObject, IDatabaseFactory2 { private readonly string _connectionStringName; private readonly ILogger _logger; public string ConnectionString { get; private set; } public string ProviderName { get; private set; } - // NO! see notes in v8 HybridAccessorBase - //[ThreadStatic] - //private static volatile UmbracoDatabase _nonHttpInstance; + //private static readonly object Locker = new object(); - private const string ItemKey = "Umbraco.Core.Persistence.DefaultDatabaseFactory"; - - private static UmbracoDatabase NonContextValue - { - get { return (UmbracoDatabase) CallContext.LogicalGetData(ItemKey); } - set - { - if (value == null) CallContext.FreeNamedDataSlot(ItemKey); - else CallContext.LogicalSetData(ItemKey, value); - } - } - - private static readonly object Locker = new object(); + // bwc imposes a weird x-dependency between database factory and scope provider... + public IScopeProviderInternal ScopeProvider { get; set; } /// /// Constructor accepting custom connection string @@ -76,251 +61,26 @@ namespace Umbraco.Core.Persistence public UmbracoDatabase CreateDatabase() { - UmbracoDatabase database; - - // gets or creates a database, using either the call context (if no http context) or - // the current request context (http context) to store it. once done using the database, - // it should be disposed - which will remove it from whatever context it is currently - // stored in. this is automatic with http context because UmbracoDatabase implements - // IDisposeOnRequestEnd, but NOT with call context. - - if (HttpContext.Current == null) - { - database = NonContextValue; - if (database == null) - { - lock (Locker) - { - database = NonContextValue; - if (database == null) - { - database = CreateDatabaseInstance(ContextOwner.CallContext); - NonContextValue = database; - } -#if DEBUG_DATABASES - else - { - Log("Get lcc", database); - } -#endif - } - } -#if DEBUG_DATABASES - else - { - Log("Get lcc", database); - } -#endif - return database; - } - - if (HttpContext.Current.Items.Contains(typeof (DefaultDatabaseFactory)) == false) - { - database = CreateDatabaseInstance(ContextOwner.HttpContext); - HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory), database); - } - else - { - database = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; -#if DEBUG_DATABASES - Log("Get ctx", database); -#endif - } - - return database; + return ScopeProvider.GetAmbientOrNoScope().Database; } - // called by UmbracoDatabase when disposed, so that the factory can de-list it from context - internal void OnDispose(UmbracoDatabase disposing) + public UmbracoDatabase CreateNewDatabase() { - var value = disposing; - switch (disposing.ContextOwner) - { - case ContextOwner.CallContext: - value = NonContextValue; - break; - case ContextOwner.HttpContext: - value = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)]; - break; - } + return CreateDatabaseInstance(); - if (value != null && value.InstanceId != disposing.InstanceId) throw new Exception("panic: wrong db."); + } - switch (disposing.ContextOwner) - { - case ContextOwner.CallContext: - NonContextValue = null; -#if DEBUG_DATABASES - Log("Clr lcc", disposing); -#endif - break; - case ContextOwner.HttpContext: - HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory)); -#if DEBUG_DATABASES - Log("Clr ctx", disposing); -#endif - break; - } - - disposing.ContextOwner = ContextOwner.None; - -#if DEBUG_DATABASES - _databases.Remove(value); -#endif - } - -#if DEBUG_DATABASES - // helps identifying when non-httpContext databases are created by logging the stack trace - private void LogCallContextStack() - { - var trace = Environment.StackTrace; - if (trace.IndexOf("ScheduledPublishing") > 0) - LogHelper.Debug("CallContext: Scheduled Publishing"); - else if (trace.IndexOf("TouchServerTask") > 0) - LogHelper.Debug("CallContext: Server Registration"); - else if (trace.IndexOf("LogScrubber") > 0) - LogHelper.Debug("CallContext: Log Scrubber"); - else - LogHelper.Debug("CallContext: " + Environment.StackTrace); - } - - private readonly List _databases = new List(); - - // helps identifying database leaks by keeping track of all instances - public List Databases { get { return _databases; } } - - private static void Log(string message, UmbracoDatabase database) - { - LogHelper.Debug(message + " (" + (database == null ? "" : database.InstanceSid) + ")."); - } -#endif - - internal enum ContextOwner - { - None, - HttpContext, - CallContext - } - - internal UmbracoDatabase CreateDatabaseInstance(ContextOwner contextOwner) + internal UmbracoDatabase CreateDatabaseInstance() { var database = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) : new UmbracoDatabase(_connectionStringName, _logger); - database.ContextOwner = contextOwner; database.DatabaseFactory = this; - //database.EnableSqlTrace = true; -#if DEBUG_DATABASES - Log("Create " + contextOwner, database); - if (contextOwner == ContextOwner.CallContext) - LogCallContextStack(); - _databases.Add(database); -#endif return database; } protected override void DisposeResources() { - UmbracoDatabase database; - - if (HttpContext.Current == null) - { - database = NonContextValue; -#if DEBUG_DATABASES - Log("Release lcc", database); -#endif - } - else - { - database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)]; -#if DEBUG_DATABASES - Log("Release ctx", database); -#endif - } - - if (database != null) database.Dispose(); // removes it from call context } - - // during tests, the thread static var can leak between tests - // this method provides a way to force-reset the variable - internal void ResetForTests() - { - var value = NonContextValue; - if (value != null) value.Dispose(); - NonContextValue = null; - } - - #region SafeCallContext - - // see notes in SafeCallContext - need to do this since we are using - // the logical call context... - - static DefaultDatabaseFactory() - { - SafeCallContext.Register(DetachAmbientDatabase, AttachAmbientDatabase); - } - - // gets a value indicating whether there is an ambient database - internal static bool HasAmbientDatabase - { - get - { - return HttpContext.Current == null - ? NonContextValue != null - : HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null; - } - } - - // detaches the current database - // ie returns the database and remove it from whatever is "context" - internal static UmbracoDatabase DetachAmbientDatabase() - { - UmbracoDatabase database; - - if (HttpContext.Current == null) - { - database = NonContextValue; - NonContextValue = null; - } - else - { - database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)]; - HttpContext.Current.Items.Remove(typeof (DefaultDatabaseFactory)); - } - - if (database != null) database.ContextOwner = ContextOwner.None; - return database; - } - - // attach a current database - // ie assign it to whatever is "context" - // throws if there already is a database - internal static void AttachAmbientDatabase(object o) - { - var database = o as UmbracoDatabase; - if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o"); - - var ambient = DetachAmbientDatabase(); - if (ambient != null) ambient.Dispose(); - - if (HttpContext.Current == null) - { - //if (NonContextValue != null) throw new InvalidOperationException(); - if (database == null) return; - - NonContextValue = database; - database.ContextOwner = ContextOwner.CallContext; - } - else - { - //if (HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null) throw new InvalidOperationException(); - if (database == null) return; - - HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] = database; - database.ContextOwner = ContextOwner.HttpContext; - } - } - - #endregion } } \ 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..0532eab6b1 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -27,15 +27,28 @@ namespace Umbraco.Core.Persistence.Factories #region Implementation of IEntityFactory - public IContent BuildEntity(DocumentDto dto) + /// + /// Builds a IContent item from the dto(s) and content type + /// + /// + /// This DTO can contain all of the information to build an IContent item, however in cases where multiple entities are being built, + /// a separate publishedDto entity will be supplied in place of the 's own + /// ResultColumn DocumentPublishedReadOnlyDto + /// + /// + /// + /// When querying for multiple content items the main DTO will not contain the ResultColumn DocumentPublishedReadOnlyDto and a separate publishedDto instance will be supplied + /// + /// + public static IContent BuildEntity(DocumentDto dto, IContentType contentType, DocumentPublishedReadOnlyDto publishedDto = null) { - 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,11 +62,16 @@ 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; + + //Check if the publishedDto has been supplied, if not the use the dto's own DocumentPublishedReadOnlyDto value + content.PublishedVersionGuid = publishedDto == null + ? (dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId) + : publishedDto.VersionId; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -64,6 +82,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/MacroFactory.cs b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs index 2ec20b08eb..02d95b3776 100644 --- a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs @@ -7,12 +7,9 @@ namespace Umbraco.Core.Persistence.Factories { internal class MacroFactory { - #region Implementation of IEntityFactory - public IMacro BuildEntity(MacroDto dto) { - var model = new Macro(dto.Id, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.Python); - + var model = new Macro(dto.Id, dto.UniqueId, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.Python); try { @@ -20,7 +17,7 @@ namespace Umbraco.Core.Persistence.Factories foreach (var p in dto.MacroPropertyDtos) { - model.Properties.Add(new MacroProperty(p.Id, p.Alias, p.Name, p.SortOrder, p.EditorAlias)); + model.Properties.Add(new MacroProperty(p.Id, p.UniqueId, p.Alias, p.Name, p.SortOrder, p.EditorAlias)); } //on initial construction we don't want to have dirty properties tracked @@ -36,8 +33,9 @@ namespace Umbraco.Core.Persistence.Factories public MacroDto BuildDto(IMacro entity) { - var dto = new MacroDto() - { + var dto = new MacroDto + { + UniqueId = entity.Key, Alias = entity.Alias, CacheByPage = entity.CacheByPage, CachePersonalized = entity.CacheByMember, @@ -58,8 +56,6 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - #endregion - private List BuildPropertyDtos(IMacro entity) { var list = new List(); @@ -67,6 +63,7 @@ namespace Umbraco.Core.Persistence.Factories { var text = new MacroPropertyDto { + UniqueId = p.Key, Alias = p.Alias, Name = p.Name, Macro = entity.Id, 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/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index b4d5d2e566..ba7a47165b 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -9,9 +9,10 @@ namespace Umbraco.Core.Persistence.Factories { internal class MemberTypeReadOnlyFactory { - public IMemberType BuildEntity(MemberTypeReadOnlyDto dto) + public IMemberType BuildEntity(MemberTypeReadOnlyDto dto, out bool needsSaving) { var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + needsSaving = false; var memberType = new MemberType(dto.ParentId); @@ -47,6 +48,12 @@ namespace Umbraco.Core.Persistence.Factories { if (dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; + // beware! + // means that we can return a memberType "from database" that has some property types + // that do *not* come from the database and therefore are incomplete eg have no key, + // no id, no dataTypeDefinitionId - ouch! - better notify caller of the situation + needsSaving = true; + //Add the standard PropertyType to the current list propertyTypes.Add(standardPropertyType.Value); 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/Factories/RelationTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs index 98d4f30042..b112535817 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs @@ -16,6 +16,7 @@ namespace Umbraco.Core.Persistence.Factories entity.DisableChangeTracking(); entity.Id = dto.Id; + entity.Key = dto.UniqueId; entity.IsBidirectional = dto.Dual; entity.Name = dto.Name; @@ -38,10 +39,13 @@ namespace Umbraco.Core.Persistence.Factories ChildObjectType = entity.ChildObjectType, Dual = entity.IsBidirectional, Name = entity.Name, - ParentObjectType = entity.ParentObjectType + ParentObjectType = entity.ParentObjectType, + UniqueId = entity.Key }; if (entity.HasIdentity) + { dto.Id = entity.Id; + } return dto; } diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 18073c088e..4eb3fe0659 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -50,7 +50,7 @@ namespace Umbraco.Core.Persistence.Factories entity.ContentTypeThumbnail = asDictionary.ContainsKey("thumbnail") ? (d.thumbnail ?? string.Empty) : string.Empty; var publishedVersion = default(Guid); - //some content items don't have a published version + //some content items don't have a published/newest version if (asDictionary.ContainsKey("publishedVersion") && asDictionary["publishedVersion"] != null) { Guid.TryParse(d.publishedVersion.ToString(), out publishedVersion); diff --git a/src/Umbraco.Core/Persistence/IDatabaseFactory.cs b/src/Umbraco.Core/Persistence/IDatabaseFactory.cs index b0efb7f94a..8def46b0fb 100644 --- a/src/Umbraco.Core/Persistence/IDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/IDatabaseFactory.cs @@ -1,12 +1,30 @@ using System; +using System.ComponentModel; namespace Umbraco.Core.Persistence { - /// - /// Used to create the UmbracoDatabase for use in the DatabaseContext - /// + [Obsolete("Use IDatabaseFactory2 instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public interface IDatabaseFactory : IDisposable { + /// + /// gets or creates the ambient database + /// + /// UmbracoDatabase CreateDatabase(); } + + /// + /// Used to create the UmbracoDatabase for use in the DatabaseContext + /// +#pragma warning disable 618 + public interface IDatabaseFactory2 : IDatabaseFactory +#pragma warning restore 618 + { + /// + /// creates a new database + /// + /// + UmbracoDatabase CreateNewDatabase(); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/LockedRepository.cs b/src/Umbraco.Core/Persistence/LockedRepository.cs index b5d2d672f2..c092a3329f 100644 --- a/src/Umbraco.Core/Persistence/LockedRepository.cs +++ b/src/Umbraco.Core/Persistence/LockedRepository.cs @@ -7,21 +7,27 @@ namespace Umbraco.Core.Persistence internal class LockedRepository where TRepository : IDisposable, IRepository { - public LockedRepository(Transaction transaction, IDatabaseUnitOfWork unitOfWork, TRepository repository) + public LockedRepository(IDatabaseUnitOfWork unitOfWork, TRepository repository) { - Transaction = transaction; UnitOfWork = unitOfWork; Repository = repository; } - public Transaction Transaction { get; private set; } + public LockedRepository(Transaction transaction, IDatabaseUnitOfWork unitOfWork, TRepository repository) + { + //Transaction = transaction; + UnitOfWork = unitOfWork; + Repository = repository; + } + + //public Transaction Transaction { get; private set; } public IDatabaseUnitOfWork UnitOfWork { get; private set; } public TRepository Repository { get; private set; } - public void Commit() - { - UnitOfWork.Commit(); - Transaction.Complete(); - } + //public void Commit() + //{ + // UnitOfWork.Commit(); + // Transaction.Complete(); + //} } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/LockingRepository.cs b/src/Umbraco.Core/Persistence/LockingRepository.cs index f513073e71..6d3d5d54c1 100644 --- a/src/Umbraco.Core/Persistence/LockingRepository.cs +++ b/src/Umbraco.Core/Persistence/LockingRepository.cs @@ -10,11 +10,11 @@ namespace Umbraco.Core.Persistence internal class LockingRepository where TRepository : IDisposable, IRepository { - private readonly IDatabaseUnitOfWorkProvider _uowProvider; - private readonly Func _repositoryFactory; + private readonly IScopeUnitOfWorkProvider _uowProvider; + private readonly Func _repositoryFactory; private readonly int[] _readLockIds, _writeLockIds; - public LockingRepository(IDatabaseUnitOfWorkProvider uowProvider, Func repositoryFactory, + public LockingRepository(IScopeUnitOfWorkProvider uowProvider, Func repositoryFactory, IEnumerable readLockIds, IEnumerable writeLockIds) { Mandate.ParameterNotNull(uowProvider, "uowProvider"); @@ -28,76 +28,88 @@ namespace Umbraco.Core.Persistence public void WithReadLocked(Action> action, bool autoCommit = true) { - var uow = _uowProvider.GetUnitOfWork(); - using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead)) + using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) { + // getting the database creates a scope and a transaction + // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) + // and will throw if outer scope (if any) has a lower isolation level + foreach (var lockId in _readLockIds) uow.Database.AcquireLockNodeReadLock(lockId); using (var repository = _repositoryFactory(uow)) { - action(new LockedRepository(transaction, uow, repository)); + action(new LockedRepository(uow, repository)); if (autoCommit == false) return; uow.Commit(); - transaction.Complete(); - } - } + + } // dispose repository => dispose uow => complete (or not) scope + } // dispose uow again => nothing } public TResult WithReadLocked(Func, TResult> func, bool autoCommit = true) { - var uow = _uowProvider.GetUnitOfWork(); - using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead)) + using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) { + // getting the database creates a scope and a transaction + // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) + // and will throw if outer scope (if any) has a lower isolation level + foreach (var lockId in _readLockIds) uow.Database.AcquireLockNodeReadLock(lockId); using (var repository = _repositoryFactory(uow)) { - var ret = func(new LockedRepository(transaction, uow, repository)); + var ret = func(new LockedRepository(uow, repository)); if (autoCommit == false) return ret; uow.Commit(); - transaction.Complete(); return ret; - } - } + + } // dispose repository => dispose uow => complete (or not) scope + } // dispose uow again => nothing } public void WithWriteLocked(Action> action, bool autoCommit = true) { - var uow = _uowProvider.GetUnitOfWork(); - using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead)) + using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) { + // getting the database creates a scope and a transaction + // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) + // and will throw if outer scope (if any) has a lower isolation level + foreach (var lockId in _writeLockIds) uow.Database.AcquireLockNodeWriteLock(lockId); using (var repository = _repositoryFactory(uow)) { - action(new LockedRepository(transaction, uow, repository)); + action(new LockedRepository(uow, repository)); if (autoCommit == false) return; uow.Commit(); - transaction.Complete(); - } - } + + } // dispose repository => dispose uow => complete (or not) scope + } // dispose uow again => nothing } public TResult WithWriteLocked(Func, TResult> func, bool autoCommit = true) { - var uow = _uowProvider.GetUnitOfWork(); - using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead)) + using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) { + // getting the database creates a scope and a transaction + // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) + // and will throw if outer scope (if any) has a lower isolation level + foreach (var lockId in _writeLockIds) uow.Database.AcquireLockNodeReadLock(lockId); using (var repository = _repositoryFactory(uow)) { - var ret = func(new LockedRepository(transaction, uow, repository)); + var ret = func(new LockedRepository(uow, repository)); if (autoCommit == false) return ret; uow.Commit(); - transaction.Complete(); return ret; - } - } + + } // dispose repository => dispose uow => complete (or not) scope + } // dispose uow again => nothing } } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 6128096943..7dc57ba7f6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -130,21 +130,21 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1034, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1034", SortOrder = 2, UniqueId = new Guid("a6857c73-d6e9-480c-b6e6-f15f6ad11125"), Text = "Content Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1035, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1035", SortOrder = 2, UniqueId = new Guid("93929b9a-93a2-4e2a-b239-d99334440a59"), Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1036, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1036", SortOrder = 2, UniqueId = new Guid("2b24165f-9782-4aa3-b459-1de4a4d21f60"), Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1040, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1040", SortOrder = 2, UniqueId = new Guid("21e798da-e06e-4eda-a511-ed257f78d4fa"), Text = "Related Links", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1045, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1045", SortOrder = 2, UniqueId = new Guid("7E3962CC-CE20-4FFC-B661-5897A894BA7E"), Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + //New UDI pickers with newer Ids + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); //TODO: We're not creating these for 7.0 //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1039, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1039", SortOrder = 2, UniqueId = new Guid("06f349a9-c949-4b6a-8660-59c10451af42"), Text = "Ultimate Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1038, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1038", SortOrder = 2, UniqueId = new Guid("1251c96c-185c-4e9b-93f4-b48205573cbd"), Text = "Simple Editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1042, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1042", SortOrder = 2, UniqueId = new Guid("0a452bd5-83f9-4bc3-8403-1286e13fb77e"), Text = "Macro Container", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); } @@ -246,17 +246,19 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 16, DataTypeId = 1034, PropertyEditorAlias = Constants.PropertyEditors.ContentPickerAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 18, DataTypeId = 1036, PropertyEditorAlias = Constants.PropertyEditors.MemberPickerAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 17, DataTypeId = 1035, PropertyEditorAlias = Constants.PropertyEditors.MultipleMediaPickerAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 21, DataTypeId = 1040, PropertyEditorAlias = Constants.PropertyEditors.RelatedLinksAlias, DbType = "Ntext" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 25, DataTypeId = 1045, PropertyEditorAlias = Constants.PropertyEditors.MultipleMediaPickerAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -26, DataTypeId = Constants.System.DefaultContentListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -27, DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -28, DataTypeId = Constants.System.DefaultMembersListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -28, DataTypeId = Constants.System.DefaultMembersListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + + //New UDI pickers with newer Ids + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 26, DataTypeId = 1046, PropertyEditorAlias = Constants.PropertyEditors.ContentPicker2Alias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 27, DataTypeId = 1047, PropertyEditorAlias = Constants.PropertyEditors.MemberPicker2Alias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 28, DataTypeId = 1048, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 29, DataTypeId = 1049, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); //TODO: We're not creating these for 7.0 //_database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 19, DataTypeId = 1038, PropertyEditorAlias = Constants.PropertyEditors.MarkdownEditorAlias, DbType = "Ntext" }); @@ -268,10 +270,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial { _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 3, Alias = "", SortOrder = 0, DataTypeNodeId = -87, Value = ",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 4, Alias = "group", SortOrder = 0, DataTypeNodeId = 1041, Value = "default" }); - - //default's for MultipleMediaPickerAlias picker - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 5, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1045, Value = "1" }); - + //defaults for the member list _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "username" }); @@ -288,12 +287,19 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); + + //default's for MultipleMediaPickerAlias picker + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 6, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1049, Value = "1" }); } private void CreateUmbracoRelationTypeData() { - _database.Insert("umbracoRelationType", "id", false, new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }); - _database.Insert("umbracoRelationType", "id", false, new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }); + var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); + relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); } private void CreateCmsTaskTypeData() @@ -306,7 +312,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial var dto = new MigrationDto { Id = 1, - Name = GlobalSettings.UmbracoMigrationName, + Name = Constants.System.UmbracoMigrationName, Version = UmbracoVersion.GetSemanticVersion().ToString(), CreateDate = DateTime.Now }; diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index ff81c94951..83ae1de7e1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial /// /// Represents the initial database schema creation by running CreateTable for all DTOs against the db. /// - internal class DatabaseSchemaCreation + public class DatabaseSchemaCreation { /// /// Constructor @@ -82,8 +82,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {43, typeof (CacheInstructionDto)}, {44, typeof (ExternalLoginDto)}, {45, typeof (MigrationDto)}, - {46, typeof (UmbracoDeployChecksumDto)}, - {47, typeof (UmbracoDeployDependencyDto)}, + //46, removed: UmbracoDeployChecksumDto + //47, removed: UmbracoDeployDependencyDto {48, typeof (RedirectUrlDto) }, {49, typeof (LockDto) } }; @@ -345,7 +345,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial /// Raises the event. /// /// The instance containing the event data. - protected internal virtual void FireBeforeCreation(DatabaseCreationEventArgs e) + internal virtual void FireBeforeCreation(DatabaseCreationEventArgs e) { if (BeforeCreation != null) { @@ -361,7 +361,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial /// Raises the event. /// /// The instance containing the event data. - protected virtual void FireAfterCreation(DatabaseCreationEventArgs e) + internal virtual void FireAfterCreation(DatabaseCreationEventArgs e) { if (AfterCreation != null) { diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 47772c5c15..a34db10839 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -47,7 +47,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial if (ValidTables.Any(x => x.InvariantEquals("umbracoMigration"))) { - var allMigrations = migrationEntryService.GetAll(GlobalSettings.UmbracoMigrationName); + var allMigrations = migrationEntryService.GetAll(Constants.System.UmbracoMigrationName); mostrecent = allMigrations.OrderByDescending(x => x.Version).Select(x => x.Version).FirstOrDefault(); } @@ -136,6 +136,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial return new Version(7, 4, 0); } + //if the error indicates a problem with the column cmsMacroProperty.uniquePropertyId then it is not version 7.6 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMacroProperty,uniquePropertyId")))) + { + return new Version(7, 5, 0); + } + return UmbracoVersion.Current; } diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs index a19aaa24ad..cd08825e2d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Migrations Logger = logger; } - internal IMigrationContext Context; + public IMigrationContext Context { get; internal set; } public abstract void Up(); public abstract void Down(); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs index e3ad6f972d..6afb0a4ca7 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Persistence.Migrations.Syntax.Create.Expressions; using Umbraco.Core.Persistence.Migrations.Syntax.Create.ForeignKey; using Umbraco.Core.Persistence.Migrations.Syntax.Create.Index; using Umbraco.Core.Persistence.Migrations.Syntax.Create.Table; +using Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions; using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; using Umbraco.Core.Persistence.SqlSyntax; @@ -27,6 +28,24 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create _databaseProviders = databaseProviders; } + public void Table() + { + var tableDefinition = DefinitionFactory.GetTableDefinition(_sqlSyntax, typeof(T)); + + AddSql(_sqlSyntax.Format(tableDefinition)); + AddSql(_sqlSyntax.FormatPrimaryKey(tableDefinition)); + foreach (var sql in _sqlSyntax.Format(tableDefinition.ForeignKeys)) + AddSql(sql); + foreach (var sql in _sqlSyntax.Format(tableDefinition.Indexes)) + AddSql(sql); + } + + private void AddSql(string sql) + { + var expression = new ExecuteSqlStatementExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { SqlStatement = sql }; + _context.Expressions.Add(expression); + } + public ICreateTableWithColumnSyntax Table(string tableName) { var expression = new CreateTableExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs index e079127705..3cb2fe7e9f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs @@ -8,6 +8,8 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create { public interface ICreateBuilder : IFluentSyntax { + void Table(); + ICreateTableWithColumnSyntax Table(string tableName); ICreateColumnOnTableSyntax Column(string columnName); diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs index 0f648d34eb..062ec4f22b 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero { - [MigrationAttribute("4.9.0", 0, GlobalSettings.UmbracoMigrationName)] + [MigrationAttribute("4.9.0", 0, Constants.System.UmbracoMigrationName)] public class RemoveUmbracoAppConstraints : MigrationBase { public RemoveUmbracoAppConstraints(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourOneZero/AddPreviewXmlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourOneZero/AddPreviewXmlTable.cs index 4e8d3165fb..4ac06d1531 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourOneZero/AddPreviewXmlTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourOneZero/AddPreviewXmlTable.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourOneZero { - [Migration("4.1.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("4.1.0", 0, Constants.System.UmbracoMigrationName)] public class AddPreviewXmlTable : MigrationBase { public AddPreviewXmlTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroPropertyTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroPropertyTable.cs index b61ac55448..8dfe51f8dc 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroPropertyTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroPropertyTable.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven /// /// Creats a unique index across two columns so we cannot have duplicate property aliases for one macro /// - [Migration("7.0.0", 5, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 5, Constants.System.UmbracoMigrationName)] public class AddIndexToCmsMacroPropertyTable : MigrationBase { private readonly bool _skipIndexCheck; diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs index d18cb430c0..e6c237d4d6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven /// /// Creates a unique index on the macro alias so we cannot have duplicates by alias /// - [Migration("7.0.0", 4, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 4, Constants.System.UmbracoMigrationName)] public class AddIndexToCmsMacroTable : MigrationBase { private readonly bool _forTesting; diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddPropertyEditorAliasColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddPropertyEditorAliasColumn.cs index e20c2cfb0b..474da442be 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddPropertyEditorAliasColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddPropertyEditorAliasColumn.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven { - [Migration("7.0.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 0, Constants.System.UmbracoMigrationName)] public class AddPropertyEditorAliasColumn : MigrationBase { public AddPropertyEditorAliasColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs index 9154c6c985..474b8a3c13 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven /// needs to be changed to editorAlias, we'll do this by removing the constraint,changing the macroPropertyType to the new /// editorAlias column (and maintaing data so we can reference it) /// - [Migration("7.0.0", 6, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 6, Constants.System.UmbracoMigrationName)] public class AlterCmsMacroPropertyTable : MigrationBase { public AlterCmsMacroPropertyTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs index d822b7593a..704fcffc84 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven { - [Migration("7.0.0", 8, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 8, Constants.System.UmbracoMigrationName)] public class AlterTagRelationsTable : MigrationBase { public AlterTagRelationsTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs index d069d8222d..32bd9d1403 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven { - [Migration("7.0.0", 9, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 9, Constants.System.UmbracoMigrationName)] public class AlterTagsTable : MigrationBase { public AlterTagsTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterUserTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterUserTable.cs index 28c3eb15f2..d82d73f7c3 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterUserTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterUserTable.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven { - [Migration("7.0.0", 3, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 3, Constants.System.UmbracoMigrationName)] public class AlterUserTable : MigrationBase { public AlterUserTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AssignMissingKeysAndIndexes.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AssignMissingKeysAndIndexes.cs index 9de1ea0871..c76031e4e4 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AssignMissingKeysAndIndexes.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AssignMissingKeysAndIndexes.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven /// and it wasn't a MySQL install. /// see: http://issues.umbraco.org/issue/U4-5707 /// - [Migration("7.0.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 0, Constants.System.UmbracoMigrationName)] public class AssignMissingKeysAndIndexes : MigrationBase { public AssignMissingKeysAndIndexes(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/DropControlIdColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/DropControlIdColumn.cs index 0917411f8b..7928255968 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/DropControlIdColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/DropControlIdColumn.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven { - [Migration("7.0.0", 2, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 2, Constants.System.UmbracoMigrationName)] public class DropControlIdColumn : MigrationBase { public DropControlIdColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/RemoveCmsMacroPropertyTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/RemoveCmsMacroPropertyTypeTable.cs index f4f4b9065c..1c83ef5972 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/RemoveCmsMacroPropertyTypeTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/RemoveCmsMacroPropertyTypeTable.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven { - [Migration("7.0.0", 7, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 7, Constants.System.UmbracoMigrationName)] public class RemoveCmsMacroPropertyTypeTable : MigrationBase { public RemoveCmsMacroPropertyTypeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateControlIdToPropertyEditorAlias.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateControlIdToPropertyEditorAlias.cs index ef464667dd..f94fb7ce2c 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateControlIdToPropertyEditorAlias.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateControlIdToPropertyEditorAlias.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven /// /// Updates the data in the changed propertyEditorAlias column after it has been changed by ChangeControlIdColumn /// - [Migration("7.0.0", 1, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 1, Constants.System.UmbracoMigrationName)] public class UpdateControlIdToPropertyEditorAlias : MigrationBase { public UpdateControlIdToPropertyEditorAlias(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs index 749996ea36..d29935acd2 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs @@ -16,7 +16,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven { - [Migration("7.0.0", 10, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", 10, Constants.System.UmbracoMigrationName)] public class UpdateRelatedLinksData : MigrationBase { public UpdateRelatedLinksData(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs index c56ae0e2f1..39d139601c 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive { - [Migration("7.5.5", 101, GlobalSettings.UmbracoMigrationName)] + [Migration("7.5.5", 101, Constants.System.UmbracoMigrationName)] public class AddLockObjects : MigrationBase { public AddLockObjects(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockTable.cs index 5dc1720a2a..9774ed62cf 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockTable.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive { - [Migration("7.5.5", 100, GlobalSettings.UmbracoMigrationName)] + [Migration("7.5.5", 100, Constants.System.UmbracoMigrationName)] public class AddLockTable : MigrationBase { public AddLockTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs index c9a0d509e6..1acb979ff6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFiv /// /// See: http://issues.umbraco.org/issue/U4-4196 /// - [Migration("7.5.5", 1, GlobalSettings.UmbracoMigrationName)] + [Migration("7.5.5", 1, Constants.System.UmbracoMigrationName)] public class UpdateAllowedMediaTypesAtRoot : MigrationBase { public UpdateAllowedMediaTypesAtRoot(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs index 508c0f284b..829631c7f4 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero { - [Migration("7.5.0", 100, GlobalSettings.UmbracoMigrationName)] + [Migration("7.5.0", 100, Constants.System.UmbracoMigrationName)] public class AddRedirectUrlTable : MigrationBase { public AddRedirectUrlTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs index 1bfef5ef6d..4c83a70fe2 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer // This migration exists for 7.3.0 but it seems like it was not always running properly // if you're upgrading from 7.3.0 or higher than we add this migration, if you're upgrading // from 7.3.0 or lower then you will already get this migration in the migration to get to 7.3.0 - [Migration("7.3.0", "7.5.0", 10, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", "7.5.0", 10, Constants.System.UmbracoMigrationName)] public class EnsureServersLockObject : MigrationBase { public EnsureServersLockObject(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs index 96523e25e8..f8c4c14bbe 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer /// /// This is here to re-remove these tables, we dropped them in 7.3 but new installs created them again so we're going to re-drop them /// - [Migration("7.5.0", 1, GlobalSettings.UmbracoMigrationName)] + [Migration("7.5.0", 1, Constants.System.UmbracoMigrationName)] public class RemoveStylesheetDataAndTablesAgain : MigrationBase { public RemoveStylesheetDataAndTablesAgain(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs index f8e6abe42e..8ac4d86290 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer /// /// See: http://issues.umbraco.org/issue/U4-8522 /// - [Migration("7.5.0", 2, GlobalSettings.UmbracoMigrationName)] + [Migration("7.5.0", 2, Constants.System.UmbracoMigrationName)] public class UpdateUniqueIndexOnCmsPropertyData : MigrationBase { public UpdateUniqueIndexOnCmsPropertyData(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddDataDecimalColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddDataDecimalColumn.cs index 442b92d2b5..26fb006e82 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddDataDecimalColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddDataDecimalColumn.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero { - [Migration("7.4.0", 1, GlobalSettings.UmbracoMigrationName)] + [Migration("7.4.0", 1, Constants.System.UmbracoMigrationName)] public class AddDataDecimalColumn : MigrationBase { public AddDataDecimalColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUmbracoDeployTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUmbracoDeployTables.cs index a39cea2ee0..0a1e6058a9 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUmbracoDeployTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUmbracoDeployTables.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero { - [Migration("7.4.0", 5, GlobalSettings.UmbracoMigrationName)] + [Migration("7.4.0", 5, Constants.System.UmbracoMigrationName)] public class AddUmbracoDeployTables : MigrationBase { public AddUmbracoDeployTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs index fe600f6b69..5f987fc966 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero { - [Migration("7.4.0", 2, GlobalSettings.UmbracoMigrationName)] + [Migration("7.4.0", 2, Constants.System.UmbracoMigrationName)] public class AddUniqueIdPropertyTypeGroupColumn : MigrationBase { public AddUniqueIdPropertyTypeGroupColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs index 20200a3230..c24048a6a7 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZer /// alias, so we need to ensure that these are initially consistent on /// all environments (based on the alias). /// - [Migration("7.4.0", 3, GlobalSettings.UmbracoMigrationName)] + [Migration("7.4.0", 3, Constants.System.UmbracoMigrationName)] public class EnsureContentTypeUniqueIdsAreConsistent : MigrationBase { public EnsureContentTypeUniqueIdsAreConsistent(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs index e1fa9e9257..7d5714218a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero { - [Migration("7.4.0", 4, GlobalSettings.UmbracoMigrationName)] + [Migration("7.4.0", 4, Constants.System.UmbracoMigrationName)] public class FixListViewMediaSortOrder : MigrationBase { public FixListViewMediaSortOrder(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/RemoveParentIdPropertyTypeGroupColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/RemoveParentIdPropertyTypeGroupColumn.cs index 3d285c2715..5f4120f243 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/RemoveParentIdPropertyTypeGroupColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/RemoveParentIdPropertyTypeGroupColumn.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero { - [Migration("7.4.0", 4, GlobalSettings.UmbracoMigrationName)] + [Migration("7.4.0", 4, Constants.System.UmbracoMigrationName)] public class RemoveParentIdPropertyTypeGroupColumn : MigrationBase { public RemoveParentIdPropertyTypeGroupColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenOneZero/AssignMissingPrimaryForMySqlKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenOneZero/AssignMissingPrimaryForMySqlKeys.cs index 6fa0eaa5dc..904a4a9349 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenOneZero/AssignMissingPrimaryForMySqlKeys.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenOneZero/AssignMissingPrimaryForMySqlKeys.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenOneZero // this is because when the 7.0.0 migrations are executed, this primary key get's created so if this migration is also executed // we will get exceptions because it is trying to create the PK two times. - [Migration("7.0.0", "7.1.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("7.0.0", "7.1.0", 0, Constants.System.UmbracoMigrationName)] public class AssignMissingPrimaryForMySqlKeys : MigrationBase { public AssignMissingPrimaryForMySqlKeys(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs new file mode 100644 index 0000000000..0cb4f297b1 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs @@ -0,0 +1,35 @@ +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + public class AddIndexToCmsMemberLoginName : MigrationBase + { + public AddIndexToCmsMemberLoginName(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false) + { + Create.Index("IX_cmsMember_LoginName").OnTable("cmsMember") + .OnColumn("LoginName") + .Ascending() + .WithOptions() + .NonClustered(); + } + } + + public override void Down() + { + Delete.Index("IX_cmsMember_LoginName").OnTable("cmsMember"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUmbracoNodePath.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUmbracoNodePath.cs new file mode 100644 index 0000000000..e59252390a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUmbracoNodePath.cs @@ -0,0 +1,35 @@ +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + public class AddIndexToUmbracoNodePath : MigrationBase + { + public AddIndexToUmbracoNodePath(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodePath")) == false) + { + Create.Index("IX_umbracoNodePath").OnTable("umbracoNode") + .OnColumn("path") + .Ascending() + .WithOptions() + .NonClustered(); + } + } + + public override void Down() + { + Delete.Index("IX_umbracoNodePath").OnTable("umbracoNode"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUser2NodePermission.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUser2NodePermission.cs new file mode 100644 index 0000000000..b510ef428c --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUser2NodePermission.cs @@ -0,0 +1,34 @@ +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + public class AddIndexToUser2NodePermission : MigrationBase + { + public AddIndexToUser2NodePermission(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoUser2NodePermission_nodeId")) == false) + { + Create.Index("IX_umbracoUser2NodePermission_nodeId").OnTable("umbracoUser2NodePermission") + .OnColumn("nodeId") + .Ascending() + .WithOptions() + .NonClustered(); + } + } + + public override void Down() + { + Delete.Index("IX_umbracoUser2NodePermission_nodeId").OnTable("cmsMember"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelationTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelationTables.cs new file mode 100644 index 0000000000..b8c0d78ef1 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelationTables.cs @@ -0,0 +1,91 @@ +using System; +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + public class AddIndexesToUmbracoRelationTables : MigrationBase + { + public AddIndexesToUmbracoRelationTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database).ToArray(); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelation_parentChildType")) == false) + { + //This will remove any corrupt/duplicate data in the relation table before the index is applied + //Ensure this executes in a defered block which will be done inside of the migration transaction + this.Execute.Code(database => + { + //We need to check if this index has corrupted data and then clear that data + var duplicates = database.Fetch("SELECT parentId,childId,relType FROM umbracoRelation GROUP BY parentId,childId,relType HAVING COUNT(*) > 1"); + if (duplicates.Count > 0) + { + //need to fix this there cannot be duplicates so we'll take the latest entries, it's really not going to matter though + foreach (var duplicate in duplicates) + { + var ids = database.Fetch("SELECT id FROM umbracoRelation WHERE parentId=@parentId AND childId=@childId AND relType=@relType ORDER BY datetime DESC", + new { parentId = duplicate.parentId, childId = duplicate.childId, relType = duplicate.relType }); + + if (ids.Count == 1) + { + //this is just a safety check, this should absolutely never happen + throw new InvalidOperationException("Duplicates were detected but could not be discovered"); + } + + //delete the others + ids = ids.Skip(0).ToList(); + + //iterate in groups of 2000 to avoid the max sql parameter limit + foreach (var idGroup in ids.InGroupsOf(2000)) + { + database.Execute("DELETE FROM umbracoRelation WHERE id IN (@ids)", new { ids = idGroup }); + } + } + } + return ""; + }); + + //unique index to prevent duplicates - and for better perf + Create.Index("IX_umbracoRelation_parentChildType").OnTable("umbracoRelation") + .OnColumn("parentId").Ascending() + .OnColumn("childId").Ascending() + .OnColumn("relType").Ascending() + .WithOptions() + .Unique(); + } + + //need indexes on alias and name for relation type since these are queried against + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_alias")) == false) + { + Create.Index("IX_umbracoRelationType_alias").OnTable("umbracoRelationType") + .OnColumn("alias") + .Ascending() + .WithOptions() + .NonClustered(); + } + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_name")) == false) + { + Create.Index("IX_umbracoRelationType_name").OnTable("umbracoRelationType") + .OnColumn("name") + .Ascending() + .WithOptions() + .NonClustered(); + } + + } + + public override void Down() + { + Delete.Index("IX_umbracoNodePath").OnTable("umbracoNode"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddMacroUniqueIdColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddMacroUniqueIdColumn.cs new file mode 100644 index 0000000000..fee0f37513 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddMacroUniqueIdColumn.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + public class AddMacroUniqueIdColumn : MigrationBase + { + public AddMacroUniqueIdColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("cmsMacro") && x.ColumnName.InvariantEquals("uniqueId")) == false) + { + Create.Column("uniqueId").OnTable("cmsMacro").AsGuid().Nullable(); + Execute.Code(UpdateMacroGuids); + Alter.Table("cmsMacro").AlterColumn("uniqueId").AsGuid().NotNullable(); + Create.Index("IX_cmsMacro_UniqueId").OnTable("cmsMacro").OnColumn("uniqueId") + .Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique(); + + } + + if (columns.Any(x => x.TableName.InvariantEquals("cmsMacroProperty") && x.ColumnName.InvariantEquals("uniquePropertyId")) == false) + { + Create.Column("uniquePropertyId").OnTable("cmsMacroProperty").AsGuid().Nullable(); + Execute.Code(UpdateMacroPropertyGuids); + Alter.Table("cmsMacroProperty").AlterColumn("uniquePropertyId").AsGuid().NotNullable(); + Create.Index("IX_cmsMacroProperty_UniquePropertyId").OnTable("cmsMacroProperty").OnColumn("uniquePropertyId") + .Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique(); + } + } + + private static string UpdateMacroGuids(Database database) + { + var updates = database.Query("SELECT id, macroAlias FROM cmsMacro") + .Select(macro => Tuple.Create((int) macro.id, ((string) macro.macroAlias).ToGuid())) + .ToList(); + + foreach (var update in updates) + database.Execute("UPDATE cmsMacro set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); + + return string.Empty; + } + + private static string UpdateMacroPropertyGuids(Database database) + { + var updates = database.Query(@"SELECT cmsMacroProperty.id id, macroPropertyAlias propertyAlias, cmsMacro.macroAlias macroAlias +FROM cmsMacroProperty +JOIN cmsMacro ON cmsMacroProperty.macro=cmsMacro.id") + .Select(prop => Tuple.Create((int) prop.id, ((string) prop.macroAlias + "____" + (string) prop.propertyAlias).ToGuid())) + .ToList(); + + foreach (var update in updates) + database.Execute("UPDATE cmsMacroProperty set uniquePropertyId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); + + return string.Empty; + } + + public override void Down() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddRelationTypeUniqueIdColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddRelationTypeUniqueIdColumn.cs new file mode 100644 index 0000000000..3fe72bb052 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddRelationTypeUniqueIdColumn.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + public class AddRelationTypeUniqueIdColumn : MigrationBase + { + public AddRelationTypeUniqueIdColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("umbracoRelationType") && x.ColumnName.InvariantEquals("typeUniqueId")) == false) + { + Create.Column("typeUniqueId").OnTable("umbracoRelationType").AsGuid().Nullable(); + Execute.Code(UpdateRelationTypeGuids); + Alter.Table("umbracoRelationType").AlterColumn("typeUniqueId").AsGuid().NotNullable(); + Create.Index("IX_umbracoRelationType_UniqueId").OnTable("umbracoRelationType").OnColumn("typeUniqueId") + .Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique(); + } + } + + private static string UpdateRelationTypeGuids(Database database) + { + var updates = database.Query("SELECT id, alias, name FROM umbracoRelationType") + .Select(relationType => Tuple.Create((int) relationType.id, ((string) relationType.alias + "____" + (string) relationType.name).ToGuid())) + .ToList(); + + foreach (var update in updates) + database.Execute("UPDATE umbracoRelationType set typeUniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); + + return string.Empty; + } + + public override void Down() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs index b50c8e5f94..e965e4dd06 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero /// /// See: http://issues.umbraco.org/issue/U4-9188 /// - [Migration("7.6.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] public class UpdateUniqueIndexOnCmsPropertyData : MigrationBase { public UpdateUniqueIndexOnCmsPropertyData(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemoveUmbracoDeployTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemoveUmbracoDeployTables.cs new file mode 100644 index 0000000000..33bcf5dbcb --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemoveUmbracoDeployTables.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + public class RemoveUmbracoDeployTables : MigrationBase + { + public RemoveUmbracoDeployTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + // there are two versions of umbracoDeployDependency, + // 1. one created by 7.4 and never used, we need to remove it (has a sourceId column) + // 2. one created by Deploy itself, we need to keep it (has a sourceUdi column) + if (tables.InvariantContains("umbracoDeployDependency")) + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + if (columns.Any(x => x.TableName.InvariantEquals("umbracoDeployDependency") && x.ColumnName.InvariantEquals("sourceId"))) + Delete.Table("umbracoDeployDependency"); + } + + // always remove umbracoDeployChecksum + if (tables.InvariantContains("umbracoDeployChecksum")) + Delete.Table("umbracoDeployChecksum"); + } + + public override void Down() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs index 50f78ca66d..c3d7857d75 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeOn /// /// This fixes the storage of user languages from the old format like en_us to en-US /// - [Migration("7.3.1", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.1", 0, Constants.System.UmbracoMigrationName)] public class UpdateUserLanguagesToIsoCode : MigrationBase { public UpdateUserLanguagesToIsoCode(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs index 91b4bd6438..a52abf1331 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeTw /// This reinserts all migrations in the migrations table to account for initial rows inserted /// on creation without identity enabled. /// - [Migration("7.3.2", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.2", 0, Constants.System.UmbracoMigrationName)] public class EnsureMigrationsTableIdentityIsCorrect : MigrationBase { public EnsureMigrationsTableIdentityIsCorrect(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddExternalLoginsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddExternalLoginsTable.cs index 0fbec244b2..b5bb24e40e 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddExternalLoginsTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddExternalLoginsTable.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 9, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 9, Constants.System.UmbracoMigrationName)] public class AddExternalLoginsTable : MigrationBase { public AddExternalLoginsTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs index eea56031d4..0baf5bff1a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 14, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 14, Constants.System.UmbracoMigrationName)] public class AddForeignKeysForLanguageAndDictionaryTables : MigrationBase { public AddForeignKeysForLanguageAndDictionaryTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddMigrationTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddMigrationTable.cs index 079dbe1465..856a1d8ff6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddMigrationTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddMigrationTable.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 11, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 11, Constants.System.UmbracoMigrationName)] public class AddMigrationTable : MigrationBase { public AddMigrationTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs index 16c0a923ae..b4ec1c20a6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 6, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 6, Constants.System.UmbracoMigrationName)] public class AddPublicAccessTables : MigrationBase { public AddPublicAccessTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs index c9db282d85..4865a77ab8 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 0, Constants.System.UmbracoMigrationName)] public class AddRelationTypeForDocumentOnDelete : MigrationBase { public AddRelationTypeForDocumentOnDelete(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs index 6a14e408a9..00ab602343 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 17, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 17, Constants.System.UmbracoMigrationName)] public class AddServerRegistrationColumnsAndLock : MigrationBase { public AddServerRegistrationColumnsAndLock(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs index 7589c7000d..070ef5d512 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 13, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 13, Constants.System.UmbracoMigrationName)] public class AddUniqueIdPropertyTypeColumn : MigrationBase { public AddUniqueIdPropertyTypeColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs index 46fde95005..01a3c61791 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 10, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 10, Constants.System.UmbracoMigrationName)] public class AddUserColumns : MigrationBase { public AddUserColumns(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CleanUpCorruptedPublishedFlags.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CleanUpCorruptedPublishedFlags.cs index d62ad7645d..f8dfc0e8bb 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CleanUpCorruptedPublishedFlags.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CleanUpCorruptedPublishedFlags.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe /// Older corrupted dbs might have multiple published flags for a content item, this shouldn't be possible /// so we need to clear the content flag on the older version /// - [Migration("7.3.0", 18, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 18, Constants.System.UmbracoMigrationName)] public class CleanUpCorruptedPublishedFlags : MigrationBase { public CleanUpCorruptedPublishedFlags(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs index afa5a1e675..d51aceb679 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 1, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 1, Constants.System.UmbracoMigrationName)] public class CreateCacheInstructionTable : MigrationBase { public CreateCacheInstructionTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs index 01db36abd9..59236106ab 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe /// /// Remove the master column after we've migrated all of the values into the 'ParentId' and Path column of Umbraco node /// - [Migration("7.3.0", 1, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 1, Constants.System.UmbracoMigrationName)] public class MigrateAndRemoveTemplateMasterColumn : MigrationBase { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateStylesheetDataToFile.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateStylesheetDataToFile.cs index 35e4befc55..40d2a6f183 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateStylesheetDataToFile.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateStylesheetDataToFile.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe /// These files will then be copied over once the entire migration is complete so that if any migration fails and the db changes are /// rolled back, the original files won't be affected. /// - [Migration("7.3.0", 2, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 2, Constants.System.UmbracoMigrationName)] public class MigrateStylesheetDataToFile : MigrationBase { public MigrateStylesheetDataToFile(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs index d60385926b..16d1c6fbf5 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 7, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 7, Constants.System.UmbracoMigrationName)] public class MovePublicAccessXmlDataToDb : MigrationBase { public MovePublicAccessXmlDataToDb(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveHelpTextColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveHelpTextColumn.cs index d4911bd65a..5370ec0de3 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveHelpTextColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveHelpTextColumn.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 8, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 8, Constants.System.UmbracoMigrationName)] public class RemoveHelpTextColumn : MigrationBase { public RemoveHelpTextColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveLanguageLocaleColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveLanguageLocaleColumn.cs index a793cc6bbc..bf57364615 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveLanguageLocaleColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveLanguageLocaleColumn.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 4, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 4, Constants.System.UmbracoMigrationName)] public class RemoveLanguageLocaleColumn : MigrationBase { public RemoveLanguageLocaleColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveStylesheetDataAndTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveStylesheetDataAndTables.cs index da6d1b957d..e6b195cbf4 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveStylesheetDataAndTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveStylesheetDataAndTables.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 3, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 3, Constants.System.UmbracoMigrationName)] public class RemoveStylesheetDataAndTables : MigrationBase { public RemoveStylesheetDataAndTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs index b842ec041a..5fef478cc6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 15, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 15, Constants.System.UmbracoMigrationName)] public class RemoveUmbracoLoginsTable : MigrationBase { public RemoveUmbracoLoginsTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs index ee6849c1e4..8eeca45cb1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 5, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 5, Constants.System.UmbracoMigrationName)] public class UpdateUniqueIdToHaveCorrectIndexType : MigrationBase { public UpdateUniqueIdToHaveCorrectIndexType(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddIndexToUmbracoNodeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddIndexToUmbracoNodeTable.cs index 87c39b5f7e..7d5109a1db 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddIndexToUmbracoNodeTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddIndexToUmbracoNodeTable.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwoZero { - [Migration("7.2.0", 3, GlobalSettings.UmbracoMigrationName)] + [Migration("7.2.0", 3, Constants.System.UmbracoMigrationName)] public class AddIndexToUmbracoNodeTable : MigrationBase { private readonly bool _skipIndexCheck; diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddMissingForeignKeyForContentType.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddMissingForeignKeyForContentType.cs index 31cb6132cc..47b1f334d3 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddMissingForeignKeyForContentType.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddMissingForeignKeyForContentType.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwoZero { - [Migration("7.2.0", 1, GlobalSettings.UmbracoMigrationName)] + [Migration("7.2.0", 1, Constants.System.UmbracoMigrationName)] public class AddMissingForeignKeyForContentType : MigrationBase { public AddMissingForeignKeyForContentType(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs index e0d8e1b5dd..ba3938dd18 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwoZero { - [Migration("7.2.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("7.2.0", 0, Constants.System.UmbracoMigrationName)] public class AlterDataTypePreValueTable : MigrationBase { public AlterDataTypePreValueTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/RemoveCmsDocumentAliasColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/RemoveCmsDocumentAliasColumn.cs index 2a8440af64..e67a8e7756 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/RemoveCmsDocumentAliasColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/RemoveCmsDocumentAliasColumn.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwoZero { - [Migration("7.2.0", 2, GlobalSettings.UmbracoMigrationName)] + [Migration("7.2.0", 2, Constants.System.UmbracoMigrationName)] public class RemoveCmsDocumentAliasColumn : MigrationBase { public RemoveCmsDocumentAliasColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs index a5b9bc1415..b4d394bec2 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 10, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 10, Constants.System.UmbracoMigrationName)] public class DeleteAppTables : MigrationBase { public DeleteAppTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/EnsureAppsTreesUpdated.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/EnsureAppsTreesUpdated.cs index 219936e0e2..49cedc6dc7 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/EnsureAppsTreesUpdated.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/EnsureAppsTreesUpdated.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 9, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 9, Constants.System.UmbracoMigrationName)] public class EnsureAppsTreesUpdated : MigrationBase { public EnsureAppsTreesUpdated(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/MoveMasterContentTypeData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/MoveMasterContentTypeData.cs index 3548d19c33..6e77050fd4 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/MoveMasterContentTypeData.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/MoveMasterContentTypeData.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 5, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 5, Constants.System.UmbracoMigrationName)] public class MoveMasterContentTypeData : MigrationBase { public MoveMasterContentTypeData(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs index 622cefc44c..ee427bb517 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 4, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 4, Constants.System.UmbracoMigrationName)] public class NewCmsContentType2ContentTypeTable : MigrationBase { public NewCmsContentType2ContentTypeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs index 7042e3c21f..e8964ced28 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 6, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 6, Constants.System.UmbracoMigrationName)] public class RemoveMasterContentTypeColumn : MigrationBase { public RemoveMasterContentTypeColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs index 9c97cd3444..c0738383d2 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 0, Constants.System.UmbracoMigrationName)] public class RenameCmsTabTable : MigrationBase { public RenameCmsTabTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs index 14d3049d7e..fff439c03d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 7, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 7, Constants.System.UmbracoMigrationName)] public class RenameTabIdColumn : MigrationBase { public RenameTabIdColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs index ede56d08e9..11168a69cb 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 3, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 3, Constants.System.UmbracoMigrationName)] public class UpdateCmsContentTypeAllowedContentTypeTable : MigrationBase { public UpdateCmsContentTypeAllowedContentTypeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs index 0443c7f0bf..eb57f87c95 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 2, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 2, Constants.System.UmbracoMigrationName)] public class UpdateCmsContentTypeTable : MigrationBase { public UpdateCmsContentTypeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs index f78048fcb1..cee55fee41 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 8, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 8, Constants.System.UmbracoMigrationName)] public class UpdateCmsContentVersionTable : MigrationBase { public UpdateCmsContentVersionTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs index 4b13c8b51d..7e0244bc57 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { - [Migration("6.0.0", 1, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", 1, Constants.System.UmbracoMigrationName)] public class UpdateCmsPropertyTypeGroupTable : MigrationBase { public UpdateCmsPropertyTypeGroupTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs index 9037422960..bcd7ac5d6f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixOneZero { - [Migration("6.1.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("6.1.0", 0, Constants.System.UmbracoMigrationName)] public class CreateServerRegistryTable : MigrationBase { public CreateServerRegistryTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs index bad80e7c3a..42a652b166 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs @@ -6,8 +6,8 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero { - [Migration("7.1.0", 3, GlobalSettings.UmbracoMigrationName)] - [Migration("6.2.0", 3, GlobalSettings.UmbracoMigrationName)] + [Migration("7.1.0", 3, Constants.System.UmbracoMigrationName)] + [Migration("6.2.0", 3, Constants.System.UmbracoMigrationName)] public class AddChangeDocumentTypePermission : MigrationBase { public AddChangeDocumentTypePermission(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs index 0938d9be91..64f2970bdf 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs @@ -8,8 +8,8 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero { - [Migration("7.1.0", 1, GlobalSettings.UmbracoMigrationName)] - [Migration("6.2.0", 1, GlobalSettings.UmbracoMigrationName)] + [Migration("7.1.0", 1, Constants.System.UmbracoMigrationName)] + [Migration("6.2.0", 1, Constants.System.UmbracoMigrationName)] public class AdditionalIndexesAndKeys : MigrationBase { public AdditionalIndexesAndKeys(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs index d8ecf855eb..5f2d6292c1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs @@ -8,8 +8,8 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero { //see: http://issues.umbraco.org/issue/U4-4430 - [Migration("7.1.0", 0, GlobalSettings.UmbracoMigrationName)] - [Migration("6.2.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("7.1.0", 0, Constants.System.UmbracoMigrationName)] + [Migration("6.2.0", 0, Constants.System.UmbracoMigrationName)] public class AssignMissingPrimaryForMySqlKeys : MigrationBase { public AssignMissingPrimaryForMySqlKeys(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys2.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys2.cs index b41bdea124..da7a5ce609 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys2.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys2.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero //We have to target this specifically to ensure this DOES NOT execute if upgrading from a version previous to 6.0, // this is because when the 6.0.0 migrations are executed, this primary key get's created so if this migration is also executed // we will get exceptions because it is trying to create the PK two times. - [Migration("6.0.0", "6.2.0", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.0", "6.2.0", 0, Constants.System.UmbracoMigrationName)] public class AssignMissingPrimaryForMySqlKeys2 : MigrationBase { public AssignMissingPrimaryForMySqlKeys2(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs index cad1c0e127..327291f34b 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs @@ -5,8 +5,8 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero { - [Migration("7.1.0", 2, GlobalSettings.UmbracoMigrationName)] - [Migration("6.2.0", 2, GlobalSettings.UmbracoMigrationName)] + [Migration("7.1.0", 2, Constants.System.UmbracoMigrationName)] + [Migration("6.2.0", 2, Constants.System.UmbracoMigrationName)] public class ChangePasswordColumn : MigrationBase { public ChangePasswordColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs index 454d0e18ed..4038bdd1e9 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs @@ -7,8 +7,8 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero { - [Migration("7.1.0", 4, GlobalSettings.UmbracoMigrationName)] - [Migration("6.2.0", 4, GlobalSettings.UmbracoMigrationName)] + [Migration("7.1.0", 4, Constants.System.UmbracoMigrationName)] + [Migration("6.2.0", 4, Constants.System.UmbracoMigrationName)] public class UpdateToNewMemberPropertyAliases : MigrationBase { public UpdateToNewMemberPropertyAliases(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs index 32c81700a8..15817d313d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne { - [Migration("6.0.2", 0, GlobalSettings.UmbracoMigrationName)] + [Migration("6.0.2", 0, Constants.System.UmbracoMigrationName)] public class UpdatePropertyTypesAndGroups : MigrationBase { public UpdatePropertyTypesAndGroups(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index b569b1c45d..79b3ce3871 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -167,12 +167,12 @@ namespace Umbraco.Core.Persistence var providerName = Constants.DatabaseProviders.SqlServer; if (ConfigurationManager.ConnectionStrings[connectionStringName] != null) { - if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName)) + if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName) == false) providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName; } else { - throw new InvalidOperationException("Can't find a connection string with the name '" + connectionStringName + "'"); + throw new NullReferenceException("Can't find a connection string with the name '" + connectionStringName + "'"); } // Store factory and connection string @@ -332,8 +332,16 @@ namespace Umbraco.Core.Persistence if (_transactionDepth == 1) { OpenSharedConnection(); - _transaction = _sharedConnection.BeginTransaction(isolationLevel); - _transactionCancelled = false; + try + { + _transaction = _sharedConnection.BeginTransaction(isolationLevel); + } + + catch (Exception e) + { + throw; + } + _transactionCancelled = false; OnBeginTransaction(); } else if (isolationLevel > _transaction.IsolationLevel) @@ -695,7 +703,7 @@ namespace Umbraco.Core.Persistence static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); + static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); public static bool SplitSqlForPaging(string sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy) { sqlSelectRemoved = null; @@ -715,10 +723,9 @@ namespace Umbraco.Core.Persistence sqlCount = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length); else sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length); - - - // Look for an "ORDER BY " clause - m = rxOrderBy.Match(sqlCount); + + // Look for an "ORDER BY " clause + m = rxOrderBy.Match(sqlCount); if (!m.Success) { sqlOrderBy = null; @@ -729,8 +736,7 @@ namespace Umbraco.Core.Persistence sqlOrderBy = g.ToString(); sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length); } - - return true; + return true; } /// @@ -812,7 +818,7 @@ namespace Umbraco.Core.Persistence result.CurrentPage = page; result.ItemsPerPage = itemsPerPage; result.TotalItems = ExecuteScalar(sqlCount, args); - result.TotalPages = result.TotalItems / itemsPerPage; + result.TotalPages = result.TotalItems / itemsPerPage; if ((result.TotalItems % itemsPerPage) != 0) result.TotalPages++; diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index 22e66935bf..8626714a43 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -105,8 +105,8 @@ namespace Umbraco.Core.Persistence // try to update var rowCount = updateCommand.IsNullOrWhiteSpace() - ? db.Update(poco) - : db.Update(updateCommand, updateArgs); + ? db.Update(poco) + : db.Update(updateCommand, updateArgs); if (rowCount > 0) return RecordPersistenceType.Update; @@ -162,7 +162,7 @@ namespace Umbraco.Core.Persistence [Obsolete("Use the DatabaseSchemaHelper instead")] public static void CreateTable(this Database db) - where T : new() + where T : new() { var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider); creator.CreateTable(); @@ -170,7 +170,7 @@ namespace Umbraco.Core.Persistence [Obsolete("Use the DatabaseSchemaHelper instead")] public static void CreateTable(this Database db, bool overwrite) - where T : new() + where T : new() { var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider); creator.CreateTable(overwrite); @@ -183,16 +183,31 @@ namespace Umbraco.Core.Persistence /// /// /// + [Obsolete("Use the method that specifies an SqlSyntaxContext instance instead")] public static void BulkInsertRecords(this Database db, IEnumerable collection) { - //don't do anything if there are no records. - if (collection.Any() == false) - return; + db.BulkInsertRecords(collection, null, SqlSyntaxContext.SqlSyntaxProvider, true, false); + } - using (var tr = db.GetTransaction()) - { - db.BulkInsertRecords(collection, tr, SqlSyntaxContext.SqlSyntaxProvider, true, true); // use native, commit - } + + /// + /// Performs the bulk insertion + /// + /// + /// + /// + /// + /// + /// If this is false this will try to just generate bulk insert statements instead of using the current SQL platform's bulk + /// insert logic. For SQLCE, bulk insert statements do not work so if this is false it will insert one at a time. + /// + /// The number of items inserted + public static int BulkInsertRecords(this Database db, + IEnumerable collection, + ISqlSyntaxProvider syntaxProvider, + bool useNativeSqlPlatformBulkInsert = true) + { + return BulkInsertRecords(db, collection, null, syntaxProvider, useNativeSqlPlatformBulkInsert, false); } /// @@ -217,6 +232,26 @@ namespace Umbraco.Core.Persistence bool useNativeSqlPlatformBulkInsert = true, bool commitTrans = false) { + db.OpenSharedConnection(); + try + { + return BulkInsertRecordsTry(db, collection, tr, syntaxProvider, useNativeSqlPlatformBulkInsert, commitTrans); + } + finally + { + db.CloseSharedConnection(); + } + } + + public static int BulkInsertRecordsTry(this Database db, + IEnumerable collection, + Transaction tr, + ISqlSyntaxProvider syntaxProvider, + bool useNativeSqlPlatformBulkInsert = true, + bool commitTrans = false) + { + if (commitTrans && tr == null) + throw new ArgumentNullException("tr", "The transaction cannot be null if commitTrans is true."); //don't do anything if there are no records. if (collection.Any() == false) @@ -224,15 +259,15 @@ namespace Umbraco.Core.Persistence return 0; } - var pd = Database.PocoData.ForType(typeof(T)); - if (pd == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T)); + var pd = Database.PocoData.ForType(typeof (T)); + if (pd == null) throw new InvalidOperationException("Could not find PocoData for " + typeof (T)); try { int processed = 0; var usedNativeSqlPlatformInserts = useNativeSqlPlatformBulkInsert - && NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed); + && NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed); if (usedNativeSqlPlatformInserts == false) { @@ -266,17 +301,13 @@ namespace Umbraco.Core.Persistence } if (commitTrans) - { tr.Complete(); - } return processed; } catch { if (commitTrans) - { tr.Dispose(); - } throw; } @@ -318,13 +349,16 @@ namespace Umbraco.Core.Persistence IEnumerable collection, out string[] sql) { + if (db == null) throw new ArgumentNullException("db"); + if (db.Connection == null) throw new ArgumentException("db.Connection is null."); + var tableName = db.EscapeTableName(pd.TableInfo.TableName); //get all columns to include and format for sql var cols = string.Join(", ", pd.Columns - .Where(c => IncludeColumn(pd, c)) - .Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray()); + .Where(c => IncludeColumn(pd, c)) + .Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray()); var itemArray = collection.ToArray(); @@ -338,9 +372,9 @@ namespace Umbraco.Core.Persistence // 4168 / 262 = 15.908... = there will be 16 trans in total //all items will be included if we have disabled db parameters - var itemsPerTrans = Math.Floor(2000.00 / paramsPerItem); + var itemsPerTrans = Math.Floor(2000.00/paramsPerItem); //there will only be one transaction if we have disabled db parameters - var numTrans = Math.Ceiling(itemArray.Length / itemsPerTrans); + var numTrans = Math.Ceiling(itemArray.Length/itemsPerTrans); var sqlQueries = new List(); var commands = new List(); @@ -348,8 +382,8 @@ namespace Umbraco.Core.Persistence for (var tIndex = 0; tIndex < numTrans; tIndex++) { var itemsForTrans = itemArray - .Skip(tIndex * (int)itemsPerTrans) - .Take((int)itemsPerTrans); + .Skip(tIndex*(int) itemsPerTrans) + .Take((int) itemsPerTrans); var cmd = db.CreateCommand(db.Connection, string.Empty); var pocoValues = new List(); @@ -399,7 +433,6 @@ namespace Umbraco.Core.Persistence /// The number of records inserted private static bool NativeSqlPlatformBulkInsertRecords(Database db, ISqlSyntaxProvider syntaxProvider, Database.PocoData pd, IEnumerable collection, out int processed) { - var dbConnection = db.Connection; //unwrap the profiled connection if there is one @@ -428,7 +461,6 @@ namespace Umbraco.Core.Persistence //could not use the SQL server's specific bulk insert operations processed = 0; return false; - } /// diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs index 33aadbde05..090410782a 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -29,6 +29,7 @@ namespace Umbraco.Core.Persistence return sql.From(sqlSyntax.GetQuotedTableName(tableName)); } + [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] public static Sql Where(this Sql sql, Expression> predicate) { var expresionist = new PocoToSqlExpressionVisitor(); @@ -36,6 +37,13 @@ namespace Umbraco.Core.Persistence return sql.Where(whereExpression, expresionist.GetSqlParameters()); } + public static Sql Where(this Sql sql, Expression> predicate, ISqlSyntaxProvider sqlSyntax) + { + var expresionist = new PocoToSqlExpressionVisitor(sqlSyntax); + var whereExpression = expresionist.Visit(predicate); + return sql.Where(whereExpression, expresionist.GetSqlParameters()); + } + private static string GetFieldName(Expression> fieldSelector, ISqlSyntaxProvider sqlSyntax) { var field = ExpressionHelper.FindProperty(fieldSelector) as PropertyInfo; diff --git a/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs b/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs new file mode 100644 index 0000000000..5c502ec170 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq.Expressions; + +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// Represents an expression which caches the visitor's result. + /// + internal class CachedExpression : Expression + { + private string _visitResult; + + /// + /// Gets or sets the inner Expression. + /// + public Expression InnerExpression { get; private set; } + + /// + /// Gets or sets the compiled SQL statement output. + /// + public string VisitResult + { + get { return _visitResult; } + set + { + if (Visited) + throw new InvalidOperationException("Cached expression has already been visited."); + _visitResult = value; + Visited = true; + } + } + + /// + /// Gets or sets a value indicating whether the cache Expression has been compiled already. + /// + public bool Visited { get; private set; } + + /// + /// Replaces the inner expression. + /// + /// expression. + /// The new expression is assumed to have different parameter but produce the same SQL statement. + public void Wrap(Expression expression) + { + InnerExpression = expression; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs index 678ceb1d8e..9ebb3d17b0 100644 --- a/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs +++ b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs @@ -9,49 +9,6 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { - /// - /// Represents an expression which caches the visitor's result. - /// - internal class CachedExpression : Expression - { - private string _visitResult; - - /// - /// Gets or sets the inner Expression. - /// - public Expression InnerExpression { get; private set; } - - /// - /// Gets or sets the compiled SQL statement output. - /// - public string VisitResult - { - get { return _visitResult; } - set - { - if (Visited) - throw new InvalidOperationException("Cached expression has already been visited."); - _visitResult = value; - Visited = true; - } - } - - /// - /// Gets or sets a value indicating whether the cache Expression has been compiled already. - /// - public bool Visited { get; private set; } - - /// - /// Replaces the inner expression. - /// - /// expression. - /// The new expression is assumed to have different parameter but produce the same SQL statement. - public void Wrap(Expression expression) - { - InnerExpression = expression; - } - } - /// /// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression. /// @@ -581,6 +538,18 @@ namespace Umbraco.Core.Persistence.Querying case "InvariantContains": case "InvariantEquals": + //special case, if it is 'Contains' and the argumet that Contains is being called on is + //Enumerable and the methodArgs is the actual member access, then it's an SQL IN claus + if (m.Object == null + && m.Arguments[0].Type != typeof(string) + && m.Arguments.Count == 2 + && methodArgs.Length == 1 + && methodArgs[0].NodeType == ExpressionType.MemberAccess + && TypeHelper.IsTypeAssignableFrom(m.Arguments[0].Type)) + { + goto case "SqlIn"; + } + string compareValue; if (methodArgs[0].NodeType != ExpressionType.Constant) @@ -597,13 +566,6 @@ namespace Umbraco.Core.Persistence.Querying compareValue = methodArgs[0].ToString(); } - //special case, if it is 'Contains' and the member that Contains is being called on is not a string, then - // we should be doing an 'In' clause - but we currently do not support this - if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) - { - throw new NotSupportedException("An array Contains method is not supported"); - } - //default column type var colType = TextColumnType.NVarchar; @@ -705,29 +667,33 @@ namespace Umbraco.Core.Persistence.Querying // } // return string.Format("{0}{1}", r, s); - //case "In": + case "SqlIn": - // var member = Expression.Convert(m.Arguments[0], typeof(object)); - // var lambda = Expression.Lambda>(member); - // var getter = lambda.Compile(); + if (m.Object == null && methodArgs.Length == 1 && methodArgs[0].NodeType == ExpressionType.MemberAccess) + { + var memberAccess = VisitMemberAccess((MemberExpression) methodArgs[0]); + + var member = Expression.Convert(m.Arguments[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); - // var inArgs = (object[])getter(); + var inArgs = (IEnumerable)getter(); - // var sIn = new StringBuilder(); - // foreach (var e in inArgs) - // { - // SqlParameters.Add(e); + var sIn = new StringBuilder(); + foreach (var e in inArgs) + { + SqlParameters.Add(e); - // sIn.AppendFormat("{0}{1}", - // sIn.Length > 0 ? "," : "", - // string.Format("@{0}", SqlParameters.Count - 1)); + sIn.AppendFormat("{0}{1}", + sIn.Length > 0 ? "," : "", + string.Format("@{0}", SqlParameters.Count - 1)); + } - // //sIn.AppendFormat("{0}{1}", - // // sIn.Length > 0 ? "," : "", - // // GetQuotedValue(e, e.GetType())); - // } + return string.Format("{0} IN ({1})", memberAccess, sIn); + } + + throw new NotSupportedException("SqlIn must contain the member being accessed"); - // return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString()); //case "Desc": // return string.Format("{0} DESC", r); //case "Alias": 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 6b527296df..4569b95853 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs @@ -14,12 +14,19 @@ namespace Umbraco.Core.Persistence.Querying { private readonly Database.PocoData _pd; - public PocoToSqlExpressionVisitor() - : base(SqlSyntaxContext.SqlSyntaxProvider) + + public PocoToSqlExpressionVisitor(ISqlSyntaxProvider syntaxProvider) + : base(syntaxProvider) { _pd = new Database.PocoData(typeof(T)); } + [Obsolete("Use the overload the specifies a SqlSyntaxProvider")] + public PocoToSqlExpressionVisitor() + : this(SqlSyntaxContext.SqlSyntaxProvider) + { + } + protected override string VisitMemberAccess(MemberExpression m) { if (m.Expression != null && diff --git a/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs b/src/Umbraco.Core/Persistence/Querying/SqlExpressionExtensions.cs similarity index 83% rename from src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs rename to src/Umbraco.Core/Persistence/Querying/SqlExpressionExtensions.cs index cbecc0a591..5a0452c3aa 100644 --- a/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs +++ b/src/Umbraco.Core/Persistence/Querying/SqlExpressionExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; namespace Umbraco.Core.Persistence.Querying @@ -6,8 +8,13 @@ namespace Umbraco.Core.Persistence.Querying /// /// String extension methods used specifically to translate into SQL /// - internal static class SqlStringExtensions + internal static class SqlExpressionExtensions { + public static bool SqlIn(this IEnumerable collection, T item) + { + return collection.Contains(item); + } + public static bool SqlWildcard(this string str, string txt, TextColumnType columnType) { var wildcardmatch = new Regex("^" + Regex.Escape(txt). diff --git a/src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs b/src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs index 2f457edfd8..fb563fa25c 100644 --- a/src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs +++ b/src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs @@ -3,48 +3,6 @@ using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Relators { - //internal class TaskUserRelator - //{ - // internal TaskDto Current; - - // internal TaskDto Map(TaskDto a, UserDto p) - // { - // // Terminating call. Since we can return null from this function - // // we need to be ready for PetaPoco to callback later with null - // // parameters - // if (a == null) - // return Current; - - // // Is this the same TaskDto as the current one we're processing - // if (Current != null && Current.Id == a.Id) - // { - // // Yes, set the user - // Current.MacroPropertyDtos.Add(p); - - // // Return null to indicate we're not done with this Macro yet - // return null; - // } - - // // This is a different Macro to the current one, or this is the - // // first time through and we don't have one yet - - // // Save the current Macro - // var prev = Current; - - // // Setup the new current Macro - // Current = a; - // Current.MacroPropertyDtos = new List(); - // //this can be null since we are doing a left join - // if (p.Alias != null) - // { - // Current.MacroPropertyDtos.Add(p); - // } - - // // Return the now populated previous Macro (or null if first time through) - // return prev; - // } - //} - internal class MacroPropertyRelator { internal MacroDto Current; diff --git a/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs index 53ffda9364..d425755579 100644 --- a/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.Repositories { internal class AuditRepository : PetaPocoRepositoryBase, IAuditRepository { - public AuditRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public AuditRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs b/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs new file mode 100644 index 0000000000..05061c47af --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs @@ -0,0 +1,31 @@ +namespace Umbraco.Core.Persistence.Repositories +{ + internal enum BaseQueryType + { + /// + /// A query to return all information for a single item + /// + /// + /// In some cases this will be the same as + /// + FullSingle, + + /// + /// A query to return all information for multiple items + /// + /// + /// In some cases this will be the same as + /// + FullMultiple, + + /// + /// A query to return the ids for items + /// + Ids, + + /// + /// A query to return the count for items + /// + Count + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs index b368488833..ee19161ae0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs @@ -17,10 +17,9 @@ namespace Umbraco.Core.Persistence.Repositories internal class ContentPreviewRepository : PetaPocoRepositoryBase> where TContent : IContentBase { - public ContentPreviewRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public ContentPreviewRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - } + { } #region Not implemented (don't need to for the purposes of this repo) protected override ContentPreviewEntity PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 9f9180c98f..37e5e80fe9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Xml; @@ -31,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ContentPreviewRepository _contentPreviewRepository; private readonly ContentXmlRepository _contentXmlRepository; - public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) + public ContentRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) : base(work, cacheHelper, logger, syntaxProvider, contentSection) { if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository"); @@ -41,8 +42,8 @@ namespace Umbraco.Core.Persistence.Repositories _templateRepository = templateRepository; _tagRepository = tagRepository; _cacheHelper = cacheHelper; - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider); - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider); + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.NoCache, logger, syntaxProvider); + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.NoCache, logger, syntaxProvider); EnsureUniqueNaming = true; } @@ -53,9 +54,9 @@ namespace Umbraco.Core.Persistence.Repositories protected override IContent PerformGet(int id) { - var sql = GetBaseQuery(false) + var sql = GetBaseQuery(BaseQueryType.FullSingle) .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,69 +71,102 @@ 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.FullMultiple); + 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), new PagingSqlQuery(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.FullMultiple); + 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), new PagingSqlQuery(translate(translatorIds))); } - #endregion - - #region Static Queries - - private readonly IQuery _publishedQuery = Query.Builder.Where(x => x.Published == true); - - #endregion + #endregion #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) + /// + /// Returns the base query to return Content + /// + /// + /// + /// + /// Content queries will differ depending on what needs to be returned: + /// * FullSingle: When querying for a single document, this will include the Outer join to fetch the content item's published version info + /// * FullMultiple: When querying for multiple documents, this will exclude the Outer join to fetch the content item's published version info - this info would need to be fetched separately + /// * Ids: This would essentially be the same as FullMultiple however the columns specified will only return the Ids for the documents + /// * Count: A query to return the count for documents + /// + 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.FullSingle) + { + //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 retrieving Ids like in paging, this is unecessary + //and causes huge performance overhead for the SQL server, especially when sorting the result. + //We also don't include this outer join when querying for multiple entities since it is much faster to fetch this information + //in a separate query. For a single entity this is ok. + + 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.FullSingle); + } + protected override string GetBaseWhereClause() { return "umbracoNode.id = @Id"; @@ -179,20 +213,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.FullMultiple)); + var sqlIds = translate(baseId, GetBaseQuery(BaseQueryType.Ids)); + + var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), new PagingSqlQuery(SqlSyntax.SelectTop(sqlIds, groupSize))) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) .ToList(); @@ -212,23 +258,29 @@ namespace Umbraco.Core.Persistence.Repositories Logger.Error("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e); } } - baseId = xmlItems.Last().NodeId; + baseId = xmlItems[xmlItems.Count - 1].NodeId; } } 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.FullMultiple)); + var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids)); + + return ProcessQuery(sqlFull, new PagingSqlQuery(sqlIds), true); } public override IContent GetByVersion(Guid versionId) { - var sql = GetBaseQuery(false); + var sql = GetBaseQuery(BaseQueryType.FullSingle); 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(); @@ -244,10 +296,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; @@ -348,6 +400,7 @@ namespace Umbraco.Core.Persistence.Repositories //Update with new correct path nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + nodeDto.ValidatePathWithException(); Database.Update(nodeDto); //Update entity with correct values @@ -486,6 +539,7 @@ namespace Umbraco.Core.Persistence.Repositories //Updates the (base) node data - umbracoNode var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + nodeDto.ValidatePathWithException(); var o = Database.Update(nodeDto); //Only update this DTO if the contentType has actually changed @@ -621,21 +675,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.FullMultiple); + 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), new PagingSqlQuery(translate(translatorIds)), true); } - + /// /// This builds the Xml document used for the XML cache /// @@ -667,10 +727,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; @@ -721,13 +785,8 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; public void ClearPublished(IContent content) { - // race cond! - var documentDtos = Database.Fetch("WHERE nodeId=@id AND published=@published", new { id = content.Id, published = true }); - foreach (var documentDto in documentDtos) - { - documentDto.Published = false; - Database.Update(documentDto); - } + var sql = "UPDATE cmsDocument SET published=0 WHERE nodeId=@id AND published=1"; + Database.Execute(sql, new {id = content.Id}); } /// @@ -807,9 +866,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, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -839,26 +898,80 @@ 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 FullMultiple SQL without the outer join to return all data required to create an IContent excluding it's published state data which this will query separately + /// + /// + /// 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, PagingSqlQuery pagingSqlQuery, 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(); + + //Go and get all of the published version data separately for this data, this is because when we are querying + //for multiple content items we don't include the outer join to fetch this data in the same query because + //it is insanely slow. Instead we just fetch the published version data separately in one query. + + //we need to parse the original SQL statement and reduce the columns to just cmsDocument.nodeId so that we can use + // the statement to go get the published data for all of the items by using an inner join + var parsedOriginalSql = "SELECT cmsDocument.nodeId " + sqlFull.SQL.Substring(sqlFull.SQL.IndexOf("FROM", StringComparison.Ordinal)); + //now remove everything from an Orderby clause and beyond + if (parsedOriginalSql.InvariantContains("ORDER BY ")) + { + parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); + } + + var publishedSql = new Sql(@"SELECT * +FROM cmsDocument AS doc2 +INNER JOIN + (" + parsedOriginalSql + @") as docData +ON doc2.nodeId = docData.nodeId +WHERE doc2.published = 1 +ORDER BY doc2.nodeId +", sqlFull.Arguments); + + //go and get the published version data, we do a Query here and not a Fetch so we are + //not allocating a whole list to memory just to allocate another list in memory since + //we are assigning this data to a keyed collection for fast lookup below + var publishedData = Database.Query(publishedSql); + var publishedDataCollection = new DocumentPublishedReadOnlyDtoCollection(); + foreach (var publishedDto in publishedData) + { + //double check that there's no corrupt db data, there should only be a single published item + if (publishedDataCollection.Contains(publishedDto.NodeId) == false) + publishedDataCollection.Add(publishedDto); + } + 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]; + DocumentPublishedReadOnlyDto publishedDto; + publishedDataCollection.TryGetValue(dto.NodeId, out publishedDto); // if the cache contains the published version, use it if (withCache) { - var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - if (cached != null && cached.Published) + var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + //only use this cached version if the dto returned is also the publish version, they must match + if (cached != null && cached.Published && dto.Published) { content[i] = cached; continue; @@ -867,9 +980,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, publishedDto); // need template if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) @@ -890,7 +1013,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(pagingSqlQuery, defs); // assign var dtoIndex = 0; @@ -910,7 +1033,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; @@ -927,8 +1050,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) @@ -979,7 +1101,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; return currentName; } - + /// /// Dispose disposable properties /// @@ -994,5 +1116,26 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; _contentPreviewRepository.Dispose(); _contentXmlRepository.Dispose(); } + + /// + /// A keyed collection for fast lookup when retrieving a separate list of published version data + /// + private class DocumentPublishedReadOnlyDtoCollection : KeyedCollection + { + protected override int GetKeyForItem(DocumentPublishedReadOnlyDto item) + { + return item.NodeId; + } + + public bool TryGetValue(int key, out DocumentPublishedReadOnlyDto val) + { + if (Dictionary == null) + { + val = null; + return false; + } + return Dictionary.TryGetValue(key, out val); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index d86f77168e..508ba06bb1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories internal abstract class ContentTypeBaseRepository : PetaPocoRepositoryBase, IReadRepository where TEntity : class, IContentTypeComposition { - protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + protected ContentTypeBaseRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index b7b4ddd583..b440d477a5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -2,19 +2,13 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -25,23 +19,15 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly ITemplateRepository _templateRepository; - public ContentTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, ITemplateRepository templateRepository) + public ContentTypeRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, ITemplateRepository templateRepository) : base(work, cache, logger, sqlSyntax) { _templateRepository = templateRepository; } - private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( - RuntimeCache, GetEntityId, () => PerformGetAll(), - //allow this cache to expire - expires:true)); - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ true); } protected override IContentType PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs index b242bb34f7..464ae16961 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories internal class ContentXmlRepository : PetaPocoRepositoryBase> where TContent : IContentBase { - public ContentXmlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public ContentXmlRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index a4d71885f8..071bf7eba8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -29,12 +29,12 @@ namespace Umbraco.Core.Persistence.Repositories private readonly IContentTypeRepository _contentTypeRepository; private readonly DataTypePreValueRepository _preValRepository; - public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, + public DataTypeDefinitionRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentTypeRepository contentTypeRepository) : base(work, cache, logger, sqlSyntax) { _contentTypeRepository = contentTypeRepository; - _preValRepository = new DataTypePreValueRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + _preValRepository = new DataTypePreValueRepository(work, CacheHelper.NoCache, logger, sqlSyntax); } #region Overrides of RepositoryBase @@ -237,7 +237,7 @@ AND umbracoNode.id <> @id", //NOTE: This is a special case, we need to clear the custom cache for pre-values here so they are not stale if devs // are querying for them in the Saved event (before the distributed call cache is clearing it) - RuntimeCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id)); + IsolatedCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id)); entity.ResetDirtyProperties(); } @@ -276,7 +276,7 @@ AND umbracoNode.id <> @id", public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId) { - var cached = RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); + var cached = IsolatedCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); if (cached != null && cached.Any()) { //return from the cache, ensure it's a cloned result @@ -295,7 +295,7 @@ AND umbracoNode.id <> @id", { //We need to see if we can find the cached PreValueCollection based on the cache key above - var cached = RuntimeCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId)); + var cached = IsolatedCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId)); if (cached != null && cached.Any()) { //return from the cache @@ -463,7 +463,7 @@ AND umbracoNode.id <> @id", + string.Join(",", collection.FormatAsDictionary().Select(x => x.Value.Id).ToArray()); //store into cache - RuntimeCache.InsertCacheItem(key, () => collection, + IsolatedCache.InsertCacheItem(key, () => collection, //30 mins new TimeSpan(0, 0, 30), //sliding is true @@ -519,10 +519,9 @@ AND umbracoNode.id <> @id", /// private class DataTypePreValueRepository : PetaPocoRepositoryBase { - public DataTypePreValueRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public DataTypePreValueRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - } + { } #region Not implemented (don't need to for the purposes of this repo) protected override PreValueEntity PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs index 971efc4f2d..2c6313f3d1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -20,25 +20,19 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class DictionaryRepository : PetaPocoRepositoryBase, IDictionaryRepository { - public DictionaryRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider syntax) + public DictionaryRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider syntax) : base(work, cache, logger, syntax) - { - } + { } - private IRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get + var options = new RepositoryCachePolicyOptions { - //custom cache policy which will not cache any results for GetAll - return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory( - RuntimeCache, - new RepositoryCachePolicyOptions - { - //allow zero to be cached - GetAllCacheAllowZeroCount = true - })); - } + //allow zero to be cached + GetAllCacheAllowZeroCount = true + }; + + return new SingleItemsOnlyRepositoryCachePolicy(runtimeCache, options); } #region Overrides of RepositoryBase @@ -52,7 +46,7 @@ namespace Umbraco.Core.Persistence.Repositories var dto = Database.Fetch(new DictionaryLanguageTextRelator().Map, sql).FirstOrDefault(); if (dto == null) return null; - + var entity = ConvertFromDto(dto); //on initial construction we don't want to have dirty properties tracked @@ -80,7 +74,7 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); sql.OrderBy(x => x.UniqueId, SqlSyntax); - + return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) .Select(x => ConvertFromDto(x)); } @@ -149,7 +143,7 @@ namespace Umbraco.Core.Persistence.Repositories translation.Key = dictionaryItem.Key; } - dictionaryItem.ResetDirtyProperties(); + dictionaryItem.ResetDirtyProperties(); } protected override void PersistUpdatedItem(IDictionaryItem entity) @@ -182,8 +176,8 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); //Clear the cache entries that exist by uniqueid/item key - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Key)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.Key)); } protected override void PersistDeletedItem(IDictionaryItem entity) @@ -194,8 +188,8 @@ namespace Umbraco.Core.Persistence.Repositories Database.Delete("WHERE id = @Id", new { Id = entity.Key }); //Clear the cache entries that exist by uniqueid/item key - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Key)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.Key)); } private void RecursiveDelete(Guid parentId) @@ -209,8 +203,8 @@ namespace Umbraco.Core.Persistence.Repositories Database.Delete("WHERE id = @Id", new { Id = dto.UniqueId }); //Clear the cache entries that exist by uniqueid/item key - RuntimeCache.ClearCacheItem(GetCacheIdKey(dto.Key)); - RuntimeCache.ClearCacheItem(GetCacheIdKey(dto.UniqueId)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(dto.Key)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(dto.UniqueId)); } } @@ -223,7 +217,7 @@ namespace Umbraco.Core.Persistence.Repositories var list = new List(); foreach (var textDto in dto.LanguageTextDtos) - { + { if (textDto.LanguageId <= 0) continue; @@ -237,7 +231,8 @@ namespace Umbraco.Core.Persistence.Repositories public IDictionaryItem Get(Guid uniqueId) { - using (var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) + // note: normal to use GlobalCache here since we're passing it to a new repository + using (var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, UnitOfWork, GlobalCache, Logger, SqlSyntax)) { return uniqueIdRepo.Get(uniqueId); } @@ -245,12 +240,13 @@ namespace Umbraco.Core.Persistence.Repositories public IDictionaryItem Get(string key) { - using (var keyRepo = new DictionaryByKeyRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) + // note: normal to use GlobalCache here since we're passing it to a new repository + using (var keyRepo = new DictionaryByKeyRepository(this, UnitOfWork, GlobalCache, Logger, SqlSyntax)) { return keyRepo.Get(key); } } - + private IEnumerable GetRootDictionaryItems() { var query = Query.Builder.Where(x => x.ParentId == null); @@ -293,7 +289,7 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly DictionaryRepository _dictionaryRepository; - public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { _dictionaryRepository = dictionaryRepository; @@ -329,20 +325,15 @@ namespace Umbraco.Core.Persistence.Repositories return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " in (@ids)"; } - private IRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get + var options = new RepositoryCachePolicyOptions { - //custom cache policy which will not cache any results for GetAll - return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory( - RuntimeCache, - new RepositoryCachePolicyOptions - { - //allow zero to be cached - GetAllCacheAllowZeroCount = true - })); - } + //allow zero to be cached + GetAllCacheAllowZeroCount = true + }; + + return new SingleItemsOnlyRepositoryCachePolicy(runtimeCache, options); } } @@ -350,7 +341,7 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly DictionaryRepository _dictionaryRepository; - public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { _dictionaryRepository = dictionaryRepository; @@ -386,23 +377,16 @@ namespace Umbraco.Core.Persistence.Repositories return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " in (@ids)"; } - private IRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get + var options = new RepositoryCachePolicyOptions { - //custom cache policy which will not cache any results for GetAll - return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory( - RuntimeCache, - new RepositoryCachePolicyOptions - { - //allow zero to be cached - GetAllCacheAllowZeroCount = true - })); - } + //allow zero to be cached + GetAllCacheAllowZeroCount = true + }; + + return new SingleItemsOnlyRepositoryCachePolicy(runtimeCache, options); } } - - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs index 7b6cc162a8..3dca2ffdd0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs @@ -18,20 +18,14 @@ namespace Umbraco.Core.Persistence.Repositories internal class DomainRepository : PetaPocoRepositoryBase, IDomainRepository { - public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public DomainRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } - private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( - RuntimeCache, GetEntityId, () => PerformGetAll(), false)); - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ false); } protected override IDomain PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index cc13275798..b741238fb9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly Guid _containerObjectType; - public EntityContainerRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, Guid containerObjectType) + public EntityContainerRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, Guid containerObjectType) : base(work, cache, logger, sqlSyntax) { var allowedContainers = new[] {Constants.ObjectTypes.DocumentTypeContainerGuid, Constants.ObjectTypes.MediaTypeContainerGuid, Constants.ObjectTypes.DataTypeContainerGuid}; @@ -29,12 +29,11 @@ namespace Umbraco.Core.Persistence.Repositories throw new InvalidOperationException("No container type exists with ID: " + _containerObjectType); } - /// - /// Do not cache anything - /// - protected override IRuntimeCacheProvider RuntimeCache + // never cache + private static readonly IRuntimeCacheProvider NullCache = new NullCacheProvider(); + protected override IRuntimeCacheProvider GetIsolatedCache(IsolatedRuntimeCache provider) { - get { return new NullCacheProvider(); } + return NullCache; } protected override EntityContainer PerformGet(int id) @@ -62,17 +61,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/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index ac7e410a8f..eb372eda56 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models; using Umbraco.Core; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; @@ -49,6 +50,124 @@ namespace Umbraco.Core.Persistence.Repositories #region Query Methods + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, IQuery filter = null) + { + bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); + bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var factory = new UmbracoEntityFactory(); + + var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql => + { + if (filter != null) + { + foreach (var filterClaus in filter.GetWhereClauses()) + { + sql.Where(filterClaus.Item1, filterClaus.Item2); + } + } + }, objectTypeId); + var translator = new SqlTranslator(sqlClause, query); + var entitySql = translator.Translate(); + var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)).OrderBy("umbracoNode.id"); + + IEnumerable result; + + if (isMedia) + { + //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! + var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); + + var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000); + var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); + + //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before + foreach (var idGroup in ids) + { + var propSql = GetPropertySql(Constants.ObjectTypes.Media) + .Where("contentNodeId IN (@ids)", new {ids = idGroup}) + .OrderBy("contentNodeId"); + + //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 = _work.Database.Query(propSql); + + //keep track of the current property data item being enumerated + var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); + var hasCurrent = false; // initially there is no enumerator.Current + + try + { + //This must be sorted by node id (which is done by SQL) 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 entity in entities) + { + // assemble the dtos for this def + // use the available enumerator.Current if any else move to next + while (hasCurrent || propertyDataSetEnumerator.MoveNext()) + { + if (propertyDataSetEnumerator.Current.contentNodeId == entity.Id) + { + hasCurrent = false; // enumerator.Current is not available + + //the property data goes into the additional data + entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty + { + PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, + Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.dataNtext) + ? propertyDataSetEnumerator.Current.dataNvarchar + : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.dataNtext) + }; + } + else + { + hasCurrent = true; // enumerator.Current is available for another def + break; // no more propertyDataDto for this def + } + } + } + } + finally + { + propertyDataSetEnumerator.Dispose(); + } + } + + result = entities; + } + else + { + var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); + result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); + } + + //The total items from the PetaPoco page query will be wrong due to the Outer join used on parent, depending on the search this will + //return duplicate results when the COUNT is used in conjuction with it, so we need to get the total on our own. + + //generate a query that does not contain the LEFT Join for parent, this would cause + //the COUNT(*) query to return the wrong + var sqlCountClause = GetBaseWhere( + (isC, isM, f) => GetBase(isC, isM, f, true), //true == is a count query + isContent, isMedia, sql => + { + if (filter != null) + { + foreach (var filterClaus in filter.GetWhereClauses()) + { + sql.Where(filterClaus.Item1, filterClaus.Item2); + } + } + }, objectTypeId); + var translatorCount = new SqlTranslator(sqlCountClause, query); + var countSql = translatorCount.Translate(); + + totalRecords = _work.Database.ExecuteScalar(countSql); + + return result; + } + public IUmbracoEntity GetByKey(Guid key) { var sql = GetBaseWhere(GetBase, false, false, key); @@ -71,8 +190,7 @@ namespace Umbraco.Core.Persistence.Repositories if (isMedia) { - //for now treat media differently - //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! var entities = _work.Database.Fetch( new UmbracoEntityRelator().Map, sql); @@ -115,8 +233,7 @@ namespace Umbraco.Core.Persistence.Repositories if (isMedia) { - //for now treat media differently - //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! var entities = _work.Database.Fetch( new UmbracoEntityRelator().Map, sql); @@ -171,8 +288,7 @@ namespace Umbraco.Core.Persistence.Repositories if (isMedia) { - //for now treat media differently - //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! var entities = _work.Database.Fetch( new UmbracoEntityRelator().Map, sql); foreach (var entity in entities) @@ -231,8 +347,7 @@ namespace Umbraco.Core.Persistence.Repositories } }); - //treat media differently for now - //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! var entities = _work.Database.Fetch( new UmbracoEntityRelator().Map, mediaSql); return entities; @@ -278,11 +393,9 @@ namespace Umbraco.Core.Persistence.Repositories return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), filter); } - private Sql GetFullSqlForMedia(Sql entitySql, Action filter = null) + private Sql GetPropertySql(string nodeObjectType) { - //this will add any dataNvarchar property to the output which can be added to the additional properties - - var joinSql = new Sql() + var sql = new Sql() .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") .From() .InnerJoin() @@ -291,7 +404,16 @@ namespace Umbraco.Core.Persistence.Repositories .On(dto => dto.Id, dto => dto.PropertyTypeId) .InnerJoin() .On(dto => dto.DataTypeId, dto => dto.DataTypeId) - .Where("umbracoNode.nodeObjectType = @nodeObjectType", new {nodeObjectType = Constants.ObjectTypes.Media}); + .Where("umbracoNode.nodeObjectType = @nodeObjectType", new { nodeObjectType = nodeObjectType }); + + return sql; + } + + private Sql GetFullSqlForMedia(Sql entitySql, Action filter = null) + { + //this will add any dataNvarchar property to the output which can be added to the additional properties + + var joinSql = GetPropertySql(Constants.ObjectTypes.Media); if (filter != null) { @@ -314,30 +436,48 @@ namespace Umbraco.Core.Persistence.Repositories protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) { - var columns = new List - { - "umbracoNode.id", - "umbracoNode.trashed", - "umbracoNode.parentID", - "umbracoNode.nodeUser", - "umbracoNode.level", - "umbracoNode.path", - "umbracoNode.sortOrder", - "umbracoNode.uniqueID", - "umbracoNode.text", - "umbracoNode.nodeObjectType", - "umbracoNode.createDate", - "COUNT(parent.parentID) as children" - }; + return GetBase(isContent, isMedia, customFilter, false); + } - if (isContent || isMedia) + protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter, bool isCount) + { + var columns = new List(); + if (isCount) { - columns.Add("published.versionId as publishedVersion"); - columns.Add("latest.versionId as newestVersion"); - columns.Add("contenttype.alias"); - columns.Add("contenttype.icon"); - columns.Add("contenttype.thumbnail"); - columns.Add("contenttype.isContainer"); + columns.Add("COUNT(*)"); + } + else + { + columns.AddRange(new List + { + "umbracoNode.id", + "umbracoNode.trashed", + "umbracoNode.parentID", + "umbracoNode.nodeUser", + "umbracoNode.level", + "umbracoNode.path", + "umbracoNode.sortOrder", + "umbracoNode.uniqueID", + "umbracoNode.text", + "umbracoNode.nodeObjectType", + "umbracoNode.createDate", + "COUNT(parent.parentID) as children" + }); + + if (isContent || isMedia) + { + if (isContent) + { + //only content has this info + columns.Add("published.versionId as publishedVersion"); + columns.Add("document.versionId as newestVersion"); + } + + columns.Add("contenttype.alias"); + columns.Add("contenttype.icon"); + columns.Add("contenttype.thumbnail"); + columns.Add("contenttype.isContainer"); + } } //Creates an SQL query to return a single row for the entity @@ -348,17 +488,24 @@ namespace Umbraco.Core.Persistence.Repositories if (isContent || isMedia) { - entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id") - .LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType") - .LeftJoin( - "(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1 GROUP BY nodeId, versionId) as published") - .On("umbracoNode.id = published.nodeId") - .LeftJoin( - "(SELECT nodeId, versionId FROM cmsDocument WHERE newest = 1 GROUP BY nodeId, versionId) as latest") - .On("umbracoNode.id = latest.nodeId"); + entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id"); + + if (isContent) + { + //only content has this info + entitySql + .InnerJoin("cmsDocument document").On("document.nodeId = umbracoNode.id") + .LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1) as published") + .On("umbracoNode.id = published.nodeId"); + } + + entitySql.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType"); } - entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); + if (isCount == false) + { + entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); + } if (customFilter != null) { @@ -372,22 +519,42 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = baseQuery(isContent, isMedia, filter) .Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = nodeObjectType }); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + return sql; } protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, int id) { var sql = baseQuery(isContent, isMedia, null) - .Where("umbracoNode.id = @Id", new { Id = id }) - .Append(GetGroupBy(isContent, isMedia)); + .Where("umbracoNode.id = @Id", new { Id = id }); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + sql.Append(GetGroupBy(isContent, isMedia)); + return sql; } protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid key) { var sql = baseQuery(isContent, isMedia, null) - .Where("umbracoNode.uniqueID = @UniqueID", new { UniqueID = key }) - .Append(GetGroupBy(isContent, isMedia)); + .Where("umbracoNode.uniqueID = @UniqueID", new { UniqueID = key }); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + sql.Append(GetGroupBy(isContent, isMedia)); + return sql; } @@ -396,6 +563,12 @@ namespace Umbraco.Core.Persistence.Repositories var sql = baseQuery(isContent, isMedia, null) .Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType", new {Id = id, NodeObjectType = nodeObjectType}); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + return sql; } @@ -404,30 +577,39 @@ namespace Umbraco.Core.Persistence.Repositories var sql = baseQuery(isContent, isMedia, null) .Where("umbracoNode.uniqueID = @UniqueID AND umbracoNode.nodeObjectType = @NodeObjectType", new { UniqueID = key, NodeObjectType = nodeObjectType }); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + return sql; } protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true) { var columns = new List - { - "umbracoNode.id", - "umbracoNode.trashed", - "umbracoNode.parentID", - "umbracoNode.nodeUser", - "umbracoNode.level", - "umbracoNode.path", - "umbracoNode.sortOrder", - "umbracoNode.uniqueID", - "umbracoNode.text", - "umbracoNode.nodeObjectType", - "umbracoNode.createDate" - }; + { + "umbracoNode.id", + "umbracoNode.trashed", + "umbracoNode.parentID", + "umbracoNode.nodeUser", + "umbracoNode.level", + "umbracoNode.path", + "umbracoNode.sortOrder", + "umbracoNode.uniqueID", + "umbracoNode.text", + "umbracoNode.nodeObjectType", + "umbracoNode.createDate" + }; if (isContent || isMedia) { - columns.Add("published.versionId"); - columns.Add("latest.versionId"); + if (isContent) + { + columns.Add("published.versionId"); + columns.Add("document.versionId"); + } columns.Add("contenttype.alias"); columns.Add("contenttype.icon"); columns.Add("contenttype.thumbnail"); @@ -458,6 +640,18 @@ namespace Umbraco.Core.Persistence.Repositories UnitOfWork.DisposeIfDisposable(); } + public bool Exists(Guid key) + { + var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("uniqueID=@uniqueID", new {uniqueID = key}); + return _work.Database.ExecuteScalar(sql) > 0; + } + + public bool Exists(int id) + { + var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("id=@id", new { id = id }); + return _work.Database.ExecuteScalar(sql) > 0; + } + #region umbracoNode POCO - Extends NodeDto [TableName("umbracoNode")] [PrimaryKey("id")] diff --git a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs index 7c80bd10b7..fcaee87975 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories { internal class ExternalLoginRepository : PetaPocoRepositoryBase, IExternalLoginRepository { - public ExternalLoginRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public ExternalLoginRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs index 7ce0d71097..03a8b7e824 100644 --- a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs @@ -234,13 +234,28 @@ namespace Umbraco.Core.Persistence.Repositories } } - /// - /// Dispose any disposable properties - /// - /// - /// Dispose the unit of work - /// - protected override void DisposeResources() + public long GetFileSize(string filename) + { + if (FileSystem.FileExists(filename) == false) + return -1; + + try + { + return FileSystem.GetSize(filename); + } + catch + { + return -1; // deal with race conds + } + } + + /// + /// Dispose any disposable properties + /// + /// + /// Dispose the unit of work + /// + protected override void DisposeResources() { _work.DisposeIfDisposable(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index e2555995d2..e04608e9c6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { + public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository { /// @@ -82,19 +83,7 @@ namespace Umbraco.Core.Persistence.Repositories /// void AddOrUpdatePreviewXml(IContent content, Func xml); - /// - /// Gets paged content results - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// - /// 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/IDeleteMediaFilesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs index 005c1d62ba..8a5a99b32a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Persistence.Repositories { + // cannot kill in v7 because it is public, kill in v8 + [Obsolete("Use MediaFileSystem.DeleteMediaFiles instead.", false)] public interface IDeleteMediaFilesRepository { /// @@ -9,6 +12,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// + [Obsolete("Use MediaFileSystem.DeleteMediaFiles instead.", false)] bool DeleteMediaFiles(IEnumerable files); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IEntityRepository.cs index 3b73aabdf4..54e86e64d4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IEntityRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories @@ -15,5 +16,34 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable GetAll(Guid objectTypeId, params Guid[] keys); IEnumerable GetByQuery(IQuery query); IEnumerable GetByQuery(IQuery query, Guid objectTypeId); + + /// + /// Gets paged results + /// + /// Query to excute + /// + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// + /// An Enumerable list of objects + IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, IQuery filter = null); + + /// + /// Returns true if the entity exists + /// + /// + /// + bool Exists(Guid key); + + /// + /// Returns true if the entity exists + /// + /// + /// + bool Exists(int id); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMacroRepository.cs index 496012ef10..6600d528ad 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMacroRepository.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - internal interface IMacroRepository : IRepositoryQueryable + internal interface IMacroRepository : IRepositoryQueryable, IReadRepository { //IEnumerable GetAll(params string[] aliases); diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index 64989f9269..4c9b1d3561 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -17,36 +17,17 @@ namespace Umbraco.Core.Persistence.Repositories /// void AddOrUpdateContentXml(IMedia content, Func xml); + /// + /// Used to remove the content xml for a content item + /// + /// + void DeleteContentXml(IMedia content); + /// /// Used to add/update preview xml for the content item /// /// /// - void AddOrUpdatePreviewXml(IMedia content, Func xml); - - /// - /// Gets paged media results - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// 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); + void AddOrUpdatePreviewXml(IMedia content, Func xml); } } \ 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..bed96890d9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -41,25 +41,6 @@ namespace Umbraco.Core.Persistence.Repositories /// int GetCountByQuery(IQuery query); - /// - /// Gets paged member results - /// - /// The query. - /// Index of the page. - /// Size of the page. - /// The total records. - /// The order by column - /// The order direction. - /// Flag to indicate when ordering by system field - /// Search query - /// - IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); - - //IEnumerable GetPagedResultsByQuery( - // Sql sql, int pageIndex, int pageSize, out int totalRecords, - // Func, int[]> resolveIds); - /// /// Used to add/update published xml for the media item /// @@ -73,6 +54,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/IPartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs index 7c8391a77a..27342fe643 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs @@ -10,5 +10,6 @@ namespace Umbraco.Core.Persistence.Repositories bool ValidatePartialView(IPartialView partialView); Stream GetFileContentStream(string filepath); void SetFileContent(string filepath, Stream content); + long GetFileSize(string filepath); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRelationTypeRepository.cs index d28f81bce0..0cf6357d24 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRelationTypeRepository.cs @@ -1,9 +1,8 @@ -using Umbraco.Core.Models; +using System; +using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IRelationTypeRepository : IRepositoryQueryable - { - - } + public interface IRelationTypeRepository : IRepositoryQueryable, IReadRepository + { } } \ 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..df8f880ea2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; using System.Xml.Linq; +using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { @@ -67,5 +70,31 @@ 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); + + /// + /// Gets paged content results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// + /// 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/IScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IScriptRepository.cs index eacb818faa..c2c0a0ae84 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IScriptRepository.cs @@ -8,5 +8,6 @@ namespace Umbraco.Core.Persistence.Repositories bool ValidateScript(Script script); Stream GetFileContentStream(string filepath); void SetFileContent(string filepath, Stream content); + long GetFileSize(string filepath); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IStylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IStylesheetRepository.cs index 323f11d339..5fadf7b01c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IStylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IStylesheetRepository.cs @@ -8,5 +8,6 @@ namespace Umbraco.Core.Persistence.Repositories bool ValidateStylesheet(Stylesheet stylesheet); Stream GetFileContentStream(string filepath); void SetFileContent(string filepath, Stream content); + long GetFileSize(string filepath); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs index 59845f53f0..fd04e21d75 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs @@ -72,5 +72,7 @@ namespace Umbraco.Core.Persistence.Repositories /// The filesystem path to the template. /// The content of the template. void SetFileContent(string filepath, Stream content); + + long GetFileSize(string filepath); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IXsltFileRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IXsltFileRepository.cs index 8bff985400..4c56b6eb18 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IXsltFileRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IXsltFileRepository.cs @@ -8,5 +8,6 @@ namespace Umbraco.Core.Persistence.Repositories bool ValidateXsltFile(XsltFile xsltFile); Stream GetFileContentStream(string filepath); void SetFileContent(string filepath, Stream content); + long GetFileSize(string filepath); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index f9a8e59cfa..8b32f4fd1f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -19,20 +19,13 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class LanguageRepository : PetaPocoRepositoryBase, ILanguageRepository { - public LanguageRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public LanguageRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - } + { } - private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( - RuntimeCache, GetEntityId, () => PerformGetAll(), false)); - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ false); } #region Overrides of RepositoryBase @@ -131,8 +124,8 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); //Clear the cache entries that exist by key/iso - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); } protected override void PersistDeletedItem(ILanguage entity) @@ -140,8 +133,8 @@ namespace Umbraco.Core.Persistence.Repositories base.PersistDeletedItem(entity); //Clear the cache entries that exist by key/iso - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); } #endregion @@ -164,7 +157,5 @@ namespace Umbraco.Core.Persistence.Repositories //use the underlying GetAll which will force cache all languages return GetAll().FirstOrDefault(x => x.IsoCode.InvariantEquals(isoCode)); } - - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs index 4d21059758..398a7cbb54 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories internal class MacroRepository : PetaPocoRepositoryBase, IMacroRepository { - public MacroRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public MacroRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } @@ -26,7 +26,17 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); + return GetBySql(sql); + } + public IMacro Get(Guid id) + { + var sql = GetBaseQuery().Where("uniqueId=@Id", new { Id = id }); + return GetBySql(sql); + } + + private IMacro GetBySql(Sql sql) + { var macroDto = Database.Fetch(new MacroPropertyRelator().Map, sql).FirstOrDefault(); if (macroDto == null) return null; @@ -41,26 +51,26 @@ namespace Umbraco.Core.Persistence.Repositories return entity; } - protected override IEnumerable PerformGetAll(params int[] ids) + public IEnumerable GetAll(params Guid[] ids) { - if (ids.Any()) - { - return PerformGetAllOnIds(ids); - } - - var sql = GetBaseQuery(false); - - return ConvertFromDtos(Database.Fetch(new MacroPropertyRelator().Map, sql)) - .ToArray();// we don't want to re-iterate again! + return ids.Length > 0 ? ids.Select(Get) : GetAllNoIds(); } - private IEnumerable PerformGetAllOnIds(params int[] ids) + public bool Exists(Guid id) { - if (ids.Any() == false) yield break; - foreach (var id in ids) - { - yield return Get(id); - } + return Get(id) != null; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + return ids.Length > 0 ? ids.Select(Get) : GetAllNoIds(); + } + + private IEnumerable GetAllNoIds() + { + var sql = GetBaseQuery(false); + return ConvertFromDtos(Database.Fetch(new MacroPropertyRelator().Map, sql)) + .ToArray();// we don't want to re-iterate again! } private IEnumerable ConvertFromDtos(IEnumerable dtos) @@ -124,7 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories var list = new List { "DELETE FROM cmsMacroProperty WHERE macro = @Id", - "DELETE FROM cmsMacro WHERE id = @Id" + "DELETE FROM cmsMacro WHERE id = @Id" }; return list; } @@ -146,7 +156,7 @@ namespace Umbraco.Core.Persistence.Repositories foreach (var propDto in dto.MacroPropertyDtos) { - //need to set the id explicitly here + // need to set the id explicitly here propDto.Macro = id; var propId = Convert.ToInt32(Database.Insert(propDto)); entity.Properties[propDto.Alias].Id = propId; @@ -165,71 +175,60 @@ namespace Umbraco.Core.Persistence.Repositories Database.Update(dto); //update the properties if they've changed - var macro = (Macro)entity; + var macro = (Macro) entity; if (macro.IsPropertyDirty("Properties") || macro.Properties.Any(x => x.IsDirty())) { - //now we need to delete any props that have been removed - foreach (var propAlias in macro.RemovedProperties) - { - //delete the property - Database.Delete("WHERE macro=@macroId AND macroPropertyAlias=@propAlias", - new { macroId = macro.Id, propAlias = propAlias }); - } + var ids = dto.MacroPropertyDtos.Where(x => x.Id > 0).Select(x => x.Id).ToArray(); + if (ids.Length > 0) + Database.Delete("WHERE macro=@macro AND id NOT IN (@ids)", new { macro = dto.Id, ids }); + else + Database.Delete("WHERE macro=@macro", new { macro = dto.Id }); - //for any that exist on the object, we need to determine if we need to update or insert + // detect new aliases, replace with temp aliases + // this ensures that we don't have collisions, ever + var aliases = new Dictionary(); foreach (var propDto in dto.MacroPropertyDtos) { - if (macro.AddedProperties.Contains(propDto.Alias)) + var prop = macro.Properties.FirstOrDefault(x => x.Id == propDto.Id); + if (prop == null) throw new Exception("oops: property."); + if (propDto.Id == 0 || prop.IsPropertyDirty("Alias")) { - //we need to insert since this was added and re-assign the new id - var propId = Convert.ToInt32(Database.Insert(propDto)); - macro.Properties[propDto.Alias].Id = propId; + var tempAlias = Guid.NewGuid().ToString("N").Substring(0, 8); + aliases[tempAlias] = propDto.Alias; + propDto.Alias = tempAlias; + } + } + + // insert or update existing properties, with temp aliases + foreach (var propDto in dto.MacroPropertyDtos) + { + if (propDto.Id == 0) + { + // insert + propDto.Id = Convert.ToInt32(Database.Insert(propDto)); + macro.Properties[aliases[propDto.Alias]].Id = propDto.Id; } else { - //This will only work if the Alias hasn't been changed - if (macro.Properties.ContainsKey(propDto.Alias)) - { - //only update if it's dirty - if (macro.Properties[propDto.Alias].IsDirty()) - { - Database.Update(propDto); - } - } - else - { - var property = macro.Properties.FirstOrDefault(x => x.Id == propDto.Id); - if (property != null && property.IsDirty()) - { - Database.Update(propDto); - } - } + // update + var property = macro.Properties.FirstOrDefault(x => x.Id == propDto.Id); + if (property == null) throw new Exception("oops: property."); + if (property.IsDirty()) + Database.Update(propDto); } } - + // replace the temp aliases with the real ones + foreach (var propDto in dto.MacroPropertyDtos) + { + if (aliases.ContainsKey(propDto.Alias) == false) continue; + + propDto.Alias = aliases[propDto.Alias]; + Database.Update(propDto); + } } entity.ResetDirtyProperties(); } - - //public IEnumerable GetAll(params string[] aliases) - //{ - // if (aliases.Any()) - // { - // var q = new Query(); - // foreach (var alias in aliases) - // { - // q.Where(macro => macro.Alias == alias); - // } - - // var wheres = string.Join(" OR ", q.WhereClauses()); - // } - // else - // { - // return GetAll(new int[] {}); - // } - - //} } } \ 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 092b7df025..4b7afe9000 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -28,15 +28,15 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ContentXmlRepository _contentXmlRepository; private readonly ContentPreviewRepository _contentPreviewRepository; - public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection) + public MediaRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection) : base(work, cache, logger, sqlSyntax, contentSection) { if (mediaTypeRepository == null) throw new ArgumentNullException("mediaTypeRepository"); if (tagRepository == null) throw new ArgumentNullException("tagRepository"); _mediaTypeRepository = mediaTypeRepository; _tagRepository = tagRepository; - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.NoCache, logger, sqlSyntax); + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.NoCache, logger, sqlSyntax); EnsureUniqueNaming = contentSection.EnsureUniqueNaming; } @@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); } - return ProcessQuery(sql); + return ProcessQuery(sql, new PagingSqlQuery(sql)); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -76,26 +76,31 @@ 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); + return ProcessQuery(sql, new PagingSqlQuery(sql)); } #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.FullSingle); } protected override string GetBaseWhereClause() @@ -138,16 +143,32 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) .OrderByDescending(x => x.VersionDate, SqlSyntax); - return ProcessQuery(sql, true); + return ProcessQuery(sql, new PagingSqlQuery(sql), true); } - private IEnumerable ProcessQuery(Sql sql, bool withCache = false) + /// + /// This is the underlying method that processes most queries for this repository + /// + /// + /// The full SQL to select all media data + /// + /// + /// The Id SQL to just return all media ids - used to process the properties for the media item + /// + /// + /// + private IEnumerable ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false) { // fetch returns a list so it's ok to iterate it in this method - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sqlFull); 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]; @@ -155,7 +176,7 @@ namespace Umbraco.Core.Persistence.Repositories // if the cache contains the item, use it if (withCache) { - var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); if (cached != null) { content[i] = cached; @@ -165,9 +186,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( @@ -180,7 +211,7 @@ namespace Umbraco.Core.Persistence.Repositories } // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sql, defs); + var propertyData = GetPropertyCollection(pagingSqlQuery, defs); // assign var dtoIndex = 0; @@ -195,7 +226,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 +236,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,9 +266,10 @@ 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)) + var sql = SqlSyntax.SelectTop(query, groupSize); + var xmlItems = ProcessQuery(sql, new PagingSqlQuery(sql)) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) .ToList(); @@ -277,6 +299,11 @@ namespace Umbraco.Core.Persistence.Repositories _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); } + public void DeleteContentXml(IMedia content) + { + _contentXmlRepository.Delete(new ContentXmlEntity(content)); + } + public void AddOrUpdatePreviewXml(IMedia content, Func xml) { _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml)); @@ -324,6 +351,7 @@ namespace Umbraco.Core.Persistence.Repositories //Update with new correct path nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + nodeDto.ValidatePathWithException(); Database.Update(nodeDto); //Update entity with correct values @@ -397,6 +425,7 @@ namespace Umbraco.Core.Persistence.Repositories //Updates the (base) node data - umbracoNode var nodeDto = dto.ContentDto.NodeDto; + nodeDto.ValidatePathWithException(); var o = Database.Update(nodeDto); //Only update this DTO if the contentType has actually changed @@ -473,74 +502,42 @@ namespace Umbraco.Core.Persistence.Repositories /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null) { - var args = new List(); - var sbWhere = new StringBuilder(); - Func> filterCallback = null; - if (filter.IsNullOrWhiteSpace() == false) + var filterSql = new Sql(); + if (filter != null) { - sbWhere - .Append("AND (") - .Append(SqlSyntax.GetQuotedTableName("umbracoNode")) - .Append(".") - .Append(SqlSyntax.GetQuotedColumnName("text")) - .Append(" LIKE @") - .Append(args.Count) - .Append(")"); - args.Add("%" + filter + "%"); - - filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); + foreach (var filterClaus in filter.GetWhereClauses()) + { + filterSql.Append(string.Format("AND ({0})", filterClaus.Item1), filterClaus.Item2); + } } - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + Func> filterCallback = () => new Tuple(filterSql.SQL, filterSql.Arguments); + + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsContentVersion", "contentId"), - sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, + (sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), 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 /// - /// + /// /// /// /// 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); - var properties = GetPropertyCollection(docSql, new[] { docDef }); + var properties = GetPropertyCollection(new PagingSqlQuery(docSql), new[] { docDef }); media.Properties = properties[dto.NodeId]; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 50a89bfd65..11542673be 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -22,23 +22,14 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class MediaTypeRepository : ContentTypeBaseRepository, IMediaTypeRepository { - - public MediaTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public MediaTypeRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } - private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( - RuntimeCache, GetEntityId, () => PerformGetAll(), - //allow this cache to expire - expires: true)); - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ true); } protected override IMediaType PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 2c0e910428..e9438b56f6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories internal class MemberGroupRepository : PetaPocoRepositoryBase, IMemberGroupRepository { - public MemberGroupRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public MemberGroupRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } @@ -131,7 +131,7 @@ namespace Umbraco.Core.Persistence.Repositories public IMemberGroup GetByName(string name) { - return RuntimeCache.GetCacheItem( + return IsolatedCache.GetCacheItem( string.Format("{0}.{1}", typeof (IMemberGroup).FullName, name), () => { @@ -147,30 +147,24 @@ namespace Umbraco.Core.Persistence.Repositories public IMemberGroup CreateIfNotExists(string roleName) { - using (var transaction = Database.GetTransaction()) + var qry = new Query().Where(group => group.Name.Equals(roleName)); + var result = GetByQuery(qry); + + if (result.Any()) return null; + + var grp = new MemberGroup { - var qry = new Query().Where(group => group.Name.Equals(roleName)); - var result = GetByQuery(qry); + Name = roleName + }; - if (result.Any()) return null; + PersistNewItem(grp); - var grp = new MemberGroup - { - Name = roleName - }; - PersistNewItem(grp); + if (UnitOfWork.Events.DispatchCancelable(SavingMemberGroup, this, new SaveEventArgs(grp))) + return null; - if (SavingMemberGroup.IsRaisedEventCancelled(new SaveEventArgs(grp), this)) - { - return null; - } + UnitOfWork.Events.Dispatch(SavedMemberGroup, this, new SaveEventArgs(grp)); - transaction.Complete(); - - SavedMemberGroup.RaiseEvent(new SaveEventArgs(grp), this); - - return grp; - } + return grp; } public IEnumerable GetMemberGroupsForMember(int memberId) @@ -221,51 +215,39 @@ namespace Umbraco.Core.Persistence.Repositories public void AssignRoles(string[] usernames, string[] roleNames) { - using (var transaction = Database.GetTransaction()) - { - //first get the member ids based on the usernames - var memberSql = new Sql(); - var memberObjectType = new Guid(Constants.ObjectTypes.Member); - memberSql.Select("umbracoNode.id") - .From() - .InnerJoin() - .On(dto => dto.NodeId, dto => dto.NodeId) - .Where(x => x.NodeObjectType == memberObjectType) - .Where("cmsMember.LoginName in (@usernames)", new { usernames = usernames }); - var memberIds = Database.Fetch(memberSql).ToArray(); + //first get the member ids based on the usernames + var memberSql = new Sql(); + var memberObjectType = new Guid(Constants.ObjectTypes.Member); + memberSql.Select("umbracoNode.id") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where(x => x.NodeObjectType == memberObjectType) + .Where("cmsMember.LoginName in (@usernames)", new { usernames = usernames }); + var memberIds = Database.Fetch(memberSql).ToArray(); - AssignRolesInternal(memberIds, roleNames); - transaction.Complete(); - } + AssignRolesInternal(memberIds, roleNames); } public void DissociateRoles(string[] usernames, string[] roleNames) { - using (var transaction = Database.GetTransaction()) - { - //first get the member ids based on the usernames - var memberSql = new Sql(); - var memberObjectType = new Guid(Constants.ObjectTypes.Member); - memberSql.Select("umbracoNode.id") - .From() - .InnerJoin() - .On(dto => dto.NodeId, dto => dto.NodeId) - .Where(x => x.NodeObjectType == memberObjectType) - .Where("cmsMember.LoginName in (@usernames)", new { usernames = usernames }); - var memberIds = Database.Fetch(memberSql).ToArray(); + //first get the member ids based on the usernames + var memberSql = new Sql(); + var memberObjectType = new Guid(Constants.ObjectTypes.Member); + memberSql.Select("umbracoNode.id") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where(x => x.NodeObjectType == memberObjectType) + .Where("cmsMember.LoginName in (@usernames)", new { usernames = usernames }); + var memberIds = Database.Fetch(memberSql).ToArray(); - DissociateRolesInternal(memberIds, roleNames); - transaction.Complete(); - } + DissociateRolesInternal(memberIds, roleNames); } public void AssignRoles(int[] memberIds, string[] roleNames) { - using (var transaction = Database.GetTransaction()) - { - AssignRolesInternal(memberIds, roleNames); - transaction.Complete(); - } + AssignRolesInternal(memberIds, roleNames); } public void AssignRolesInternal(int[] memberIds, string[] roleNames) @@ -284,15 +266,13 @@ namespace Umbraco.Core.Persistence.Repositories var missingRoles = roleNames.Except(existingRoles); var missingGroups = missingRoles.Select(x => new MemberGroup {Name = x}).ToArray(); - if (SavingMemberGroup.IsRaisedEventCancelled(new SaveEventArgs(missingGroups), this)) - { + if (UnitOfWork.Events.DispatchCancelable(SavingMemberGroup, this, new SaveEventArgs(missingGroups))) return; - } + foreach (var m in missingGroups) - { PersistNewItem(m); - } - SavedMemberGroup.RaiseEvent(new SaveEventArgs(missingGroups), this); + + UnitOfWork.Events.Dispatch(SavedMemberGroup, this, new SaveEventArgs(missingGroups)); //now go get all the dto's for roles with these role names var rolesForNames = Database.Fetch(existingSql).ToArray(); @@ -333,11 +313,7 @@ namespace Umbraco.Core.Persistence.Repositories public void DissociateRoles(int[] memberIds, string[] roleNames) { - using (var transaction = Database.GetTransaction()) - { - DissociateRolesInternal(memberIds, roleNames); - transaction.Complete(); - } + DissociateRolesInternal(memberIds, roleNames); } private void DissociateRolesInternal(int[] memberIds, string[] roleNames) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index dcab898685..95028f99cd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ContentXmlRepository _contentXmlRepository; private readonly ContentPreviewRepository _contentPreviewRepository; - public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IContentSection contentSection) + public MemberRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IContentSection contentSection) : base(work, cache, logger, sqlSyntax, contentSection) { if (memberTypeRepository == null) throw new ArgumentNullException("memberTypeRepository"); @@ -37,8 +37,8 @@ namespace Umbraco.Core.Persistence.Repositories _memberTypeRepository = memberTypeRepository; _tagRepository = tagRepository; _memberGroupRepository = memberGroupRepository; - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.NoCache, logger, sqlSyntax); + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.NoCache, logger, sqlSyntax); } #region Overrides of RepositoryBase @@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); } - return ProcessQuery(sql); + return ProcessQuery(sql, new PagingSqlQuery(sql)); } @@ -90,7 +90,7 @@ namespace Umbraco.Core.Persistence.Repositories baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)) .OrderBy(x => x.SortOrder); - return ProcessQuery(baseQuery); + return ProcessQuery(baseQuery, new PagingSqlQuery(baseQuery)); } else { @@ -98,7 +98,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate() .OrderBy(x => x.SortOrder); - return ProcessQuery(sql); + return ProcessQuery(sql, new PagingSqlQuery(sql)); } } @@ -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.FullSingle); } protected override string GetBaseWhereClause() @@ -380,8 +385,8 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) .OrderByDescending(x => x.VersionDate, SqlSyntax); - return ProcessQuery(sql, true); - } + return ProcessQuery(sql, new PagingSqlQuery(sql), true); + } public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) { @@ -403,7 +408,8 @@ namespace Umbraco.Core.Persistence.Repositories query = query .Where(x => x.NodeId > baseId) .OrderBy(x => x.NodeId, SqlSyntax); - var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) + var sql = SqlSyntax.SelectTop(query, groupSize); + var xmlItems = ProcessQuery(sql, new PagingSqlQuery(sql)) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) .ToList(); @@ -444,7 +450,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId); var media = factory.BuildEntity(dto); - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }); + var properties = GetPropertyCollection(new PagingSqlQuery(sql), new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }); media.Properties = properties[dto.NodeId]; @@ -535,7 +541,7 @@ namespace Umbraco.Core.Persistence.Repositories .OrderByDescending(x => x.VersionDate) .OrderBy(x => x.SortOrder); - return ProcessQuery(sql); + return ProcessQuery(sql, new PagingSqlQuery(sql)); } @@ -581,45 +587,22 @@ namespace Umbraco.Core.Persistence.Repositories /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using PetaPoco paging (SQL paging) /// public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null) { - var args = new List(); - var sbWhere = new StringBuilder(); - Func> filterCallback = null; - if (filter.IsNullOrWhiteSpace() == false) + var filterSql = new Sql(); + if (filter != null) { - //This will build up the where clause - even though the same 'filter' is being - //applied to both columns, the parameters values passed to PetaPoco need to be - //duplicated, otherwise it gets confused :/ - var columnFilters = new List> + foreach (var filterClaus in filter.GetWhereClauses()) { - new Tuple("umbracoNode", "text"), - new Tuple("cmsMember", "LoginName") - }; - sbWhere.Append("AND ("); - for (int i = 0; i < columnFilters.Count; i++) - { - sbWhere - .Append("(") - .Append(SqlSyntax.GetQuotedTableName(columnFilters[i].Item1)) - .Append(".") - .Append(SqlSyntax.GetQuotedColumnName(columnFilters[i].Item2)) - .Append(" LIKE @") - .Append(args.Count) - .Append(") "); - args.Add(string.Format("%{0}%", filter)); - if (i < (columnFilters.Count - 1)) - { - sbWhere.Append("OR "); - } + filterSql.Append(string.Format("AND ({0})", filterClaus.Item1), filterClaus.Item2); } - sbWhere.Append(")"); - filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); } - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + Func> filterCallback = () => new Tuple(filterSql.SQL, filterSql.Arguments); + + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsMember", "nodeId"), - sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, + (sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -659,10 +642,21 @@ namespace Umbraco.Core.Persistence.Repositories return base.GetEntityPropertyNameForOrderBy(orderBy); } - private IEnumerable ProcessQuery(Sql sql, bool withCache = false) + /// + /// This is the underlying method that processes most queries for this repository + /// + /// + /// The full SQL to select all member data + /// + /// + /// The Id SQL to just return all member ids - used to process the properties for the member item + /// + /// + /// + private IEnumerable ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false) { // fetch returns a list so it's ok to iterate it in this method - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sqlFull); var content = new IMember[dtos.Count]; var defs = new List(); @@ -674,7 +668,7 @@ namespace Umbraco.Core.Persistence.Repositories // if the cache contains the item, use it if (withCache) { - var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); if (cached != null) { content[i] = cached; @@ -699,7 +693,7 @@ namespace Umbraco.Core.Persistence.Repositories } // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(sql, defs); + var propertyData = GetPropertyCollection(pagingSqlQuery, defs); // assign var dtoIndex = 0; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index d06399a85b..7e0e11173d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -21,25 +21,16 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class MemberTypeRepository : ContentTypeBaseRepository, IMemberTypeRepository { - - public MemberTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public MemberTypeRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } - private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( - RuntimeCache, GetEntityId, () => PerformGetAll(), - //allow this cache to expire - expires: true)); - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ true); } - + protected override IMemberType PerformGet(int id) { //use the underlying GetAll which will force cache all content types @@ -300,13 +291,19 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - private static IEnumerable BuildFromDtos(List dtos) + private IEnumerable BuildFromDtos(List dtos) { if (dtos == null || dtos.Any() == false) return Enumerable.Empty(); var factory = new MemberTypeReadOnlyFactory(); - return dtos.Select(factory.BuildEntity); + return dtos.Select(x => + { + bool needsSaving; + var memberType = factory.BuildEntity(x, out needsSaving); + if (needsSaving) PersistUpdatedItem(memberType); + return memberType; + }).ToList(); } /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs index b4a21f5d21..bd5db622d6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Persistence.Repositories { internal class MigrationEntryRepository : PetaPocoRepositoryBase, IMigrationEntryRepository { - public MigrationEntryRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public MigrationEntryRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs index 9e6e3cf47c..aa7ebeacc8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class NotificationsRepository + internal class NotificationsRepository : IDisposable { private readonly IDatabaseUnitOfWork _unitOfWork; @@ -102,5 +102,10 @@ namespace Umbraco.Core.Persistence.Repositories _unitOfWork.Database.Insert(dto); return new Notification(dto.NodeId, dto.UserId, dto.Action, nodeType); } + + public void Dispose() + { + _unitOfWork.Dispose(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index aa671dccec..3d6acb777b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -25,11 +25,11 @@ namespace Umbraco.Core.Persistence.Repositories internal class PermissionRepository where TEntity : class, IAggregateRoot { - private readonly IDatabaseUnitOfWork _unitOfWork; + private readonly IScopeUnitOfWork _unitOfWork; private readonly IRuntimeCacheProvider _runtimeCache; private readonly ISqlSyntaxProvider _sqlSyntax; - internal PermissionRepository(IDatabaseUnitOfWork unitOfWork, CacheHelper cache, ISqlSyntaxProvider sqlSyntax) + internal PermissionRepository(IScopeUnitOfWork unitOfWork, CacheHelper cache, ISqlSyntaxProvider sqlSyntax) { _unitOfWork = unitOfWork; //Make this repository use an isolated cache @@ -126,37 +126,32 @@ namespace Umbraco.Core.Persistence.Repositories public void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds) { var db = _unitOfWork.Database; - using (var trans = db.GetTransaction()) + + //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit + foreach (var idGroup in entityIds.InGroupsOf(2000)) { - //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit - foreach (var idGroup in entityIds.InGroupsOf(2000)) - { - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND nodeId in (@nodeIds)", - new { userId = userId, nodeIds = idGroup }); - } - - var toInsert = new List(); - foreach (var p in permissions) - { - foreach (var e in entityIds) - { - toInsert.Add(new User2NodePermissionDto - { - NodeId = e, - Permission = p.ToString(CultureInfo.InvariantCulture), - UserId = userId - }); - } - } - - _unitOfWork.Database.BulkInsertRecords(toInsert, trans); - - trans.Complete(); - - //Raise the event - AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(toInsert), false), this); + db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND nodeId in (@nodeIds)", + new { userId = userId, nodeIds = idGroup }); } + + var toInsert = new List(); + foreach (var p in permissions) + { + foreach (var e in entityIds) + { + toInsert.Add(new User2NodePermissionDto + { + NodeId = e, + Permission = p.ToString(CultureInfo.InvariantCulture), + UserId = userId + }); + } + } + + _unitOfWork.Database.BulkInsertRecords(toInsert, _sqlSyntax); + + //Raise the event + _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(toInsert), false)); } /// @@ -168,31 +163,25 @@ namespace Umbraco.Core.Persistence.Repositories public void AssignUserPermission(int userId, char permission, params int[] entityIds) { var db = _unitOfWork.Database; - using (var trans = db.GetTransaction()) - { - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND permission=@permission AND nodeId in (@entityIds)", - new - { - userId = userId, - permission = permission.ToString(CultureInfo.InvariantCulture), - entityIds = entityIds - }); - - var actions = entityIds.Select(id => new User2NodePermissionDto + db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND permission=@permission AND nodeId in (@entityIds)", + new { - NodeId = id, - Permission = permission.ToString(CultureInfo.InvariantCulture), - UserId = userId - }).ToArray(); + userId = userId, + permission = permission.ToString(CultureInfo.InvariantCulture), + entityIds = entityIds + }); - _unitOfWork.Database.BulkInsertRecords(actions, trans); + var actions = entityIds.Select(id => new User2NodePermissionDto + { + NodeId = id, + Permission = permission.ToString(CultureInfo.InvariantCulture), + UserId = userId + }).ToArray(); - trans.Complete(); - - //Raise the event - AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(actions), false), this); - } + _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); + + //Raise the event + _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); } /// @@ -204,31 +193,25 @@ namespace Umbraco.Core.Persistence.Repositories public void AssignEntityPermission(TEntity entity, char permission, IEnumerable userIds) { var db = _unitOfWork.Database; - using (var trans = db.GetTransaction()) - { - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE nodeId=@nodeId AND permission=@permission AND userId in (@userIds)", + db.Execute("DELETE FROM umbracoUser2NodePermission WHERE nodeId=@nodeId AND permission=@permission AND userId in (@userIds)", new { - nodeId = entity.Id, + nodeId = entity.Id, permission = permission.ToString(CultureInfo.InvariantCulture), userIds = userIds }); - var actions = userIds.Select(id => new User2NodePermissionDto - { - NodeId = entity.Id, - Permission = permission.ToString(CultureInfo.InvariantCulture), - UserId = id - }).ToArray(); + var actions = userIds.Select(id => new User2NodePermissionDto + { + NodeId = entity.Id, + Permission = permission.ToString(CultureInfo.InvariantCulture), + UserId = id + }).ToArray(); - _unitOfWork.Database.BulkInsertRecords(actions, trans); + _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); - trans.Complete(); - - //Raise the event - AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(actions), false), this); - } + //Raise the event + _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); } /// @@ -242,25 +225,19 @@ namespace Umbraco.Core.Persistence.Repositories public void ReplaceEntityPermissions(EntityPermissionSet permissionSet) { var db = _unitOfWork.Database; - using (var trans = db.GetTransaction()) + db.Execute("DELETE FROM umbracoUser2NodePermission WHERE nodeId=@nodeId", new { nodeId = permissionSet.EntityId }); + + var actions = permissionSet.UserPermissionsSet.Select(p => new User2NodePermissionDto { - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE nodeId=@nodeId", new { nodeId = permissionSet.EntityId }); + NodeId = permissionSet.EntityId, + Permission = p.Permission, + UserId = p.UserId + }).ToArray(); - var actions = permissionSet.UserPermissionsSet.Select(p => new User2NodePermissionDto - { - NodeId = permissionSet.EntityId, - Permission = p.Permission, - UserId = p.UserId - }).ToArray(); - - _unitOfWork.Database.BulkInsertRecords(actions, trans); - - trans.Complete(); - - //Raise the event - AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(actions), false), this); - } + _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); + + //Raise the event + _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); } private static IEnumerable ConvertToPermissionList(IEnumerable result) diff --git a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs index fe363fea16..2a34aeb153 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Data.SqlServerCe; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; @@ -20,7 +19,7 @@ namespace Umbraco.Core.Persistence.Repositories { public ISqlSyntaxProvider SqlSyntax { get; private set; } - protected PetaPocoRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + protected PetaPocoRepositoryBase(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger) { if (sqlSyntax == null) throw new ArgumentNullException("sqlSyntax"); @@ -28,11 +27,11 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// Returns the database Unit of Work added to the repository + /// Returns the Scope Unit of Work added to the repository /// - protected internal new IDatabaseUnitOfWork UnitOfWork + protected internal new IScopeUnitOfWork UnitOfWork { - get { return (IDatabaseUnitOfWork)base.UnitOfWork; } + get { return (IScopeUnitOfWork)base.UnitOfWork; } } protected UmbracoDatabase Database diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index 37200b2172..ee045c725f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -15,19 +15,13 @@ namespace Umbraco.Core.Persistence.Repositories { internal class PublicAccessRepository : PetaPocoRepositoryBase, IPublicAccessRepository { - public PublicAccessRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public PublicAccessRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } - private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( - RuntimeCache, GetEntityId, () => PerformGetAll(), false)); - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ false); } protected override PublicAccessEntry PerformGet(Guid id) diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index f17a0dd0d2..cb5f7c8810 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories internal abstract class RecycleBinRepository : VersionableRepositoryBase, IRecycleBinRepository where TEntity : class, IUmbracoEntity { - protected RecycleBinRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection) + protected RecycleBinRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection) : base(work, cache, logger, sqlSyntax, contentSection) { } @@ -63,6 +63,7 @@ namespace Umbraco.Core.Persistence.Repositories FormatDeleteStatement("cmsContentVersion", "ContentId"), FormatDeleteStatement("cmsContentXml", "nodeId"), FormatDeleteStatement("cmsContent", "nodeId"), + //TODO: Why is this being done? We just delete this exact data in the next line "UPDATE umbracoNode SET parentID = '" + RecycleBinId + "' WHERE trashed = '1' AND nodeObjectType = @NodeObjectType", "DELETE FROM umbracoNode WHERE trashed = '1' AND nodeObjectType = @NodeObjectType" }; @@ -91,14 +92,18 @@ namespace Umbraco.Core.Persistence.Repositories } } + /// + /// A delete statement that will delete anything in the table specified where its PK (keyName) is found in the + /// list of umbracoNode.id that have trashed flag set + /// + /// + /// + /// private string FormatDeleteStatement(string tableName, string keyName) { - //This query works with sql ce and sql server: - //DELETE FROM umbracoUser2NodeNotify WHERE umbracoUser2NodeNotify.nodeId IN - //(SELECT nodeId FROM umbracoUser2NodeNotify as TB1 INNER JOIN umbracoNode as TB2 ON TB1.nodeId = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = 'C66BA18E-EAF3-4CFF-8A22-41B16D66A972') return string.Format( - "DELETE FROM {0} WHERE {0}.{1} IN (SELECT TB1.{1} FROM {0} as TB1 INNER JOIN umbracoNode as TB2 ON TB1.{1} = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)", + "DELETE FROM {0} WHERE {0}.{1} IN (SELECT id FROM umbracoNode WHERE trashed = '1' AND nodeObjectType = @NodeObjectType)", tableName, keyName); } diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs index 5452e868a0..acab12a10a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Persistence.Repositories { internal class RedirectUrlRepository : PetaPocoRepositoryBase, IRedirectUrlRepository { - public RedirectUrlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public RedirectUrlRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs index 4511ebe35d..30bdbaffbe 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly IRelationTypeRepository _relationTypeRepository; - public RelationRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IRelationTypeRepository relationTypeRepository) + public RelationRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IRelationTypeRepository relationTypeRepository) : base(work, cache, logger, sqlSyntax) { _relationTypeRepository = relationTypeRepository; @@ -73,12 +73,16 @@ namespace Umbraco.Core.Persistence.Repositories RelationFactory factory = null; var relationTypeId = -1; + // the ToList() here is important because we are using _relationTypeRepository and we + // cannot wait until the result is actually enumerated to do so, because that would + // mean we kinda capture the current unit of work and reuse it after it's been disposed + return dtos.Select(x => { if (relationTypeId != x.RelationType) factory = new RelationFactory(_relationTypeRepository.Get(relationTypeId = x.RelationType)); return DtoToEntity(x, factory); - }); + }).ToList(); } private static IRelation DtoToEntity(RelationDto dto, RelationFactory factory) diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs index c0a110feca..ba578b93f1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs @@ -19,21 +19,13 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class RelationTypeRepository : PetaPocoRepositoryBase, IRelationTypeRepository { - public RelationTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public RelationTypeRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } - // assuming we don't have tons of relation types, use a FullDataSet policy, ie - // cache the entire GetAll result once in a single collection - which can expire - private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - return _cachePolicyFactory - ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( - RuntimeCache, GetEntityId, () => PerformGetAll(), expires: true)); - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ true); } #region Overrides of RepositoryBase @@ -44,6 +36,17 @@ namespace Umbraco.Core.Persistence.Repositories return GetAll().FirstOrDefault(x => x.Id == id); } + public IRelationType Get(Guid id) + { + // use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); + } + + public bool Exists(Guid id) + { + return Get(id) != null; + } + protected override IEnumerable PerformGetAll(params int[] ids) { var sql = GetBaseQuery(false); @@ -57,6 +60,15 @@ namespace Umbraco.Core.Persistence.Repositories return dtos.Select(x => DtoToEntity(x, factory)); } + public IEnumerable GetAll(params Guid[] ids) + { + // should not happen due to the cache policy + if (ids.Any()) + throw new NotImplementedException(); + + return GetAll(new int[0]); + } + protected override IEnumerable PerformGetByQuery(IQuery query) { var sqlClause = GetBaseQuery(false); diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 41946d48d4..9061be62e4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -1,21 +1,20 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Cache; -using Umbraco.Core.Collections; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories { internal abstract class RepositoryBase : DisposableObject { private readonly IUnitOfWork _work; - private readonly CacheHelper _cache; + private readonly CacheHelper _globalCache; protected RepositoryBase(IUnitOfWork work, CacheHelper cache, ILogger logger) { @@ -24,7 +23,7 @@ namespace Umbraco.Core.Persistence.Repositories if (logger == null) throw new ArgumentNullException("logger"); Logger = logger; _work = work; - _cache = cache; + _globalCache = cache; } /// @@ -43,18 +42,18 @@ namespace Umbraco.Core.Persistence.Repositories get { return (Guid)_work.Key; } } - protected CacheHelper RepositoryCache + /// + /// Gets the global application cache. + /// + protected CacheHelper GlobalCache { - get { return _cache; } + get { return _globalCache; } } /// - /// The runtime cache used for this repo - by standard this is the runtime cache exposed by the CacheHelper but can be overridden + /// Gets the repository isolated cache. /// - protected virtual IRuntimeCacheProvider RuntimeCache - { - get { return _cache.RuntimeCache; } - } + protected abstract IRuntimeCacheProvider IsolatedCache { get; } public static string GetCacheIdKey(object id) { @@ -82,10 +81,9 @@ namespace Umbraco.Core.Persistence.Repositories { } - #region Static Queries - private IQuery _hasIdQuery; + private static IQuery _hasIdQuery; #endregion @@ -97,38 +95,112 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The runtime cache used for this repo by default is the isolated cache for this type /// - protected override IRuntimeCacheProvider RuntimeCache - { - get { return RepositoryCache.IsolatedRuntimeCache.GetOrCreateCache(); } - } - - private IRepositoryCachePolicyFactory _cachePolicyFactory; - /// - /// Returns the Cache Policy for the repository - /// - /// - /// The Cache Policy determines how each entity or entity collection is cached - /// - protected virtual IRepositoryCachePolicyFactory CachePolicyFactory + private IRuntimeCacheProvider _isolatedCache; + protected override IRuntimeCacheProvider IsolatedCache { get { - return _cachePolicyFactory ?? (_cachePolicyFactory = new DefaultRepositoryCachePolicyFactory( - RuntimeCache, - new RepositoryCachePolicyOptions(() => - { - //create it once if it is needed (no need for locking here) - if (_hasIdQuery == null) - { - _hasIdQuery = Query.Builder.Where(x => x.Id != 0); - } + if (_isolatedCache != null) return _isolatedCache; - //Get count of all entities of current type (TEntity) to ensure cached result is correct - return PerformCount(_hasIdQuery); - }))); + var scope = ((ScopeUnitOfWork) UnitOfWork).Scope; // fixme cast! + IsolatedRuntimeCache provider; + switch (scope.RepositoryCacheMode) + { + case RepositoryCacheMode.Default: + provider = GlobalCache.IsolatedRuntimeCache; + break; + case RepositoryCacheMode.Scoped: + provider = scope.IsolatedRuntimeCache; + break; + default: + throw new Exception("oops: cache mode."); + } + + return _isolatedCache = GetIsolatedCache(provider); } } + protected virtual IRuntimeCacheProvider GetIsolatedCache(IsolatedRuntimeCache provider) + { + return provider.GetOrCreateCache(); + } + + // this is a *bad* idea because PerformCount captures the current repository and its UOW + // + //private static RepositoryCachePolicyOptions _defaultOptions; + //protected virtual RepositoryCachePolicyOptions DefaultOptions + //{ + // get + // { + // return _defaultOptions ?? (_defaultOptions + // = new RepositoryCachePolicyOptions(() => + // { + // // get count of all entities of current type (TEntity) to ensure cached result is correct + // // create query once if it is needed (no need for locking here) - query is static! + // var query = _hasIdQuery ?? (_hasIdQuery = Query.Builder.Where(x => x.Id != 0)); + // return PerformCount(query); + // })); + // } + //} + + protected virtual RepositoryCachePolicyOptions DefaultOptions + { + get + { + return new RepositoryCachePolicyOptions(() => + { + // get count of all entities of current type (TEntity) to ensure cached result is correct + // create query once if it is needed (no need for locking here) - query is static! + var query = _hasIdQuery ?? (_hasIdQuery = Query.Builder.Where(x => x.Id != 0)); + return PerformCount(query); + }); + } + } + + // this would be better for perfs BUT it breaks the tests - l8tr + // + //private static IRepositoryCachePolicy _defaultCachePolicy; + //protected virtual IRepositoryCachePolicy DefaultCachePolicy + //{ + // get + // { + // return _defaultCachePolicy ?? (_defaultCachePolicy + // = new DefaultRepositoryCachePolicy(IsolatedCache, DefaultOptions)); + // } + //} + + private IRepositoryCachePolicy _cachePolicy; + protected IRepositoryCachePolicy CachePolicy + { + get + { + if (_cachePolicy != null) return _cachePolicy; + + if (GlobalCache == CacheHelper.NoCache) + return _cachePolicy = NoRepositoryCachePolicy.Instance; + + _cachePolicy = CreateCachePolicy(IsolatedCache); + var scope = ((ScopeUnitOfWork) UnitOfWork).Scope; // fixme cast! + switch (scope.RepositoryCacheMode) + { + case RepositoryCacheMode.Default: + break; + case RepositoryCacheMode.Scoped: + _cachePolicy = _cachePolicy.Scoped(GetIsolatedCache(GlobalCache.IsolatedRuntimeCache), scope); + break; + default: + throw new Exception("oops: cache mode."); + } + + return _cachePolicy; + } + } + + protected virtual IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) + { + return new DefaultRepositoryCachePolicy(runtimeCache, DefaultOptions); + } + /// /// Adds or Updates an entity of type TEntity /// @@ -166,10 +238,7 @@ namespace Umbraco.Core.Persistence.Repositories /// public TEntity Get(TId id) { - using (var p = CachePolicyFactory.CreatePolicy()) - { - return p.Get(id, PerformGet); - } + return CachePolicy.Get(id, PerformGet, PerformGetAll); } protected abstract IEnumerable PerformGetAll(params TId[] ids); @@ -192,13 +261,9 @@ namespace Umbraco.Core.Persistence.Repositories throw new InvalidOperationException("Cannot perform a query with more than 2000 parameters"); } - using (var p = CachePolicyFactory.CreatePolicy()) - { - var result = p.GetAll(ids, PerformGetAll); - return result; - } + return CachePolicy.GetAll(ids, PerformGetAll); } - + protected abstract IEnumerable PerformGetByQuery(IQuery query); /// /// Gets a list of entities by the passed in query @@ -220,10 +285,7 @@ namespace Umbraco.Core.Persistence.Repositories /// public bool Exists(TId id) { - using (var p = CachePolicyFactory.CreatePolicy()) - { - return p.Exists(id, PerformExists); - } + return CachePolicy.Exists(id, PerformExists, PerformGetAll); } protected abstract int PerformCount(IQuery query); @@ -236,19 +298,14 @@ namespace Umbraco.Core.Persistence.Repositories { return PerformCount(query); } - + /// /// Unit of work method that tells the repository to persist the new entity /// /// public virtual void PersistNewItem(IEntity entity) { - var casted = (TEntity)entity; - - using (var p = CachePolicyFactory.CreatePolicy()) - { - p.CreateOrUpdate(casted, PersistNewItem); - } + CachePolicy.Create((TEntity) entity, PersistNewItem); } /// @@ -257,12 +314,7 @@ namespace Umbraco.Core.Persistence.Repositories /// public virtual void PersistUpdatedItem(IEntity entity) { - var casted = (TEntity)entity; - - using (var p = CachePolicyFactory.CreatePolicy()) - { - p.CreateOrUpdate(casted, PersistUpdatedItem); - } + CachePolicy.Update((TEntity) entity, PersistUpdatedItem); } /// @@ -271,20 +323,13 @@ namespace Umbraco.Core.Persistence.Repositories /// public virtual void PersistDeletedItem(IEntity entity) { - var casted = (TEntity)entity; - - using (var p = CachePolicyFactory.CreatePolicy()) - { - p.Remove(casted, PersistDeletedItem); - } + CachePolicy.Delete((TEntity) entity, PersistDeletedItem); } - protected abstract void PersistNewItem(TEntity item); protected abstract void PersistUpdatedItem(TEntity item); protected abstract void PersistDeletedItem(TEntity item); - /// /// Dispose disposable properties /// diff --git a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs index 02794ef1fb..e3fad1aaf7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs @@ -15,12 +15,14 @@ namespace Umbraco.Core.Persistence.Repositories { internal class ServerRegistrationRepository : PetaPocoRepositoryBase, IServerRegistrationRepository { - private readonly ICacheProvider _staticCache; + private readonly ICacheProvider _globalCache; - public ServerRegistrationRepository(IDatabaseUnitOfWork work, ICacheProvider staticCache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax) + public ServerRegistrationRepository(IScopeUnitOfWork work, ICacheProvider globalCache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, CacheHelper.NoCache, logger, sqlSyntax) { - _staticCache = staticCache; + // managing the cache our own way (no policy etc) + // fixme - ServerRegistrationRepository does *not* implement scoped cache at the moment! + _globalCache = globalCache; } protected override int PerformCount(IQuery query) @@ -49,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories // the cache is populated by ReloadCache which should only be called from methods // that ensure proper locking (at least, read-lock in ReadCommited) of the repo. - var all = _staticCache.GetCacheItem>(CacheKey, Enumerable.Empty); + var all = _globalCache.GetCacheItem>(CacheKey, Enumerable.Empty); return ids.Length == 0 ? all : all.Where(x => ids.Contains(x.Id)); } @@ -75,7 +77,7 @@ namespace Umbraco.Core.Persistence.Repositories { var list = new List { - "DELETE FROM umbracoServer WHERE id = @Id" + "DELETE FROM umbracoServer WHERE id = @Id" }; return list; } @@ -127,8 +129,8 @@ namespace Umbraco.Core.Persistence.Repositories .Select(x => factory.BuildEntity(x)) .Cast() .ToArray(); - _staticCache.ClearCacheItem(CacheKey); - _staticCache.GetCacheItem(CacheKey, () => all); + _globalCache.ClearCacheItem(CacheKey); + _globalCache.GetCacheItem(CacheKey, () => all); } public void DeactiveStaleServers(TimeSpan staleTimeout) diff --git a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs index a88672ea6f..67c9149422 100644 --- a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories where TDto: class { - protected SimpleGetRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + protected SimpleGetRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs index 45fff2e43b..7515b8513a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs @@ -144,6 +144,20 @@ namespace Umbraco.Core.Persistence.Repositories FileSystem.AddFile(filepath, content, true); } + public long GetFileSize(string filepath) + { + if (FileSystem.FileExists(filepath) == false) return -1; + + try + { + return FileSystem.GetSize(filepath); + } + catch + { + return -1; // deal with race conds + } + } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs index 142740e9d8..0456e16bf3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories { internal class TagRepository : PetaPocoRepositoryBase, ITagRepository { - internal TagRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + internal TagRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs index c02362f8fd..f90e81428b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories { internal class TaskRepository : PetaPocoRepositoryBase, ITaskRepository { - public TaskRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public TaskRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs index a970880669..71c3d2d1a4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Persistence.Repositories { internal class TaskTypeRepository : PetaPocoRepositoryBase, ITaskTypeRepository { - public TaskTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public TaskTypeRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 2e93c7c678..c9c11e5fae 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Text; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -17,9 +15,7 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Core.Sync; namespace Umbraco.Core.Persistence.Repositories { @@ -34,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ViewHelper _viewHelper; private readonly MasterPageHelper _masterPageHelper; - internal TemplateRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IFileSystem masterpageFileSystem, IFileSystem viewFileSystem, ITemplatesSection templateConfig) + internal TemplateRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IFileSystem masterpageFileSystem, IFileSystem viewFileSystem, ITemplatesSection templateConfig) : base(work, cache, logger, sqlSyntax) { _masterpagesFileSystem = masterpageFileSystem; @@ -44,16 +40,9 @@ namespace Umbraco.Core.Persistence.Repositories _masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); } - - private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; - protected override IRepositoryCachePolicyFactory CachePolicyFactory + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( - RuntimeCache, GetEntityId, () => PerformGetAll(), false)); - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ false); } #region Overrides of RepositoryBase @@ -219,7 +208,12 @@ namespace Umbraco.Core.Persistence.Repositories { entity.Path = string.Concat(parent.Path, ",", entity.Id); } - + else + { + //this means that the master template has been removed, so we need to reset the template's + //path to be at the root + entity.Path = string.Concat("-1,", entity.Id); + } } //Get TemplateDto from db to get the Primary key of the entity @@ -508,6 +502,11 @@ namespace Umbraco.Core.Persistence.Repositories GetFileSystem(filepath).AddFile(filepath, content, true); } + public long GetFileSize(string filepath) + { + return GetFileSystem(filepath).GetSize(filepath); + } + private IFileSystem GetFileSystem(string filepath) { var ext = Path.GetExtension(filepath); diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 4652ce47be..bad4af0ebc 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.Repositories private readonly IUserTypeRepository _userTypeRepository; private readonly CacheHelper _cacheHelper; - public UserRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax, IUserTypeRepository userTypeRepository) + public UserRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax, IUserTypeRepository userTypeRepository) : base(work, cacheHelper, logger, sqlSyntax) { _userTypeRepository = userTypeRepository; diff --git a/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs index 1fd94ca357..81e3a5765e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class UserTypeRepository : PetaPocoRepositoryBase, IUserTypeRepository { - public UserTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public UserTypeRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index ded026bbec..d802d10c66 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,14 +28,12 @@ using Umbraco.Core.Media; namespace Umbraco.Core.Persistence.Repositories { - using SqlSyntax; - internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase where TEntity : class, IAggregateRoot { private readonly IContentSection _contentSection; - protected VersionableRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection) + protected VersionableRepositoryBase(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection) : base(work, cache, logger, sqlSyntax) { _contentSection = contentSection; @@ -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, IEnumerable> 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.FullMultiple); 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 @@ -434,38 +462,32 @@ namespace Umbraco.Core.Persistence.Repositories totalRecords = Convert.ToInt32(pagedResult.TotalItems); //NOTE: We need to check the actual items returned, not the 'totalRecords', that is because if you request a page number - // that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in + // that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in // the pageResult, then the GetAll will actually return ALL records in the db. if (pagedResult.Items.Any()) { - //Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query + //Create the inner paged query that was used above to get the paged result, we'll use that as the inner sub query var args = sqlNodeIdsWithSort.Arguments; string sqlStringCount, sqlStringPage; Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); - //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, new PagingSqlQuery(Database, sqlNodeIdsWithSort, pageIndex, pageSize)); } else { @@ -475,38 +497,56 @@ namespace Umbraco.Core.Persistence.Repositories return result; } + /// + /// Gets the property collection for a non-paged query + /// + /// + /// + /// protected IDictionary GetPropertyCollection( - Sql docSql, - IEnumerable documentDefs) + Sql sql, + IReadOnlyCollection documentDefs) { - if (documentDefs.Any() == false) return new Dictionary(); + return GetPropertyCollection(new PagingSqlQuery(sql), documentDefs); + } - //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use + /// + /// Gets the property collection for a query + /// + /// + /// + /// + protected IDictionary GetPropertyCollection( + PagingSqlQuery pagingSqlQuery, + IReadOnlyCollection documentDefs) + { + if (documentDefs.Count == 0) return new Dictionary(); + + //initialize to the query passed in + var docSql = pagingSqlQuery.PrePagedSql; + + //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use // the statement to go get the property data for all of the items by using an inner join var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal)); - //now remove everything from an Orderby clause and beyond - if (parsedOriginalSql.InvariantContains("ORDER BY ")) + + if (pagingSqlQuery.HasPaging) { + //if this is a paged query, build the paged query with the custom column substitution, then re-assign + docSql = pagingSqlQuery.BuildPagedQuery("{0}"); + parsedOriginalSql = docSql.SQL; + } + else if (parsedOriginalSql.InvariantContains("ORDER BY ")) + { + //now remove everything from an Orderby clause and beyond if this is unpaged data parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); } - 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 @@ -514,29 +554,82 @@ WHERE EXISTS( INNER JOIN cmsPropertyType ON b.datatypeNodeId = cmsPropertyType.dataTypeId INNER JOIN - (" + string.Format(parsedOriginalSql, "DISTINCT cmsContent.contentType") + @") as docData + (" + string.Format(parsedOriginalSql, "cmsContent.contentType") + @") as docData ON cmsPropertyType.contentTypeId = docData.contentType WHERE a.id = b.id)", docSql.Arguments); - 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(); + var hasCurrent = false; // initially there is no enumerator.Current + + 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 properties 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(); + // assemble the dtos for this def + // use the available enumerator.Current if any else move to next + var propertyDataDtos = new List(); + while (hasCurrent || propertyDataSetEnumerator.MoveNext()) + { + if (propertyDataSetEnumerator.Current.NodeId == def.Id) + { + hasCurrent = false; // enumerator.Current is not available + propertyDataDtos.Add(propertyDataSetEnumerator.Current); + } + else + { + hasCurrent = true; // enumerator.Current is available for another def + break; // no more propertyDataDto for this def + } + } + + var properties = PropertyFactory.BuildEntity(propertyDataDtos, compositionProperties, def.CreateDate, def.VersionDate).ToArray(); foreach (var property in properties) { @@ -553,7 +646,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(); @@ -561,9 +654,7 @@ WHERE EXISTS( var preVals = new PreValueCollection(asDictionary); - var contentPropData = new ContentPropertyData(property.Value, - preVals, - new Dictionary()); + var contentPropData = new ContentPropertyData(property.Value, preVals); TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); } @@ -576,31 +667,15 @@ WHERE EXISTS( result[def.Id] = new PropertyCollection(properties); } } - - return result; - } - - public class DocumentDefinition - { - /// - /// Initializes a new instance of the class. - /// - public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition) + finally { - Id = id; - Version = version; - VersionDate = versionDate; - CreateDate = createDate; - Composition = composition; + propertyDataSetEnumerator.Dispose(); } - public int Id { get; set; } - public Guid Version { get; set; } - public DateTime VersionDate { get; set; } - public DateTime CreateDate { get; set; } - public IContentTypeComposition Composition { get; set; } - } + return result; + } + protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy) { // Translate the passed order by field (which were originally defined for in-memory object sorting @@ -689,5 +764,99 @@ WHERE EXISTS( return allsuccess; } + + /// + /// For Paging, repositories must support returning different query for the query type specified + /// + /// + /// + protected abstract Sql GetBaseQuery(BaseQueryType queryType); + + internal class DocumentDefinition + { + /// + /// Initializes a new instance of the class. + /// + public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition) + { + Id = id; + Version = version; + VersionDate = versionDate; + CreateDate = createDate; + Composition = composition; + } + + public int Id { get; set; } + public Guid Version { get; set; } + public DateTime VersionDate { get; set; } + public DateTime CreateDate { get; set; } + public IContentTypeComposition Composition { get; set; } + } + + /// + /// An object representing a query that may contain paging information + /// + internal class PagingSqlQuery + { + public Sql PrePagedSql { get; private set; } + + public PagingSqlQuery(Sql prePagedSql) + { + PrePagedSql = prePagedSql; + } + + public virtual bool HasPaging + { + get { return false; } + } + + public virtual Sql BuildPagedQuery(string selectColumns) + { + throw new InvalidOperationException("This query has no paging information"); + } + } + + /// + /// An object representing a query that contains paging information + /// + /// + internal class PagingSqlQuery : PagingSqlQuery + { + private readonly Database _db; + private readonly long _pageIndex; + private readonly int _pageSize; + + public PagingSqlQuery(Database db, Sql prePagedSql, long pageIndex, int pageSize) : base(prePagedSql) + { + _db = db; + _pageIndex = pageIndex; + _pageSize = pageSize; + } + + public override bool HasPaging + { + get { return _pageSize > 0; } + } + + /// + /// Creates a paged query based on the original query and subtitutes the selectColumns specified + /// + /// + /// + public override Sql BuildPagedQuery(string selectColumns) + { + if (HasPaging == false) throw new InvalidOperationException("This query has no paging information"); + + var resultSql = string.Format("SELECT {0} {1}", selectColumns, PrePagedSql.SQL.Substring(PrePagedSql.SQL.IndexOf("FROM", StringComparison.Ordinal))); + + //this query is meant to be paged so we need to generate the paging syntax + //Create the inner paged query that was used above to get the paged result, we'll use that as the inner sub query + var args = PrePagedSql.Arguments; + string sqlStringCount, sqlStringPage; + _db.BuildPageQueries(_pageIndex * _pageSize, _pageSize, resultSql, ref args, out sqlStringCount, out sqlStringPage); + + return new Sql(sqlStringPage, args); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index e04c898015..d489929775 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -1,5 +1,6 @@ using Umbraco.Core.Configuration; using System; +using System.ComponentModel; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -19,7 +20,7 @@ namespace Umbraco.Core.Persistence private readonly ILogger _logger; private readonly ISqlSyntaxProvider _sqlSyntax; private readonly CacheHelper _cacheHelper; - private readonly CacheHelper _noCache; + private readonly CacheHelper _nullCache; private readonly IUmbracoSettingsSection _settings; #region Ctors @@ -52,7 +53,7 @@ namespace Umbraco.Core.Persistence }; } - _noCache = CacheHelper.CreateDisabledCacheHelper(); + _nullCache = CacheHelper.NoCache; _logger = logger; _sqlSyntax = sqlSyntax; _settings = settings; @@ -76,54 +77,54 @@ namespace Umbraco.Core.Persistence { if (cacheHelper == null) throw new ArgumentNullException("cacheHelper"); _cacheHelper = cacheHelper; - _noCache = CacheHelper.CreateDisabledCacheHelper(); + _nullCache = CacheHelper.NoCache; } [Obsolete("Use the ctor specifying all dependencies instead")] public RepositoryFactory(bool disableAllCache) - : this(disableAllCache ? CacheHelper.CreateDisabledCacheHelper() : ApplicationContext.Current.ApplicationCache, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider, UmbracoConfig.For.UmbracoSettings()) + : this(disableAllCache ? CacheHelper.NoCache : ApplicationContext.Current.ApplicationCache, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider, UmbracoConfig.For.UmbracoSettings()) { } #endregion - public virtual IExternalLoginRepository CreateExternalLoginRepository(IDatabaseUnitOfWork uow) + public virtual IExternalLoginRepository CreateExternalLoginRepository(IScopeUnitOfWork uow) { return new ExternalLoginRepository(uow, _cacheHelper, _logger, _sqlSyntax); } - public virtual IPublicAccessRepository CreatePublicAccessRepository(IDatabaseUnitOfWork uow) + public virtual IPublicAccessRepository CreatePublicAccessRepository(IScopeUnitOfWork uow) { return new PublicAccessRepository(uow, _cacheHelper, _logger, _sqlSyntax); } - public virtual ITaskRepository CreateTaskRepository(IDatabaseUnitOfWork uow) + public virtual ITaskRepository CreateTaskRepository(IScopeUnitOfWork uow) { return new TaskRepository(uow, - _noCache, //never cache + _nullCache, //never cache _logger, _sqlSyntax); } - public virtual IAuditRepository CreateAuditRepository(IDatabaseUnitOfWork uow) + public virtual IAuditRepository CreateAuditRepository(IScopeUnitOfWork uow) { return new AuditRepository(uow, - _noCache, //never cache + _nullCache, //never cache _logger, _sqlSyntax); } - public virtual ITagRepository CreateTagRepository(IDatabaseUnitOfWork uow) + public virtual ITagRepository CreateTagRepository(IScopeUnitOfWork uow) { return new TagRepository( uow, _cacheHelper, _logger, _sqlSyntax); } - public virtual IContentRepository CreateContentRepository(IDatabaseUnitOfWork uow) + public virtual IContentRepository CreateContentRepository(IScopeUnitOfWork uow) { return new ContentRepository( uow, @@ -139,7 +140,7 @@ namespace Umbraco.Core.Persistence }; } - public virtual IContentTypeRepository CreateContentTypeRepository(IDatabaseUnitOfWork uow) + public virtual IContentTypeRepository CreateContentTypeRepository(IScopeUnitOfWork uow) { return new ContentTypeRepository( uow, @@ -148,7 +149,7 @@ namespace Umbraco.Core.Persistence CreateTemplateRepository(uow)); } - public virtual IDataTypeDefinitionRepository CreateDataTypeDefinitionRepository(IDatabaseUnitOfWork uow) + public virtual IDataTypeDefinitionRepository CreateDataTypeDefinitionRepository(IScopeUnitOfWork uow) { return new DataTypeDefinitionRepository( uow, @@ -157,7 +158,7 @@ namespace Umbraco.Core.Persistence CreateContentTypeRepository(uow)); } - public virtual IDictionaryRepository CreateDictionaryRepository(IDatabaseUnitOfWork uow) + public virtual IDictionaryRepository CreateDictionaryRepository(IScopeUnitOfWork uow) { return new DictionaryRepository( uow, @@ -166,7 +167,7 @@ namespace Umbraco.Core.Persistence _sqlSyntax); } - public virtual ILanguageRepository CreateLanguageRepository(IDatabaseUnitOfWork uow) + public virtual ILanguageRepository CreateLanguageRepository(IScopeUnitOfWork uow) { return new LanguageRepository( uow, @@ -174,7 +175,7 @@ namespace Umbraco.Core.Persistence _logger, _sqlSyntax); } - public virtual IMediaRepository CreateMediaRepository(IDatabaseUnitOfWork uow) + public virtual IMediaRepository CreateMediaRepository(IScopeUnitOfWork uow) { return new MediaRepository( uow, @@ -185,7 +186,7 @@ namespace Umbraco.Core.Persistence _settings.Content); } - public virtual IMediaTypeRepository CreateMediaTypeRepository(IDatabaseUnitOfWork uow) + public virtual IMediaTypeRepository CreateMediaTypeRepository(IScopeUnitOfWork uow) { return new MediaTypeRepository( uow, @@ -193,16 +194,16 @@ namespace Umbraco.Core.Persistence _logger, _sqlSyntax); } - public virtual IRelationRepository CreateRelationRepository(IDatabaseUnitOfWork uow) + public virtual IRelationRepository CreateRelationRepository(IScopeUnitOfWork uow) { return new RelationRepository( uow, - _noCache, + _nullCache, _logger, _sqlSyntax, CreateRelationTypeRepository(uow)); } - public virtual IRelationTypeRepository CreateRelationTypeRepository(IDatabaseUnitOfWork uow) + public virtual IRelationTypeRepository CreateRelationTypeRepository(IScopeUnitOfWork uow) { return new RelationTypeRepository( uow, @@ -225,12 +226,19 @@ namespace Umbraco.Core.Persistence return new PartialViewMacroRepository(uow, FileSystemProviderManager.Current.MacroPartialsFileSystem); } + public virtual IStylesheetRepository CreateStylesheetRepository(IUnitOfWork uow) + { + return new StylesheetRepository(uow, FileSystemProviderManager.Current.StylesheetsFileSystem); + } + + [Obsolete("Do not use this method, use the method with only the single unit of work parameter")] + [EditorBrowsable(EditorBrowsableState.Never)] public virtual IStylesheetRepository CreateStylesheetRepository(IUnitOfWork uow, IDatabaseUnitOfWork db) { return new StylesheetRepository(uow, FileSystemProviderManager.Current.StylesheetsFileSystem); } - public virtual ITemplateRepository CreateTemplateRepository(IDatabaseUnitOfWork uow) + public virtual ITemplateRepository CreateTemplateRepository(IScopeUnitOfWork uow) { return new TemplateRepository(uow, _cacheHelper, @@ -245,15 +253,15 @@ namespace Umbraco.Core.Persistence return new XsltFileRepository(uow, FileSystemProviderManager.Current.XsltFileSystem); } - public virtual IMigrationEntryRepository CreateMigrationEntryRepository(IDatabaseUnitOfWork uow) + public virtual IMigrationEntryRepository CreateMigrationEntryRepository(IScopeUnitOfWork uow) { return new MigrationEntryRepository( uow, - _noCache, //never cache + _nullCache, //never cache _logger, _sqlSyntax); } - public virtual IServerRegistrationRepository CreateServerRegistrationRepository(IDatabaseUnitOfWork uow) + public virtual IServerRegistrationRepository CreateServerRegistrationRepository(IScopeUnitOfWork uow) { return new ServerRegistrationRepository( uow, @@ -261,7 +269,7 @@ namespace Umbraco.Core.Persistence _logger, _sqlSyntax); } - public virtual IUserTypeRepository CreateUserTypeRepository(IDatabaseUnitOfWork uow) + public virtual IUserTypeRepository CreateUserTypeRepository(IScopeUnitOfWork uow) { return new UserTypeRepository( uow, @@ -270,7 +278,7 @@ namespace Umbraco.Core.Persistence _logger, _sqlSyntax); } - public virtual IUserRepository CreateUserRepository(IDatabaseUnitOfWork uow) + public virtual IUserRepository CreateUserRepository(IScopeUnitOfWork uow) { return new UserRepository( uow, @@ -280,14 +288,14 @@ namespace Umbraco.Core.Persistence CreateUserTypeRepository(uow)); } - internal virtual IMacroRepository CreateMacroRepository(IDatabaseUnitOfWork uow) + internal virtual IMacroRepository CreateMacroRepository(IScopeUnitOfWork uow) { return new MacroRepository(uow, _cacheHelper, _logger, _sqlSyntax); } - public virtual IMemberRepository CreateMemberRepository(IDatabaseUnitOfWork uow) + public virtual IMemberRepository CreateMemberRepository(IScopeUnitOfWork uow) { return new MemberRepository( uow, @@ -299,38 +307,38 @@ namespace Umbraco.Core.Persistence _settings.Content); } - public virtual IMemberTypeRepository CreateMemberTypeRepository(IDatabaseUnitOfWork uow) + public virtual IMemberTypeRepository CreateMemberTypeRepository(IScopeUnitOfWork uow) { return new MemberTypeRepository(uow, _cacheHelper, _logger, _sqlSyntax); } - public virtual IMemberGroupRepository CreateMemberGroupRepository(IDatabaseUnitOfWork uow) + public virtual IMemberGroupRepository CreateMemberGroupRepository(IScopeUnitOfWork uow) { return new MemberGroupRepository(uow, _cacheHelper, _logger, _sqlSyntax); } - public virtual IEntityRepository CreateEntityRepository(IDatabaseUnitOfWork uow) + public virtual IEntityRepository CreateEntityRepository(IScopeUnitOfWork uow) { return new EntityRepository(uow); } - public virtual IDomainRepository CreateDomainRepository(IDatabaseUnitOfWork uow) + public virtual IDomainRepository CreateDomainRepository(IScopeUnitOfWork uow) { return new DomainRepository(uow, _cacheHelper, _logger, _sqlSyntax); } - public ITaskTypeRepository CreateTaskTypeRepository(IDatabaseUnitOfWork uow) + public ITaskTypeRepository CreateTaskTypeRepository(IScopeUnitOfWork uow) { return new TaskTypeRepository(uow, - _noCache, //never cache + _nullCache, //never cache _logger, _sqlSyntax); } - internal virtual EntityContainerRepository CreateEntityContainerRepository(IDatabaseUnitOfWork uow, Guid containerObjectType) + internal virtual EntityContainerRepository CreateEntityContainerRepository(IScopeUnitOfWork uow, Guid containerObjectType) { return new EntityContainerRepository( uow, @@ -339,7 +347,7 @@ namespace Umbraco.Core.Persistence containerObjectType); } - public IRedirectUrlRepository CreateRedirectUrlRepository(IDatabaseUnitOfWork uow) + public IRedirectUrlRepository CreateRedirectUrlRepository(IScopeUnitOfWork uow) { return new RedirectUrlRepository( uow, @@ -347,10 +355,5 @@ namespace Umbraco.Core.Persistence _logger, _sqlSyntax); } - - internal IStylesheetRepository CreateStylesheetRepository(IDatabaseUnitOfWork uow) - { - return new StylesheetRepository(uow, FileSystemProviderManager.Current.StylesheetsFileSystem); - } } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs index 1231765f20..f0bafdacf7 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs @@ -1,7 +1,23 @@ -namespace Umbraco.Core.Persistence.SqlSyntax +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.SqlSyntax { internal static class SqlSyntaxProviderExtensions { + public static IEnumerable GetDefinedIndexesDefinitions(this ISqlSyntaxProvider sql, Database db) + { + return sql.GetDefinedIndexes(db) + .Select(x => new DbIndexDefinition() + { + TableName = x.Item1, + IndexName = x.Item2, + ColumnName = x.Item3, + IsUnique = x.Item4 + }).ToArray(); + } + /// /// Returns the quotes tableName.columnName combo /// diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 0b370c9978..c451117847 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence /// can then override any additional execution (such as additional loggging, functionality, etc...) that we need to without breaking compatibility since we'll always be exposing /// this object instead of the base PetaPoco database object. /// - public class UmbracoDatabase : Database, IDisposeOnRequestEnd + public class UmbracoDatabase : Database { private readonly ILogger _logger; private readonly Guid _instanceId = Guid.NewGuid(); @@ -29,7 +29,6 @@ namespace Umbraco.Core.Persistence private int _spid = -1; #endif - internal DefaultDatabaseFactory.ContextOwner ContextOwner = DefaultDatabaseFactory.ContextOwner.None; internal DefaultDatabaseFactory DatabaseFactory = null; /// @@ -57,6 +56,20 @@ namespace Umbraco.Core.Persistence /// internal bool EnableSqlTrace { get; set; } + public bool InTransaction { get; private set; } + + public override void OnBeginTransaction() + { + base.OnBeginTransaction(); + InTransaction = true; + } + + public override void OnEndTransaction() + { + base.OnEndTransaction(); + InTransaction = false; + } + #if DEBUG_DATABASES private const bool EnableSqlTraceDefault = true; #else @@ -142,6 +155,8 @@ namespace Umbraco.Core.Persistence // propagate timeout if none yet #if DEBUG_DATABASES + // determines the database connection SPID for debugging + if (DatabaseType == DBType.MySql) { using (var command = connection.CreateCommand()) @@ -206,6 +221,7 @@ namespace Umbraco.Core.Persistence } #if DEBUG_DATABASES + // ensures the database does not have an open reader, for debugging DatabaseDebugHelper.SetCommand(cmd, InstanceSid + " [T" + Thread.CurrentThread.ManagedThreadId + "]"); var refsobj = DatabaseDebugHelper.GetReferencedObjects(cmd.Connection); if (refsobj != null) _logger.Debug("Oops!" + Environment.NewLine + refsobj); @@ -258,14 +274,5 @@ namespace Umbraco.Core.Persistence //use the defaults base.BuildSqlDbSpecificPagingQuery(databaseType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); -#if DEBUG_DATABASES - LogHelper.Debug("Dispose (" + InstanceSid + ")."); -#endif - if (DatabaseFactory != null) DatabaseFactory.OnDispose(this); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWork.cs index c4c31849eb..b7f09999bb 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWork.cs @@ -1,142 +1,20 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; -using System.Transactions; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.UnitOfWork { - /// - /// Represents the Unit of Work implementation for working with files - /// - internal class FileUnitOfWork : IUnitOfWork + internal class FileUnitOfWork : ScopeUnitOfWork { - private Guid _key; - private readonly Queue _operations = new Queue(); + // fixme + // soon as FileUnitOfWork inherits from ScopeUnitOfWork it does not make any sense anymore to keep this class around?! - public FileUnitOfWork() - { - _key = Guid.NewGuid(); - } - - #region Implementation of IUnitOfWork - - /// - /// Registers an instance to be added through this - /// - /// The - /// The participating in the transaction - public void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository) - { - _operations.Enqueue( - new Operation - { - Entity = entity, - Repository = repository, - Type = TransactionType.Insert - }); - } - - /// - /// Registers an instance to be changed through this - /// - /// The - /// The participating in the transaction - public void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository) - { - _operations.Enqueue( - new Operation - { - Entity = entity, - Repository = repository, - Type = TransactionType.Update - }); - } - - /// - /// Registers an instance to be removed through this - /// - /// The - /// The participating in the transaction - public void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository) - { - _operations.Enqueue( - new Operation - { - Entity = entity, - Repository = repository, - Type = TransactionType.Delete - }); - } - - public void Commit() - { - //NOTE: I'm leaving this in here for reference, but this is useless, transaction scope + Files doesn't do anything, - // the closest you can get is transactional NTFS, but that requires distributed transaction coordinator and some other libs/wrappers, - // plus MS has not deprecated it anyways. To do transactional IO we'd have to write this ourselves using temporary files and then - // on committing move them to their correct place. - //using(var scope = new TransactionScope()) - //{ - // // Commit the transaction - // scope.Complete(); - //} - - while (_operations.Count > 0) - { - var operation = _operations.Dequeue(); - switch (operation.Type) - { - case TransactionType.Insert: - operation.Repository.PersistNewItem(operation.Entity); - break; - case TransactionType.Delete: - operation.Repository.PersistDeletedItem(operation.Entity); - break; - case TransactionType.Update: - operation.Repository.PersistUpdatedItem(operation.Entity); - break; - } - } - - // Clear everything - _operations.Clear(); - _key = Guid.NewGuid(); - } - - public object Key - { - get { return _key; } - } - - #endregion - - #region Operation - - /// - /// Provides a snapshot of an entity and the repository reference it belongs to. - /// - private sealed class Operation - { - /// - /// Gets or sets the entity. - /// - /// The entity. - public IEntity Entity { get; set; } - - /// - /// Gets or sets the repository. - /// - /// The repository. - public IUnitOfWorkRepository Repository { get; set; } - - /// - /// Gets or sets the type of operation. - /// - /// The type of operation. - public TransactionType Type { get; set; } - } - - #endregion + public FileUnitOfWork(IScopeProvider scopeProvider, IsolationLevel isolationLevel = IsolationLevel.Unspecified) + : base(scopeProvider, isolationLevel) + { } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWorkProvider.cs index c27e86a515..0abded7639 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWorkProvider.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWorkProvider.cs @@ -1,17 +1,29 @@ -namespace Umbraco.Core.Persistence.UnitOfWork +using System; +using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Persistence.UnitOfWork { /// - /// Represents a Unit of Work Provider for creating a + /// Represents a Unit of Work Provider for creating a file unit of work /// - public class FileUnitOfWorkProvider : IUnitOfWorkProvider + public class FileUnitOfWorkProvider : ScopeUnitOfWorkProvider // fixme - what's the point? { - #region Implementation of IUnitOfWorkProvider + [Obsolete("Use the ctor specifying a IScopeProvider instead")] + public FileUnitOfWorkProvider() + : this(new ScopeProvider(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, LoggerResolver.Current.Logger))) + { } - public IUnitOfWork GetUnitOfWork() + public FileUnitOfWorkProvider(IScopeProvider scopeProvider) + : base(scopeProvider) + { } + + // fixme - returning a IScopeUnitOfWork instead of a IUnitOfWork here is a breaking change! + // this is bad, we're going to end up creating // units of work which is ... really bad + public override IScopeUnitOfWork GetUnitOfWork() { - return new FileUnitOfWork(); + // fixme - no point returning a FileUnitOfWork if its equivalent to ScopeUnitOfWork + return new ScopeUnitOfWork(ScopeProvider); } - - #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWorkProvider.cs index c18c08d8bb..a63299d0a6 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWorkProvider.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWorkProvider.cs @@ -6,5 +6,5 @@ namespace Umbraco.Core.Persistence.UnitOfWork public interface IDatabaseUnitOfWorkProvider { IDatabaseUnitOfWork GetUnitOfWork(); - } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IScopeUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IScopeUnitOfWork.cs new file mode 100644 index 0000000000..e5232092c5 --- /dev/null +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IScopeUnitOfWork.cs @@ -0,0 +1,12 @@ +using Umbraco.Core.Events; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Persistence.UnitOfWork +{ + public interface IScopeUnitOfWork : IDatabaseUnitOfWork + { + IScope Scope { get; } + EventMessages Messages { get; } + IEventDispatcher Events { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IScopeUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IScopeUnitOfWorkProvider.cs new file mode 100644 index 0000000000..ffab842909 --- /dev/null +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IScopeUnitOfWorkProvider.cs @@ -0,0 +1,36 @@ +using System.Data; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Persistence.UnitOfWork +{ + /// + /// Provides scoped units of work. + /// + public interface IScopeUnitOfWorkProvider : IDatabaseUnitOfWorkProvider + { + // gets the scope provider + IScopeProvider ScopeProvider { get; } + + // creates a unit of work + // redefine the method to indicate it returns an IScopeUnitOfWork and + // not anymore only an IDatabaseUnitOfWork as IDatabaseUnitOfWorkProvider does + new IScopeUnitOfWork GetUnitOfWork(); + + // creates a unit of work + // support specifying an isolation level + // support auto-commit - beware! + // fixme - should we be able to specify ALL scope options? + // fixme - that isolation level thing is a pain because of the above existing method + IScopeUnitOfWork GetUnitOfWork(bool commit); + IScopeUnitOfWork GetUnitOfWork(IsolationLevel isolationLevel, bool commit = false); + + // creates a readonly unit of work + // the readonly unit of work will not accept operations, and will auto-complete + // of course it would be a bad idea to use it for anything else than reading + // fixme - implement that one FOR REAL not as an extension method + // the extension method was a ugly hack + // or maybe we want a autocommit flag in the ctor? + //IScopeUnitOfWork GetReadOnlyUnitOfWork(); + //IScopeUnitOfWork GetReadOnlyUnitOfWork(bool autocommit = false); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs index 8c909fc554..56f6c6af98 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs @@ -1,83 +1,40 @@ using System; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.UnitOfWork { /// - /// Represents a Unit of Work Provider for creating a + /// Represents a Unit of Work Provider for creating a /// - public class PetaPocoUnitOfWorkProvider : IDatabaseUnitOfWorkProvider - { - private readonly IDatabaseFactory _dbFactory; - + public class PetaPocoUnitOfWorkProvider : ScopeUnitOfWorkProvider + { [Obsolete("Use the constructor specifying an ILogger instead")] public PetaPocoUnitOfWorkProvider() - : this(new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, LoggerResolver.Current.Logger)) - { - - } + : base(new ScopeProvider(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, LoggerResolver.Current.Logger))) + { } [Obsolete("Use the constructor specifying an ILogger instead")] public PetaPocoUnitOfWorkProvider(string connectionString, string providerName) - : this(new DefaultDatabaseFactory(connectionString, providerName, LoggerResolver.Current.Logger)) + : base(new ScopeProvider(new DefaultDatabaseFactory(connectionString, providerName, LoggerResolver.Current.Logger))) + { } + + public PetaPocoUnitOfWorkProvider(ILogger logger) + : base(new ScopeProvider(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, logger))) { } /// - /// Parameterless constructor uses defaults - /// - public PetaPocoUnitOfWorkProvider(ILogger logger) - : this(new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, logger)) - { - - } - - /// - /// Constructor accepting custom connectino string and provider name + /// Constructor accepting custom connection string and provider name /// /// /// Connection String to use with Database /// Database Provider for the Connection String public PetaPocoUnitOfWorkProvider(ILogger logger, string connectionString, string providerName) - : this(new DefaultDatabaseFactory(connectionString, providerName, logger)) + : base(new ScopeProvider(new DefaultDatabaseFactory(connectionString, providerName, logger))) { } - /// - /// Constructor accepting an IDatabaseFactory instance - /// - /// - public PetaPocoUnitOfWorkProvider(IDatabaseFactory dbFactory) - { - Mandate.ParameterNotNull(dbFactory, "dbFactory"); - _dbFactory = dbFactory; - } - - #region Implementation of IUnitOfWorkProvider - - /// - /// Creates a Unit of work with a new UmbracoDatabase instance for the work item/transaction. - /// - /// - /// - /// Each PetaPoco UOW uses it's own Database object, not the shared Database object that comes from - /// the ApplicationContext.Current.DatabaseContext.Database. This is because each transaction should use it's own Database - /// and we Dispose of this Database object when the UOW is disposed. - /// - public IDatabaseUnitOfWork GetUnitOfWork() - { - return new PetaPocoUnitOfWork(_dbFactory.CreateDatabase()); - } - - #endregion - - /// - /// Static helper method to return a new unit of work - /// - /// - internal static IDatabaseUnitOfWork CreateUnitOfWork(ILogger logger) - { - var provider = new PetaPocoUnitOfWorkProvider(logger); - return provider.GetUnitOfWork(); - } + public PetaPocoUnitOfWorkProvider(IScopeProvider scopeProvider) + : base(scopeProvider) + { } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs similarity index 54% rename from src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs rename to src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs index a5337e854c..1e6ae7f5b4 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs @@ -1,183 +1,222 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Persistence.UnitOfWork -{ - /// - /// Represents the Unit of Work implementation for PetaPoco - /// - internal class PetaPocoUnitOfWork : DisposableObject, IDatabaseUnitOfWork - { - - /// - /// Used for testing - /// - internal Guid InstanceId { get; private set; } - - private Guid _key; - private readonly Queue _operations = new Queue(); - - /// - /// Creates a new unit of work instance - /// - /// - /// - /// This should normally not be used directly and should be created with the UnitOfWorkProvider - /// - internal PetaPocoUnitOfWork(UmbracoDatabase database) - { - Database = database; - _key = Guid.NewGuid(); - InstanceId = Guid.NewGuid(); - } - - /// - /// Registers an instance to be added through this - /// - /// The - /// The participating in the transaction - public void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository) - { - _operations.Enqueue(new Operation - { - Entity = entity, - Repository = repository, - Type = TransactionType.Insert - }); - } - - /// - /// Registers an instance to be changed through this - /// - /// The - /// The participating in the transaction - public void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository) - { - _operations.Enqueue( - new Operation - { - Entity = entity, - Repository = repository, - Type = TransactionType.Update - }); - } - - /// - /// Registers an instance to be removed through this - /// - /// The - /// The participating in the transaction - public void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository) - { - _operations.Enqueue( - new Operation - { - Entity = entity, - Repository = repository, - Type = TransactionType.Delete - }); - } - - /// - /// Commits all batched changes within the scope of a PetaPoco transaction - /// - /// - /// Unlike a typical unit of work, this UOW will let you commit more than once since a new transaction is creaed per - /// Commit() call instead of having one Transaction per UOW. - /// - public void Commit() - { - Commit(null); - } - - /// - /// Commits all batched changes within the scope of a PetaPoco transaction - /// - /// - /// Allows you to set a callback which is executed before the transaction is committed, allow you to add additional SQL - /// operations to the overall commit process after the queue has been processed. - /// - internal void Commit(Action transactionCompleting) - { - using (var transaction = Database.GetTransaction()) - { - while (_operations.Count > 0) - { - var operation = _operations.Dequeue(); - switch (operation.Type) - { - case TransactionType.Insert: - operation.Repository.PersistNewItem(operation.Entity); - break; - case TransactionType.Delete: - operation.Repository.PersistDeletedItem(operation.Entity); - break; - case TransactionType.Update: - operation.Repository.PersistUpdatedItem(operation.Entity); - break; - } - } - - //Execute the callback if there is one - if (transactionCompleting != null) - { - transactionCompleting(Database); - } - - transaction.Complete(); - } - - // Clear everything - _operations.Clear(); - _key = Guid.NewGuid(); - } - - public object Key - { - get { return _key; } - } - - public UmbracoDatabase Database { get; private set; } - - #region Operation - - /// - /// Provides a snapshot of an entity and the repository reference it belongs to. - /// - private sealed class Operation - { - /// - /// Gets or sets the entity. - /// - /// The entity. - public IEntity Entity { get; set; } - - /// - /// Gets or sets the repository. - /// - /// The repository. - public IUnitOfWorkRepository Repository { get; set; } - - /// - /// Gets or sets the type of operation. - /// - /// The type of operation. - public TransactionType Type { get; set; } - } - - #endregion - - /// - /// Ensures disposable objects are disposed - /// - /// - /// Ensures that the Transaction instance is disposed of - /// - protected override void DisposeResources() - { - _operations.Clear(); - } - } +using System; +using System.Collections.Generic; +using System.Data; +using Umbraco.Core.Events; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Persistence.UnitOfWork +{ + /// + /// Represents a scoped unit of work. + /// + internal class ScopeUnitOfWork : DisposableObject, IScopeUnitOfWork + { + private readonly Queue _operations = new Queue(); + private readonly IsolationLevel _isolationLevel; + private readonly IScopeProvider _scopeProvider; + private bool _completeScope; + private IScope _scope; + private Guid _key; + + /// + /// Used for testing + /// + internal Guid InstanceId { get; private set; } + + /// + /// Creates a new unit of work instance + /// + /// + /// + /// + /// + /// This should normally not be used directly and should be created with the UnitOfWorkProvider + /// + internal ScopeUnitOfWork(IScopeProvider scopeProvider, IsolationLevel isolationLevel = IsolationLevel.Unspecified, bool commit = false) + { + _scopeProvider = scopeProvider; + _isolationLevel = isolationLevel; + _key = Guid.NewGuid(); + InstanceId = Guid.NewGuid(); + + // be false by default + // if set to true... the UnitOfWork is "auto-commit" which means that even in the case of + // an exception, the scope would still be completed - ppl should use it with great care! + _completeScope = commit; + } + + /// + /// Registers an instance to be added through this + /// + /// The + /// The participating in the transaction + public void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository) + { + _operations.Enqueue(new Operation + { + Entity = entity, + Repository = repository, + Type = TransactionType.Insert + }); + } + + /// + /// Registers an instance to be changed through this + /// + /// The + /// The participating in the transaction + public void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository) + { + _operations.Enqueue( + new Operation + { + Entity = entity, + Repository = repository, + Type = TransactionType.Update + }); + } + + /// + /// Registers an instance to be removed through this + /// + /// The + /// The participating in the transaction + public void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository) + { + _operations.Enqueue( + new Operation + { + Entity = entity, + Repository = repository, + Type = TransactionType.Delete + }); + } + + /// + /// Commits all batched changes within the scope of a PetaPoco transaction + /// + /// + /// Unlike a typical unit of work, this UOW will let you commit more than once since a new transaction is creaed per + /// Commit() call instead of having one Transaction per UOW. + /// + public void Commit() + { + Commit(null); + } + + /// + /// Commits all batched changes within the scope of a PetaPoco transaction + /// + /// + /// Allows you to set a callback which is executed before the transaction is committed, allow you to add additional SQL + /// operations to the overall commit process after the queue has been processed. + /// + internal void Commit(Action transactionCompleting) + { + // this happens in a scope-managed transaction + + // in case anything goes wrong + _completeScope = false; + + while (_operations.Count > 0) + { + var operation = _operations.Dequeue(); + switch (operation.Type) + { + case TransactionType.Insert: + operation.Repository.PersistNewItem(operation.Entity); + break; + case TransactionType.Delete: + operation.Repository.PersistDeletedItem(operation.Entity); + break; + case TransactionType.Update: + operation.Repository.PersistUpdatedItem(operation.Entity); + break; + } + } + + if (transactionCompleting != null) + transactionCompleting(Database); + + // all is ok + _completeScope = true; + + // Clear everything + _operations.Clear(); + _key = Guid.NewGuid(); + } + + public object Key + { + get { return _key; } + } + + public IScope Scope + { + // TODO + // once we are absolutely sure that our UOW cannot be disposed more than once, + // this should throw if the UOW has already been disposed, NOT recreate a scope! + get { return _scope ?? (_scope = _scopeProvider.CreateScope(_isolationLevel)); } + } + + public UmbracoDatabase Database + { + get { return Scope.Database; } + } + + public EventMessages Messages + { + get { return Scope.Messages; } + } + + public IEventDispatcher Events + { + get { return Scope.Events; } + } + + #region Operation + + /// + /// Provides a snapshot of an entity and the repository reference it belongs to. + /// + private sealed class Operation + { + /// + /// Gets or sets the entity. + /// + /// The entity. + public IEntity Entity { get; set; } + + /// + /// Gets or sets the repository. + /// + /// The repository. + public IUnitOfWorkRepository Repository { get; set; } + + /// + /// Gets or sets the type of operation. + /// + /// The type of operation. + public TransactionType Type { get; set; } + } + + #endregion + + /// + /// Ensures disposable objects are disposed + /// + /// + /// Ensures that the Transaction instance is disposed of + /// + protected override void DisposeResources() + { + _operations.Clear(); + if (_scope == null) return; + + if (_completeScope) _scope.Complete(); + _scope.Dispose(); + _scope = null; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWorkProvider.cs new file mode 100644 index 0000000000..2e5950e3b2 --- /dev/null +++ b/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWorkProvider.cs @@ -0,0 +1,71 @@ +using System.Data; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Persistence.UnitOfWork +{ + public abstract class ScopeUnitOfWorkProvider : IScopeUnitOfWorkProvider + { + /// + /// Constructor accepting a instance + /// + /// + protected ScopeUnitOfWorkProvider(IScopeProvider scopeProvider) + { + Mandate.ParameterNotNull(scopeProvider, "scopeProvider"); + ScopeProvider = scopeProvider; + } + + /// + public IScopeProvider ScopeProvider { get; private set; } + + // explicit implementation + IDatabaseUnitOfWork IDatabaseUnitOfWorkProvider.GetUnitOfWork() + { + return new ScopeUnitOfWork(ScopeProvider); + } + + /// + /// Creates a Unit of work with a new UmbracoDatabase instance for the work item/transaction. + /// + /// + /// + /// Each PetaPoco UOW uses it's own Database object, not the shared Database object that comes from + /// the ApplicationContext.Current.DatabaseContext.Database. This is because each transaction should use it's own Database + /// and we Dispose of this Database object when the UOW is disposed. + /// fixme NO we dispose of it when the transaction completes + /// fixme just inheritdoc! + /// + public virtual IScopeUnitOfWork GetUnitOfWork() + { + return new ScopeUnitOfWork(ScopeProvider); + } + + /// + /// Creates a Unit of work with a new UmbracoDatabase instance for the work item/transaction. + /// + /// + /// + /// Each PetaPoco UOW uses it's own Database object, not the shared Database object that comes from + /// the ApplicationContext.Current.DatabaseContext.Database. This is because each transaction should use it's own Database + /// and we Dispose of this Database object when the UOW is disposed. + /// fixme NO + /// fixme just inheritdoc! + /// + public IScopeUnitOfWork GetUnitOfWork(IsolationLevel isolationLevel) + { + return new ScopeUnitOfWork(ScopeProvider, isolationLevel); + } + + /// + public IScopeUnitOfWork GetUnitOfWork(bool commit) + { + return new ScopeUnitOfWork(ScopeProvider, commit: commit); + } + + /// + public IScopeUnitOfWork GetUnitOfWork(IsolationLevel isolationLevel, bool commit) + { + return new ScopeUnitOfWork(ScopeProvider, isolationLevel, commit: commit); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs index 102f0ee88f..af4a597ea3 100644 --- a/src/Umbraco.Core/PluginManager.cs +++ b/src/Umbraco.Core/PluginManager.cs @@ -420,18 +420,31 @@ namespace Umbraco.Core internal void UpdateCachedPluginsFile(IEnumerable typesFound, TypeResolutionKind resolutionType) { var filePath = GetPluginListFilePath(); - XDocument xml; - try + XDocument xml = null; + + if (File.Exists(filePath)) { - xml = XDocument.Load(filePath); - } - catch - { - //if there's an exception loading then this is somehow corrupt, we'll just replace it. - File.Delete(filePath); - //create the document and the root - xml = new XDocument(new XElement("plugins")); + try + { + xml = XDocument.Load(filePath); + } + catch + { + try + { + File.Delete(filePath); // file is corrupt somehow + } + catch + { + // on-purpose, does not matter + } + xml = null; + } } + + // else replace the xml, create the document and the root + if (xml == null) xml = new XDocument(new XElement("plugins")); + if (xml.Root == null) { //if for some reason there is no root, create it @@ -458,6 +471,9 @@ namespace Umbraco.Core //now we have the type element, we need to clear any previous types as children and add/update it with new ones typeElement.ReplaceNodes(typesFound.Select(x => new XElement("add", new XAttribute("type", x.AssemblyQualifiedName)))); //save the xml file + var dir = Path.GetDirectoryName(filePath); + if (Directory.Exists(dir) == false) + Directory.CreateDirectory(dir); xml.Save(filePath); } diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs index 204612b3d9..a74ebb8fec 100644 --- a/src/Umbraco.Core/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs @@ -3,9 +3,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; -using Umbraco.Core.Deploy; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Umbraco.Core")] @@ -13,16 +12,14 @@ using Umbraco.Core.Deploy; [assembly: AssemblyConfiguration("")] [assembly: AssemblyProduct("Umbraco CMS")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("130a6b5c-50e7-4df3-a0dd-e9e7eb0b7c5c")] -[assembly: DeploySupport("1.0.0-alpha000")] - [assembly: InternalsVisibleTo("umbraco")] [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("Umbraco.Extensions")] @@ -32,7 +29,7 @@ using Umbraco.Core.Deploy; [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")] @@ -45,7 +42,10 @@ using Umbraco.Core.Deploy; [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/PropertyEditors/PreValueField.cs b/src/Umbraco.Core/PropertyEditors/PreValueField.cs index 3cf4e960ea..2b66f7a6a8 100644 --- a/src/Umbraco.Core/PropertyEditors/PreValueField.cs +++ b/src/Umbraco.Core/PropertyEditors/PreValueField.cs @@ -15,6 +15,7 @@ namespace Umbraco.Core.PropertyEditors public PreValueField() { Validators = new List(); + Config = new Dictionary(); //check for an attribute and fill the values var att = GetType().GetCustomAttribute(false); @@ -79,5 +80,11 @@ namespace Umbraco.Core.PropertyEditors /// [JsonProperty("validation", ItemConverterType = typeof(ManifestValidatorConverter))] public List Validators { get; private set; } + + /// + /// This allows for custom configuration to be injected into the pre-value editor + /// + [JsonProperty("config")] + public IDictionary Config { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs index 6cecc9dff5..c2498ecc7a 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs @@ -38,6 +38,7 @@ namespace Umbraco.Core.PropertyEditors IsParameterEditor = _attribute.IsParameterEditor; Icon = _attribute.Icon; Group = _attribute.Group; + IsDeprecated = _attribute.IsDeprecated; } } @@ -90,6 +91,9 @@ namespace Umbraco.Core.PropertyEditors get { return CreateValueEditor(); } } + [JsonIgnore] + public bool IsDeprecated { get; internal set; } + [JsonIgnore] IValueEditor IParameterEditor.ValueEditor { diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs index d120753185..41e4ccc74e 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs @@ -60,6 +60,12 @@ namespace Umbraco.Core.PropertyEditors public string ValueType { get; set; } public bool IsParameterEditor { get; set; } + /// + /// If set to true, this property editor will not show up in the DataType's drop down list + /// if there is not already one of them chosen for a DataType + /// + public bool IsDeprecated { get; set; } + /// /// If this is is true than the editor will be displayed full width without a label /// diff --git a/src/Umbraco.Core/PropertyEditors/TagValueType.cs b/src/Umbraco.Core/PropertyEditors/TagValueType.cs index e5f65933ab..e837d1e8bf 100644 --- a/src/Umbraco.Core/PropertyEditors/TagValueType.cs +++ b/src/Umbraco.Core/PropertyEditors/TagValueType.cs @@ -14,7 +14,8 @@ /// The list of tags will be supplied by the property editor's ConvertEditorToDb method result which will need to return an IEnumerable{string} value /// /// - /// if the ConvertEditorToDb doesn't return an IEnumerable{string} then an exception will be thrown. + /// if the ConvertEditorToDb doesn't return an IEnumerable{string} then it will automatically try to be detected as either CSV or JSON and if neither of those match + /// an exception will be thrown. /// CustomTagList } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs index b3db026c89..029d1c3546 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs @@ -5,7 +5,6 @@ using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Grid; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; diff --git a/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs b/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs index 63f32c41c5..6c08822a71 100644 --- a/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs @@ -1,11 +1,12 @@ +using System; using System.Collections.Generic; +using System.ComponentModel; using Umbraco.Core.Models; namespace Umbraco.Core.Publishing { - /// - /// Abstract class for the implementation of an , which provides the events used for publishing/unpublishing. - /// + [Obsolete("This class is not intended to be used and will be removed in future versions, see IPublishingStrategy instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public abstract class BasePublishingStrategy : IPublishingStrategy { diff --git a/src/Umbraco.Core/Publishing/IPublishingStrategy.cs b/src/Umbraco.Core/Publishing/IPublishingStrategy.cs index 0408409488..93be145779 100644 --- a/src/Umbraco.Core/Publishing/IPublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/IPublishingStrategy.cs @@ -1,9 +1,80 @@ using System.Collections.Generic; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Publishing { /// + /// TODO: This is a compatibility hack, we want to get rid of IPublishingStrategy all together or just have it as an internal + /// helper class but we can't just remove it now, we also cannot just change it, so the current IPublishingStrategy one will simply not be used + /// in our own code, if for some odd reason someone else is using it, then fine it will continue to work with hacks but won't be used by us. + /// + internal interface IPublishingStrategy2 + { + /// + /// Publishes a single piece of Content + /// + /// + /// to publish + /// Id of the User issueing the publish operation + /// True if the publish operation was successfull and not cancelled, otherwise false + Attempt Publish(IScopeUnitOfWork uow, IContent content, int userId); + + /// + /// Publishes a list of Content + /// + /// + /// An enumerable list of + /// Id of the User issueing the publish operation + /// + /// True if the publish operation was successfull and not cancelled, otherwise false + IEnumerable> PublishWithChildren(IScopeUnitOfWork uow, IEnumerable content, int userId, bool includeUnpublishedDocuments); + + /// + /// Unpublishes a single piece of Content + /// + /// + /// to unpublish + /// Id of the User issueing the unpublish operation + /// True if the unpublish operation was successfull and not cancelled, otherwise false + Attempt UnPublish(IScopeUnitOfWork uow, IContent content, int userId); + + /// + /// Call to fire event that updating the published content has finalized. + /// + /// + /// This seperation of the OnPublished event is done to ensure that the Content + /// has been properly updated (committed unit of work) and xml saved in the db. + /// + /// + /// thats being published + void PublishingFinalized(IScopeUnitOfWork uow, IContent content); + + /// + /// Call to fire event that updating the published content has finalized. + /// + /// + /// An enumerable list of thats being published + /// Boolean indicating whether its all content that is republished + void PublishingFinalized(IScopeUnitOfWork uow, IEnumerable content, bool isAllRepublished); + + /// + /// Call to fire event that updating the unpublished content has finalized. + /// + /// + /// thats being unpublished + void UnPublishingFinalized(IScopeUnitOfWork uow, IContent content); + + /// + /// Call to fire event that updating the unpublished content has finalized. + /// + /// + /// An enumerable list of thats being unpublished + void UnPublishingFinalized(IScopeUnitOfWork uow, IEnumerable content); + } + + /// + /// TODO: This should be obsoleted but if we did that then the Publish/Unpublish events on the content service would show that the param is obsoleted /// Defines the Publishing Strategy /// public interface IPublishingStrategy diff --git a/src/Umbraco.Core/Publishing/PublishingStrategy.cs b/src/Umbraco.Core/Publishing/PublishingStrategy.cs index c6d4f19baf..e09b26a74d 100644 --- a/src/Umbraco.Core/Publishing/PublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/PublishingStrategy.cs @@ -1,27 +1,47 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; namespace Umbraco.Core.Publishing { - //TODO: Do we need this anymore?? + //TODO: Do we need this anymore?? - get rid of it! + /// /// Currently acts as an interconnection between the new public api and the legacy api for publishing /// - public class PublishingStrategy : BasePublishingStrategy + [EditorBrowsable(EditorBrowsableState.Never)] + public class PublishingStrategy : BasePublishingStrategy, IPublishingStrategy2 { + private readonly IScopeProvider _scopeProvider; private readonly IEventMessagesFactory _eventMessagesFactory; private readonly ILogger _logger; + [Obsolete("This class is not intended to be used, it will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public PublishingStrategy(IEventMessagesFactory eventMessagesFactory, ILogger logger) { if (eventMessagesFactory == null) throw new ArgumentNullException("eventMessagesFactory"); if (logger == null) throw new ArgumentNullException("logger"); + _scopeProvider = new ScopeProvider(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, logger)); + _eventMessagesFactory = eventMessagesFactory; + _logger = logger; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public PublishingStrategy(IScopeProvider scopeProvider, IEventMessagesFactory eventMessagesFactory, ILogger logger) + { + if (eventMessagesFactory == null) throw new ArgumentNullException("eventMessagesFactory"); + if (logger == null) throw new ArgumentNullException("logger"); + _scopeProvider = scopeProvider; _eventMessagesFactory = eventMessagesFactory; _logger = logger; } @@ -29,14 +49,14 @@ namespace Umbraco.Core.Publishing /// /// Publishes a single piece of Content /// + /// /// to publish /// Id of the User issueing the publish operation - internal Attempt PublishInternal(IContent content, int userId) + Attempt IPublishingStrategy2.Publish(IScopeUnitOfWork uow, IContent content, int userId) { var evtMsgs = _eventMessagesFactory.Get(); - if (Publishing.IsRaisedEventCancelled( - new PublishEventArgs(content, evtMsgs), this)) + if (uow.Events.DispatchCancelable(Publishing, this, new PublishEventArgs(content, evtMsgs), "Publishing")) { _logger.Info( string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", content.Name, content.Id)); @@ -87,12 +107,17 @@ namespace Umbraco.Core.Publishing /// True if the publish operation was successfull and not cancelled, otherwise false public override bool Publish(IContent content, int userId) { - return PublishInternal(content, userId).Success; + using (var uow = new ScopeUnitOfWork(_scopeProvider)) + { + uow.Commit(); + return ((IPublishingStrategy2)this).Publish(uow, content, userId).Success; + } } /// /// Publishes a list of content items /// + /// /// /// /// @@ -120,8 +145,8 @@ namespace Umbraco.Core.Publishing /// the user definitely wants to publish it even if it has never been published before. /// /// - internal IEnumerable> PublishWithChildrenInternal( - IEnumerable content, int userId, bool includeUnpublishedDocuments = true) + IEnumerable> IPublishingStrategy2.PublishWithChildren(IScopeUnitOfWork uow, + IEnumerable content, int userId, bool includeUnpublishedDocuments) { var statuses = new List>(); @@ -180,8 +205,7 @@ namespace Umbraco.Core.Publishing } //Fire Publishing event - if (Publishing.IsRaisedEventCancelled( - new PublishEventArgs(item, evtMsgs), this)) + if (uow.Events.DispatchCancelable(Publishing, this, new PublishEventArgs(item, evtMsgs), "Publishing")) { //the publishing has been cancelled. _logger.Info( @@ -280,13 +304,13 @@ namespace Umbraco.Core.Publishing // any document that fails to publish... var hasPublishedVersion = ApplicationContext.Current.Services.ContentService.HasPublishedVersion(content.Id); - if (hasPublishedVersion && !includeUnpublishedDocuments) + if (hasPublishedVersion && includeUnpublishedDocuments == false) { //it has a published version but our flag tells us to not include un-published documents and therefore we should // not be forcing decendant/child documents to be published if their parent fails. parentsIdsCancelled.Add(content.Id); } - else if (!hasPublishedVersion) + else if (hasPublishedVersion == false) { //it doesn't have a published version so we certainly cannot publish it's children. parentsIdsCancelled.Add(content.Id); @@ -301,15 +325,21 @@ namespace Umbraco.Core.Publishing /// True if the publish operation was successfull and not cancelled, otherwise false public override bool PublishWithChildren(IEnumerable content, int userId) { - var result = PublishWithChildrenInternal(content, userId); + using (var uow = new ScopeUnitOfWork(_scopeProvider)) + { + var result = ((IPublishingStrategy2)this).PublishWithChildren(uow, content, userId, true); - //NOTE: This previously always returned true so I've left it that way. It returned true because (from Morten)... - // ... if one item couldn't be published it wouldn't be correct to return false. - // in retrospect it should have returned a list of with Ids and Publish Status - // come to think of it ... the cache would still be updated for a failed item or at least tried updated. - // It would call the Published event for the entire list, but if the Published property isn't set to True it - // wouldn't actually update the cache for that item. But not really ideal nevertheless... - return true; + uow.Commit(); + + //NOTE: This previously always returned true so I've left it that way. It returned true because (from Morten)... + // ... if one item couldn't be published it wouldn't be correct to return false. + // in retrospect it should have returned a list of with Ids and Publish Status + // come to think of it ... the cache would still be updated for a failed item or at least tried updated. + // It would call the Published event for the entire list, but if the Published property isn't set to True it + // wouldn't actually update the cache for that item. But not really ideal nevertheless... + return true; + } + } /// @@ -320,21 +350,26 @@ namespace Umbraco.Core.Publishing /// True if the unpublish operation was successfull and not cancelled, otherwise false public override bool UnPublish(IContent content, int userId) { - return UnPublishInternal(content, userId).Success; + using (var uow = new ScopeUnitOfWork(_scopeProvider)) + { + uow.Commit(); + return ((IPublishingStrategy2)this).UnPublish(uow, content, userId).Success; + } } /// /// Unpublishes a list of Content /// + /// /// An enumerable list of /// Id of the User issueing the unpublish operation /// A list of publish statuses - private IEnumerable> UnPublishInternal(IEnumerable content, int userId) + private IEnumerable> UnPublishInternal(IScopeUnitOfWork uow, IEnumerable content, int userId) { - return content.Select(x => UnPublishInternal(x, userId)); + return content.Select(x => ((IPublishingStrategy2)this).UnPublish(uow, x, userId)); } - private Attempt UnPublishInternal(IContent content, int userId) + Attempt IPublishingStrategy2.UnPublish(IScopeUnitOfWork uow, IContent content, int userId) { // content should (is assumed to ) be the newest version, which may not be published // don't know how to test this, so it's not verified @@ -345,8 +380,7 @@ namespace Umbraco.Core.Publishing var evtMsgs = _eventMessagesFactory.Get(); //Fire UnPublishing event - if (UnPublishing.IsRaisedEventCancelled( - new PublishEventArgs(content, evtMsgs), this)) + if (uow.Events.DispatchCancelable(UnPublishing, this, new PublishEventArgs(content, evtMsgs), "UnPublishing")) { _logger.Info( string.Format("Content '{0}' with Id '{1}' will not be unpublished, the event was cancelled.", content.Name, content.Id)); @@ -383,15 +417,21 @@ namespace Umbraco.Core.Publishing /// True if the unpublish operation was successfull and not cancelled, otherwise false public override bool UnPublish(IEnumerable content, int userId) { - var result = UnPublishInternal(content, userId); + using (var uow = new ScopeUnitOfWork(_scopeProvider)) + { + var result = UnPublishInternal(uow, content, userId); + uow.Commit(); - //NOTE: This previously always returned true so I've left it that way. It returned true because (from Morten)... - // ... if one item couldn't be published it wouldn't be correct to return false. - // in retrospect it should have returned a list of with Ids and Publish Status - // come to think of it ... the cache would still be updated for a failed item or at least tried updated. - // It would call the Published event for the entire list, but if the Published property isn't set to True it - // wouldn't actually update the cache for that item. But not really ideal nevertheless... - return true; + //NOTE: This previously always returned true so I've left it that way. It returned true because (from Morten)... + // ... if one item couldn't be published it wouldn't be correct to return false. + // in retrospect it should have returned a list of with Ids and Publish Status + // come to think of it ... the cache would still be updated for a failed item or at least tried updated. + // It would call the Published event for the entire list, but if the Published property isn't set to True it + // wouldn't actually update the cache for that item. But not really ideal nevertheless... + return true; + } + + } /// @@ -404,9 +444,12 @@ namespace Umbraco.Core.Publishing /// thats being published public override void PublishingFinalized(IContent content) { - var evtMsgs = _eventMessagesFactory.Get(); - Published.RaiseEvent( - new PublishEventArgs(content, false, false, evtMsgs), this); + using (var uow = new ScopeUnitOfWork(_scopeProvider)) + { + ((IPublishingStrategy2) this).PublishingFinalized(uow, content); + uow.Commit(); + } + } /// @@ -416,10 +459,11 @@ namespace Umbraco.Core.Publishing /// Boolean indicating whether its all content that is republished public override void PublishingFinalized(IEnumerable content, bool isAllRepublished) { - var evtMsgs = _eventMessagesFactory.Get(); - Published.RaiseEvent( - new PublishEventArgs(content, false, isAllRepublished, evtMsgs), this); - + using (var uow = new ScopeUnitOfWork(_scopeProvider)) + { + ((IPublishingStrategy2)this).PublishingFinalized(uow, content, isAllRepublished); + uow.Commit(); + } } /// @@ -428,9 +472,11 @@ namespace Umbraco.Core.Publishing /// thats being unpublished public override void UnPublishingFinalized(IContent content) { - var evtMsgs = _eventMessagesFactory.Get(); - UnPublished.RaiseEvent( - new PublishEventArgs(content, false, false, evtMsgs), this); + using (var uow = new ScopeUnitOfWork(_scopeProvider)) + { + ((IPublishingStrategy2)this).UnPublishingFinalized(uow, content); + uow.Commit(); + } } /// @@ -439,31 +485,64 @@ namespace Umbraco.Core.Publishing /// An enumerable list of thats being unpublished public override void UnPublishingFinalized(IEnumerable content) { - var evtMsgs = _eventMessagesFactory.Get(); - UnPublished.RaiseEvent( - new PublishEventArgs(content, false, false, evtMsgs), this); + using (var uow = new ScopeUnitOfWork(_scopeProvider)) + { + ((IPublishingStrategy2)this).UnPublishingFinalized(uow, content); + uow.Commit(); + } } /// /// Occurs before publish /// + [Obsolete("Use events on the ContentService")] + [EditorBrowsable(EditorBrowsableState.Never)] public static event TypedEventHandler> Publishing; /// /// Occurs after publish /// + [Obsolete("Use events on the ContentService")] + [EditorBrowsable(EditorBrowsableState.Never)] public static event TypedEventHandler> Published; /// /// Occurs before unpublish /// + [Obsolete("Use events on the ContentService")] + [EditorBrowsable(EditorBrowsableState.Never)] public static event TypedEventHandler> UnPublishing; /// /// Occurs after unpublish /// - public static event TypedEventHandler> UnPublished; + [Obsolete("Use events on the ContentService")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static event TypedEventHandler> UnPublished; + + void IPublishingStrategy2.PublishingFinalized(IScopeUnitOfWork uow, IContent content) + { + var evtMsgs = _eventMessagesFactory.Get(); + uow.Events.Dispatch(Published, this, new PublishEventArgs(content, false, false, evtMsgs), "Published"); + } + void IPublishingStrategy2.PublishingFinalized(IScopeUnitOfWork uow, IEnumerable content, bool isAllRepublished) + { + var evtMsgs = _eventMessagesFactory.Get(); + uow.Events.Dispatch(Published, this, new PublishEventArgs(content, false, isAllRepublished, evtMsgs), "Published"); + } + void IPublishingStrategy2.UnPublishingFinalized(IScopeUnitOfWork uow, IContent content) + { + var evtMsgs = _eventMessagesFactory.Get(); + uow.Events.Dispatch(UnPublished, this, new PublishEventArgs(content, false, false, evtMsgs), "UnPublished"); + } + + void IPublishingStrategy2.UnPublishingFinalized(IScopeUnitOfWork uow, IEnumerable content) + { + var evtMsgs = _eventMessagesFactory.Get(); + uow.Events.Dispatch(UnPublished, this, new PublishEventArgs(content, false, false, evtMsgs), "UnPublished"); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs b/src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs new file mode 100644 index 0000000000..4c88e1c1b5 --- /dev/null +++ b/src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Core.Scoping +{ + public interface IInstanceIdentifiable + { + Guid InstanceId { get; } + } +} diff --git a/src/Umbraco.Core/Scoping/IScope.cs b/src/Umbraco.Core/Scoping/IScope.cs new file mode 100644 index 0000000000..4f178b80bc --- /dev/null +++ b/src/Umbraco.Core/Scoping/IScope.cs @@ -0,0 +1,45 @@ +using System; +using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Scoping +{ + /// + /// Represents a scope. + /// + public interface IScope : IDisposable, IInstanceIdentifiable + { + /// + /// Gets the scope database. + /// + UmbracoDatabase Database { get; } + + /// + /// Gets the scope event messages. + /// + EventMessages Messages { get; } + + /// + /// Gets the event manager + /// + IEventDispatcher Events { get; } + + /// + /// Gets the repository cache mode. + /// + RepositoryCacheMode RepositoryCacheMode { get; } + + /// + /// Gets the isolated cache. + /// + IsolatedRuntimeCache IsolatedRuntimeCache { get; } + + /// + /// Completes the scope. + /// + /// A value indicating whether the scope has been successfully completed. + /// Can return false if any child scope has not completed. + bool Complete(); + } +} diff --git a/src/Umbraco.Core/Scoping/IScopeInternal.cs b/src/Umbraco.Core/Scoping/IScopeInternal.cs new file mode 100644 index 0000000000..c1c28b41fe --- /dev/null +++ b/src/Umbraco.Core/Scoping/IScopeInternal.cs @@ -0,0 +1,18 @@ +using System.Data; +using Umbraco.Core.Events; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Scoping +{ + internal interface IScopeInternal : IScope + { + IScopeInternal ParentScope { get; } + bool CallContext { get; } + IsolationLevel IsolationLevel { get; } + UmbracoDatabase DatabaseOrNull { get; } + EventMessages MessagesOrNull { get; } + bool ScopedFileSystems { get; } + void ChildCompleted(bool? completed); + void Reset(); + } +} diff --git a/src/Umbraco.Core/Scoping/IScopeProvider.cs b/src/Umbraco.Core/Scoping/IScopeProvider.cs new file mode 100644 index 0000000000..754fb63aa7 --- /dev/null +++ b/src/Umbraco.Core/Scoping/IScopeProvider.cs @@ -0,0 +1,75 @@ +using System; +using System.Data; +using Umbraco.Core.Events; +#if DEBUG_SCOPES +using System.Collections.Generic; +#endif + +namespace Umbraco.Core.Scoping +{ + /// + /// Provides scopes. + /// + public interface IScopeProvider + { + /// + /// Creates an ambient scope. + /// + /// The created ambient scope. + /// + /// The created scope becomes the ambient scope. + /// If an ambient scope already exists, it becomes the parent of the created scope. + /// When the created scope is disposed, the parent scope becomes the ambient scope again. + /// + IScope CreateScope( + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, + IEventDispatcher eventDispatcher = null, + bool? scopeFileSystems = null, + bool callContext = false); + + /// + /// Creates a detached scope. + /// + /// A detached scope. + /// + /// A detached scope is not ambient and has no parent. + /// It is meant to be attached by . + /// + IScope CreateDetachedScope( + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, + IEventDispatcher eventDispatcher = null, + bool? scopeFileSystems = null); + + /// + /// Attaches a scope. + /// + /// The scope to attach. + /// A value indicating whether to force usage of call context. + /// + /// Only a scope created by can be attached. + /// + void AttachScope(IScope scope, bool callContext = false); + + /// + /// Detaches a scope. + /// + /// The detached scope. + /// + /// Only a scope previously attached by can be detached. + /// + IScope DetachScope(); + + /// + /// Gets the scope context. + /// + ScopeContext Context { get; } + +#if DEBUG_SCOPES + Dictionary CallContextObjects { get; } + IEnumerable ScopeInfos { get; } + ScopeInfo GetScopeInfo(IScope scope); +#endif + } +} diff --git a/src/Umbraco.Core/Scoping/IScopeProviderInternal.cs b/src/Umbraco.Core/Scoping/IScopeProviderInternal.cs new file mode 100644 index 0000000000..0cd03117eb --- /dev/null +++ b/src/Umbraco.Core/Scoping/IScopeProviderInternal.cs @@ -0,0 +1,31 @@ +namespace Umbraco.Core.Scoping +{ + /// + /// Provides scopes. + /// + /// Extends with internal features. + internal interface IScopeProviderInternal : IScopeProvider + { + /// + /// Gets the ambient context. + /// + ScopeContext AmbientContext { get; } + + /// + /// Gets the ambient scope. + /// + IScopeInternal AmbientScope { get; } + + /// + /// Gets the ambient scope if any, else creates and returns a . + /// + IScopeInternal GetAmbientOrNoScope(); + + /// + /// Resets the ambient scope. + /// + /// Resets the ambient scope (not completed anymore) and disposes the + /// entire scopes chain until there is no more scopes. + void Reset(); + } +} diff --git a/src/Umbraco.Core/Scoping/NoScope.cs b/src/Umbraco.Core/Scoping/NoScope.cs new file mode 100644 index 0000000000..a21815173c --- /dev/null +++ b/src/Umbraco.Core/Scoping/NoScope.cs @@ -0,0 +1,147 @@ +using System; +using System.Data; +using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Scoping +{ + /// + /// Implements when there is no scope. + /// + internal class NoScope : IScopeInternal + { + private readonly ScopeProvider _scopeProvider; + private bool _disposed; + + private UmbracoDatabase _database; + private EventMessages _messages; + + public NoScope(ScopeProvider scopeProvider) + { + _scopeProvider = scopeProvider; +#if DEBUG_SCOPES + _scopeProvider.RegisterScope(this); +#endif + } + + private readonly Guid _instanceId = Guid.NewGuid(); + public Guid InstanceId { get { return _instanceId; } } + + /// + public bool CallContext { get { return false; } } + + /// + public RepositoryCacheMode RepositoryCacheMode + { + get { return RepositoryCacheMode.Default; } + } + + /// + public IsolatedRuntimeCache IsolatedRuntimeCache { get { throw new NotSupportedException(); } } + + /// + public UmbracoDatabase Database + { + get + { + EnsureNotDisposed(); + return _database ?? (_database = _scopeProvider.DatabaseFactory.CreateNewDatabase()); + } + } + + public UmbracoDatabase DatabaseOrNull + { + get + { + EnsureNotDisposed(); + return _database; + } + } + + /// + public EventMessages Messages + { + get + { + EnsureNotDisposed(); + if (_messages != null) return _messages; + + // see comments in Scope + + var factory = ScopeLifespanMessagesFactory.Current; + if (factory == null) + { + _messages = new EventMessages(); + } + else + { + _messages = factory.GetFromHttpContext(); + if (_messages == null) + factory.Set(_messages = new EventMessages()); + } + + return _messages; + } + } + + public EventMessages MessagesOrNull + { + get + { + EnsureNotDisposed(); + + // see comments in Scope + + if (_messages != null) return _messages; + + var factory = ScopeLifespanMessagesFactory.Current; + return factory == null ? null : factory.GetFromHttpContext(); + } + } + + /// + public IEventDispatcher Events + { + get { throw new NotSupportedException(); } + } + + /// + public bool Complete() + { + throw new NotSupportedException(); + } + + private void EnsureNotDisposed() + { + if (_disposed) + throw new ObjectDisposedException("this"); + } + + public void Dispose() + { + EnsureNotDisposed(); + + if (this != _scopeProvider.AmbientScope) + throw new InvalidOperationException("Not the ambient scope."); + +#if DEBUG_SCOPES + _scopeProvider.Disposed(this); +#endif + + if (_database != null) + _database.Dispose(); + + _scopeProvider.SetAmbient(null); + + _disposed = true; + GC.SuppressFinalize(this); + } + + public IScopeInternal ParentScope { get { return null; } } + public IsolationLevel IsolationLevel { get {return IsolationLevel.Unspecified; } } + public bool ScopedFileSystems { get { return false; } } + public void ChildCompleted(bool? completed) { } + public void Reset() { } + } +} diff --git a/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs b/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs new file mode 100644 index 0000000000..2b25f2eb59 --- /dev/null +++ b/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Core.Scoping +{ + public enum RepositoryCacheMode + { + // ? + Unspecified = 0, + + // the default, full L2 cache + Default = 1, + + // a scoped cache + // reads from and writes to a local cache + // clears the global cache on completion + Scoped = 2 + } +} diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs new file mode 100644 index 0000000000..8abbecacbe --- /dev/null +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -0,0 +1,497 @@ +using System; +using System.Data; +using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Scoping +{ + /// + /// Implements . + /// + /// Not thread-safe obviously. + internal class Scope : IScopeInternal + { + private readonly ScopeProvider _scopeProvider; + private readonly IsolationLevel _isolationLevel; + private readonly RepositoryCacheMode _repositoryCacheMode; + private readonly bool? _scopeFileSystem; + private readonly ScopeContext _scopeContext; + private bool _callContext; + private bool _disposed; + private bool? _completed; + + private IsolatedRuntimeCache _isolatedRuntimeCache; + private UmbracoDatabase _database; + private EventMessages _messages; + private ICompletable _fscope; + private IEventDispatcher _eventDispatcher; + + // this is v7, in v8 this has to change to RepeatableRead + private const IsolationLevel DefaultIsolationLevel = IsolationLevel.ReadCommitted; + + // initializes a new scope + private Scope(ScopeProvider scopeProvider, + Scope parent, ScopeContext scopeContext, bool detachable, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, + IEventDispatcher eventDispatcher = null, + bool? scopeFileSystems = null, + bool callContext = false) + { + _scopeProvider = scopeProvider; + _scopeContext = scopeContext; + _isolationLevel = isolationLevel; + _repositoryCacheMode = repositoryCacheMode; + _eventDispatcher = eventDispatcher; + _scopeFileSystem = scopeFileSystems; + _callContext = callContext; + Detachable = detachable; + +#if DEBUG_SCOPES + _scopeProvider.RegisterScope(this); + Console.WriteLine("create " + _instanceId.ToString("N").Substring(0, 8)); +#endif + + if (detachable) + { + if (parent != null) throw new ArgumentException("Cannot set parent on detachable scope.", "parent"); + if (scopeContext != null) throw new ArgumentException("Cannot set context on detachable scope.", "scopeContext"); + + // detachable creates its own scope context + _scopeContext = new ScopeContext(); + + // see note below + if (scopeFileSystems == true) + _fscope = FileSystemProviderManager.Current.Shadow(Guid.NewGuid()); + + return; + } + + if (parent != null) + { + ParentScope = parent; + + // cannot specify a different mode! + if (repositoryCacheMode != RepositoryCacheMode.Unspecified && parent.RepositoryCacheMode != repositoryCacheMode) + throw new ArgumentException("Cannot be different from parent.", "repositoryCacheMode"); + + // cannot specify a dispatcher! + if (_eventDispatcher != null) + throw new ArgumentException("Cannot be specified on nested scope.", "eventDispatcher"); + + // cannot specify a different fs scope! + if (scopeFileSystems != null && parent._scopeFileSystem != scopeFileSystems) + throw new ArgumentException("Cannot be different from parent.", "scopeFileSystems"); + } + else + { + // the FS scope cannot be "on demand" like the rest, because we would need to hook into + // every scoped FS to trigger the creation of shadow FS "on demand", and that would be + // pretty pointless since if scopeFileSystems is true, we *know* we want to shadow + if (scopeFileSystems == true) + _fscope = FileSystemProviderManager.Current.Shadow(Guid.NewGuid()); + } + } + + // initializes a new scope + public Scope(ScopeProvider scopeProvider, bool detachable, + ScopeContext scopeContext, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, + IEventDispatcher eventDispatcher = null, + bool? scopeFileSystems = null, + bool callContext = false) + : this(scopeProvider, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext) + { } + + // initializes a new scope in a nested scopes chain, with its parent + public Scope(ScopeProvider scopeProvider, Scope parent, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, + IEventDispatcher eventDispatcher = null, + bool? scopeFileSystems = null, + bool callContext = false) + : this(scopeProvider, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext) + { } + + // initializes a new scope, replacing a NoScope instance + public Scope(ScopeProvider scopeProvider, NoScope noScope, + ScopeContext scopeContext, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, + IEventDispatcher eventDispatcher = null, + bool? scopeFileSystems = null, + bool callContext = false) + : this(scopeProvider, null, scopeContext, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext) + { + // steal everything from NoScope + _database = noScope.DatabaseOrNull; + _messages = noScope.MessagesOrNull; + + // make sure the NoScope can be replaced ie not in a transaction + if (_database != null && _database.InTransaction) + throw new Exception("NoScope instance is not free."); + } + + private readonly Guid _instanceId = Guid.NewGuid(); + public Guid InstanceId { get { return _instanceId; } } + + // a value indicating whether to force call-context + public bool CallContext + { + get + { + if (_callContext) return true; + if (ParentScope != null) return ParentScope.CallContext; + return false; + } + set { _callContext = value; } + } + + public bool ScopedFileSystems + { + get + { + if (ParentScope != null) return ParentScope.ScopedFileSystems; + return _fscope != null; + } + } + + /// + public RepositoryCacheMode RepositoryCacheMode + { + get + { + if (_repositoryCacheMode != RepositoryCacheMode.Unspecified) return _repositoryCacheMode; + if (ParentScope != null) return ParentScope.RepositoryCacheMode; + return RepositoryCacheMode.Default; + } + } + + /// + public IsolatedRuntimeCache IsolatedRuntimeCache + { + get + { + if (ParentScope != null) return ParentScope.IsolatedRuntimeCache; + + return _isolatedRuntimeCache ?? (_isolatedRuntimeCache + = new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); + } + } + + // a value indicating whether the scope is detachable + // ie whether it was created by CreateDetachedScope + public bool Detachable { get; private set; } + + // the parent scope (in a nested scopes chain) + public IScopeInternal ParentScope { get; set; } + + public bool Attached { get; set; } + + // the original scope (when attaching a detachable scope) + public IScopeInternal OrigScope { get; set; } + + // the original context (when attaching a detachable scope) + public ScopeContext OrigContext { get; set; } + + // the context (for attaching & detaching only) + public ScopeContext Context + { + get { return _scopeContext; } + } + + public IsolationLevel IsolationLevel + { + get + { + if (_isolationLevel != IsolationLevel.Unspecified) return _isolationLevel; + if (ParentScope != null) return ParentScope.IsolationLevel; + return DefaultIsolationLevel; + } + } + + /// + public UmbracoDatabase Database + { + get + { + EnsureNotDisposed(); + if (ParentScope != null) + { + var database = ParentScope.Database; + if (_isolationLevel > IsolationLevel.Unspecified && database.CurrentTransactionIsolationLevel < _isolationLevel) + throw new Exception("Scope requires isolation level " + _isolationLevel + ", but got " + database.CurrentTransactionIsolationLevel + " from parent."); + _database = database; + } + + if (_database != null) + { + // if the database has been created by a Scope instance it has to be + // in a transaction, however it can be a database that was stolen from + // a NoScope instance, in which case we need to enter a transaction, as + // a scope implies a transaction, always + if (_database.InTransaction) + return _database; + } + else + { + // create a new database + _database = _scopeProvider.DatabaseFactory.CreateNewDatabase(); + } + + // enter a transaction, as a scope implies a transaction, always + try + { + _database.BeginTransaction(IsolationLevel); + return _database; + } + catch + { + _database.Dispose(); + _database = null; + throw; + } + } + } + + public UmbracoDatabase DatabaseOrNull + { + get + { + EnsureNotDisposed(); + return ParentScope == null ? _database : ParentScope.DatabaseOrNull; + } + } + + /// + public EventMessages Messages + { + get + { + EnsureNotDisposed(); + if (ParentScope != null) return ParentScope.Messages; + + if (_messages != null) return _messages; + + // this is ugly - in v7 for backward compatibility reasons, EventMessages need + // to survive way longer that the scopes, and kinda resides on its own in http + // context, but must also be in scopes for when we do async and lose http context + // TODO refactor in v8 + + var factory = ScopeLifespanMessagesFactory.Current; + if (factory == null) + { + _messages = new EventMessages(); + } + else + { + _messages = factory.GetFromHttpContext(); + if (_messages == null) + factory.Set(_messages = new EventMessages()); + } + + return _messages; + } + } + + public EventMessages MessagesOrNull + { + get + { + EnsureNotDisposed(); + if (ParentScope != null) return ParentScope.MessagesOrNull; + + // see comments in Messages + + if (_messages != null) return _messages; + + var factory = ScopeLifespanMessagesFactory.Current; + return factory == null ? null : factory.GetFromHttpContext(); + } + } + + /// + public IEventDispatcher Events + { + get + { + EnsureNotDisposed(); + if (ParentScope != null) return ParentScope.Events; + return _eventDispatcher ?? (_eventDispatcher = new ScopeEventDispatcher()); + } + } + + /// + public bool Complete() + { + if (_completed.HasValue == false) + _completed = true; + return _completed.Value; + } + + public void Reset() + { + _completed = null; + } + + public void ChildCompleted(bool? completed) + { + // if child did not complete we cannot complete + if (completed.HasValue == false || completed.Value == false) + _completed = false; + } + + private void EnsureNotDisposed() + { + if (_disposed) + throw new ObjectDisposedException("this"); + } + + public void Dispose() + { + EnsureNotDisposed(); + + if (this != _scopeProvider.AmbientScope) + { +#if DEBUG_SCOPES + var ambient = _scopeProvider.AmbientScope; + Logging.LogHelper.Debug("Dispose error (" + (ambient == null ? "no" : "other") + " ambient)"); + if (ambient == null) + throw new InvalidOperationException("Not the ambient scope (no ambient scope)."); + var infos = _scopeProvider.GetScopeInfo(ambient); + throw new InvalidOperationException("Not the ambient scope (see current ambient ctor stack trace).\r\n" + infos.CtorStack); +#else + throw new InvalidOperationException("Not the ambient scope."); +#endif + } + + var parent = ParentScope; + _scopeProvider.AmbientScope = parent; + +#if DEBUG_SCOPES + _scopeProvider.Disposed(this); +#endif + + if (parent != null) + parent.ChildCompleted(_completed); + else + DisposeLastScope(); + + _disposed = true; + GC.SuppressFinalize(this); + } + + private void DisposeLastScope() + { + // figure out completed + var completed = _completed.HasValue && _completed.Value; + + // deal with database + var databaseException = false; + if (_database != null) + { + try + { + if (completed) + _database.CompleteTransaction(); + else + _database.AbortTransaction(); + } + catch + { + databaseException = true; + throw; + } + finally + { + _database.Dispose(); + _database = null; + + if (databaseException) + RobustExit(false, true); + } + } + + RobustExit(completed, false); + } + + // this chains some try/finally blocks to + // - complete and dispose the scoped filesystems + // - deal with events if appropriate + // - remove the scope context if it belongs to this scope + // - deal with detachable scopes + // here, + // - completed indicates whether the scope has been completed + // can be true or false, but in both cases the scope is exiting + // in a normal way + // - onException indicates whether completing/aborting the database + // transaction threw an exception, in which case 'completed' has + // to be false + events don't trigger and we just to some cleanup + // to ensure we don't leave a scope around, etc + private void RobustExit(bool completed, bool onException) + { + if (onException) completed = false; + + TryFinally(() => + { + if (_scopeFileSystem == true) + { + if (completed) + _fscope.Complete(); + _fscope.Dispose(); + _fscope = null; + } + }, () => + { + // deal with events + if (onException == false && _eventDispatcher != null) + _eventDispatcher.ScopeExit(completed); + }, () => + { + // if *we* created it, then get rid of it + if (_scopeProvider.AmbientContext == _scopeContext) + { + try + { + _scopeProvider.AmbientContext.ScopeExit(completed); + } + finally + { + _scopeProvider.SetAmbient(null); + } + } + }, () => + { + if (Detachable) + { + // get out of the way, restore original + _scopeProvider.SetAmbient(OrigScope, OrigContext); + Attached = false; + OrigScope = null; + OrigContext = null; + } + }); + } + + private static void TryFinally(params Action[] actions) + { + TryFinally(0, actions); + } + + private static void TryFinally(int index, Action[] actions) + { + if (index == actions.Length) return; + try + { + actions[index](); + } + finally + { + TryFinally(index + 1, actions); + } + } + } +} diff --git a/src/Umbraco.Core/Scoping/ScopeContext.cs b/src/Umbraco.Core/Scoping/ScopeContext.cs new file mode 100644 index 0000000000..cca0be560d --- /dev/null +++ b/src/Umbraco.Core/Scoping/ScopeContext.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Scoping +{ + public class ScopeContext : IInstanceIdentifiable + { + private Dictionary _enlisted; + + public void ScopeExit(bool completed) + { + List exceptions = null; + foreach (var enlisted in Enlisted.Values) + { + try + { + enlisted.Execute(completed); + } + catch (Exception e) + { + if (exceptions == null) + exceptions = new List(); + exceptions.Add(e); + } + } + if (exceptions != null) + throw new AggregateException("Exceptions were thrown by listed actions.", exceptions); + } + + private readonly Guid _instanceId = Guid.NewGuid(); + public Guid InstanceId { get { return _instanceId; } } + + private IDictionary Enlisted + { + get + { + return _enlisted ?? (_enlisted + = new Dictionary()); + } + } + + private interface IEnlistedObject + { + void Execute(bool completed); + } + + private class EnlistedObject : IEnlistedObject + { + private readonly Action _action; + + public EnlistedObject(T item) + { + Item = item; + } + + public EnlistedObject(T item, Action action) + { + Item = item; + _action = action; + } + + public T Item { get; private set; } + + public void Execute(bool completed) + { + _action(completed, Item); + } + } + + /// + public T Enlist(string key, Func creator) + { + IEnlistedObject enlisted; + if (Enlisted.TryGetValue(key, out enlisted)) + { + var enlistedAs = enlisted as EnlistedObject; + if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key."); + return enlistedAs.Item; + } + var enlistedOfT = new EnlistedObject(creator()); + Enlisted[key] = enlistedOfT; + return enlistedOfT.Item; + } + + /// + public T Enlist(string key, Func creator, Action action) + { + IEnlistedObject enlisted; + if (Enlisted.TryGetValue(key, out enlisted)) + { + var enlistedAs = enlisted as EnlistedObject; + if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key."); + return enlistedAs.Item; + } + var enlistedOfT = new EnlistedObject(creator(), action); + Enlisted[key] = enlistedOfT; + return enlistedOfT.Item; + } + + /// + public void Enlist(string key, Action action) + { + IEnlistedObject enlisted; + if (Enlisted.TryGetValue(key, out enlisted)) + { + var enlistedAs = enlisted as EnlistedObject; + if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key."); + return; + } + var enlistedOfT = new EnlistedObject(null, (completed, item) => action(completed)); + Enlisted[key] = enlistedOfT; + } + } +} diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs new file mode 100644 index 0000000000..501f8f7faa --- /dev/null +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -0,0 +1,609 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Runtime.Remoting.Messaging; +using System.Text; +using System.Web; +using Umbraco.Core.Events; +using Umbraco.Core.Persistence; +#if DEBUG_SCOPES +using System.Linq; +#endif + +namespace Umbraco.Core.Scoping +{ + /// + /// Implements . + /// + internal class ScopeProvider : IScopeProviderInternal + { + public ScopeProvider(IDatabaseFactory2 databaseFactory) + { + DatabaseFactory = databaseFactory; + } + + static ScopeProvider() + { + SafeCallContext.Register( + () => + { + var scope = GetCallContextObject(ScopeItemKey); + var context = GetCallContextObject(ContextItemKey); + SetCallContextObject(ScopeItemKey, null); + SetCallContextObject(ContextItemKey, null); + return Tuple.Create(scope, context); + }, + o => + { + // cannot re-attached over leaked scope/context + // except of course over NoScope (which leaks) + var ambientScope = GetCallContextObject(ScopeItemKey); + if (ambientScope != null) + { + var ambientNoScope = ambientScope as NoScope; + if (ambientNoScope == null) + throw new Exception("Found leaked scope when restoring call context."); + + // this should rollback any pending transaction + ambientNoScope.Dispose(); + } + if (GetCallContextObject(ContextItemKey) != null) + throw new Exception("Found leaked context when restoring call context."); + + var t = (Tuple) o; + SetCallContextObject(ScopeItemKey, t.Item1); + SetCallContextObject(ContextItemKey, t.Item2); + }); + } + + public IDatabaseFactory2 DatabaseFactory { get; private set; } + + #region Context + + // objects that go into the logical call context better be serializable else they'll eventually + // cause issues whenever some cross-AppDomain code executes - could be due to ReSharper running + // tests, any other things (see https://msdn.microsoft.com/en-us/library/dn458353(v=vs.110).aspx), + // but we don't want to make all of our objects serializable since they are *not* meant to be + // used in cross-AppDomain scenario anyways. + // in addition, whatever goes into the logical call context is serialized back and forth any + // time cross-AppDomain code executes, so if we put an "object" there, we'll can *another* + // "object" instance - and so we cannot use a random object as a key. + // so what we do is: we register a guid in the call context, and we keep a table mapping those + // guids to the actual objects. the guid serializes back and forth without causing any issue, + // and we can retrieve the actual objects from the table. + // only issue: how are we supposed to clear the table? we can't, really. objects should take + // care of de-registering themselves from context. + // everything we use does, except the NoScope scope, which just stays there + // + // during tests, NoScope can to into call context... nothing much we can do about it + + private static readonly object StaticCallContextObjectsLock = new object(); + private static readonly Dictionary StaticCallContextObjects + = new Dictionary(); + +#if DEBUG_SCOPES + public Dictionary CallContextObjects + { + get + { + lock (StaticCallContextObjectsLock) + { + // capture in a dictionary + return StaticCallContextObjects.ToDictionary(x => x.Key, x => x.Value); + } + } + } +#endif + + private static T GetCallContextObject(string key) + where T : class + { + var objectKey = CallContext.LogicalGetData(key).AsGuid(); + if (objectKey == Guid.Empty) return null; + + lock (StaticCallContextObjectsLock) + { + object callContextObject; + if (StaticCallContextObjects.TryGetValue(objectKey, out callContextObject)) + { +#if DEBUG_SCOPES + Logging.LogHelper.Debug("Got " + typeof(T).Name + " Object " + objectKey.ToString("N").Substring(0, 8)); + //Logging.LogHelper.Debug("At:\r\n" + Head(Environment.StackTrace, 24)); +#endif + return (T) callContextObject; + } + + Logging.LogHelper.Warn("Missed " + typeof(T).Name + " Object " + objectKey.ToString("N").Substring(0, 8)); +#if DEBUG_SCOPES + //Logging.LogHelper.Debug("At:\r\n" + Head(Environment.StackTrace, 24)); +#endif + return null; + } + } + + private static void SetCallContextObject(string key, IInstanceIdentifiable value) + { +#if DEBUG_SCOPES + // manage the 'context' that contains the scope (null, "http" or "call") + // only for scopes of course! + if (key == ScopeItemKey) + { + // first, null-register the existing value + var ambientKey = CallContext.LogicalGetData(ScopeItemKey).AsGuid(); + object o = null; + lock (StaticCallContextObjectsLock) + { + if (ambientKey != default(Guid)) + StaticCallContextObjects.TryGetValue(ambientKey, out o); + } + var ambientScope = o as IScope; + if (ambientScope != null) RegisterContext(ambientScope, null); + // then register the new value + var scope = value as IScope; + if (scope != null) RegisterContext(scope, "call"); + } +#endif + if (value == null) + { + var objectKey = CallContext.LogicalGetData(key).AsGuid(); + CallContext.FreeNamedDataSlot(key); + if (objectKey == default (Guid)) return; + lock (StaticCallContextObjectsLock) + { +#if DEBUG_SCOPES + Logging.LogHelper.Debug("Remove Object " + objectKey.ToString("N").Substring(0, 8)); + //Logging.LogHelper.Debug("At:\r\n" + Head(Environment.StackTrace, 24)); +#endif + StaticCallContextObjects.Remove(objectKey); + } + } + else + { + // note - we are *not* detecting an already-existing value + // because our code in this class *always* sets to null before + // setting to a real value + var objectKey = value.InstanceId; + lock (StaticCallContextObjectsLock) + { +#if DEBUG_SCOPES + Logging.LogHelper.Debug("AddObject " + objectKey.ToString("N").Substring(0, 8)); + //Logging.LogHelper.Debug("At:\r\n" + Head(Environment.StackTrace, 24)); +#endif + StaticCallContextObjects.Add(objectKey, value); + } + CallContext.LogicalSetData(key, objectKey); + } + } + + internal static Func HttpContextItemsGetter { get; set; } + + private static IDictionary HttpContextItems + { + get + { + return HttpContextItemsGetter == null + ? (HttpContext.Current == null ? null : HttpContext.Current.Items) + : HttpContextItemsGetter(); + } + } + + public static T GetHttpContextObject(string key, bool required = true) + where T : class + { + var httpContextItems = HttpContextItems; + if (httpContextItems != null) + return (T)httpContextItems[key]; + if (required) + throw new Exception("HttpContext.Current is null."); + return null; + } + + private static bool SetHttpContextObject(string key, object value, bool required = true) + { + var httpContextItems = HttpContextItems; + if (httpContextItems == null) + { + if (required) + throw new Exception("HttpContext.Current is null."); + return false; + } +#if DEBUG_SCOPES + // manage the 'context' that contains the scope (null, "http" or "call") + // only for scopes of course! + if (key == ScopeItemKey) + { + // first, null-register the existing value + var ambientScope = (IScope)httpContextItems[ScopeItemKey]; + if (ambientScope != null) RegisterContext(ambientScope, null); + // then register the new value + var scope = value as IScope; + if (scope != null) RegisterContext(scope, "http"); + } +#endif + if (value == null) + httpContextItems.Remove(key); + else + httpContextItems[key] = value; + return true; + } + +#endregion + + #region Ambient Context + + internal const string ContextItemKey = "Umbraco.Core.Scoping.ScopeContext"; + + internal static ScopeContext AmbientContextInternal + { + get + { + // try http context, fallback onto call context + var value = GetHttpContextObject(ContextItemKey, false); + return value ?? GetCallContextObject(ContextItemKey); + } + set + { + // clear both + SetHttpContextObject(ContextItemKey, null, false); + SetCallContextObject(ContextItemKey, null); + if (value == null) return; + + // set http/call context + if (SetHttpContextObject(ContextItemKey, value, false) == false) + SetCallContextObject(ContextItemKey, value); + } + } + + /// + public ScopeContext AmbientContext + { + get { return AmbientContextInternal; } + } + + #endregion + + #region Ambient Scope + + internal const string ScopeItemKey = "Umbraco.Core.Scoping.Scope"; + internal const string ScopeRefItemKey = "Umbraco.Core.Scoping.ScopeReference"; + + // only 1 instance which can be disposed and disposed again + private static readonly ScopeReference StaticScopeReference = new ScopeReference(new ScopeProvider(null)); + + internal static IScopeInternal AmbientScopeInternal + { + get + { + // try http context, fallback onto call context + var value = GetHttpContextObject(ScopeItemKey, false); + return value ?? GetCallContextObject(ScopeItemKey); + } + set + { + // clear both + SetHttpContextObject(ScopeItemKey, null, false); + SetHttpContextObject(ScopeRefItemKey, null, false); + SetCallContextObject(ScopeItemKey, null); + if (value == null) return; + + // set http/call context + if (value.CallContext == false && SetHttpContextObject(ScopeItemKey, value, false)) + SetHttpContextObject(ScopeRefItemKey, StaticScopeReference); + else + SetCallContextObject(ScopeItemKey, value); + } + } + + /// + public IScopeInternal AmbientScope + { + get { return AmbientScopeInternal; } + internal set { AmbientScopeInternal = value; } + } + + /// + public IScopeInternal GetAmbientOrNoScope() + { + return AmbientScope ?? (AmbientScope = new NoScope(this)); + } + + #endregion + + public void SetAmbient(IScopeInternal scope, ScopeContext context = null) + { + // clear all + SetHttpContextObject(ScopeItemKey, null, false); + SetHttpContextObject(ScopeRefItemKey, null, false); + SetCallContextObject(ScopeItemKey, null); + SetHttpContextObject(ContextItemKey, null, false); + SetCallContextObject(ContextItemKey, null); + if (scope == null) + { + if (context != null) + throw new ArgumentException("Must be null if scope is null.", "context"); + return; + } + + if (scope.CallContext == false && SetHttpContextObject(ScopeItemKey, scope, false)) + { + SetHttpContextObject(ScopeRefItemKey, StaticScopeReference); + SetHttpContextObject(ContextItemKey, context); + } + else + { + SetCallContextObject(ScopeItemKey, scope); + SetCallContextObject(ContextItemKey, context); + } + } + + /// + public IScope CreateDetachedScope( + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, + IEventDispatcher eventDispatcher = null, + bool? scopeFileSystems = null) + { + return new Scope(this, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); + } + + /// + public void AttachScope(IScope other, bool callContext = false) + { + var otherScope = other as Scope; + if (otherScope == null) + throw new ArgumentException("Not a Scope instance."); + + if (otherScope.Detachable == false) + throw new ArgumentException("Not a detachable scope."); + + if (otherScope.Attached) + throw new InvalidOperationException("Already attached."); + + otherScope.Attached = true; + otherScope.OrigScope = AmbientScope; + otherScope.OrigContext = AmbientContext; + + otherScope.CallContext = callContext; + SetAmbient(otherScope, otherScope.Context); + } + + /// + public IScope DetachScope() + { + var ambient = AmbientScope; + if (ambient == null) + throw new InvalidOperationException("There is no ambient scope."); + + var noScope = ambient as NoScope; + if (noScope != null) + throw new InvalidOperationException("Cannot detach NoScope."); + + var scope = ambient as Scope; + if (scope == null) + throw new Exception("Ambient scope is not a Scope instance."); + + if (scope.Detachable == false) + throw new InvalidOperationException("Ambient scope is not detachable."); + + SetAmbient(scope.OrigScope, scope.OrigContext); + scope.OrigScope = null; + scope.OrigContext = null; + scope.Attached = false; + return scope; + } + + /// + public IScope CreateScope( + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, + IEventDispatcher eventDispatcher = null, + bool? scopeFileSystems = null, + bool callContext = false) + { + var ambient = AmbientScope; + if (ambient == null) + { + var ambientContext = AmbientContext; + var newContext = ambientContext == null ? new ScopeContext() : null; + var scope = new Scope(this, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext); + // assign only if scope creation did not throw! + SetAmbient(scope, newContext ?? ambientContext); + return scope; + } + + // replace noScope with a real one + var noScope = ambient as NoScope; + if (noScope != null) + { +#if DEBUG_SCOPES + Disposed(noScope); +#endif + // peta poco nulls the shared connection after each command unless there's a trx + var database = noScope.DatabaseOrNull; + if (database != null && database.InTransaction) + throw new Exception("NoScope is in a transaction."); + var ambientContext = AmbientContext; + var newContext = ambientContext == null ? new ScopeContext() : null; + var scope = new Scope(this, noScope, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext); + // assign only if scope creation did not throw! + SetAmbient(scope, newContext ?? ambientContext); + return scope; + } + + var ambientScope = ambient as Scope; + if (ambientScope == null) throw new Exception("Ambient scope is not a Scope instance."); + + var nested = new Scope(this, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext); + SetAmbient(nested, AmbientContext); + return nested; + } + + /// + public void Reset() + { + var scope = AmbientScope as Scope; + if (scope != null) + scope.Reset(); + + StaticScopeReference.Dispose(); + } + + /// + public ScopeContext Context + { + get { return AmbientContext; } + } + +#if DEBUG_SCOPES + // this code needs TLC + // + // the idea here is to keep in a list all the scopes that have been created, and to remove them + // when they are disposed, so we can track leaks, ie scopes that would not be properly taken + // care of by our code + // + // note: the code could probably be optimized... but this is NOT supposed to go into any real + // live build, either production or debug - it's just a debugging tool for the time being + + // helps identifying when non-httpContext scopes are created by logging the stack trace + //private void LogCallContextStack() + //{ + // var trace = Environment.StackTrace; + // if (trace.IndexOf("ScheduledPublishing") > 0) + // LogHelper.Debug("CallContext: Scheduled Publishing"); + // else if (trace.IndexOf("TouchServerTask") > 0) + // LogHelper.Debug("CallContext: Server Registration"); + // else if (trace.IndexOf("LogScrubber") > 0) + // LogHelper.Debug("CallContext: Log Scrubber"); + // else + // LogHelper.Debug("CallContext: " + Environment.StackTrace); + //} + + // all scope instances that are currently beeing tracked + private static readonly object StaticScopeInfosLock = new object(); + private static readonly Dictionary StaticScopeInfos = new Dictionary(); + + public IEnumerable ScopeInfos + { + get + { + lock (StaticScopeInfosLock) + { + return StaticScopeInfos.Values.ToArray(); // capture in an array + } + } + } + + public ScopeInfo GetScopeInfo(IScope scope) + { + lock (StaticScopeInfosLock) + { + ScopeInfo scopeInfo; + return StaticScopeInfos.TryGetValue(scope, out scopeInfo) ? scopeInfo : null; + } + } + + //private static void Log(string message, UmbracoDatabase database) + //{ + // LogHelper.Debug(message + " (" + (database == null ? "" : database.InstanceSid) + ")."); + //} + + // register a scope and capture its ctor stacktrace + public void RegisterScope(IScope scope) + { + lock (StaticScopeInfosLock) + { + if (StaticScopeInfos.ContainsKey(scope)) throw new Exception("oops: already registered."); + Logging.LogHelper.Debug("Register " + scope.InstanceId.ToString("N").Substring(0, 8)); + StaticScopeInfos[scope] = new ScopeInfo(scope, Environment.StackTrace); + } + } + + // register that a scope is in a 'context' + // 'context' that contains the scope (null, "http" or "call") + public static void RegisterContext(IScope scope, string context) + { + lock (StaticScopeInfosLock) + { + ScopeInfo info; + if (StaticScopeInfos.TryGetValue(scope, out info) == false) info = null; + if (info == null) + { + if (context == null) return; + throw new Exception("oops: unregistered scope."); + } + var sb = new StringBuilder(); + var s = scope; + while (s != null) + { + if (sb.Length > 0) sb.Append(" < "); + sb.Append(s.InstanceId.ToString("N").Substring(0, 8)); + var ss = s as IScopeInternal; + s = ss == null ? null : ss.ParentScope; + } + Logging.LogHelper.Debug("Register " + (context ?? "null") + " context " + sb); + if (context == null) info.NullStack = Environment.StackTrace; + //Logging.LogHelper.Debug("At:\r\n" + Head(Environment.StackTrace, 16)); + info.Context = context; + } + } + + private static string Head(string s, int count) + { + var pos = 0; + var i = 0; + while (i < count && pos >= 0) + { + pos = s.IndexOf("\r\n", pos + 1, StringComparison.OrdinalIgnoreCase); + i++; + } + if (pos < 0) return s; + return s.Substring(0, pos); + } + + public void Disposed(IScope scope) + { + lock (StaticScopeInfosLock) + { + if (StaticScopeInfos.ContainsKey(scope)) + { + // enable this by default + //Console.WriteLine("unregister " + scope.InstanceId.ToString("N").Substring(0, 8)); + StaticScopeInfos.Remove(scope); + Logging.LogHelper.Debug("Remove " + scope.InstanceId.ToString("N").Substring(0, 8)); + + // instead, enable this to keep *all* scopes + // beware, there can be a lot of scopes! + //info.Disposed = true; + //info.DisposedStack = Environment.StackTrace; + } + } + } +#endif + } + +#if DEBUG_SCOPES + public class ScopeInfo + { + public ScopeInfo(IScope scope, string ctorStack) + { + Scope = scope; + Created = DateTime.Now; + CtorStack = ctorStack; + } + + public IScope Scope { get; private set; } // the scope itself + + // the scope's parent identifier + public Guid Parent { get { return (Scope is NoScope || ((Scope) Scope).ParentScope == null) ? Guid.Empty : ((Scope) Scope).ParentScope.InstanceId; } } + + public DateTime Created { get; private set; } // the date time the scope was created + public bool Disposed { get; set; } // whether the scope has been disposed already + public string Context { get; set; } // the current 'context' that contains the scope (null, "http" or "lcc") + + public string CtorStack { get; private set; } // the stacktrace of the scope ctor + public string DisposedStack { get; set; } // the stacktrace when disposed + public string NullStack { get; set; } // the stacktrace when the 'context' that contains the scope went null + } +#endif +} diff --git a/src/Umbraco.Core/Scoping/ScopeReference.cs b/src/Umbraco.Core/Scoping/ScopeReference.cs new file mode 100644 index 0000000000..998f21c587 --- /dev/null +++ b/src/Umbraco.Core/Scoping/ScopeReference.cs @@ -0,0 +1,30 @@ +namespace Umbraco.Core.Scoping +{ + /// + /// References a scope. + /// + /// Should go into HttpContext to indicate there is also an IScope in context + /// that needs to be disposed at the end of the request (the scope, and the entire scopes + /// chain). + internal class ScopeReference : IDisposeOnRequestEnd // implies IDisposable + { + private readonly IScopeProviderInternal _scopeProvider; + + public ScopeReference(IScopeProviderInternal scopeProvider) + { + _scopeProvider = scopeProvider; + } + + public void Dispose() + { + // dispose the entire chain (if any) + // reset (don't commit by default) + IScopeInternal scope; + while ((scope = _scopeProvider.AmbientScope) != null) + { + scope.Reset(); + scope.Dispose(); + } + } + } +} diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index 7c65b43291..71bd2474cd 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -35,7 +35,7 @@ namespace Umbraco.Core.Security public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context, ILogger logger) { return new BackOfficeSignInManager( - context.GetBackOfficeUserManager(), + context.GetBackOfficeUserManager(), context.Authentication, logger, context.Request); @@ -48,8 +48,8 @@ namespace Umbraco.Core.Security /// public override async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) { - var result = await base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout); - + var result = await PasswordSignInAsyncImpl(userName, password, isPersistent, shouldLockout); + switch (result) { case SignInStatus.Success: @@ -69,7 +69,7 @@ namespace Umbraco.Core.Security case SignInStatus.RequiresVerification: _logger.WriteCore(TraceEventType.Information, 0, string.Format( - "Login attempt failed for username {0} from IP address {1}, the user requires verification", + "Login attempt requires verification for username {0} from IP address {1}", userName, _request.RemoteIpAddress), null, null); break; @@ -87,6 +87,68 @@ namespace Umbraco.Core.Security return result; } + /// + /// Borrowed from Micorosoft's underlying sign in manager which is not flexible enough to tell it to use a different cookie type + /// + /// + /// + /// + /// + /// + private async Task PasswordSignInAsyncImpl(string userName, string password, bool isPersistent, bool shouldLockout) + { + if (UserManager == null) + { + return SignInStatus.Failure; + } + var user = await UserManager.FindByNameAsync(userName); + if (user == null) + { + return SignInStatus.Failure; + } + if (await UserManager.IsLockedOutAsync(user.Id)) + { + return SignInStatus.LockedOut; + } + if (await UserManager.CheckPasswordAsync(user, password)) + { + await UserManager.ResetAccessFailedCountAsync(user.Id); + return await SignInOrTwoFactor(user, isPersistent); + } + if (shouldLockout) + { + // If lockout is requested, increment access failed count which might lock out the user + await UserManager.AccessFailedAsync(user.Id); + if (await UserManager.IsLockedOutAsync(user.Id)) + { + return SignInStatus.LockedOut; + } + } + return SignInStatus.Failure; + } + + /// + /// Borrowed from Micorosoft's underlying sign in manager which is not flexible enough to tell it to use a different cookie type + /// + /// + /// + /// + private async Task SignInOrTwoFactor(BackOfficeIdentityUser user, bool isPersistent) + { + var id = Convert.ToString(user.Id); + if (await UserManager.GetTwoFactorEnabledAsync(user.Id) + && (await UserManager.GetValidTwoFactorProvidersAsync(user.Id)).Count > 0) + { + var identity = new ClaimsIdentity(Constants.Security.BackOfficeTwoFactorAuthenticationType); + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id)); + identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName)); + AuthenticationManager.SignIn(identity); + return SignInStatus.RequiresVerification; + } + await SignInAsync(user, isPersistent, false); + return SignInStatus.Success; + } + /// /// Creates a user identity and then signs the identity using the AuthenticationManager /// @@ -100,11 +162,11 @@ namespace Umbraco.Core.Security // Clear any partial cookies from external or two factor partial sign ins AuthenticationManager.SignOut( - Constants.Security.BackOfficeExternalAuthenticationType, + Constants.Security.BackOfficeExternalAuthenticationType, Constants.Security.BackOfficeTwoFactorAuthenticationType); var nowUtc = DateTime.Now.ToUniversalTime(); - + if (rememberBrowser) { var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id)); @@ -114,7 +176,7 @@ namespace Umbraco.Core.Security AllowRefresh = true, IssuedUtc = nowUtc, ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes) - }, userIdentity, rememberBrowserIdentity); + }, userIdentity, rememberBrowserIdentity); } else { @@ -127,11 +189,46 @@ namespace Umbraco.Core.Security }, userIdentity); } + //track the last login date + user.LastLoginDateUtc = DateTime.UtcNow; + await UserManager.UpdateAsync(user); + _logger.WriteCore(TraceEventType.Information, 0, string.Format( "Login attempt succeeded for username {0} from IP address {1}", user.UserName, _request.RemoteIpAddress), null, null); } + + /// + /// Get the user id that has been verified already or -1. + /// + /// + /// + /// Replaces the underlying call which is not flexible and doesn't support a custom cookie + /// + public new async Task GetVerifiedUserIdAsync() + { + var result = await AuthenticationManager.AuthenticateAsync(Constants.Security.BackOfficeTwoFactorAuthenticationType); + if (result != null && result.Identity != null && string.IsNullOrEmpty(result.Identity.GetUserId()) == false) + { + return ConvertIdFromString(result.Identity.GetUserId()); + } + return -1; + } + + /// + /// Get the username that has been verified already or null. + /// + /// + public async Task GetVerifiedUserNameAsync() + { + var result = await AuthenticationManager.AuthenticateAsync(Constants.Security.BackOfficeTwoFactorAuthenticationType); + if (result != null && result.Identity != null && string.IsNullOrEmpty(result.Identity.GetUserName()) == false) + { + return result.Identity.GetUserName(); + } + return null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 08c6f54dcf..889c7004d7 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -625,6 +625,12 @@ namespace Umbraco.Core.Security var anythingChanged = false; //don't assign anything if nothing has changed as this will trigger //the track changes of the model + if ((user.LastLoginDate != default(DateTime) && identityUser.LastLoginDateUtc.HasValue == false) + || identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value) + { + anythingChanged = true; + user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime(); + } if (user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false) { anythingChanged = true; diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 3e02d6aec2..859d76b9e7 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -76,7 +76,8 @@ namespace Umbraco.Core.Security private bool _requiresQuestionAndAnswer; private bool _requiresUniqueEmail; private string _customHashAlgorithmType ; - internal bool UseLegacyEncoding; + + public bool UseLegacyEncoding { get; private set; } #region Properties 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/AuditService.cs b/src/Umbraco.Core/Services/AuditService.cs index 3e076084f2..642d89cb39 100644 --- a/src/Umbraco.Core/Services/AuditService.cs +++ b/src/Umbraco.Core/Services/AuditService.cs @@ -7,18 +7,17 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { - public sealed class AuditService : RepositoryService, IAuditService + public sealed class AuditService : ScopeRepositoryService, IAuditService { public AuditService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - } + { } public void Add(AuditType type, string comment, int userId, int objectId) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateAuditRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repo = RepositoryFactory.CreateAuditRepository(uow); repo.AddOrUpdate(new AuditItem(objectId, comment, type, userId)); uow.Commit(); } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 6223e5933b..0723431ada 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2,12 +2,11 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.IO; using System.Linq; using System.Threading; using System.Xml; using System.Xml.Linq; -using Umbraco.Core.Auditing; -using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -18,23 +17,23 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Services { /// /// Represents the Content Service, which is an easy access to operations involving /// - public class ContentService : RepositoryService, IContentService, IContentServiceOperations + public class ContentService : ScopeRepositoryService, IContentService, IContentServiceOperations { - private readonly IPublishingStrategy _publishingStrategy; + private readonly IPublishingStrategy2 _publishingStrategy; private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; private readonly IUserService _userService; - //Support recursive locks because some of the methods that require locking call other methods that require locking. + //Support recursive locks because some of the methods that require locking call other methods that require locking. //for example, the Move method needs to be locked but this calls the Save method which also needs to be locked. private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); @@ -43,57 +42,55 @@ namespace Umbraco.Core.Services RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, - IPublishingStrategy publishingStrategy, IDataTypeService dataTypeService, IUserService userService) : base(provider, repositoryFactory, logger, eventMessagesFactory) { - if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy"); if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); if (userService == null) throw new ArgumentNullException("userService"); - _publishingStrategy = publishingStrategy; + _publishingStrategy = new PublishingStrategy(UowProvider.ScopeProvider, eventMessagesFactory, logger); _dataTypeService = dataTypeService; _userService = userService; } #region Static Queries - private readonly IQuery _notTrashedQuery = Query.Builder.Where(x => x.Trashed == false); + private IQuery _notTrashedQuery; #endregion public int CountPublished(string contentTypeAlias = null) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); return repository.CountPublished(); } } public int Count(string contentTypeAlias = null) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); return repository.Count(contentTypeAlias); } } public int CountChildren(int parentId, string contentTypeAlias = null) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); return repository.CountChildren(parentId, contentTypeAlias); } } public int CountDescendants(int parentId, string contentTypeAlias = null) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); return repository.CountDescendants(parentId, contentTypeAlias); } } @@ -105,10 +102,11 @@ namespace Umbraco.Core.Services /// public void ReplaceContentPermissions(EntityPermissionSet permissionSet) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateContentRepository(uow); repository.ReplaceContentPermissions(permissionSet); + uow.Commit(); } } @@ -120,10 +118,11 @@ namespace Umbraco.Core.Services /// public void AssignContentPermission(IContent entity, char permission, IEnumerable userIds) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateContentRepository(uow); repository.AssignEntityPermission(entity, permission, userIds); + uow.Commit(); } } @@ -134,9 +133,9 @@ namespace Umbraco.Core.Services /// public IEnumerable GetPermissionsForEntity(IContent content) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); return repository.GetPermissionsForEntity(content.Id); } } @@ -162,25 +161,33 @@ namespace Umbraco.Core.Services var parent = GetById(content.ParentId); content.Path = string.Concat(parent.IfNotNull(x => x.Path, content.ParentId.ToString()), ",", content.Id); - - if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parentId), this)) + // fixme - why are we creating a UOW here and not a SCOPE FFS! + using (var uow = UowProvider.GetUnitOfWork(/*commit: true*/)) // FIXME + we are WRITING audit info FFS! { - content.WasCancelled = true; - return content; - } + // fixme + if (uow.Events.DispatchCancelable(Creating, this, new NewEventArgs(content, contentTypeAlias, parentId))) + //if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parentId), this, uow.Events)) + { + content.WasCancelled = true; + return content; + } - content.CreatorId = userId; - content.WriterId = userId; + content.CreatorId = userId; + content.WriterId = userId; - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this); + // fixme + uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, contentTypeAlias, parentId)); + //Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this, uow.Events); - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) - { + // fixme + var auditRepo = RepositoryFactory.CreateAuditRepository(uow); auditRepo.AddOrUpdate(new AuditItem(content.Id, string.Format("Content '{0}' was created", name), AuditType.New, content.CreatorId)); uow.Commit(); } + // fixme duplicate + NOOOOO not another UOW! + //Audit(AuditType.New, string.Format("Content '{0}' was created", name), content.CreatorId, content.Id); + return content; } @@ -206,17 +213,21 @@ namespace Umbraco.Core.Services var content = new Content(name, parent, contentType); content.Path = string.Concat(parent.Path, ",", content.Id); - if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parent), this)) + using (var uow = UowProvider.GetUnitOfWork()) { - content.WasCancelled = true; - return content; + if (uow.Events.DispatchCancelable(Creating, this, new NewEventArgs(content, contentTypeAlias, parent))) + { + content.WasCancelled = true; + return content; + } + + content.CreatorId = userId; + content.WriterId = userId; + + uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, contentTypeAlias, parent)); } - content.CreatorId = userId; - content.WriterId = userId; - - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parent), this); - + // MOVE into the UOW + COMMIT the f*cking thing Audit(AuditType.New, string.Format("Content '{0}' was created", name), content.CreatorId, content.Id); return content; @@ -240,35 +251,33 @@ namespace Umbraco.Core.Services var contentType = FindContentTypeByAlias(contentTypeAlias); var content = new Content(name, parentId, contentType); - //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found - // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. - if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parentId), this)) + using (var uow = UowProvider.GetUnitOfWork()) { - content.WasCancelled = true; - return content; - } + //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found + // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. + if (uow.Events.DispatchCancelable(Creating, this, new NewEventArgs(content, contentTypeAlias, parentId))) + { + content.WasCancelled = true; + return content; + } - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) - { - content.WasCancelled = true; - return content; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content))) + { + content.WasCancelled = true; + return content; + } + var repository = RepositoryFactory.CreateContentRepository(uow); content.CreatorId = userId; content.WriterId = userId; repository.AddOrUpdate(content); //Generate a new preview repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); uow.Commit(); + + uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false)); + uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, contentTypeAlias, parentId)); } - Saved.RaiseEvent(new SaveEventArgs(content, false), this); - - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this); - Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); return content; @@ -294,35 +303,34 @@ namespace Umbraco.Core.Services var contentType = FindContentTypeByAlias(contentTypeAlias); var content = new Content(name, parent, contentType); - //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found - // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. - if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parent), this)) + using (var uow = UowProvider.GetUnitOfWork()) { - content.WasCancelled = true; - return content; - } + //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found + // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. + if (uow.Events.DispatchCancelable(Creating, this, new NewEventArgs(content, contentTypeAlias, parent))) + { + content.WasCancelled = true; + return content; + } - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) - { - content.WasCancelled = true; - return content; - } + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content))) + { + content.WasCancelled = true; + return content; + } - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { + var repository = RepositoryFactory.CreateContentRepository(uow); content.CreatorId = userId; content.WriterId = userId; repository.AddOrUpdate(content); //Generate a new preview repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); uow.Commit(); + + uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false)); + uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, contentTypeAlias, parent)); } - Saved.RaiseEvent(new SaveEventArgs(content, false), this); - - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parent), this); - Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); return content; @@ -335,8 +343,9 @@ namespace Umbraco.Core.Services /// public IContent GetById(int id) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); return repository.Get(id); } } @@ -351,8 +360,10 @@ namespace Umbraco.Core.Services var idsArray = ids.ToArray(); if (idsArray.Length == 0) return Enumerable.Empty(); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateContentRepository(uow); + //ensure that the result has the order based on the ids passed in var result = repository.GetAll(idsArray); @@ -364,6 +375,7 @@ namespace Umbraco.Core.Services return content.TryGetValue(x, out c) ? c : null; }).WhereNotNull(); + uow.Commit(); return sortedResult; } } @@ -375,8 +387,9 @@ namespace Umbraco.Core.Services /// public IContent GetById(Guid key) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); var query = Query.Builder.Where(x => x.Key == key); var contents = repository.GetByQuery(query); return contents.SingleOrDefault(); @@ -390,23 +403,21 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetContentOfContentType(int id) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); var query = Query.Builder.Where(x => x.ContentTypeId == id); - var contents = repository.GetByQuery(query); - - return contents; + return repository.GetByQuery(query); } } internal IEnumerable GetPublishedContentOfContentType(int id) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); var query = Query.Builder.Where(x => x.ContentTypeId == id); - var contents = repository.GetByPublishedVersion(query); - - return contents; + return repository.GetByPublishedVersion(query); } } @@ -417,12 +428,11 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetByLevel(int level) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString())); - var contents = repository.GetByQuery(query); - - return contents; + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.Level == level && x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString()) == false); + return repository.GetByQuery(query); } } @@ -433,8 +443,9 @@ namespace Umbraco.Core.Services /// An item public IContent GetByVersion(Guid versionId) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); return repository.GetByVersion(versionId); } } @@ -447,10 +458,10 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetVersions(int id) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var versions = repository.GetAllVersions(id); - return versions; + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetAllVersions(id); } } @@ -462,10 +473,10 @@ namespace Umbraco.Core.Services /// public IEnumerable GetVersionIds(int id, int maxRows) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var versions = repository.GetVersionIds(id, maxRows); - return versions; + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetVersionIds(id, maxRows); } } @@ -494,8 +505,9 @@ namespace Umbraco.Core.Services if (ids.Any() == false) return new List(); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); return repository.GetAll(ids); } } @@ -507,12 +519,11 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetChildren(int id) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); var query = Query.Builder.Where(x => x.ParentId == id); - var contents = repository.GetByQuery(query).OrderBy(x => x.SortOrder); - - return contents; + return repository.GetByQuery(query).OrderBy(x => x.SortOrder); } } @@ -561,8 +572,10 @@ namespace Umbraco.Core.Services { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); var query = Query.Builder; //if the id is System Root, then just get all @@ -575,15 +588,13 @@ namespace Umbraco.Core.Services { filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); - - return contents; + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); } } [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); @@ -601,8 +612,8 @@ namespace Umbraco.Core.Services /// Field to order by /// 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 = "") + /// 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 = "") { return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); } @@ -618,13 +629,14 @@ namespace Umbraco.Core.Services /// Direction to order by /// Flag to indicate when ordering by system field /// Search text filter - /// An Enumerable list of objects + /// An Enumerable list of objects public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, string filter) { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); var query = Query.Builder; //if the id is System Root, then just get all @@ -637,9 +649,7 @@ namespace Umbraco.Core.Services { filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); - - return contents; + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); } } @@ -660,8 +670,10 @@ namespace Umbraco.Core.Services Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder; //if the id is System Root, then just get all @@ -669,9 +681,7 @@ namespace Umbraco.Core.Services { query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); - - return contents; + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); } } @@ -683,12 +693,12 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetChildrenByName(int parentId, string name) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); - var contents = repository.GetByQuery(query); + var repository = RepositoryFactory.CreateContentRepository(uow); - return contents; + var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); + return repository.GetByQuery(query); } } @@ -714,13 +724,17 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetDescendants(IContent content) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + //This is a check to ensure that the path is correct for this entity to avoid problems like: http://issues.umbraco.org/issue/U4-9336 due to data corruption + if (content.ValidatePath() == false) + throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", content.Id, content.Path, content.ParentId)); + + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); + var pathMatch = content.Path + ","; var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id); - var contents = repository.GetByQuery(query); - - return contents; + return repository.GetByQuery(query); } } @@ -778,12 +792,12 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetRootContent() { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); - var contents = repository.GetByQuery(query); + var repository = RepositoryFactory.CreateContentRepository(uow); - return contents; + var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); + return repository.GetByQuery(query); } } @@ -793,8 +807,15 @@ namespace Umbraco.Core.Services /// internal IEnumerable GetAllPublished() { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + //create it once if it is needed (no need for locking here) + if (_notTrashedQuery == null) { + _notTrashedQuery = Query.Builder.Where(x => x.Trashed == false); + } + + using (var uow = UowProvider.GetUnitOfWork(commit: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); return repository.GetByPublishedVersion(_notTrashedQuery); } } @@ -805,12 +826,11 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetContentForExpiration() { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var query = Query.Builder.Where(x => x.Published == true && x.ExpireDate <= DateTime.Now); - var contents = repository.GetByQuery(query); - - return contents; + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.Published && x.ExpireDate <= DateTime.Now); + return repository.GetByQuery(query); } } @@ -820,12 +840,11 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetContentForRelease() { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now); - var contents = repository.GetByQuery(query); - - return contents; + return repository.GetByQuery(query); } } @@ -835,12 +854,11 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetContentInRecycleBin() { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); var query = Query.Builder.Where(x => x.Path.Contains(Constants.System.RecycleBinContent.ToInvariantString())); - var contents = repository.GetByQuery(query); - - return contents; + return repository.GetByQuery(query); } } @@ -858,11 +876,11 @@ namespace Umbraco.Core.Services internal int CountChildren(int id) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); var query = Query.Builder.Where(x => x.ParentId == id); - var count = repository.Count(query); - return count; + return repository.Count(query); } } @@ -873,11 +891,11 @@ namespace Umbraco.Core.Services /// True if the content has any published version otherwise False public bool HasPublishedVersion(int id) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentRepository(uow); var query = Query.Builder.Where(x => x.Published == true && x.Id == id && x.Trashed == false); - int count = repository.Count(query); - return count > 0; + return repository.Count(query) > 0; } } @@ -900,12 +918,12 @@ namespace Umbraco.Core.Services } /// - /// This will rebuild the xml structures for content in the database. + /// This will rebuild the xml structures for content in the database. /// /// This is not used for anything /// True if publishing succeeded, otherwise False /// - /// This is used for when a document type alias or a document type property is changed, the xml will need to + /// This is used for when a document type alias or a document type property is changed, the xml will need to /// be regenerated. /// public bool RePublishAll(int userId = 0) @@ -923,7 +941,7 @@ namespace Umbraco.Core.Services } /// - /// This will rebuild the xml structures for content in the database. + /// This will rebuild the xml structures for content in the database. /// /// /// If specified will only rebuild the xml for the content type's specified, otherwise will update the structure @@ -986,42 +1004,52 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting the Content Attempt IContentServiceOperations.MoveToRecycleBin(IContent content, int userId) { - var evtMsgs = EventMessagesFactory.Get(); + return MoveToRecycleBinDo(content, userId, false); + } + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// Move an item to the Recycle Bin will result in the item being unpublished + /// The to delete + /// Optional Id of the User deleting the Content + /// + /// A boolean indicating to ignore this item's descendant list from also being moved to the recycle bin. This is required for the DeleteContentOfTypes method + /// because it has already looked up all descendant nodes that will need to be recycled + /// TODO: Fix all of this, it will require a reasonable refactor and most of this stuff should be done at the repo level instead of service sub operations + /// + private Attempt MoveToRecycleBinDo(IContent content, int userId, bool ignoreDescendants) + { + var evtMsgs = EventMessagesFactory.Get(); using (new WriteLock(Locker)) { - var originalPath = content.Path; - - if (Trashing.IsRaisedEventCancelled( - new MoveEventArgs(evtMsgs, new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), - this)) + using (var uow = UowProvider.GetUnitOfWork()) { - return OperationStatus.Cancelled(evtMsgs); - } + //Hack: this ensures that the entity's path is valid and if not it fixes/persists it + //see: http://issues.umbraco.org/issue/U4-9336 + content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate); + var originalPath = content.Path; + if (uow.Events.DispatchCancelable(Trashing, this, new MoveEventArgs(evtMsgs, new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), "Trashing")) + { + return OperationStatus.Cancelled(evtMsgs); + } + var moveInfo = new List> + { + new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent) + }; - var moveInfo = new List> - { - new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent) - }; + //get descendents to process of the content item that is being moved to trash - must be done before changing the state below + //must be processed with shallowest levels first + var descendants = ignoreDescendants ? Enumerable.Empty() : GetDescendants(content).OrderBy(x => x.Level); - //Make sure that published content is unpublished before being moved to the Recycle Bin - if (HasPublishedVersion(content.Id)) - { - //TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled! - UnPublish(content, userId); - } - - //Unpublish descendents of the content item that is being moved to trash - var descendants = GetDescendants(content).OrderBy(x => x.Level).ToList(); - foreach (var descendant in descendants) - { - //TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled! - UnPublish(descendant, userId); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { + //Do the updates for this item + var repository = RepositoryFactory.CreateContentRepository(uow); + //Make sure that published content is unpublished before being moved to the Recycle Bin + if (HasPublishedVersion(content.Id)) + { + //TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled! + UnPublish(content, userId); + } content.WriterId = userId; content.ChangeTrashedState(true); repository.AddOrUpdate(content); @@ -1029,17 +1057,19 @@ namespace Umbraco.Core.Services //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId foreach (var descendant in descendants) { - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - + //TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled! + UnPublish(descendant, userId); descendant.WriterId = userId; descendant.ChangeTrashedState(true, descendant.ParentId); repository.AddOrUpdate(descendant); + + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); } uow.Commit(); - } - Trashed.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); + uow.Events.Dispatch(Trashed, this, new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), "Trashed"); + } Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); @@ -1150,32 +1180,31 @@ namespace Umbraco.Core.Services /// /// Saves a collection of objects. - /// + /// /// Collection of to save /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. + /// Optional boolean indicating whether or not to raise events. Attempt IContentServiceOperations.Save(IEnumerable contents, int userId, bool raiseEvents) { var asArray = contents.ToArray(); var evtMsgs = EventMessagesFactory.Get(); - if (raiseEvents) + using (var uow = UowProvider.GetUnitOfWork()) { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(asArray, evtMsgs), - this)) + if (raiseEvents) { - return OperationStatus.Cancelled(evtMsgs); + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(asArray, evtMsgs))) + { + return OperationStatus.Cancelled(evtMsgs); + } } - } - using (new WriteLock(Locker)) - { - var containsNew = asArray.Any(x => x.HasIdentity == false); - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (new WriteLock(Locker)) { + var containsNew = asArray.Any(x => x.HasIdentity == false); + var repository = RepositoryFactory.CreateContentRepository(uow); + if (containsNew) { foreach (var content in asArray) @@ -1206,7 +1235,7 @@ namespace Umbraco.Core.Services } if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false, evtMsgs), this); + uow.Events.Dispatch(Saved, this, new SaveEventArgs(asArray, false, evtMsgs)); Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); @@ -1229,37 +1258,33 @@ namespace Umbraco.Core.Services using (new WriteLock(Locker)) { - if (Deleting.IsRaisedEventCancelled( - new DeleteEventArgs(content, evtMsgs), - this)) + using (var uow = UowProvider.GetUnitOfWork()) { - return OperationStatus.Cancelled(evtMsgs); - } + if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(content, evtMsgs), "Deleting")) + { + return OperationStatus.Cancelled(evtMsgs); + } - //Make sure that published content is unpublished before being deleted - if (HasPublishedVersion(content.Id)) - { - UnPublish(content, userId); - } + //Make sure that published content is unpublished before being deleted + if (HasPublishedVersion(content.Id)) + { + UnPublish(content, userId); + } - //Delete children before deleting the 'possible parent' - var children = GetChildren(content.Id); - foreach (var child in children) - { - Delete(child, userId); - } + //Delete children before deleting the 'possible parent' + var children = GetChildren(content.Id); + foreach (var child in children) + { + Delete(child, userId); + } + + var repository = RepositoryFactory.CreateContentRepository(uow); - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { repository.Delete(content); uow.Commit(); var args = new DeleteEventArgs(content, false, evtMsgs); - Deleted.RaiseEvent(args, this); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); + uow.Events.Dispatch(Deleted, this, args, "Deleted"); // fixme why the event name?! } Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); @@ -1305,6 +1330,58 @@ namespace Umbraco.Core.Services ((IContentServiceOperations)this).Save(contents, userId, raiseEvents); } + /// + /// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. + /// + /// Id of the + /// Optional Id of the user issueing the delete operation + public void DeleteContentOfTypes(IEnumerable contentTypeIds, int userId = 0) + { + using (new WriteLock(Locker)) + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + //track the 'root' items of the collection of nodes discovered to delete, we need to use + //these items to lookup descendants that are not of this doc type so they can be transfered + //to the recycle bin + IDictionary rootItems; + var contentToDelete = this.TrackDeletionsForDeleteContentOfTypes(contentTypeIds, repository, out rootItems).ToArray(); + + if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(contentToDelete), "Deleting")) + { + uow.Commit(); + return; + } + + //Determine the items that will need to be recycled (that are children of these content items but not of these content types) + var contentToRecycle = this.TrackTrashedForDeleteContentOfTypes(contentTypeIds, rootItems, repository); + + // do it INSIDE the UOW because nested UOW kinda should work + // fixme - and then we probably don't need the whole mess? + // nesting UOW works, it's just that the outer one NEEDS to be flushed beforehand + // nevertheless, it would be nicer to create a global scope and inner uow + + //move each item to the bin starting with the deepest items + foreach (var child in contentToRecycle.OrderByDescending(x => x.Level)) + { + MoveToRecycleBinDo(child, userId, true); + } + + foreach (var content in contentToDelete) + { + Delete(content, userId); + } + + uow.Commit(); + + Audit(AuditType.Delete, + string.Format("Delete Content of Types {0} performed by user", string.Join(",", contentTypeIds)), + userId, Constants.System.Root); + + } + } + /// /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. /// @@ -1313,47 +1390,7 @@ namespace Umbraco.Core.Services /// Optional Id of the user issueing the delete operation public void DeleteContentOfType(int contentTypeId, int userId = 0) { - //TODO: This currently this is called from the ContentTypeService but that needs to change, - // if we are deleting a content type, we should just delete the data and do this operation slightly differently. - // This method will recursively go lookup every content item, check if any of it's descendants are - // of a different type, move them to the recycle bin, then permanently delete the content items. - // The main problem with this is that for every content item being deleted, events are raised... - // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - //NOTE What about content that has the contenttype as part of its composition? - var query = Query.Builder.Where(x => x.ContentTypeId == contentTypeId); - var contents = repository.GetByQuery(query).ToArray(); - - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(contents), this)) - return; - - foreach (var content in contents.OrderByDescending(x => x.ParentId)) - { - //Look for children of current content and move that to trash before the current content is deleted - var c = content; - var childQuery = Query.Builder.Where(x => x.Path.StartsWith(c.Path)); - var children = repository.GetByQuery(childQuery); - - foreach (var child in children) - { - if (child.ContentType.Id != contentTypeId) - MoveToRecycleBin(child, userId); - } - - //Permantly delete the content - Delete(content, userId); - } - } - - Audit(AuditType.Delete, - string.Format("Delete Content of Type {0} performed by user", contentTypeId), - userId, Constants.System.Root); - } + DeleteContentOfTypes(new[] {contentTypeId}, userId); } /// @@ -1379,19 +1416,22 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting versions of a Content object public void DeleteVersions(int id, DateTime versionDate, int userId = 0) { - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) - return; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), "DeletingVersions")) + { + // fixme - not consistent, shall we commit or not when cancelling?! + uow.Commit(); + return; + } + var repository = RepositoryFactory.CreateContentRepository(uow); repository.DeleteVersions(id, versionDate); uow.Commit(); + + uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), "DeletedVersions"); + + Audit(AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); - - Audit(AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); } /// @@ -1406,25 +1446,25 @@ namespace Umbraco.Core.Services { using (new WriteLock(Locker)) { - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) - return; - - if (deletePriorVersions) + using (var uow = UowProvider.GetUnitOfWork()) { - var content = GetByVersion(versionId); - DeleteVersions(id, content.UpdateDate, userId); - } + if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, specificVersion: versionId), "DeletingVersions")) + return; - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { + if (deletePriorVersions) + { + var content = GetByVersion(versionId); + DeleteVersions(id, content.UpdateDate, userId); + } + + var repository = RepositoryFactory.CreateContentRepository(uow); repository.DeleteVersion(versionId); uow.Commit(); + + uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), "DeletedVersions"); + + Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); - - Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); } } @@ -1461,21 +1501,25 @@ namespace Umbraco.Core.Services return; } - if (Moving.IsRaisedEventCancelled( - new MoveEventArgs( - new MoveEventInfo(content, content.Path, parentId)), this)) + // fixme - we NEED something for the events + // so even if we don't create a true unit of work (no need) we can create a scope! + // what a spectacular mess + using (IScope scope = UowProvider.ScopeProvider.CreateScope()) { - return; + if (scope.Events.DispatchCancelable(Moving, this, new MoveEventArgs(new MoveEventInfo(content, content.Path, parentId)), "Moving")) + return; + + //used to track all the moved entities to be given to the event + var moveInfo = new List>(); + + //call private method that does the recursive moving + PerformMove(content, parentId, userId, moveInfo); + + scope.Events.Dispatch(Moved, this, new MoveEventArgs(false, moveInfo.ToArray()), "Moved"); + + scope.Complete(); } - //used to track all the moved entities to be given to the event - var moveInfo = new List>(); - - //call private method that does the recursive moving - PerformMove(content, parentId, userId, moveInfo); - - Moved.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this); - Audit(AuditType.Move, "Move Content performed by user", userId, content.Id); } } @@ -1492,8 +1536,10 @@ namespace Umbraco.Core.Services bool success; var nodeObjectType = new Guid(Constants.ObjectTypes.Document); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateContentRepository(uow); + //Create a dictionary of ids -> dictionary of property aliases + values entities = repository.GetEntitiesInRecycleBin() .ToDictionary( @@ -1502,22 +1548,24 @@ namespace Umbraco.Core.Services files = ((ContentRepository)repository).GetFilesInRecycleBinForUploadField(); - if (EmptyingRecycleBin.IsRaisedEventCancelled(new RecycleBinEventArgs(nodeObjectType, entities, files), this)) + if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, entities, files))) + { + uow.Commit(); // fixme not consistent! // fixme not consistent! return; + } success = repository.EmptyRecycleBin(); - EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, entities, files, success), this); - - if (success) - repository.DeleteMediaFiles(files); + uow.Events.Dispatch(EmptiedRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, entities, files, success)); + uow.Commit(); } } + // fixme outside the uow! Audit(AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent); } /// - /// Copies an object by creating a new Content object of the same type and copies all data from the current + /// Copies an object by creating a new Content object of the same type and copies all data from the current /// to the new copy which is returned. Recursively copies all children. /// /// The to copy @@ -1531,7 +1579,7 @@ namespace Umbraco.Core.Services } /// - /// Copies an object by creating a new Content object of the same type and copies all data from the current + /// Copies an object by creating a new Content object of the same type and copies all data from the current /// to the new copy which is returned. /// /// The to copy @@ -1553,51 +1601,62 @@ namespace Umbraco.Core.Services // A copy should never be set to published automatically even if the original was. copy.ChangePublishedState(PublishedState.Unpublished); - if (Copying.IsRaisedEventCancelled(new CopyEventArgs(content, copy, parentId), this)) - return null; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + // fixme mess! + using (var scope = UowProvider.ScopeProvider.CreateScope()) { - // Update the create author and last edit author - copy.CreatorId = userId; - copy.WriterId = userId; - - repository.AddOrUpdate(copy); - //add or update a preview - repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - uow.Commit(); - - - //Special case for the associated tags - //TODO: Move this to the repository layer in a single transaction! - //don't copy tags data in tags table if the item is in the recycle bin - if (parentId != Constants.System.RecycleBinContent) + using (var uow = UowProvider.GetUnitOfWork()) { - - var tags = uow.Database.Fetch("WHERE nodeId = @Id", new { Id = content.Id }); - foreach (var tag in tags) + if (uow.Events.DispatchCancelable(Copying, this, new CopyEventArgs(content, copy, parentId))) { - uow.Database.Insert(new TagRelationshipDto { NodeId = copy.Id, TagId = tag.TagId, PropertyTypeId = tag.PropertyTypeId }); + uow.Commit(); + return null; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + // Update the create author and last edit author + copy.CreatorId = userId; + copy.WriterId = userId; + + repository.AddOrUpdate(copy); + //add or update a preview + repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + uow.Commit(); + + + //Special case for the associated tags + //TODO: Move this to the repository layer in a single transaction! + //don't copy tags data in tags table if the item is in the recycle bin + if (parentId != Constants.System.RecycleBinContent) + { + + var tags = uow.Database.Fetch("WHERE nodeId = @Id", new { Id = content.Id }); + foreach (var tag in tags) + { + uow.Database.Insert(new TagRelationshipDto { NodeId = copy.Id, TagId = tag.TagId, PropertyTypeId = tag.PropertyTypeId }); + } + } + + uow.Commit(); + } + + if (recursive) + { + //Look for children and copy those as well + var children = GetChildren(content.Id); + foreach (var child in children) + { + //TODO: This shouldn't recurse back to this method, it should be done in a private method + // that doesn't have a nested lock and so we can perform the entire operation in one commit. + Copy(child, copy.Id, relateToOriginal, true, userId); } } + scope.Events.Dispatch(Copied, this, new CopyEventArgs(content, copy, false, parentId, relateToOriginal)); + Audit(AuditType.Copy, "Copy Content performed by user", content.WriterId, content.Id); + + scope.Complete(); } - if (recursive) - { - //Look for children and copy those as well - var children = GetChildren(content.Id); - foreach (var child in children) - { - //TODO: This shouldn't recurse back to this method, it should be done in a private method - // that doesn't have a nested lock and so we can perform the entire operation in one commit. - Copy(child, copy.Id, relateToOriginal, true, userId); - } - } - - Copied.RaiseEvent(new CopyEventArgs(content, copy, false, parentId, relateToOriginal), this); - - Audit(AuditType.Copy, "Copy Content performed by user", content.WriterId, content.Id); return copy; } } @@ -1611,17 +1670,20 @@ namespace Umbraco.Core.Services /// True if sending publication was succesfull otherwise false public bool SendToPublication(IContent content, int userId = 0) { - if (SendingToPublish.IsRaisedEventCancelled(new SendToPublishEventArgs(content), this)) - return false; + using (var uow = UowProvider.GetUnitOfWork()) + { + if (uow.Events.DispatchCancelable(SendingToPublish, this, new SendToPublishEventArgs(content))) + return false; - //Save before raising event - Save(content, userId); + //Save before raising event + Save(content, userId); - SentToPublish.RaiseEvent(new SendToPublishEventArgs(content, false), this); + uow.Events.Dispatch(SentToPublish, this, new SendToPublishEventArgs(content, false)); - Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); + Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); - return true; + return true; + } } /// @@ -1640,12 +1702,17 @@ namespace Umbraco.Core.Services { var content = GetByVersion(versionId); - if (RollingBack.IsRaisedEventCancelled(new RollbackEventArgs(content), this)) - return content; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(RollingBack, this, new RollbackEventArgs(content))) + { + uow.Commit(); + return content; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + content.WriterId = userId; content.CreatorId = userId; content.ChangePublishedState(PublishedState.Unpublished); @@ -1654,12 +1721,10 @@ namespace Umbraco.Core.Services //add or update a preview repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); uow.Commit(); + uow.Events.Dispatch(RolledBack, this, new RollbackEventArgs(content, false)); + Audit(AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id); } - RolledBack.RaiseEvent(new RollbackEventArgs(content, false), this); - - Audit(AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id); - return content; } @@ -1677,21 +1742,25 @@ namespace Umbraco.Core.Services /// True if sorting succeeded, otherwise False public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) { - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(items), this)) - return false; - } - var shouldBePublished = new List(); var shouldBeSaved = new List(); - var asArray = items.ToArray(); using (new WriteLock(Locker)) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var asArray = items.ToArray(); + if (raiseEvents) + { + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(asArray))) + { + uow.Commit(); + return false; + } + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + int i = 0; foreach (var content in asArray) { @@ -1711,7 +1780,7 @@ namespace Umbraco.Core.Services if (content.Published) { //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! - var published = _publishingStrategy.Publish(content, userId); + var published = _publishingStrategy.Publish(uow, content, userId).Success; shouldBePublished.Add(content); } else @@ -1729,34 +1798,61 @@ namespace Umbraco.Core.Services } uow.Commit(); + + if (raiseEvents) + uow.Events.Dispatch(Saved, this, new SaveEventArgs(asArray, false)); + + + if (shouldBePublished.Any()) + { + //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! + _publishingStrategy.PublishingFinalized(uow, shouldBePublished, false); + } } } - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); - - if (shouldBePublished.Any()) - { - //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! - _publishingStrategy.PublishingFinalized(shouldBePublished, false); - } - - + // fixme - out of uow? Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); 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 /// /// public XmlDocument BuildXmlCache() { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateContentRepository(uow); var result = repository.BuildXmlCache(); + uow.Commit(); return result; } } @@ -1770,9 +1866,10 @@ namespace Umbraco.Core.Services /// public void RebuildXmlStructures(params int[] contentTypeIds) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.RebuildXmlStructures( content => _entitySerializer.Serialize(this, _dataTypeService, _userService, content), contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); @@ -1793,12 +1890,12 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects internal IEnumerable GetPublishedDescendants(IContent content) { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); - var contents = repository.GetByPublishedVersion(query); + var repository = RepositoryFactory.CreateContentRepository(uow); - return contents; + var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); + return repository.GetByPublishedVersion(query); } } @@ -1806,11 +1903,28 @@ namespace Umbraco.Core.Services #region Private Methods + /// + /// Hack: This is used to fix some data if an entity's properties are invalid/corrupt + /// + /// + private void QuickUpdate(IContent content) + { + if (content == null) throw new ArgumentNullException("content"); + if (content.HasIdentity == false) throw new InvalidOperationException("Cannot update an entity without an Identity"); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.AddOrUpdate(content); + uow.Commit(); + } + } + private void Audit(AuditType type, string message, int userId, int objectId) { - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var auditRepo = RepositoryFactory.CreateAuditRepository(uow); auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); uow.Commit(); } @@ -1860,7 +1974,7 @@ namespace Umbraco.Core.Services //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine Save(content, false, userId); - //TODO: This shouldn't be here! This needs to be part of the repository logic but in order to fix this we need to + //TODO: This shouldn't be here! This needs to be part of the repository logic but in order to fix this we need to // change how this method calls "Save" as it needs to save using an internal method using (var uow = UowProvider.GetUnitOfWork()) { @@ -1873,6 +1987,7 @@ namespace Umbraco.Core.Services int result = exists ? uow.Database.Update(poco) : Convert.ToInt32(uow.Database.Insert(poco)); + uow.Commit(); } } } @@ -1898,7 +2013,7 @@ namespace Umbraco.Core.Services /// /// The to publish along with its children /// Optional Id of the User issueing the publishing - /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published + /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published /// /// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published /// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that @@ -1913,6 +2028,10 @@ namespace Umbraco.Core.Services using (new WriteLock(Locker)) { + //Hack: this ensures that the entity's path is valid and if not it fixes/persists it + //see: http://issues.umbraco.org/issue/U4-9336 + content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate); + var result = new List>(); //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published @@ -1949,19 +2068,20 @@ namespace Umbraco.Core.Services list.Add(content); //include parent item list.AddRange(GetDescendants(content)); - var internalStrategy = (PublishingStrategy)_publishingStrategy; + var internalStrategy = _publishingStrategy; - //Publish and then update the database with new status - var publishedOutcome = internalStrategy.PublishWithChildrenInternal(list, userId, includeUnpublished).ToArray(); - var published = publishedOutcome - .Where(x => x.Success || x.Result.StatusType == PublishStatusType.SuccessAlreadyPublished) - // ensure proper order (for events) - cannot publish a child before its parent! - .OrderBy(x => x.Result.ContentItem.Level) - .ThenBy(x => x.Result.ContentItem.SortOrder); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + //Publish and then update the database with new status + var publishedOutcome = internalStrategy.PublishWithChildren(uow, list, userId, includeUnpublished).ToArray(); + var published = publishedOutcome + .Where(x => x.Success || x.Result.StatusType == PublishStatusType.SuccessAlreadyPublished) + // ensure proper order (for events) - cannot publish a child before its parent! + .OrderBy(x => x.Result.ContentItem.Level) + .ThenBy(x => x.Result.ContentItem.SortOrder); + + var repository = RepositoryFactory.CreateContentRepository(uow); + //NOTE The Publish with subpages-dialog was used more as a republish-type-thing, so we'll have to include PublishStatusType.SuccessAlreadyPublished //in the updated-list, so the Published event is triggered with the expected set of pages and the xml is updated. foreach (var item in published) @@ -1977,14 +2097,12 @@ namespace Umbraco.Core.Services uow.Commit(); + //Save xml to db and call following method to fire event: + _publishingStrategy.PublishingFinalized(uow, updated, false); + + Audit(AuditType.Publish, "Publish with Children performed by user", userId, content.Id); + return publishedOutcome; } - //Save xml to db and call following method to fire event: - _publishingStrategy.PublishingFinalized(updated, false); - - Audit(AuditType.Publish, "Publish with Children performed by user", userId, content.Id); - - - return publishedOutcome; } } @@ -2009,12 +2127,17 @@ namespace Umbraco.Core.Services return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.SuccessAlreadyUnPublished, evtMsgs)); // already unpublished } - var unpublished = _publishingStrategy.UnPublish(content, userId); - if (unpublished == false) return Attempt.Fail(new UnPublishStatus(content, UnPublishedStatusType.FailedCancelledByEvent, evtMsgs)); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var unpublished = _publishingStrategy.UnPublish(uow, content, userId); + if (unpublished == false) + { + uow.Commit(); + return Attempt.Fail(new UnPublishStatus(content, UnPublishedStatusType.FailedCancelledByEvent, evtMsgs)); + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + content.WriterId = userId; repository.AddOrUpdate(content); // is published is not newest, reset the published flag on published version @@ -2023,10 +2146,12 @@ namespace Umbraco.Core.Services repository.DeleteContentXml(content); uow.Commit(); + + //Delete xml from db? and call following method to fire event through PublishingStrategy to update cache + if (omitCacheRefresh == false) + _publishingStrategy.UnPublishingFinalized(uow, content); } - //Delete xml from db? and call following method to fire event through PublishingStrategy to update cache - if (omitCacheRefresh == false) - _publishingStrategy.UnPublishingFinalized(content); + Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id); @@ -2044,47 +2169,46 @@ namespace Umbraco.Core.Services { var evtMsgs = EventMessagesFactory.Get(); - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(content, evtMsgs), this)) - { - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); - } - } - using (new WriteLock(Locker)) { - //Has this content item previously been published? If so, we don't need to refresh the children - var previouslyPublished = content.HasIdentity && HasPublishedVersion(content.Id); //content might not have an id - var publishStatus = new PublishStatus(content, PublishStatusType.Success, evtMsgs); //initially set to success - - //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - publishStatus.StatusType = CheckAndLogIsPublishable(content); - //if it is not successful, then check if the props are valid - if ((int)publishStatus.StatusType < 10) + using (var uow = UowProvider.GetUnitOfWork()) { - //Content contains invalid property values and can therefore not be published - fire event? - publishStatus.StatusType = CheckAndLogIsValid(content); - //set the invalid properties (if there are any) - publishStatus.InvalidProperties = ((ContentBase)content).LastInvalidProperties; - } - //if we're still successful, then publish using the strategy - if (publishStatus.StatusType == PublishStatusType.Success) - { - var internalStrategy = (PublishingStrategy)_publishingStrategy; - //Publish and then update the database with new status - var publishResult = internalStrategy.PublishInternal(content, userId); - //set the status type to the publish result - publishStatus.StatusType = publishResult.Result.StatusType; - } + if (raiseEvents) + { + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content, evtMsgs))) + { + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); + } + } - //we are successfully published if our publishStatus is still Successful - bool published = publishStatus.StatusType == PublishStatusType.Success; + //Has this content item previously been published? If so, we don't need to refresh the children + var previouslyPublished = content.HasIdentity && HasPublishedVersion(content.Id); //content might not have an id + var publishStatus = new PublishStatus(content, PublishStatusType.Success, evtMsgs); //initially set to success + + //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published + publishStatus.StatusType = CheckAndLogIsPublishable(content); + //if it is not successful, then check if the props are valid + if ((int)publishStatus.StatusType < 10) + { + //Content contains invalid property values and can therefore not be published - fire event? + publishStatus.StatusType = CheckAndLogIsValid(content); + //set the invalid properties (if there are any) + publishStatus.InvalidProperties = ((ContentBase)content).LastInvalidProperties; + } + //if we're still successful, then publish using the strategy + if (publishStatus.StatusType == PublishStatusType.Success) + { + //Publish and then update the database with new status + var publishResult = _publishingStrategy.Publish(uow, content, userId); + //set the status type to the publish result + publishStatus.StatusType = publishResult.Result.StatusType; + } + + //we are successfully published if our publishStatus is still Successful + bool published = publishStatus.StatusType == PublishStatusType.Success; + + var repository = RepositoryFactory.CreateContentRepository(uow); - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { if (published == false) { content.ChangePublishedState(PublishedState.Saved); @@ -2108,28 +2232,28 @@ namespace Umbraco.Core.Services } uow.Commit(); + + if (raiseEvents) + uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false, evtMsgs)); + + //Save xml to db and call following method to fire event through PublishingStrategy to update cache + if (published) + { + _publishingStrategy.PublishingFinalized(uow, content); + } + + //We need to check if children and their publish state to ensure that we 'republish' content that was previously published + if (published && previouslyPublished == false && HasChildren(content.Id)) + { + var descendants = GetPublishedDescendants(content); + + _publishingStrategy.PublishingFinalized(uow, descendants, false); + } + + Audit(AuditType.Publish, "Save and Publish performed by user", userId, content.Id); + + return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus); } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(content, false, evtMsgs), this); - - //Save xml to db and call following method to fire event through PublishingStrategy to update cache - if (published) - { - _publishingStrategy.PublishingFinalized(content); - } - - //We need to check if children and their publish state to ensure that we 'republish' content that was previously published - if (published && previouslyPublished == false && HasChildren(content.Id)) - { - var descendants = GetPublishedDescendants(content); - - _publishingStrategy.PublishingFinalized(descendants, false); - } - - Audit(AuditType.Publish, "Save and Publish performed by user", userId, content.Id); - - return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus); } } @@ -2144,21 +2268,25 @@ namespace Umbraco.Core.Services { var evtMsgs = EventMessagesFactory.Get(); - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(content, evtMsgs), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - } - using (new WriteLock(Locker)) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (raiseEvents) + { + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content, evtMsgs))) + { + return OperationStatus.Cancelled(evtMsgs); + } + } + + if (string.IsNullOrWhiteSpace(content.Name)) + { + throw new ArgumentException("Cannot save content with empty name."); + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + if (content.HasIdentity == false) { content.CreatorId = userId; @@ -2175,13 +2303,13 @@ namespace Umbraco.Core.Services repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); uow.Commit(); + + if (raiseEvents) + uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false, evtMsgs)); + + Audit(AuditType.Save, "Save Content performed by user", userId, content.Id); } - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(content, false, evtMsgs), this); - - Audit(AuditType.Save, "Save Content performed by user", userId, content.Id); - return OperationStatus.Success(evtMsgs); } } @@ -2268,8 +2396,10 @@ namespace Umbraco.Core.Services private IContentType FindContentTypeByAlias(string contentTypeAlias) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); var types = repository.GetByQuery(query); @@ -2334,7 +2464,7 @@ namespace Umbraco.Core.Services #region Event Handlers /// /// Occurs before Delete - /// + /// public static event TypedEventHandler> Deleting; /// @@ -2344,7 +2474,7 @@ namespace Umbraco.Core.Services /// /// Occurs before Delete Versions - /// + /// public static event TypedEventHandler DeletingVersions; /// diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index 4919a81217..d2ce1de2f8 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -1,5 +1,10 @@ +using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Repositories; namespace Umbraco.Core.Services { @@ -34,5 +39,145 @@ namespace Umbraco.Core.Services { return mediaService.CountChildren(Constants.System.RecycleBinMedia) > 0; } + + /// + /// Used for the DeleteContentOfType(s) methods to find content items to be deleted based on the content type ids passed in + /// + /// + /// + /// The content type ids being deleted + /// + /// + /// Returns a dictionary (path, TContent) of the root items discovered in the data set of items to be deleted, this can then be used + /// to search for content that needs to be trashed as a result of this. + /// + /// + /// The content items to be deleted + /// + /// + /// An internal extension method used for the DeleteContentOfTypes (DeleteMediaOfTypes) methods so that logic can be shared to avoid code duplication. + /// + internal static IEnumerable TrackDeletionsForDeleteContentOfTypes(this IContentServiceBase contentService, + IEnumerable contentTypeIds, + IRepositoryVersionable repository, + out IDictionary rootItems) + where TContent: IContentBase + { + var contentToDelete = new List(); + + //track the 'root' items of the collection of nodes discovered to delete, we need to use + //these items to lookup descendants that are not of this doc type so they can be transfered + //to the recycle bin + rootItems = new Dictionary(); + + var query = Query.Builder.Where(x => contentTypeIds.Contains(x.ContentTypeId)); + + //TODO: What about content that has the contenttype as part of its composition? + + long pageIndex = 0; + const int pageSize = 10000; + int currentPageSize; + do + { + long total; + + //start at the highest level + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "umbracoNode.level", Direction.Ascending, true).ToArray(); + + // need to store decendants count before filtering, in order for loop to work correctly + currentPageSize = contents.Length; + + //loop through the items, check if if the item exists already in the hierarchy of items tracked + //and if not, we need to add it as a 'root' item to be used to lookup later + foreach (var content in contents) + { + var pathParts = content.Path.Split(','); + var found = false; + + for (int i = 1; i < pathParts.Length; i++) + { + var currPath = "-1," + string.Join(",", Enumerable.Range(1, i).Select(x => pathParts[x])); + if (rootItems.Keys.Contains(currPath)) + { + //this content item's ancestor already exists in the root collection + found = true; + break; + } + } + + if (found == false) + { + rootItems[content.Path] = content; + } + + //track content for deletion + contentToDelete.Add(content); + } + + pageIndex++; + } while (currentPageSize == pageSize); + + return contentToDelete; + } + + /// + /// Used for the DeleteContentOfType(s) methods to find content items to be trashed based on the content type ids passed in + /// + /// + /// + /// The content type ids being deleted + /// + /// + /// + /// The content items to be trashed + /// + /// + /// An internal extension method used for the DeleteContentOfTypes (DeleteMediaOfTypes) methods so that logic can be shared to avoid code duplication. + /// + internal static IEnumerable TrackTrashedForDeleteContentOfTypes(this IContentServiceBase contentService, + IEnumerable contentTypeIds, + IDictionary rootItems, + IRepositoryVersionable repository) + where TContent : IContentBase + { + const int pageSize = 10000; + var contentToRecycle = new List(); + + //iterate over the root items found in the collection to be deleted, then discover which descendant items + //need to be moved to the recycle bin + foreach (var content in rootItems) + { + //Look for children of current content and move that to trash before the current content is deleted + var c = content; + var pathMatch = string.Format("{0},", c.Value.Path); + var descendantQuery = Query.Builder.Where(x => x.Path.StartsWith(pathMatch)); + + long pageIndex = 0; + int currentPageSize; + + do + { + long total; + + var descendants = repository.GetPagedResultsByQuery(descendantQuery, pageIndex, pageSize, out total, "umbracoNode.id", Direction.Ascending, true).ToArray(); + + foreach (var d in descendants) + { + //track for recycling if this item is not of a contenttype that is being deleted + if (contentTypeIds.Contains(d.ContentTypeId) == false) + { + contentToRecycle.Add(d); + } + } + + // need to store decendants count before filtering, in order for loop to work correctly + currentPageSize = descendants.Length; + + pageIndex++; + } while (currentPageSize == pageSize); + } + + return contentToRecycle; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 31e86cd128..ce5c6dde34 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -47,9 +47,10 @@ namespace Umbraco.Core.Services public Attempt> CreateContentTypeContainer(int parentId, string name, int userId = 0) { var evtMsgs = EventMessagesFactory.Get(); - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid); + try { var container = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) @@ -59,17 +60,16 @@ namespace Umbraco.Core.Services CreatorId = userId }; - if (SavingContentTypeContainer.IsRaisedEventCancelled( - new SaveEventArgs(container, evtMsgs), - this)) + if (uow.Events.DispatchCancelable(SavingContentTypeContainer, this, new SaveEventArgs(container, evtMsgs))) { + uow.Commit(); return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); } repo.AddOrUpdate(container); uow.Commit(); - SavedContentTypeContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + uow.Events.Dispatch(SavedContentTypeContainer, this, new SaveEventArgs(container, evtMsgs), "SavedContentTypeContainer"); //TODO: Audit trail ? return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); @@ -84,9 +84,10 @@ namespace Umbraco.Core.Services public Attempt> CreateMediaTypeContainer(int parentId, string name, int userId = 0) { var evtMsgs = EventMessagesFactory.Get(); - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid); + try { var container = new EntityContainer(Constants.ObjectTypes.MediaTypeGuid) @@ -96,17 +97,16 @@ namespace Umbraco.Core.Services CreatorId = userId }; - if (SavingMediaTypeContainer.IsRaisedEventCancelled( - new SaveEventArgs(container, evtMsgs), - this)) + if (uow.Events.DispatchCancelable(SavingMediaTypeContainer, this, new SaveEventArgs(container, evtMsgs))) { + uow.Commit(); return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); } repo.AddOrUpdate(container); uow.Commit(); - SavedMediaTypeContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + uow.Events.Dispatch(SavedMediaTypeContainer, this, new SaveEventArgs(container, evtMsgs), "SavedMediaTypeContainer"); //TODO: Audit trail ? return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); @@ -122,14 +122,16 @@ namespace Umbraco.Core.Services { return SaveContainer( SavingContentTypeContainer, SavedContentTypeContainer, - container, Constants.ObjectTypes.DocumentTypeContainerGuid, "document type", userId); + container, Constants.ObjectTypes.DocumentTypeContainerGuid, "document type", + "SavedContentTypeContainer", userId); } public Attempt SaveMediaTypeContainer(EntityContainer container, int userId = 0) { return SaveContainer( SavingMediaTypeContainer, SavedMediaTypeContainer, - container, Constants.ObjectTypes.MediaTypeContainerGuid, "media type", userId); + container, Constants.ObjectTypes.MediaTypeContainerGuid, "media type", + "SavedMediaTypeContainer", userId); } private Attempt SaveContainer( @@ -137,7 +139,9 @@ namespace Umbraco.Core.Services TypedEventHandler> savedEvent, EntityContainer container, Guid containerObjectType, - string objectTypeName, int userId) + string objectTypeName, + string savedEventName, + int userId) { var evtMsgs = EventMessagesFactory.Get(); @@ -153,22 +157,21 @@ namespace Umbraco.Core.Services return OperationStatus.Exception(evtMsgs, ex); } - if (savingEvent.IsRaisedEventCancelled( - new SaveEventArgs(container, evtMsgs), - this)) + using (var uow = UowProvider.GetUnitOfWork()) { - return OperationStatus.Cancelled(evtMsgs); - } + // fixme - how does it work w/name?! + if (uow.Events.DispatchCancelable(savingEvent, this, new SaveEventArgs(container, evtMsgs))) + //if (savingEvent.IsRaisedEventCancelled(new SaveEventArgs(container, evtMsgs), this, UowProvider)) + { + return OperationStatus.Cancelled(evtMsgs); + } - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, containerObjectType)) - { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, containerObjectType); repo.AddOrUpdate(container); uow.Commit(); + uow.Events.Dispatch(savedEvent, this, new SaveEventArgs(container, evtMsgs), savedEventName); } - savedEvent.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); - //TODO: Audit trail ? return OperationStatus.Success(evtMsgs); @@ -186,28 +189,27 @@ namespace Umbraco.Core.Services private EntityContainer GetContainer(int containerId, Guid containerObjectType) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, containerObjectType)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var container = repo.Get(containerId); - return container; + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, containerObjectType); + return repo.Get(containerId); } } public IEnumerable GetMediaTypeContainers(int[] containerIds) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid); return repo.GetAll(containerIds); } } public IEnumerable GetMediaTypeContainers(string name, int level) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid); return repo.Get(name, level); } } @@ -234,9 +236,9 @@ namespace Umbraco.Core.Services public IEnumerable GetContentTypeContainers(int[] containerIds) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid); return repo.GetAll(containerIds); } } @@ -263,19 +265,18 @@ namespace Umbraco.Core.Services private EntityContainer GetContainer(Guid containerId, Guid containerObjectType) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, containerObjectType)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var container = repo.Get(containerId); - return container; + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, containerObjectType); + return repo.Get(containerId); } } public IEnumerable GetContentTypeContainers(string name, int level) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid); return repo.Get(name, level); } } @@ -283,23 +284,22 @@ namespace Umbraco.Core.Services public Attempt DeleteContentTypeContainer(int containerId, int userId = 0) { var evtMsgs = EventMessagesFactory.Get(); - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid); var container = repo.Get(containerId); - if (container == null) return OperationStatus.NoOperation(evtMsgs); + if (container == null) return OperationStatus.NoOperation(evtMsgs); // fixme commit? - if (DeletingContentTypeContainer.IsRaisedEventCancelled( - new DeleteEventArgs(container, evtMsgs), - this)) + if (uow.Events.DispatchCancelable(DeletingContentTypeContainer, this, new DeleteEventArgs(container, evtMsgs))) { + uow.Commit(); return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); } repo.Delete(container); uow.Commit(); - DeletedContentTypeContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + uow.Events.Dispatch(DeletedContentTypeContainer, this, new DeleteEventArgs(container, evtMsgs)); return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? @@ -309,23 +309,23 @@ namespace Umbraco.Core.Services public Attempt DeleteMediaTypeContainer(int containerId, int userId = 0) { var evtMsgs = EventMessagesFactory.Get(); - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork()) { - var container = repo.Get(containerId); - if (container == null) return OperationStatus.NoOperation(evtMsgs); + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid); - if (DeletingMediaTypeContainer.IsRaisedEventCancelled( - new DeleteEventArgs(container, evtMsgs), - this)) + var container = repo.Get(containerId); + if (container == null) return OperationStatus.NoOperation(evtMsgs); // fixme commit? + + if (uow.Events.DispatchCancelable(DeletingMediaTypeContainer, this, new DeleteEventArgs(container, evtMsgs))) { + uow.Commit(); return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); } repo.Delete(container); uow.Commit(); - DeletedMediaTypeContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + uow.Events.Dispatch(DeletedMediaTypeContainer, this, new DeleteEventArgs(container, evtMsgs)); return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? @@ -340,8 +340,9 @@ namespace Umbraco.Core.Services /// public IEnumerable GetAllPropertyTypeAliases() { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); return repository.GetAllPropertyTypeAliases(); } } @@ -356,8 +357,9 @@ namespace Umbraco.Core.Services /// public IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); return repository.GetAllContentTypeAliases(objectTypes); } } @@ -452,8 +454,9 @@ namespace Umbraco.Core.Services /// public IContentType GetContentType(int id) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); return repository.Get(id); } } @@ -465,8 +468,9 @@ namespace Umbraco.Core.Services /// public IContentType GetContentType(string alias) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); return repository.Get(alias); } } @@ -478,8 +482,9 @@ namespace Umbraco.Core.Services /// public IContentType GetContentType(Guid id) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); return repository.Get(id); } } @@ -491,8 +496,9 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetAllContentTypes(params int[] ids) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); return repository.GetAll(ids); } } @@ -504,8 +510,9 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetAllContentTypes(IEnumerable ids) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); return repository.GetAll(ids.ToArray()); } } @@ -517,11 +524,11 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetContentTypeChildren(int id) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); var query = Query.Builder.Where(x => x.ParentId == id); - var contentTypes = repository.GetByQuery(query); - return contentTypes; + return repository.GetByQuery(query); } } @@ -532,13 +539,14 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetContentTypeChildren(Guid id) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + var found = GetContentType(id); if (found == null) return Enumerable.Empty(); var query = Query.Builder.Where(x => x.ParentId == found.Id); - var contentTypes = repository.GetByQuery(query); - return contentTypes; + return repository.GetByQuery(query); } } @@ -549,11 +557,12 @@ namespace Umbraco.Core.Services /// True if the content type has any children otherwise False public bool HasChildren(int id) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == id); - int count = repository.Count(query); - return count > 0; + return repository.Count(query) > 0; } } @@ -564,13 +573,14 @@ namespace Umbraco.Core.Services /// True if the content type has any children otherwise False public bool HasChildren(Guid id) { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + var found = GetContentType(id); if (found == null) return false; var query = Query.Builder.Where(x => x.ParentId == found.Id); - int count = repository.Count(query); - return count > 0; + return repository.Count(query) > 0; } } @@ -616,16 +626,18 @@ namespace Umbraco.Core.Services public int CountContentTypes() { - using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); return repository.Count(Query.Builder); } } public int CountMediaTypes() { - using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); return repository.Count(Query.Builder); } } @@ -719,14 +731,23 @@ 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; - using (new WriteLock(Locker)) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(SavingContentType, this, new SaveEventArgs(contentType))) + { + uow.Commit(); + return; + } + + if (string.IsNullOrWhiteSpace(contentType.Name)) + { + throw new ArgumentException("Cannot save content type with empty name."); + } + + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + ValidateLocked(contentType); // throws if invalid contentType.CreatorId = userId; repository.AddOrUpdate(contentType); @@ -734,10 +755,14 @@ namespace Umbraco.Core.Services uow.Commit(); } - UpdateContentXmlStructure(contentType); + UpdateContentXmlStructure(contentType); // fixme wtf here? } - SavedContentType.RaiseEvent(new SaveEventArgs(contentType, false), this); - Audit(AuditType.Save, string.Format("Save ContentType performed by user"), userId, contentType.Id); + using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess + { + scope.Events.Dispatch(SavedContentType, this, new SaveEventArgs(contentType, false)); + scope.Complete(); + } + Audit(AuditType.Save, "Save ContentType performed by user", userId, contentType.Id); } /// @@ -749,15 +774,18 @@ namespace Umbraco.Core.Services { var asArray = contentTypes.ToArray(); - if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return; - using (new WriteLock(Locker)) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { - // all-or-nothing, validate them all first + if (uow.Events.DispatchCancelable(SavingContentType, this, new SaveEventArgs(asArray))) + { + uow.Commit(); + return; + } + + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + foreach (var contentType in asArray) { ValidateLocked(contentType); // throws if invalid @@ -772,10 +800,14 @@ namespace Umbraco.Core.Services uow.Commit(); } - UpdateContentXmlStructure(asArray.Cast().ToArray()); + UpdateContentXmlStructure(asArray.Cast().ToArray()); // fixme wtf } - SavedContentType.RaiseEvent(new SaveEventArgs(asArray, false), this); - Audit(AuditType.Save, string.Format("Save ContentTypes performed by user"), userId, -1); + using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess + { + scope.Events.Dispatch(SavedContentType, this, new SaveEventArgs(asArray, false)); + scope.Complete(); + } + Audit(AuditType.Save, "Save ContentTypes performed by user", userId, -1); } /// @@ -785,33 +817,29 @@ namespace Umbraco.Core.Services /// Optional id of the user issueing the delete /// Deleting a will delete all the objects based on this public void Delete(IContentType contentType, int userId = 0) - { - if (DeletingContentType.IsRaisedEventCancelled(new DeleteEventArgs(contentType), this)) - return; - + { using (new WriteLock(Locker)) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { - //TODO: This needs to change, if we are deleting a content type, we should just delete the data, - // this method will recursively go lookup every content item, check if any of it's descendants are - // of a different type, move them to the recycle bin, then permanently delete the content items. - // The main problem with this is that for every content item being deleted, events are raised... - // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. - - var deletedContentTypes = new List() {contentType}; - deletedContentTypes.AddRange(contentType.Descendants().OfType()); - - foreach (var deletedContentType in deletedContentTypes) + if (uow.Events.DispatchCancelable(DeletingContentType, this, new DeleteEventArgs(contentType))) { - _contentService.DeleteContentOfType(deletedContentType.Id); + uow.Commit(); + return; } + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + + //If we are deleting this content type, we are also deleting it's descendents! + var deletedContentTypes = new List() {contentType}; + deletedContentTypes.AddRange(contentType.Descendants((IContentTypeService)this).OfType()); + + _contentService.DeleteContentOfTypes(deletedContentTypes.Select(x => x.Id), userId); + repository.Delete(contentType); uow.Commit(); - DeletedContentType.RaiseEvent(new DeleteEventArgs(deletedContentTypes.DistinctBy(x => x.Id), false), this); + uow.Events.Dispatch(DeletedContentType, this, new DeleteEventArgs(deletedContentTypes.DistinctBy(x => x.Id), false)); } Audit(AuditType.Delete, string.Format("Delete ContentType performed by user"), userId, contentType.Id); @@ -829,27 +857,27 @@ namespace Umbraco.Core.Services public void Delete(IEnumerable contentTypes, int userId = 0) { var asArray = contentTypes.ToArray(); - - if (DeletingContentType.IsRaisedEventCancelled(new DeleteEventArgs(asArray), this)) - return; - + using (new WriteLock(Locker)) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { - var deletedContentTypes = new List(); - deletedContentTypes.AddRange(asArray); + if (uow.Events.DispatchCancelable(DeletingContentType, this, new DeleteEventArgs(asArray))) + { + uow.Commit(); + return; + } + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + + //If we are deleting this content type, we are also deleting it's descendents! + var deletedContentTypes = new List(asArray); foreach (var contentType in asArray) { - deletedContentTypes.AddRange(contentType.Descendants().OfType()); + deletedContentTypes.AddRange(contentType.Descendants((IContentTypeService)this).OfType()); } - foreach (var deletedContentType in deletedContentTypes) - { - _contentService.DeleteContentOfType(deletedContentType.Id); - } + _contentService.DeleteContentOfTypes(deletedContentTypes.Select(x => x.Id), userId); foreach (var contentType in asArray) { @@ -858,7 +886,7 @@ namespace Umbraco.Core.Services uow.Commit(); - DeletedContentType.RaiseEvent(new DeleteEventArgs(deletedContentTypes.DistinctBy(x => x.Id), false), this); + uow.Events.Dispatch(DeletedContentType, this, new DeleteEventArgs(deletedContentTypes.DistinctBy(x => x.Id), false)); } Audit(AuditType.Delete, string.Format("Delete ContentTypes performed by user"), userId, -1); @@ -872,8 +900,9 @@ namespace Umbraco.Core.Services /// public IMediaType GetMediaType(int id) { - using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); return repository.Get(id); } } @@ -885,8 +914,9 @@ namespace Umbraco.Core.Services /// public IMediaType GetMediaType(string alias) { - using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); return repository.Get(alias); } } @@ -898,8 +928,9 @@ namespace Umbraco.Core.Services /// public IMediaType GetMediaType(Guid id) { - using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); return repository.Get(id); } } @@ -911,8 +942,9 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetAllMediaTypes(params int[] ids) { - using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); return repository.GetAll(ids); } } @@ -924,8 +956,9 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetAllMediaTypes(IEnumerable ids) { - using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); return repository.GetAll(ids.ToArray()); } } @@ -937,11 +970,11 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetMediaTypeChildren(int id) { - using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); var query = Query.Builder.Where(x => x.ParentId == id); - var contentTypes = repository.GetByQuery(query); - return contentTypes; + return repository.GetByQuery(query); } } @@ -952,13 +985,13 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetMediaTypeChildren(Guid id) { - using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); var found = GetMediaType(id); if (found == null) return Enumerable.Empty(); var query = Query.Builder.Where(x => x.ParentId == found.Id); - var contentTypes = repository.GetByQuery(query); - return contentTypes; + return repository.GetByQuery(query); } } @@ -969,11 +1002,11 @@ namespace Umbraco.Core.Services /// True if the media type has any children otherwise False public bool MediaTypeHasChildren(int id) { - using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); var query = Query.Builder.Where(x => x.ParentId == id); - int count = repository.Count(query); - return count > 0; + return repository.Count(query) > 0; } } @@ -984,13 +1017,13 @@ namespace Umbraco.Core.Services /// True if the media type has any children otherwise False public bool MediaTypeHasChildren(Guid id) { - using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); var found = GetMediaType(id); if (found == null) return false; var query = Query.Builder.Where(x => x.ParentId == found.Id); - int count = repository.Count(query); - return count > 0; + return repository.Count(query) > 0; } } @@ -998,20 +1031,14 @@ namespace Umbraco.Core.Services { var evtMsgs = EventMessagesFactory.Get(); - if (MovingMediaType.IsRaisedEventCancelled( - new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, containerId)), - this)) - { - return Attempt.Fail( - new OperationStatus( - MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - var moveInfo = new List>(); - var uow = UowProvider.GetUnitOfWork(); - using (var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) - using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(MovingMediaType, this, new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, containerId)))) + return Attempt.Fail(new OperationStatus(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); + + var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid); + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); try { EntityContainer container = null; @@ -1029,10 +1056,9 @@ namespace Umbraco.Core.Services new OperationStatus(ex.Operation, evtMsgs)); } uow.Commit(); + uow.Events.Dispatch(MovedMediaType, this, new MoveEventArgs(false, evtMsgs, moveInfo.ToArray())); } - MovedMediaType.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); - return Attempt.Succeed( new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); } @@ -1041,20 +1067,14 @@ namespace Umbraco.Core.Services { var evtMsgs = EventMessagesFactory.Get(); - if (MovingContentType.IsRaisedEventCancelled( - new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, containerId)), - this)) - { - return Attempt.Fail( - new OperationStatus( - MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - var moveInfo = new List>(); - var uow = UowProvider.GetUnitOfWork(); - using (var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) - using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(MovingContentType, this, new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, containerId)))) + return Attempt.Fail(new OperationStatus(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); + + var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid); + var repository = RepositoryFactory.CreateContentTypeRepository(uow); try { EntityContainer container = null; @@ -1072,10 +1092,9 @@ namespace Umbraco.Core.Services new OperationStatus(ex.Operation, evtMsgs)); } uow.Commit(); + uow.Events.Dispatch(MovedContentType, this, new MoveEventArgs(false, evtMsgs, moveInfo.ToArray())); } - MovedContentType.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); - return Attempt.Succeed( new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); } @@ -1085,10 +1104,11 @@ namespace Umbraco.Core.Services var evtMsgs = EventMessagesFactory.Get(); IMediaType copy; - var uow = UowProvider.GetUnitOfWork(); - using (var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) - using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid); + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); + try { if (containerId > 0) @@ -1128,10 +1148,11 @@ namespace Umbraco.Core.Services var evtMsgs = EventMessagesFactory.Get(); IContentType copy; - var uow = UowProvider.GetUnitOfWork(); - using (var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) - using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid); + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + try { if (containerId > 0) @@ -1173,26 +1194,33 @@ namespace Umbraco.Core.Services /// Optional Id of the user saving the MediaType public void Save(IMediaType mediaType, int userId = 0) { - if (SavingMediaType.IsRaisedEventCancelled(new SaveEventArgs(mediaType), this)) - return; - using (new WriteLock(Locker)) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(SavingMediaType, this, new SaveEventArgs(mediaType))) + { + uow.Commit(); + return; + } + + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); + ValidateLocked(mediaType); // throws if invalid mediaType.CreatorId = userId; repository.AddOrUpdate(mediaType); uow.Commit(); - } - UpdateContentXmlStructure(mediaType); - } + UpdateContentXmlStructure(mediaType); // fixme wtf - SavedMediaType.RaiseEvent(new SaveEventArgs(mediaType, false), this); - Audit(AuditType.Save, string.Format("Save MediaType performed by user"), userId, mediaType.Id); + using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess + { + scope.Events.Dispatch(SavedMediaType, this, new SaveEventArgs(mediaType, false)); + scope.Complete(); + } + Audit(AuditType.Save, "Save MediaType performed by user", userId, mediaType.Id); + } } /// @@ -1203,16 +1231,19 @@ namespace Umbraco.Core.Services public void Save(IEnumerable mediaTypes, int userId = 0) { var asArray = mediaTypes.ToArray(); - - if (SavingMediaType.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return; - + using (new WriteLock(Locker)) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { - // all-or-nothing, validate them all first + if (uow.Events.DispatchCancelable(SavingMediaType, this, new SaveEventArgs(asArray))) + { + uow.Commit(); + return; + } + + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); + foreach (var mediaType in asArray) { ValidateLocked(mediaType); // throws if invalid @@ -1227,11 +1258,14 @@ namespace Umbraco.Core.Services uow.Commit(); } - UpdateContentXmlStructure(asArray.Cast().ToArray()); + UpdateContentXmlStructure(asArray.Cast().ToArray()); // fixme wtf + using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess + { + scope.Events.Dispatch(SavedMediaType, this, new SaveEventArgs(asArray, false)); + scope.Complete(); + } + Audit(AuditType.Save, "Save MediaTypes performed by user", userId, -1); } - - SavedMediaType.RaiseEvent(new SaveEventArgs(asArray, false), this); - Audit(AuditType.Save, string.Format("Save MediaTypes performed by user"), userId, -1); } /// @@ -1242,25 +1276,33 @@ namespace Umbraco.Core.Services /// Deleting a will delete all the objects based on this public void Delete(IMediaType mediaType, int userId = 0) { - if (DeletingMediaType.IsRaisedEventCancelled(new DeleteEventArgs(mediaType), this)) - return; + //TODO: Share all of this logic with the Delete IContentType methods, no need for code duplication + using (new WriteLock(Locker)) { - _mediaService.DeleteMediaOfType(mediaType.Id, userId); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { - var deletedMediaTypes = new List() {mediaType}; - deletedMediaTypes.AddRange(mediaType.Descendants().OfType()); + if (uow.Events.DispatchCancelable(DeletingMediaType, this, new DeleteEventArgs(mediaType))) + { + uow.Commit(); + return; + } + + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); + + //If we are deleting this content type, we are also deleting it's descendents! + var deletedMediaTypes = new List { mediaType }; + deletedMediaTypes.AddRange(mediaType.Descendants((IContentTypeService)this).OfType()); + + _mediaService.DeleteMediaOfTypes(deletedMediaTypes.Select(x => x.Id), userId); repository.Delete(mediaType); uow.Commit(); - DeletedMediaType.RaiseEvent(new DeleteEventArgs(deletedMediaTypes.DistinctBy(x => x.Id), false), this); + uow.Events.Dispatch(DeletedMediaType, this, new DeleteEventArgs(deletedMediaTypes.DistinctBy(x => x.Id), false)); } - Audit(AuditType.Delete, string.Format("Delete MediaType performed by user"), userId, mediaType.Id); + Audit(AuditType.Delete, "Delete MediaType performed by user", userId, mediaType.Id); } } @@ -1272,34 +1314,42 @@ namespace Umbraco.Core.Services /// Deleting a will delete all the objects based on this public void Delete(IEnumerable mediaTypes, int userId = 0) { - var asArray = mediaTypes.ToArray(); + //TODO: Share all of this logic with the Delete IContentType methods, no need for code duplication - if (DeletingMediaType.IsRaisedEventCancelled(new DeleteEventArgs(asArray), this)) - return; + var asArray = mediaTypes.ToArray(); + using (new WriteLock(Locker)) { - foreach (var mediaType in asArray) + using (var uow = UowProvider.GetUnitOfWork()) { - _mediaService.DeleteMediaOfType(mediaType.Id); - } + if (uow.Events.DispatchCancelable(DeletingMediaType, this, new DeleteEventArgs(asArray))) + { + uow.Commit(); + return; + } - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) - { - var deletedMediaTypes = new List(); - deletedMediaTypes.AddRange(asArray); + var repository = RepositoryFactory.CreateMediaTypeRepository(uow); + + //If we are deleting this content type, we are also deleting it's descendents! + var deletedMediaTypes = new List(asArray); + foreach (var mediaType in asArray) + { + deletedMediaTypes.AddRange(mediaType.Descendants((IContentTypeService)this).OfType()); + } + + _mediaService.DeleteMediaOfTypes(deletedMediaTypes.Select(x => x.Id), userId); foreach (var mediaType in asArray) { - deletedMediaTypes.AddRange(mediaType.Descendants().OfType()); repository.Delete(mediaType); } + uow.Commit(); - DeletedMediaType.RaiseEvent(new DeleteEventArgs(deletedMediaTypes.DistinctBy(x => x.Id), false), this); + uow.Events.Dispatch(DeletedMediaType, this, new DeleteEventArgs(deletedMediaTypes.DistinctBy(x => x.Id), false)); } - Audit(AuditType.Delete, string.Format("Delete MediaTypes performed by user"), userId, -1); + Audit(AuditType.Delete, "Delete MediaTypes performed by user", userId, -1); } } @@ -1341,8 +1391,8 @@ namespace Umbraco.Core.Services string safeAlias = contentType.Alias.ToUmbracoAlias(); if (safeAlias != null) { - strictSchemaBuilder.AppendLine(String.Format("", safeAlias)); - strictSchemaBuilder.AppendLine(String.Format("", safeAlias)); + strictSchemaBuilder.AppendLine(string.Format("", safeAlias)); + strictSchemaBuilder.AppendLine(string.Format("", safeAlias)); } } @@ -1360,9 +1410,9 @@ namespace Umbraco.Core.Services private void Audit(AuditType type, string message, int userId, int objectId) { - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var auditRepo = RepositoryFactory.CreateAuditRepository(uow); auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); uow.Commit(); } diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs index df29012b90..8b8103be6e 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { - public class ContentTypeServiceBase : RepositoryService + public class ContentTypeServiceBase : ScopeRepositoryService { public ContentTypeServiceBase(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(provider, repositoryFactory, logger, eventMessagesFactory) @@ -62,7 +62,7 @@ namespace Umbraco.Core.Services { //if a property was deleted or alias changed, then update all content of the current content type // and all of it's desscendant doc types. - toUpdate.AddRange(contentType.DescendantsAndSelf()); + toUpdate.AddRange(contentType.DescendantsAndSelf(this)); } } } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 2cdcfc76d5..cd904a813f 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; -using Umbraco.Core.Auditing; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -19,7 +17,7 @@ namespace Umbraco.Core.Services /// /// Represents the DataType Service, which is an easy access to operations involving /// - public class DataTypeService : RepositoryService, IDataTypeService + public class DataTypeService : ScopeRepositoryService, IDataTypeService { public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) @@ -32,9 +30,10 @@ namespace Umbraco.Core.Services public Attempt> CreateContainer(int parentId, string name, int userId = 0) { var evtMsgs = EventMessagesFactory.Get(); - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + try { var container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) @@ -44,17 +43,16 @@ namespace Umbraco.Core.Services CreatorId = userId }; - if (SavingContainer.IsRaisedEventCancelled( - new SaveEventArgs(container, evtMsgs), - this)) + if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) { + uow.Commit(); return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); } repo.AddOrUpdate(container); uow.Commit(); - SavedContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); //TODO: Audit trail ? return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); @@ -68,29 +66,27 @@ namespace Umbraco.Core.Services public EntityContainer GetContainer(int containerId) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var container = repo.Get(containerId); - return container; + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(containerId); } } public EntityContainer GetContainer(Guid containerId) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - var container = repo.Get(containerId); - return container; + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(containerId); } } public IEnumerable GetContainers(string name, int level) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); return repo.Get(name, level); } } @@ -112,9 +108,9 @@ namespace Umbraco.Core.Services public IEnumerable GetContainers(int[] containerIds) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); return repo.GetAll(containerIds); } } @@ -135,22 +131,17 @@ namespace Umbraco.Core.Services return OperationStatus.Exception(evtMsgs, ex); } - if (SavingContainer.IsRaisedEventCancelled( - new SaveEventArgs(container, evtMsgs), - this)) + using (var uow = UowProvider.GetUnitOfWork()) { - return OperationStatus.Cancelled(evtMsgs); - } + if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) + return OperationStatus.Cancelled(evtMsgs); - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) - { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); repo.AddOrUpdate(container); uow.Commit(); + uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); } - SavedContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); - //TODO: Audit trail ? return OperationStatus.Success(evtMsgs); @@ -159,23 +150,22 @@ namespace Umbraco.Core.Services public Attempt DeleteContainer(int containerId, int userId = 0) { var evtMsgs = EventMessagesFactory.Get(); - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); var container = repo.Get(containerId); if (container == null) return OperationStatus.NoOperation(evtMsgs); - if (DeletingContainer.IsRaisedEventCancelled( - new DeleteEventArgs(container, evtMsgs), - this)) + if (uow.Events.DispatchCancelable(DeletingContainer, this, new DeleteEventArgs(container, evtMsgs))) { + uow.Commit(); return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); } repo.Delete(container); uow.Commit(); - DeletedContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + uow.Events.Dispatch(DeletedContainer, this, new DeleteEventArgs(container, evtMsgs)); return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? @@ -191,8 +181,9 @@ namespace Umbraco.Core.Services /// public IDataTypeDefinition GetDataTypeDefinitionByName(string name) { - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); return repository.GetByQuery(new Query().Where(x => x.Name == name)).FirstOrDefault(); } } @@ -204,8 +195,9 @@ namespace Umbraco.Core.Services /// public IDataTypeDefinition GetDataTypeDefinitionById(int id) { - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); return repository.Get(id); } } @@ -217,11 +209,12 @@ namespace Umbraco.Core.Services /// public IDataTypeDefinition GetDataTypeDefinitionById(Guid id) { - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); var query = Query.Builder.Where(x => x.Key == id); - var definitions = repository.GetByQuery(query); + var definitions = repository.GetByQuery(query); return definitions.FirstOrDefault(); } } @@ -245,12 +238,11 @@ namespace Umbraco.Core.Services /// Collection of objects with a matching contorl id public IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias) { - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); var query = Query.Builder.Where(x => x.PropertyEditorAlias == propertyEditorAlias); - var definitions = repository.GetByQuery(query); - - return definitions; + return repository.GetByQuery(query); } } @@ -261,8 +253,9 @@ namespace Umbraco.Core.Services /// An enumerable list of objects public IEnumerable GetAllDataTypeDefinitions(params int[] ids) { - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); return repository.GetAll(ids); } } @@ -274,17 +267,16 @@ namespace Umbraco.Core.Services /// An enumerable list of string values public IEnumerable GetPreValuesByDataTypeId(int id) { - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + //now convert the collection to a string list var collection = repository.GetPreValuesCollectionByDataTypeId(id); //now convert the collection to a string list - var list = collection.FormatAsDictionary() - .Select(x => x.Value.Value) - .ToList(); - return list; + return collection.FormatAsDictionary().Select(x => x.Value.Value).ToList(); } } - + /// /// Returns the PreValueCollection for the specified data type /// @@ -292,8 +284,9 @@ namespace Umbraco.Core.Services /// public PreValueCollection GetPreValuesCollectionByDataTypeId(int id) { - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); return repository.GetPreValuesCollectionByDataTypeId(id); } } @@ -305,8 +298,9 @@ namespace Umbraco.Core.Services /// PreValue as a string public string GetPreValueAsString(int id) { - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); return repository.GetPreValueAsString(id); } } @@ -315,20 +309,15 @@ namespace Umbraco.Core.Services { var evtMsgs = EventMessagesFactory.Get(); - if (Moving.IsRaisedEventCancelled( - new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, parentId)), - this)) - { - return Attempt.Fail( - new OperationStatus( - MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - var moveInfo = new List>(); - var uow = UowProvider.GetUnitOfWork(); - using (var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(Moving, this, new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, parentId)))) + return Attempt.Fail(new OperationStatus(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); + + var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + try { EntityContainer container = null; @@ -346,10 +335,9 @@ namespace Umbraco.Core.Services new OperationStatus(ex.Operation, evtMsgs)); } uow.Commit(); + uow.Events.Dispatch(Moved, this, new MoveEventArgs(false, evtMsgs, moveInfo.ToArray())); } - Moved.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); - return Attempt.Succeed( new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); } @@ -361,20 +349,30 @@ 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; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(dataTypeDefinition))) + { + uow.Commit(); + return; + } + + if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name)) + { + throw new ArgumentException("Cannot save datatype with empty name."); + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + dataTypeDefinition.CreatorId = userId; repository.AddOrUpdate(dataTypeDefinition); uow.Commit(); - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); + uow.Events.Dispatch(Saved, this, new SaveEventArgs(dataTypeDefinition, false)); } - Audit(AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); + // FIXME transaction! + Audit(AuditType.Save, "Save DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); } /// @@ -395,15 +393,19 @@ namespace Umbraco.Core.Services /// Boolean indicating whether or not to raise events public void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents) { - if (raiseEvents) + using (var uow = UowProvider.GetUnitOfWork()) { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinitions), this)) - return; - } + if (raiseEvents) + { + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(dataTypeDefinitions))) + { + uow.Commit(); + return; + } + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) - { foreach (var dataTypeDefinition in dataTypeDefinitions) { dataTypeDefinition.CreatorId = userId; @@ -412,7 +414,7 @@ namespace Umbraco.Core.Services uow.Commit(); if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinitions, false), this); + uow.Events.Dispatch(Saved, this, new SaveEventArgs(dataTypeDefinitions, false)); } Audit(AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); @@ -430,26 +432,23 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.GetUnitOfWork()) { - using (var transaction = uow.Database.GetTransaction()) + var sortOrderObj = + uow.Database.ExecuteScalar( + "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); + int sortOrder; + if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) { - var sortOrderObj = - uow.Database.ExecuteScalar( - "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); - int sortOrder; - if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) - { - sortOrder = 1; - } - - foreach (var value in values) - { - var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; - uow.Database.Insert(dto); - sortOrder++; - } - - transaction.Complete(); + sortOrder = 1; } + + foreach (var value in values) + { + var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; + uow.Database.Insert(dto); + sortOrder++; + } + + uow.Commit(); } } @@ -464,7 +463,7 @@ namespace Umbraco.Core.Services /// public void SavePreValues(int dataTypeId, IDictionary values) { - var dtd = this.GetDataTypeDefinitionById(dataTypeId); + var dtd = GetDataTypeDefinitionById(dataTypeId); if (dtd == null) { throw new InvalidOperationException("No data type found for id " + dataTypeId); @@ -485,9 +484,10 @@ namespace Umbraco.Core.Services { //TODO: Should we raise an event here since we are really saving values for the data type? - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + repository.AddOrUpdatePreValues(dataTypeDefinition, values); uow.Commit(); } @@ -501,16 +501,17 @@ namespace Umbraco.Core.Services /// public void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0) { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) - return; - - // if preValues contain the data type, override the data type definition accordingly - if (values != null && values.ContainsKey(Constants.PropertyEditors.PreValueKeys.DataValueType)) - dataTypeDefinition.DatabaseType = PropertyValueEditor.GetDatabaseType(values[Constants.PropertyEditors.PreValueKeys.DataValueType].Value); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(dataTypeDefinition))) + return; + + // if preValues contain the data type, override the data type definition accordingly + if (values != null && values.ContainsKey(Constants.PropertyEditors.PreValueKeys.DataValueType)) + dataTypeDefinition.DatabaseType = PropertyValueEditor.GetDatabaseType(values[Constants.PropertyEditors.PreValueKeys.DataValueType].Value); + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + dataTypeDefinition.CreatorId = userId; //add/update the dtd @@ -521,9 +522,9 @@ namespace Umbraco.Core.Services uow.Commit(); - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); + uow.Events.Dispatch(Saved, this, new SaveEventArgs(dataTypeDefinition, false)); } - + Audit(AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); } @@ -537,21 +538,22 @@ namespace Umbraco.Core.Services /// to delete /// Optional Id of the user issueing the deletion public void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0) - { - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(dataTypeDefinition), this)) - return; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) - { - repository.Delete(dataTypeDefinition); + { + using (var uow = UowProvider.GetUnitOfWork()) + { + if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(dataTypeDefinition))) + return; - uow.Commit(); + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - Deleted.RaiseEvent(new DeleteEventArgs(dataTypeDefinition, false), this); - } + repository.Delete(dataTypeDefinition); - Audit(AuditType.Delete, string.Format("Delete DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); + uow.Commit(); + + uow.Events.Dispatch(Deleted, this, new DeleteEventArgs(dataTypeDefinition, false)); + } + + Audit(AuditType.Delete, "Delete DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); } /// @@ -577,9 +579,9 @@ namespace Umbraco.Core.Services private void Audit(AuditType type, string message, int userId, int objectId) { - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var auditRepo = RepositoryFactory.CreateAuditRepository(uow); auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); uow.Commit(); } diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs index 3ffcb92778..64232fc5ab 100644 --- a/src/Umbraco.Core/Services/DomainService.cs +++ b/src/Umbraco.Core/Services/DomainService.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { - public class DomainService : RepositoryService, IDomainService + public class DomainService : ScopeRepositoryService, IDomainService { public DomainService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(provider, repositoryFactory, logger, eventMessagesFactory) @@ -19,9 +19,9 @@ namespace Umbraco.Core.Services public bool Exists(string domainName) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateDomainRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repo = RepositoryFactory.CreateDomainRepository(uow); return repo.Exists(domainName); } } @@ -29,57 +29,56 @@ namespace Umbraco.Core.Services public Attempt Delete(IDomain domain) { var evtMsgs = EventMessagesFactory.Get(); - if (Deleting.IsRaisedEventCancelled( - new DeleteEventArgs(domain, evtMsgs), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateDomainRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(domain, evtMsgs))) + return OperationStatus.Cancelled(evtMsgs); + + var repository = RepositoryFactory.CreateDomainRepository(uow); repository.Delete(domain); - uow.Commit(); + uow.Commit(); + + var args = new DeleteEventArgs(domain, false, evtMsgs); + uow.Events.Dispatch(Deleted, this, args); + return OperationStatus.Success(evtMsgs); } - var args = new DeleteEventArgs(domain, false, evtMsgs); - Deleted.RaiseEvent(args, this); - return OperationStatus.Success(evtMsgs); + } public IDomain GetByName(string name) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateDomainRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateDomainRepository(uow); return repository.GetByName(name); } } public IDomain GetById(int id) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateDomainRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { - return repo.Get(id); + var repo = RepositoryFactory.CreateDomainRepository(uow); + return repo.Get(id); } } public IEnumerable GetAll(bool includeWildcards) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateDomainRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repo = RepositoryFactory.CreateDomainRepository(uow); return repo.GetAll(includeWildcards); } } public IEnumerable GetAssignedDomains(int contentId, bool includeWildcards) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateDomainRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repo = RepositoryFactory.CreateDomainRepository(uow); return repo.GetAssignedDomains(contentId, includeWildcards); } } @@ -87,22 +86,20 @@ namespace Umbraco.Core.Services public Attempt Save(IDomain domainEntity) { var evtMsgs = EventMessagesFactory.Get(); - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(domainEntity, evtMsgs), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateDomainRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(domainEntity, evtMsgs))) + return OperationStatus.Cancelled(evtMsgs); + + var repository = RepositoryFactory.CreateDomainRepository(uow); repository.AddOrUpdate(domainEntity); uow.Commit(); + uow.Events.Dispatch(Saved, this, new SaveEventArgs(domainEntity, false, evtMsgs)); + return OperationStatus.Success(evtMsgs); } - Saved.RaiseEvent(new SaveEventArgs(domainEntity, false, evtMsgs), this); - return OperationStatus.Success(evtMsgs); + } #region Event Handlers diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index a00ebd6510..a9478f2ade 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -9,12 +9,13 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { - public class EntityService : RepositoryService, IEntityService + public class EntityService : ScopeRepositoryService, IEntityService { private readonly IRuntimeCacheProvider _runtimeCache; private readonly Dictionary>> _supportedObjectTypes; @@ -64,7 +65,7 @@ namespace Umbraco.Core.Services #region Static Queries - private readonly IQuery _rootEntityQuery = Query.Builder.Where(x => x.ParentId == -1); + private IQuery _rootEntityQuery; #endregion @@ -91,6 +92,7 @@ namespace Umbraco.Core.Services case UmbracoObjectTypes.Member: case UmbracoObjectTypes.DataType: case UmbracoObjectTypes.DocumentTypeContainer: + uow.Commit(); return uow.Database.ExecuteScalar(new Sql().Select("id").From().Where(dto => dto.UniqueId == key)); case UmbracoObjectTypes.RecycleBin: case UmbracoObjectTypes.Stylesheet: @@ -129,6 +131,7 @@ namespace Umbraco.Core.Services case UmbracoObjectTypes.DocumentType: case UmbracoObjectTypes.Member: case UmbracoObjectTypes.DataType: + uow.Commit(); return uow.Database.ExecuteScalar(new Sql().Select("uniqueID").From().Where(dto => dto.NodeId == id)); case UmbracoObjectTypes.RecycleBin: case UmbracoObjectTypes.Stylesheet: @@ -149,9 +152,12 @@ namespace Umbraco.Core.Services { if (loadBaseType) { - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { - return repository.GetByKey(key); + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetByKey(key); + uow.Commit(); + return ret; } } @@ -179,9 +185,12 @@ namespace Umbraco.Core.Services { if (loadBaseType) { - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { - return repository.Get(id); + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.Get(id); + uow.Commit(); + return ret; } } @@ -198,9 +207,12 @@ namespace Umbraco.Core.Services if (loadBaseType) { var objectTypeId = umbracoObjectType.GetGuid(); - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { - return repository.GetByKey(key, objectTypeId); + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetByKey(key, objectTypeId); + uow.Commit(); + return ret; } } @@ -229,9 +241,12 @@ namespace Umbraco.Core.Services if (loadBaseType) { var objectTypeId = umbracoObjectType.GetGuid(); - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { - return repository.Get(id, objectTypeId); + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.Get(id, objectTypeId); + uow.Commit(); + return ret; } } @@ -261,9 +276,12 @@ namespace Umbraco.Core.Services { if (loadBaseType) { - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { - return repository.Get(id); + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.Get(id); + uow.Commit(); + return ret; } } @@ -285,13 +303,16 @@ namespace Umbraco.Core.Services /// An public virtual IUmbracoEntity GetParent(int id) { - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateEntityRepository(uow); var entity = repository.Get(id); + if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) return null; - - return repository.Get(entity.ParentId); + var ret = repository.Get(entity.ParentId); + uow.Commit(); + return ret; } } @@ -303,14 +324,18 @@ namespace Umbraco.Core.Services /// An public virtual IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType) { - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateEntityRepository(uow); var entity = repository.Get(id); + if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) return null; - var objectTypeId = umbracoObjectType.GetGuid(); - return repository.Get(entity.ParentId, objectTypeId); + + var ret = repository.Get(entity.ParentId, objectTypeId); + uow.Commit(); + return ret; } } @@ -321,11 +346,13 @@ namespace Umbraco.Core.Services /// An enumerable list of objects public virtual IEnumerable GetChildren(int parentId) { - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateEntityRepository(uow); var query = Query.Builder.Where(x => x.ParentId == parentId); - var contents = repository.GetByQuery(query); + var contents = repository.GetByQuery(query); + uow.Commit(); return contents; } } @@ -339,11 +366,122 @@ namespace Umbraco.Core.Services public virtual IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType) { var objectTypeId = umbracoObjectType.GetGuid(); - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateEntityRepository(uow); var query = Query.Builder.Where(x => x.ParentId == parentId); - var contents = repository.GetByQuery(query, objectTypeId); + var contents = repository.GetByQuery(query, objectTypeId); + uow.Commit(); + return contents; + } + } + + /// + /// Returns a paged collection of children + /// + /// The parent id to return children for + /// + /// + /// + /// + /// + /// + /// + /// + public IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == parentId && x.Trashed == false); + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + uow.Commit(); + return contents; + } + } + + /// + /// Returns a paged collection of descendants + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + + var query = Query.Builder; + //if the id is System Root, then just get all + if (id != Constants.System.Root) + query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + uow.Commit(); + return contents; + } + } + + /// + /// Returns a paged collection of descendants from the root + /// + /// + /// + /// + /// + /// + /// + /// + /// true/false to include trashed objects + /// + public IEnumerable GetPagedDescendantsFromRoot(UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + + var query = Query.Builder; + //don't include trashed if specfied + if (includeTrashed == false) + { + query.Where(x => x.Trashed == false); + } + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + uow.Commit(); return contents; } } @@ -355,13 +493,15 @@ namespace Umbraco.Core.Services /// An enumerable list of objects public virtual IEnumerable GetDescendents(int id) { - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateEntityRepository(uow); var entity = repository.Get(id); var pathMatch = entity.Path + ","; var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != id); - var entities = repository.GetByQuery(query); + var entities = repository.GetByQuery(query); + uow.Commit(); return entities; } } @@ -375,12 +515,14 @@ namespace Umbraco.Core.Services public virtual IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType) { var objectTypeId = umbracoObjectType.GetGuid(); - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateEntityRepository(uow); var entity = repository.Get(id); var query = Query.Builder.Where(x => x.Path.StartsWith(entity.Path) && x.Id != id); - var entities = repository.GetByQuery(query, objectTypeId); + var entities = repository.GetByQuery(query, objectTypeId); + uow.Commit(); return entities; } } @@ -392,11 +534,18 @@ namespace Umbraco.Core.Services /// An enumerable list of objects public virtual IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType) { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + //create it once if it is needed (no need for locking here) + if (_rootEntityQuery == null) { - var entities = repository.GetByQuery(_rootEntityQuery, objectTypeId); + _rootEntityQuery = Query.Builder.Where(x => x.ParentId == -1); + } + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entities = repository.GetByQuery(_rootEntityQuery, objectTypeId); + uow.Commit(); return entities; } } @@ -436,9 +585,12 @@ namespace Umbraco.Core.Services }); var objectTypeId = umbracoObjectType.GetGuid(); - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { - return repository.GetAll(objectTypeId, ids); + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetAll(objectTypeId, ids); + uow.Commit(); + return ret; } } @@ -453,9 +605,12 @@ namespace Umbraco.Core.Services }); var objectTypeId = umbracoObjectType.GetGuid(); - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { - return repository.GetAll(objectTypeId, keys); + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetAll(objectTypeId, keys); + uow.Commit(); + return ret; } } @@ -476,9 +631,12 @@ namespace Umbraco.Core.Services ("The passed in type is not supported"); }); - using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { - return repository.GetAll(objectTypeId, ids); + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetAll(objectTypeId, ids); + uow.Commit(); + return ret; } } @@ -560,5 +718,27 @@ namespace Umbraco.Core.Services return attribute.ModelType; } + + public bool Exists(Guid key) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var exists = repository.Exists(key); + uow.Commit(); + return exists; + } + } + + public bool Exists(int id) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var exists = repository.Exists(id); + uow.Commit(); + return exists; + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ExternalLoginService.cs b/src/Umbraco.Core/Services/ExternalLoginService.cs index 91ca77872d..71ad0fcce9 100644 --- a/src/Umbraco.Core/Services/ExternalLoginService.cs +++ b/src/Umbraco.Core/Services/ExternalLoginService.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { - public class ExternalLoginService : RepositoryService, IExternalLoginService + public class ExternalLoginService : ScopeRepositoryService, IExternalLoginService { public ExternalLoginService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(provider, repositoryFactory, logger, eventMessagesFactory) @@ -24,9 +24,12 @@ namespace Umbraco.Core.Services /// public IEnumerable GetAll(int userId) { - using (var repo = RepositoryFactory.CreateExternalLoginRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { - return repo.GetByQuery(new Query().Where(x => x.UserId == userId)); + var repo = RepositoryFactory.CreateExternalLoginRepository(uow); + var ret = repo.GetByQuery(new Query().Where(x => x.UserId == userId)); + uow.Commit(); + return ret; } } @@ -38,10 +41,12 @@ namespace Umbraco.Core.Services /// public IEnumerable Find(UserLoginInfo login) { - using (var repo = RepositoryFactory.CreateExternalLoginRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { - return repo.GetByQuery(new Query() - .Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider)); + var repo = RepositoryFactory.CreateExternalLoginRepository(uow); + var ret = repo.GetByQuery(new Query().Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider)); + uow.Commit(); + return ret; } } @@ -52,9 +57,9 @@ namespace Umbraco.Core.Services /// public void SaveUserLogins(int userId, IEnumerable logins) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateExternalLoginRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repo = RepositoryFactory.CreateExternalLoginRepository(uow); repo.SaveUserLogins(userId, logins); uow.Commit(); } @@ -66,9 +71,9 @@ namespace Umbraco.Core.Services /// public void DeleteUserLogins(int userId) { - var uow = UowProvider.GetUnitOfWork(); - using (var repo = RepositoryFactory.CreateExternalLoginRepository(uow)) + using (var uow = UowProvider.GetUnitOfWork()) { + var repo = RepositoryFactory.CreateExternalLoginRepository(uow); repo.DeleteUserLogins(userId); uow.Commit(); } diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index aa722e0133..ec886d788d 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -15,25 +15,30 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { + // note + // file unit of work are just IUnitOfWork which is not IDisposable + // they don't really participate in the scope etc ;( + // FIXME not anymore now that Shan's made FileUnitOfWork a normal UOW? then WTF? + /// /// Represents the File Service, which is an easy access to operations involving objects like Scripts, Stylesheets and Templates /// - public class FileService : RepositoryService, IFileService + public class FileService : ScopeRepositoryService, IFileService { - private readonly IUnitOfWorkProvider _fileUowProvider; + private readonly IScopeUnitOfWorkProvider _fileUowProvider; private const string PartialViewHeader = "@inherits Umbraco.Web.Mvc.UmbracoTemplatePage"; private const string PartialViewMacroHeader = "@inherits Umbraco.Web.Macros.PartialViewMacroPage"; public FileService( - IUnitOfWorkProvider fileProvider, - IDatabaseUnitOfWorkProvider dataProvider, + IScopeUnitOfWorkProvider fileProvider, + IDatabaseUnitOfWorkProvider dataProvider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(dataProvider, repositoryFactory, logger, eventMessagesFactory) { - _fileUowProvider = fileProvider; + _fileUowProvider = fileProvider; } @@ -45,8 +50,11 @@ namespace Umbraco.Core.Services /// An enumerable list of objects public IEnumerable GetStylesheets(params string[] names) { - using (var repository = RepositoryFactory.CreateStylesheetRepository(_fileUowProvider.GetUnitOfWork(), UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + // FIXME is it ok to use the same UOW here twice? + // review everywhere in this file!! + var repository = RepositoryFactory.CreateStylesheetRepository(uow, uow); return repository.GetAll(names); } } @@ -58,8 +66,9 @@ namespace Umbraco.Core.Services /// A object public Stylesheet GetStylesheetByName(string name) { - using (var repository = RepositoryFactory.CreateStylesheetRepository(_fileUowProvider.GetUnitOfWork(), UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateStylesheetRepository(uow, uow); return repository.Get(name); } } @@ -71,19 +80,21 @@ namespace Umbraco.Core.Services /// public void SaveStylesheet(Stylesheet stylesheet, int userId = 0) { - if (SavingStylesheet.IsRaisedEventCancelled(new SaveEventArgs(stylesheet), this)) - return; - - var uow = _fileUowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateStylesheetRepository(uow, UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { + if (uow.Events.DispatchCancelable(SavingStylesheet, this, new SaveEventArgs(stylesheet))) + { + uow.Commit(); + return; + } + + var repository = RepositoryFactory.CreateStylesheetRepository(uow, uow); repository.AddOrUpdate(stylesheet); uow.Commit(); - - SavedStylesheet.RaiseEvent(new SaveEventArgs(stylesheet, false), this); + uow.Events.Dispatch(SavedStylesheet, this, new SaveEventArgs(stylesheet, false)); } - Audit(AuditType.Save, string.Format("Save Stylesheet performed by user"), userId, -1); + Audit(AuditType.Save, "Save Stylesheet performed by user", userId, -1); } /// @@ -93,22 +104,29 @@ namespace Umbraco.Core.Services /// public void DeleteStylesheet(string path, int userId = 0) { - var uow = _fileUowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateStylesheetRepository(uow, UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork()) { + var repository = RepositoryFactory.CreateStylesheetRepository(uow, uow); var stylesheet = repository.Get(path); - if (stylesheet == null) return; - - if (DeletingStylesheet.IsRaisedEventCancelled(new DeleteEventArgs(stylesheet), this)) + if (stylesheet == null) + { + uow.Commit(); return; + } + + if (uow.Events.DispatchCancelable(DeletingStylesheet, this, new DeleteEventArgs(stylesheet))) + { + uow.Commit(); + return; + } repository.Delete(stylesheet); uow.Commit(); - DeletedStylesheet.RaiseEvent(new DeleteEventArgs(stylesheet, false), this); - - Audit(AuditType.Delete, string.Format("Delete Stylesheet performed by user"), userId, -1); + uow.Events.Dispatch(DeletedStylesheet, this, new DeleteEventArgs(stylesheet, false)); } + + Audit(AuditType.Delete, string.Format("Delete Stylesheet performed by user"), userId, -1); } /// @@ -118,10 +136,9 @@ namespace Umbraco.Core.Services /// True if Stylesheet is valid, otherwise false public bool ValidateStylesheet(Stylesheet stylesheet) { - - var uow = _fileUowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateStylesheetRepository(uow, UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(commit: true)) { + var repository = RepositoryFactory.CreateStylesheetRepository(uow, uow); return repository.ValidateStylesheet(stylesheet); } } @@ -135,8 +152,9 @@ namespace Umbraco.Core.Services /// An enumerable list of objects public IEnumerable - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs deleted file mode 100644 index c08e4ba48c..0000000000 --- a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs +++ /dev/null @@ -1,280 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Web; -using System.Web.UI; -using System.Web.UI.WebControls; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Web.Trees; -using Umbraco.Web.UI.Controls; -using umbraco; -using umbraco.BasePages; -using umbraco.cms.businesslogic.template; -using umbraco.cms.helpers; -using umbraco.cms.presentation.Trees; -using Umbraco.Core; -using umbraco.uicontrols; - -namespace Umbraco.Web.UI.Umbraco.Settings.Views -{ - public partial class EditView : global::umbraco.BasePages.UmbracoEnsuredPage - { - private Template _template; - public MenuButton SaveButton; - - public EditView() - { - CurrentApp = global::umbraco.BusinessLogic.DefaultApps.settings.ToString(); - } - - /// - /// The type of MVC/Umbraco view the editor is editing - /// - public enum ViewEditorType - { - Template, - PartialView, - PartialViewMacro - } - - /// - /// Returns the type of view being edited - /// - protected ViewEditorType EditorType - { - get - { - if (_template != null) return ViewEditorType.Template; - if (Request.QueryString["treeType"].IsNullOrWhiteSpace() == false && Request.QueryString["treeType"].InvariantEquals("partialViewMacros")) return ViewEditorType.PartialViewMacro; - return ViewEditorType.PartialView; - } - } - - protected string TemplateTreeSyncPath { get; private set; } - - /// - /// This view is shared between different trees so we'll look for the query string - /// - protected string CurrentTreeType - { - get - { - if (Request.QueryString["treeType"].IsNullOrWhiteSpace()) - { - return TreeDefinitionCollection.Instance.FindTree().Tree.Alias; - } - return Request.CleanForXss("treeType"); - } - } - - /// - /// Returns the original file name that the editor was loaded with - /// - /// - /// this is used for editing a partial view - /// - protected string OriginalFileName { get; private set; } - - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - - if (!IsPostBack) - { - - //configure screen for editing a template - if (_template != null) - { - MasterTemplate.Items.Add(new ListItem(ui.Text("none"), "0")); - var selectedTemplate = string.Empty; - - foreach (var t in Template.GetAllAsList()) - { - if (t.Id == _template.Id) continue; - - var li = new ListItem(t.Text, t.Id.ToString(CultureInfo.InvariantCulture)); - li.Attributes.Add("id", t.Alias.Replace(" ", "") + ".cshtml"); - MasterTemplate.Items.Add(li); - } - - try - { - if (_template.MasterTemplate > 0) - MasterTemplate.SelectedValue = _template.MasterTemplate.ToString(CultureInfo.InvariantCulture); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred setting a master template id", ex); - } - - MasterTemplate.SelectedValue = selectedTemplate; - NameTxt.Text = _template.GetRawText(); - AliasTxt.Text = _template.Alias; - editorSource.Text = _template.Design; - PathPrefix.Visible = false; - } - else - { - //configure editor for editing a file.... - - NameTxt.Text = OriginalFileName; - var svce = ApplicationContext.Current.Services.FileService; - var file = EditorType == ViewEditorType.PartialView - ? svce.GetPartialView(OriginalFileName) - : svce.GetPartialViewMacro(OriginalFileName); - editorSource.Text = file.Content; - - const string prefixFormat = "{0}"; - PathPrefix.Text = string.Format(prefixFormat, EditorType == ViewEditorType.PartialView - ? "Partials/" - : "MacroPartials/"); - } - } - - ClientTools - .SetActiveTreeType(CurrentTreeType) - .SyncTree(TemplateTreeSyncPath, false); - } - - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - //check if a templateId is assigned, meaning we are editing a template - if (!Request.QueryString["templateID"].IsNullOrWhiteSpace()) - { - _template = new Template(int.Parse(Request.QueryString["templateID"])); - TemplateTreeSyncPath = "-1,init," + _template.Path.Replace("-1,", ""); - } - else if (!Request.QueryString["file"].IsNullOrWhiteSpace()) - { - //we are editing a view (i.e. partial view) - OriginalFileName = HttpUtility.UrlDecode(Request.QueryString["file"]); - - //TemplateTreeSyncPath = "-1,init," + Path.GetFileName(OriginalFileName); - - TemplateTreeSyncPath = DeepLink.GetTreePathFromFilePath(OriginalFileName.TrimStart("MacroPartials/").TrimStart("Partials/")); - } - else - { - throw new InvalidOperationException("Cannot render the editor without a supplied templateId or a file"); - } - - Panel1.hasMenu = true; - var editor = Panel1.NewTabPage(ui.Text("template")); - editor.Controls.Add(Pane8); - - var props = Panel1.NewTabPage(ui.Text("properties")); - props.Controls.Add(Pane7); - - - SaveButton = Panel1.Menu.NewButton(); - SaveButton.Text = ui.Text("save"); - SaveButton.ButtonType = MenuButtonType.Primary; - SaveButton.ID = "save"; - SaveButton.CssClass = "client-side"; - - Panel1.Text = ui.Text("edittemplate"); - pp_name.Text = ui.Text("name", base.getUser()); - pp_alias.Text = ui.Text("alias", base.getUser()); - pp_masterTemplate.Text = ui.Text("mastertemplate", base.getUser()); - - // Editing buttons - MenuIconI umbField = editorSource.Menu.NewIcon(); - umbField.ImageURL = UmbracoPath + "/images/editor/insField.gif"; - umbField.OnClickCommand = - ClientTools.Scripts.OpenModalWindow( - IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/umbracoField.aspx?objectId=" + - editorSource.ClientID + "&tagName=UMBRACOGETDATA&mvcView=true", ui.Text("template", "insertPageField"), 640, 550); - umbField.AltText = ui.Text("template", "insertPageField"); - - - // TODO: Update icon - MenuIconI umbDictionary = editorSource.Menu.NewIcon(); - umbDictionary.ImageURL = GlobalSettings.Path + "/images/editor/dictionaryItem.gif"; - umbDictionary.OnClickCommand = - ClientTools.Scripts.OpenModalWindow( - IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/umbracoField.aspx?objectId=" + - editorSource.ClientID + "&tagName=UMBRACOGETDICTIONARY&mvcView=true", ui.Text("template", "insertDictionaryItem"), - 640, 550); - umbDictionary.AltText = "Insert umbraco dictionary item"; - - var macroSplitButton = new InsertMacroSplitButton - { - ClientCallbackInsertMacroMarkup = "function(alias) {editViewEditor.insertMacroMarkup(alias);}", - ClientCallbackOpenMacroModel = "function(alias) {editViewEditor.openMacroModal(alias);}" - }; - editorSource.Menu.InsertNewControl(macroSplitButton, 40); - - MenuIconI umbTemplateQueryBuilder = editorSource.Menu.NewIcon(); - umbTemplateQueryBuilder.ImageURL = UmbracoPath + "/images/editor/inshtml.gif"; - umbTemplateQueryBuilder.OnClickCommand = "editViewEditor.openQueryModal()"; - umbTemplateQueryBuilder.AltText = "Open query builder"; - - if (_template == null) - { - InitializeEditorForPartialView(); - } - else - { - InitializeEditorForTemplate(); - } - - } - - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/codeEditorSave.asmx")); - ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/legacyAjaxCalls.asmx")); - } - - /// - /// Configure the editor for partial view editing - /// - private void InitializeEditorForPartialView() - { - pp_masterTemplate.Visible = false; - pp_alias.Visible = false; - pp_name.Text = "Filename"; - } - - /// - /// Configure the editor for editing a template - /// - private void InitializeEditorForTemplate() - { - - //TODO: implement content placeholders, etc... just like we had in v5 - - editorSource.Menu.InsertSplitter(); - - MenuIconI umbRenderBody = editorSource.Menu.NewIcon(); - umbRenderBody.ImageURL = UmbracoPath + "/images/editor/renderbody.gif"; - //umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder"); - umbRenderBody.AltText = "Insert @RenderBody()"; - - umbRenderBody.OnClickCommand = "editViewEditor.insertRenderBody()"; - - MenuIconI umbSection = editorSource.Menu.NewIcon(); - umbSection.ImageURL = UmbracoPath + "/images/editor/masterpagePlaceHolder.gif"; - //umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder"); - umbSection.AltText = "Insert Section"; - - umbSection.OnClickCommand = "editViewEditor.openSnippetModal('section')"; - - MenuIconI umbRenderSection = editorSource.Menu.NewIcon(); - umbRenderSection.ImageURL = UmbracoPath + "/images/editor/masterpageContent.gif"; - //umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder"); - umbRenderSection.AltText = "Insert @RenderSection"; - - umbRenderSection.OnClickCommand = "editViewEditor.openSnippetModal('rendersection')"; - - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.designer.cs b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.designer.cs deleted file mode 100644 index dd81da023a..0000000000 --- a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.designer.cs +++ /dev/null @@ -1,132 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco.Settings.Views { - - - public partial class EditView { - - /// - /// JsInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; - - /// - /// Panel1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.TabView Panel1; - - /// - /// Pane8 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.Pane Pane8; - - /// - /// pp_source control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel pp_source; - - /// - /// editorSource control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.CodeArea editorSource; - - /// - /// Pane7 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.Pane Pane7; - - /// - /// pp_name control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel pp_name; - - /// - /// PathPrefix control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal PathPrefix; - - /// - /// NameTxt control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox NameTxt; - - /// - /// pp_alias control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel pp_alias; - - /// - /// AliasTxt control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox AliasTxt; - - /// - /// pp_masterTemplate control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel pp_masterTemplate; - - /// - /// MasterTemplate control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.DropDownList MasterTemplate; - } -} diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index 9ad3174a28..2ca0b6003a 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -382,7 +382,7 @@ xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='Newtonsoft.Json']])"/> - + + + + + + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index ec7948a09a..52cfb000b6 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -14,7 +14,13 @@
- + + +
+
+
+ + @@ -119,7 +125,7 @@ - + @@ -388,7 +394,7 @@ - + @@ -419,7 +425,10 @@ - + + + + @@ -434,4 +443,9 @@ + + + + + diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index e2be9e72b6..b1b9039528 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Events; @@ -10,7 +11,6 @@ using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using System.Linq; using System.Reflection; -using umbraco.cms.businesslogic.web; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Publishing; @@ -31,35 +31,35 @@ namespace Umbraco.Web.Cache LogHelper.Info("Initializing Umbraco internal event handlers for cache refreshing"); //bind to application tree events - ApplicationTreeService.Deleted += ApplicationTreeDeleted; - ApplicationTreeService.Updated += ApplicationTreeUpdated; - ApplicationTreeService.New += ApplicationTreeNew; + ApplicationTreeService.Deleted += ApplicationTreeService_Deleted; + ApplicationTreeService.Updated += ApplicationTreeService_Updated; + ApplicationTreeService.New += ApplicationTreeService_New; //bind to application events - SectionService.Deleted += ApplicationDeleted; - SectionService.New += ApplicationNew; + SectionService.Deleted += SectionService_Deleted; + SectionService.New += SectionService_New; //bind to user / user type events - UserService.SavedUserType += UserServiceSavedUserType; - UserService.DeletedUserType += UserServiceDeletedUserType; - UserService.SavedUser += UserServiceSavedUser; - UserService.DeletedUser += UserServiceDeletedUser; + UserService.SavedUserType += UserService_SavedUserType; + UserService.DeletedUserType += UserService_DeletedUserType; + UserService.SavedUser += UserService_SavedUser; + UserService.DeletedUser += UserService_DeletedUser; //Bind to dictionary events - LocalizationService.DeletedDictionaryItem += LocalizationServiceDeletedDictionaryItem; - LocalizationService.SavedDictionaryItem += LocalizationServiceSavedDictionaryItem; + LocalizationService.DeletedDictionaryItem += LocalizationService_DeletedDictionaryItem; + LocalizationService.SavedDictionaryItem += LocalizationService_SavedDictionaryItem; //Bind to data type events //NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 - DataTypeService.Deleted += DataTypeServiceDeleted; - DataTypeService.Saved += DataTypeServiceSaved; + DataTypeService.Deleted += DataTypeService_Deleted; + DataTypeService.Saved += DataTypeService_Saved; //Bind to stylesheet events - FileService.SavedStylesheet += FileServiceSavedStylesheet; - FileService.DeletedStylesheet += FileServiceDeletedStylesheet; + FileService.SavedStylesheet += FileService_SavedStylesheet; + FileService.DeletedStylesheet += FileService_DeletedStylesheet; //Bind to domain events @@ -68,17 +68,17 @@ namespace Umbraco.Web.Cache //Bind to language events - LocalizationService.SavedLanguage += LocalizationServiceSavedLanguage; - LocalizationService.DeletedLanguage += LocalizationServiceDeletedLanguage; + LocalizationService.SavedLanguage += LocalizationService_SavedLanguage; + LocalizationService.DeletedLanguage += LocalizationService_DeletedLanguage; //Bind to content type events - ContentTypeService.SavedContentType += ContentTypeServiceSavedContentType; - ContentTypeService.SavedMediaType += ContentTypeServiceSavedMediaType; - ContentTypeService.DeletedContentType += ContentTypeServiceDeletedContentType; - ContentTypeService.DeletedMediaType += ContentTypeServiceDeletedMediaType; - MemberTypeService.Saved += MemberTypeServiceSaved; - MemberTypeService.Deleted += MemberTypeServiceDeleted; + ContentTypeService.SavedContentType += ContentTypeService_SavedContentType; + ContentTypeService.SavedMediaType += ContentTypeService_SavedMediaType; + ContentTypeService.DeletedContentType += ContentTypeService_DeletedContentType; + ContentTypeService.DeletedMediaType += ContentTypeService_DeletedMediaType; + MemberTypeService.Saved += MemberTypeService_Saved; + MemberTypeService.Deleted += MemberTypeService_Deleted; //Bind to permission events @@ -90,53 +90,174 @@ namespace Umbraco.Web.Cache //Bind to template events - FileService.SavedTemplate += FileServiceSavedTemplate; - FileService.DeletedTemplate += FileServiceDeletedTemplate; + FileService.SavedTemplate += FileService_SavedTemplate; + FileService.DeletedTemplate += FileService_DeletedTemplate; //Bind to macro events - MacroService.Saved += MacroServiceSaved; - MacroService.Deleted += MacroServiceDeleted; + MacroService.Saved += MacroService_Saved; + MacroService.Deleted += MacroService_Deleted; //Bind to member events - MemberService.Saved += MemberServiceSaved; - MemberService.Deleted += MemberServiceDeleted; + MemberService.Saved += MemberService_Saved; + MemberService.Deleted += MemberService_Deleted; MemberGroupService.Saved += MemberGroupService_Saved; MemberGroupService.Deleted += MemberGroupService_Deleted; //Bind to media events - MediaService.Saved += MediaServiceSaved; - MediaService.Deleted += MediaServiceDeleted; - MediaService.Moved += MediaServiceMoved; - MediaService.Trashed += MediaServiceTrashed; - MediaService.EmptiedRecycleBin += MediaServiceEmptiedRecycleBin; + MediaService.Saved += MediaService_Saved; + MediaService.Deleted += MediaService_Deleted; + MediaService.Moved += MediaService_Moved; + MediaService.Trashed += MediaService_Trashed; + MediaService.EmptiedRecycleBin += MediaService_EmptiedRecycleBin; //Bind to content events - this is for unpublished content syncing across servers (primarily for examine) - ContentService.Saved += ContentServiceSaved; - ContentService.Deleted += ContentServiceDeleted; - ContentService.Copied += ContentServiceCopied; + ContentService.Saved += ContentService_Saved; + ContentService.Deleted += ContentService_Deleted; + ContentService.Copied += ContentService_Copied; //TODO: The Move method of the content service fires Saved/Published events during its execution so we don't need to listen to moved //ContentService.Moved += ContentServiceMoved; - ContentService.Trashed += ContentServiceTrashed; - ContentService.EmptiedRecycleBin += ContentServiceEmptiedRecycleBin; + ContentService.Trashed += ContentService_Trashed; + ContentService.EmptiedRecycleBin += ContentService_EmptiedRecycleBin; - PublishingStrategy.Published += PublishingStrategy_Published; - PublishingStrategy.UnPublished += PublishingStrategy_UnPublished; + ContentService.Published += ContentService_Published; + ContentService.UnPublished += ContentService_UnPublished; //public access events PublicAccessService.Saved += PublicAccessService_Saved; PublicAccessService.Deleted += PublicAccessService_Deleted; - RelationService.SavedRelationType += RelationType_Saved; - RelationService.DeletedRelationType += RelationType_Deleted; + RelationService.SavedRelationType += RelationService_SavedRelationType; + RelationService.DeletedRelationType += RelationService_DeletedRelationType; + } + + // for tests + internal void Destroy() + { + //bind to application tree events + ApplicationTreeService.Deleted -= ApplicationTreeService_Deleted; + ApplicationTreeService.Updated -= ApplicationTreeService_Updated; + ApplicationTreeService.New -= ApplicationTreeService_New; + + //bind to application events + SectionService.Deleted -= SectionService_Deleted; + SectionService.New -= SectionService_New; + + //bind to user / user type events + UserService.SavedUserType -= UserService_SavedUserType; + UserService.DeletedUserType -= UserService_DeletedUserType; + UserService.SavedUser -= UserService_SavedUser; + UserService.DeletedUser -= UserService_DeletedUser; + + //Bind to dictionary events + + LocalizationService.DeletedDictionaryItem -= LocalizationService_DeletedDictionaryItem; + LocalizationService.SavedDictionaryItem -= LocalizationService_SavedDictionaryItem; + + //Bind to data type events + //NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 + + DataTypeService.Deleted -= DataTypeService_Deleted; + DataTypeService.Saved -= DataTypeService_Saved; + + //Bind to stylesheet events + + FileService.SavedStylesheet -= FileService_SavedStylesheet; + FileService.DeletedStylesheet -= FileService_DeletedStylesheet; + + //Bind to domain events + + DomainService.Saved -= DomainService_Saved; + DomainService.Deleted -= DomainService_Deleted; + + //Bind to language events + + LocalizationService.SavedLanguage -= LocalizationService_SavedLanguage; + LocalizationService.DeletedLanguage -= LocalizationService_DeletedLanguage; + + //Bind to content type events + + ContentTypeService.SavedContentType -= ContentTypeService_SavedContentType; + ContentTypeService.SavedMediaType -= ContentTypeService_SavedMediaType; + ContentTypeService.DeletedContentType -= ContentTypeService_DeletedContentType; + ContentTypeService.DeletedMediaType -= ContentTypeService_DeletedMediaType; + MemberTypeService.Saved -= MemberTypeService_Saved; + MemberTypeService.Deleted -= MemberTypeService_Deleted; + + //Bind to permission events + + //TODO: Wrap legacy permissions so we can get rid of this + Permission.New -= PermissionNew; + Permission.Updated -= PermissionUpdated; + Permission.Deleted -= PermissionDeleted; + PermissionRepository.AssignedPermissions -= CacheRefresherEventHandler_AssignedPermissions; + + //Bind to template events + + FileService.SavedTemplate -= FileService_SavedTemplate; + FileService.DeletedTemplate -= FileService_DeletedTemplate; + + //Bind to macro events + + MacroService.Saved -= MacroService_Saved; + MacroService.Deleted -= MacroService_Deleted; + + //Bind to member events + + MemberService.Saved -= MemberService_Saved; + MemberService.Deleted -= MemberService_Deleted; + MemberGroupService.Saved -= MemberGroupService_Saved; + MemberGroupService.Deleted -= MemberGroupService_Deleted; + + //Bind to media events + + MediaService.Saved -= MediaService_Saved; + MediaService.Deleted -= MediaService_Deleted; + MediaService.Moved -= MediaService_Moved; + MediaService.Trashed -= MediaService_Trashed; + MediaService.EmptiedRecycleBin -= MediaService_EmptiedRecycleBin; + + //Bind to content events - this is for unpublished content syncing across servers (primarily for examine) + + ContentService.Saved -= ContentService_Saved; + ContentService.Deleted -= ContentService_Deleted; + ContentService.Copied -= ContentService_Copied; + //TODO: The Move method of the content service fires Saved/Published events during its execution so we don't need to listen to moved + //ContentService.Moved -= ContentServiceMoved; + ContentService.Trashed -= ContentService_Trashed; + ContentService.EmptiedRecycleBin -= ContentService_EmptiedRecycleBin; + + ContentService.Published -= ContentService_Published; + ContentService.UnPublished -= ContentService_UnPublished; + + //public access events + PublicAccessService.Saved -= PublicAccessService_Saved; + PublicAccessService.Deleted -= PublicAccessService_Deleted; + + RelationService.SavedRelationType -= RelationService_SavedRelationType; + RelationService.DeletedRelationType -= RelationService_DeletedRelationType; } #region Publishing - void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs e) + // IPublishingStrategy (obsolete) events are proxied into ContentService, which works fine when + // events are actually raised, but not when they are handled by HandleEvents, so we have to have + // these proxy methods that are *not* registered against any event *but* used by HandleEvents. + + static void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs e) + { + ContentService_UnPublished(sender, e); + } + + static void PublishingStrategy_Published(IPublishingStrategy sender, PublishEventArgs e) + { + ContentService_Published(sender, e); + } + + static void ContentService_UnPublished(IPublishingStrategy sender, PublishEventArgs e) { if (e.PublishedEntities.Any()) { @@ -158,12 +279,12 @@ namespace Umbraco.Web.Cache /// /// Refreshes the xml cache for a single node by removing it /// - private void UnPublishSingle(IContent content) + private static void UnPublishSingle(IContent content) { DistributedCache.Instance.RemovePageCache(content); } - void PublishingStrategy_Published(IPublishingStrategy sender, PublishEventArgs e) + static void ContentService_Published(IPublishingStrategy sender, PublishEventArgs e) { if (e.PublishedEntities.Any()) { @@ -188,7 +309,7 @@ namespace Umbraco.Web.Cache /// /// Refreshes the xml cache for all nodes /// - private void UpdateEntireCache() + private static void UpdateEntireCache() { DistributedCache.Instance.RefreshAllPageCache(); } @@ -196,7 +317,7 @@ namespace Umbraco.Web.Cache /// /// Refreshes the xml cache for nodes in list /// - private void UpdateMultipleContentCache(IEnumerable content) + private static void UpdateMultipleContentCache(IEnumerable content) { DistributedCache.Instance.RefreshPageCache(content.ToArray()); } @@ -204,7 +325,7 @@ namespace Umbraco.Web.Cache /// /// Refreshes the xml cache for a single node /// - private void UpdateSingleContentCache(IContent content) + private static void UpdateSingleContentCache(IContent content) { DistributedCache.Instance.RefreshPageCache(content); } @@ -218,7 +339,7 @@ namespace Umbraco.Web.Cache DistributedCache.Instance.RefreshPublicAccess(); } - private void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs e) + static void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs e) { DistributedCache.Instance.RefreshPublicAccess(); } @@ -227,7 +348,7 @@ namespace Umbraco.Web.Cache #region Content service event handlers - static void ContentServiceEmptiedRecycleBin(IContentService sender, RecycleBinEventArgs e) + static void ContentService_EmptiedRecycleBin(IContentService sender, RecycleBinEventArgs e) { if (e.RecycleBinEmptiedSuccessfully && e.IsContentRecycleBin) { @@ -244,7 +365,7 @@ namespace Umbraco.Web.Cache /// This is for the unpublished page refresher - the entity will be unpublished before being moved to the trash /// and the unpublished event will take care of remove it from any published caches /// - static void ContentServiceTrashed(IContentService sender, MoveEventArgs e) + static void ContentService_Trashed(IContentService sender, MoveEventArgs e) { DistributedCache.Instance.RefreshUnpublishedPageCache( e.MoveInfoCollection.Select(x => x.Entity).ToArray()); @@ -259,7 +380,7 @@ namespace Umbraco.Web.Cache /// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the /// case then we need to clear all user permissions cache. /// - static void ContentServiceCopied(IContentService sender, CopyEventArgs e) + static void ContentService_Copied(IContentService sender, CopyEventArgs e) { //check if permissions have changed var permissionsChanged = ((Content)e.Copy).WasPropertyDirty("PermissionsChanged"); @@ -277,7 +398,7 @@ namespace Umbraco.Web.Cache ///
/// /// - static void ContentServiceDeleted(IContentService sender, DeleteEventArgs e) + static void ContentService_Deleted(IContentService sender, DeleteEventArgs e) { DistributedCache.Instance.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray()); } @@ -294,7 +415,7 @@ namespace Umbraco.Web.Cache /// When an entity is created new permissions may be assigned to it based on it's parent, if that is the /// case then we need to clear all user permissions cache. /// - static void ContentServiceSaved(IContentService sender, SaveEventArgs e) + static void ContentService_Saved(IContentService sender, SaveEventArgs e) { var clearUserPermissions = false; e.SavedEntities.ForEach(x => @@ -327,41 +448,41 @@ namespace Umbraco.Web.Cache #endregion #region ApplicationTree event handlers - static void ApplicationTreeNew(ApplicationTree sender, EventArgs e) + static void ApplicationTreeService_New(ApplicationTree sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationTreeCache(); } - static void ApplicationTreeUpdated(ApplicationTree sender, EventArgs e) + static void ApplicationTreeService_Updated(ApplicationTree sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationTreeCache(); } - static void ApplicationTreeDeleted(ApplicationTree sender, EventArgs e) + static void ApplicationTreeService_Deleted(ApplicationTree sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationTreeCache(); } #endregion #region Application event handlers - static void ApplicationNew(Section sender, EventArgs e) + static void SectionService_New(Section sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationCache(); } - static void ApplicationDeleted(Section sender, EventArgs e) + static void SectionService_Deleted(Section sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationCache(); } #endregion #region UserType event handlers - static void UserServiceDeletedUserType(IUserService sender, DeleteEventArgs e) + static void UserService_DeletedUserType(IUserService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserTypeCache(x.Id)); } - static void UserServiceSavedUserType(IUserService sender, SaveEventArgs e) + static void UserService_SavedUserType(IUserService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserTypeCache(x.Id)); } @@ -370,12 +491,12 @@ namespace Umbraco.Web.Cache #region Dictionary event handlers - static void LocalizationServiceSavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) + static void LocalizationService_SavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDictionaryCache(x.Id)); } - static void LocalizationServiceDeletedDictionaryItem(ILocalizationService sender, DeleteEventArgs e) + static void LocalizationService_DeletedDictionaryItem(ILocalizationService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDictionaryCache(x.Id)); } @@ -383,12 +504,12 @@ namespace Umbraco.Web.Cache #endregion #region DataType event handlers - static void DataTypeServiceSaved(IDataTypeService sender, SaveEventArgs e) + static void DataTypeService_Saved(IDataTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDataTypeCache(x)); } - static void DataTypeServiceDeleted(IDataTypeService sender, DeleteEventArgs e) + static void DataTypeService_Deleted(IDataTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDataTypeCache(x)); } @@ -398,12 +519,12 @@ namespace Umbraco.Web.Cache #region Stylesheet and stylesheet property event handlers - static void FileServiceDeletedStylesheet(IFileService sender, DeleteEventArgs e) + static void FileService_DeletedStylesheet(IFileService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveStylesheetCache(x)); } - static void FileServiceSavedStylesheet(IFileService sender, SaveEventArgs e) + static void FileService_SavedStylesheet(IFileService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshStylesheetCache(x)); } @@ -430,7 +551,7 @@ namespace Umbraco.Web.Cache ///
/// /// - static void LocalizationServiceDeletedLanguage(ILocalizationService sender, DeleteEventArgs e) + static void LocalizationService_DeletedLanguage(ILocalizationService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveLanguageCache(x)); } @@ -440,7 +561,7 @@ namespace Umbraco.Web.Cache ///
/// /// - static void LocalizationServiceSavedLanguage(ILocalizationService sender, SaveEventArgs e) + static void LocalizationService_SavedLanguage(ILocalizationService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshLanguageCache(x)); } @@ -453,7 +574,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentTypeServiceDeletedMediaType(IContentTypeService sender, DeleteEventArgs e) + static void ContentTypeService_DeletedMediaType(IContentTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveMediaTypeCache(x)); } @@ -463,7 +584,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentTypeServiceDeletedContentType(IContentTypeService sender, DeleteEventArgs e) + static void ContentTypeService_DeletedContentType(IContentTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(contentType => DistributedCache.Instance.RemoveContentTypeCache(contentType)); } @@ -473,7 +594,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void MemberTypeServiceDeleted(IMemberTypeService sender, DeleteEventArgs e) + static void MemberTypeService_Deleted(IMemberTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(contentType => DistributedCache.Instance.RemoveMemberTypeCache(contentType)); } @@ -483,7 +604,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentTypeServiceSavedMediaType(IContentTypeService sender, SaveEventArgs e) + static void ContentTypeService_SavedMediaType(IContentTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMediaTypeCache(x)); } @@ -493,7 +614,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentTypeServiceSavedContentType(IContentTypeService sender, SaveEventArgs e) + static void ContentTypeService_SavedContentType(IContentTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(contentType => DistributedCache.Instance.RefreshContentTypeCache(contentType)); } @@ -503,7 +624,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void MemberTypeServiceSaved(IMemberTypeService sender, SaveEventArgs e) + static void MemberTypeService_Saved(IMemberTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMemberTypeCache(x)); } @@ -512,7 +633,8 @@ namespace Umbraco.Web.Cache #endregion #region User/permissions event handlers - + + //fixme: this isn't named correct static void CacheRefresherEventHandler_AssignedPermissions(PermissionRepository sender, SaveEventArgs e) { var userIds = e.SavedEntities.Select(x => x.UserId).Distinct(); @@ -534,12 +656,12 @@ namespace Umbraco.Web.Cache InvalidateCacheForPermissionsChange(sender); } - static void UserServiceSavedUser(IUserService sender, SaveEventArgs e) + static void UserService_SavedUser(IUserService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserCache(x.Id)); } - static void UserServiceDeletedUser(IUserService sender, DeleteEventArgs e) + static void UserService_DeletedUser(IUserService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserCache(x.Id)); } @@ -569,7 +691,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void FileServiceDeletedTemplate(IFileService sender, DeleteEventArgs e) + static void FileService_DeletedTemplate(IFileService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveTemplateCache(x.Id)); } @@ -579,7 +701,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void FileServiceSavedTemplate(IFileService sender, SaveEventArgs e) + static void FileService_SavedTemplate(IFileService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshTemplateCache(x.Id)); } @@ -588,7 +710,7 @@ namespace Umbraco.Web.Cache #region Macro event handlers - void MacroServiceDeleted(IMacroService sender, DeleteEventArgs e) + static void MacroService_Deleted(IMacroService sender, DeleteEventArgs e) { foreach (var entity in e.DeletedEntities) { @@ -596,7 +718,7 @@ namespace Umbraco.Web.Cache } } - void MacroServiceSaved(IMacroService sender, SaveEventArgs e) + static void MacroService_Saved(IMacroService sender, SaveEventArgs e) { foreach (var entity in e.SavedEntities) { @@ -608,7 +730,7 @@ namespace Umbraco.Web.Cache #region Media event handlers - static void MediaServiceEmptiedRecycleBin(IMediaService sender, RecycleBinEventArgs e) + static void MediaService_EmptiedRecycleBin(IMediaService sender, RecycleBinEventArgs e) { if (e.RecycleBinEmptiedSuccessfully && e.IsMediaRecycleBin) { @@ -616,22 +738,22 @@ namespace Umbraco.Web.Cache } } - static void MediaServiceTrashed(IMediaService sender, MoveEventArgs e) + static void MediaService_Trashed(IMediaService sender, MoveEventArgs e) { DistributedCache.Instance.RemoveMediaCacheAfterRecycling(e.MoveInfoCollection.ToArray()); } - static void MediaServiceMoved(IMediaService sender, MoveEventArgs e) + static void MediaService_Moved(IMediaService sender, MoveEventArgs e) { DistributedCache.Instance.RefreshMediaCacheAfterMoving(e.MoveInfoCollection.ToArray()); } - static void MediaServiceDeleted(IMediaService sender, DeleteEventArgs e) + static void MediaService_Deleted(IMediaService sender, DeleteEventArgs e) { DistributedCache.Instance.RemoveMediaCachePermanently(e.DeletedEntities.Select(x => x.Id).ToArray()); } - static void MediaServiceSaved(IMediaService sender, SaveEventArgs e) + static void MediaService_Saved(IMediaService sender, SaveEventArgs e) { DistributedCache.Instance.RefreshMediaCache(e.SavedEntities.ToArray()); } @@ -639,12 +761,12 @@ namespace Umbraco.Web.Cache #region Member event handlers - static void MemberServiceDeleted(IMemberService sender, DeleteEventArgs e) + static void MemberService_Deleted(IMemberService sender, DeleteEventArgs e) { DistributedCache.Instance.RemoveMemberCache(e.DeletedEntities.ToArray()); } - static void MemberServiceSaved(IMemberService sender, SaveEventArgs e) + static void MemberService_Saved(IMemberService sender, SaveEventArgs e) { DistributedCache.Instance.RefreshMemberCache(e.SavedEntities.ToArray()); } @@ -672,14 +794,14 @@ namespace Umbraco.Web.Cache #region Relation type event handlers - private static void RelationType_Saved(IRelationService sender, SaveEventArgs args) + static void RelationService_SavedRelationType(IRelationService sender, SaveEventArgs args) { var dc = DistributedCache.Instance; foreach (var e in args.SavedEntities) dc.RefreshRelationTypeCache(e.Id); } - private static void RelationType_Deleted(IRelationService sender, DeleteEventArgs args) + static void RelationService_DeletedRelationType(IRelationService sender, DeleteEventArgs args) { var dc = DistributedCache.Instance; foreach (var e in args.DeletedEntities) @@ -687,5 +809,60 @@ namespace Umbraco.Web.Cache } #endregion + + + /// + /// This will inspect the event metadata and execute it's affiliated handler if one is found + /// + /// + internal static void HandleEvents(IEnumerable events) + { + foreach (var e in events) + { + var handler = FindHandler(e); + if (handler == null) continue; + + handler.Invoke(null, new object[] { e.Sender, e.Args }); + } + } + + /// + /// Used to cache all candidate handlers + /// + private static readonly Lazy CandidateHandlers = new Lazy(() => + { + var candidates = + + typeof(CacheRefresherEventHandler).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.Name.Contains("_")) + .Select(x => new + { + method = x, + nameParts = x.Name.Split(new[] { "_" }, StringSplitOptions.RemoveEmptyEntries), + methodParams = x.GetParameters() + }) + .Where(x => x.nameParts.Length == 2 && x.methodParams.Length == 2 && typeof(EventArgs).IsAssignableFrom(x.methodParams[1].ParameterType)) + .Select(x => x.method) + .ToArray(); + + return candidates; + }); + + /// + /// Used to cache all found event handlers + /// + private static readonly ConcurrentDictionary FoundHandlers = new ConcurrentDictionary(); + + internal static MethodInfo FindHandler(IEventDefinition eventDefinition) + { + return FoundHandlers.GetOrAdd(eventDefinition, definition => + { + var candidates = CandidateHandlers.Value; + + var found = candidates.FirstOrDefault(x => x.Name == string.Format("{0}_{1}", eventDefinition.Sender.GetType().Name, eventDefinition.EventName)); + + return found; + }); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 246571d479..f3520aeed2 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -58,7 +58,7 @@ namespace Umbraco.Web.Cache : (contentType is IMediaType) ? typeof(IMediaType).Name : typeof(IMemberType).Name, - DescendantPayloads = contentType.Descendants().Select(x => FromContentType(x)).ToArray(), + DescendantPayloads = contentType.Descendants(ApplicationContext.Current.Services.ContentTypeService).Select(x => FromContentType(x)).ToArray(), WasDeleted = isDeleted, PropertyRemoved = contentType.WasPropertyDirty("HasPropertyTypeBeenRemoved"), AliasChanged = contentType.WasPropertyDirty("Alias"), @@ -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/Cache/PageCacheRefresher.cs b/src/Umbraco.Web/Cache/PageCacheRefresher.cs index 75118a6748..fc6e0c8d20 100644 --- a/src/Umbraco.Web/Cache/PageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PageCacheRefresher.cs @@ -74,7 +74,7 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); - content.Instance.ClearDocumentCache(id); + content.Instance.ClearDocumentCache(id, false); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); ClearAllIsolatedCacheByEntityType(); @@ -95,7 +95,7 @@ namespace Umbraco.Web.Cache public override void Remove(IContent instance) { ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); - content.Instance.ClearDocumentCache(new Document(instance)); + content.Instance.ClearDocumentCache(new Document(instance), false); XmlPublishedContent.ClearRequest(); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index e86e48a18b..d446df5683 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -11,11 +11,11 @@ namespace Umbraco.Web.Controllers public class UmbLoginController : SurfaceController { [HttpPost] - public ActionResult HandleLogin([Bind(Prefix="loginModel")]LoginModel model) + public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model) { if (ModelState.IsValid == false) { - return CurrentUmbracoPage(); + return CurrentUmbracoPage(); } if (Members.Login(model.Username, model.Password) == false) @@ -30,11 +30,20 @@ namespace Umbraco.Web.Controllers //if there is a specified path to redirect to then use it if (model.RedirectUrl.IsNullOrWhiteSpace() == false) { - return Redirect(model.RedirectUrl); + // validate the redirect url + if (Url.IsLocalUrl(model.RedirectUrl)) + { + return Redirect(model.RedirectUrl); + } + else + { + // if it's not a local url we'll redirect to the root of the current site + return Redirect(base.CurrentPage.Site().Url); + } } //redirect to current page by default - + return RedirectToCurrentUmbracoPage(); //return RedirectToCurrentUmbracoUrl(); } diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs index fdb4145160..cb53713e3c 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs +++ b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs @@ -137,7 +137,7 @@ namespace Umbraco.Web.Dictionary { //ensure it's stored/retrieved from request cache //NOTE: This is no longer necessary since these are cached at the runtime level, but we can leave it here for now. - return _requestCacheProvider.GetCacheItem(typeof (DefaultCultureDictionary).Name + "Culture", + return _requestCacheProvider.GetCacheItem(typeof (DefaultCultureDictionary).Name + "Culture" + Culture.Name, () => _localizationService.GetLanguageByIsoCode(Culture.Name)); } } diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 14dbdac1d4..0489fcbb70 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -139,18 +139,7 @@ namespace Umbraco.Web.Editors //get the user var user = Security.GetBackOfficeUser(loginModel.Username); - var userDetail = Mapper.Map(user); - //update the userDetail and set their remaining seconds - userDetail.SecondsUntilTimeout = TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes).TotalSeconds; - - //create a response with the userDetail object - var response = Request.CreateResponse(HttpStatusCode.OK, userDetail); - - //ensure the user is set for the current request - Request.SetPrincipalForRequest(user); - - return response; - + return SetPrincipalAndReturnUserDetail(user); case SignInStatus.RequiresVerification: var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions; @@ -161,7 +150,7 @@ namespace Umbraco.Web.Editors HttpStatusCode.BadRequest, "UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions))); } - + var twofactorView = twofactorOptions.GetTwoFactorView( TryGetOwinContext().Result, UmbracoContext, @@ -175,10 +164,13 @@ namespace Umbraco.Web.Editors typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string")); } + var attemptedUser = Security.GetBackOfficeUser(loginModel.Username); + //create a with information to display a custom two factor send code view - var verifyResponse = Request.CreateResponse(HttpStatusCode.OK, new + var verifyResponse = Request.CreateResponse(HttpStatusCode.PaymentRequired, new { - twoFactorView = twofactorView + twoFactorView = twofactorView, + userId = attemptedUser.Id }); return verifyResponse; @@ -233,25 +225,74 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK); } - private string ConstructCallbackUrl(int userId, string code) + /// + /// Used to retrived the 2FA providers for code submission + /// + /// + [SetAngularAntiForgeryTokens] + public async Task> Get2FAProviders() { - // Get an mvc helper to get the url - var http = EnsureHttpContext(); - var urlHelper = new UrlHelper(http.Request.RequestContext); - var action = urlHelper.Action("ValidatePasswordResetCode", "BackOffice", - new - { - area = GlobalSettings.UmbracoMvcArea, - u = userId, - r = code - }); + var userId = await SignInManager.GetVerifiedUserIdAsync(); + if (userId < 0) + { + Logger.Warn("Get2FAProviders :: No verified user found, returning 404"); + throw new HttpResponseException(HttpStatusCode.NotFound); + } + var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId); + return userFactors; + } + + [SetAngularAntiForgeryTokens] + public async Task PostSend2FACode([FromBody]string provider) + { + if (provider.IsNullOrWhiteSpace()) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var userId = await SignInManager.GetVerifiedUserIdAsync(); + if (userId < 0) + { + Logger.Warn("Get2FAProviders :: No verified user found, returning 404"); + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + // Generate the token and send it + if (await SignInManager.SendTwoFactorCodeAsync(provider) == false) + { + return BadRequest("Invalid code"); + } + return Ok(); + } + + [SetAngularAntiForgeryTokens] + public async Task PostVerify2FACode(Verify2FACodeModel model) + { + if (ModelState.IsValid == false) + { + return Request.CreateValidationErrorResponse(ModelState); + } + + var userName = await SignInManager.GetVerifiedUserNameAsync(); + if (userName == null) + { + Logger.Warn("Get2FAProviders :: No verified user found, returning 404"); + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: true, rememberBrowser: false); + switch (result) + { + case SignInStatus.Success: + //get the user + var user = Security.GetBackOfficeUser(userName); + return SetPrincipalAndReturnUserDetail(user); + case SignInStatus.LockedOut: + return Request.CreateValidationErrorResponse("User is locked out"); + case SignInStatus.Failure: + default: + return Request.CreateValidationErrorResponse("Invalid code"); + } + } - // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = new Uri(ApplicationContext.UmbracoApplicationUrl); - var callbackUri = new Uri(applicationUri, action); - return callbackUri.ToString(); - } - /// /// Processes a set password request. Validates the request and sets a new password. /// @@ -269,13 +310,6 @@ namespace Umbraco.Web.Editors result.Errors.Any() ? result.Errors.First() : "Set password failed"); } - private HttpContextBase EnsureHttpContext() - { - var attempt = this.TryGetHttpContext(); - if (attempt.Success == false) - throw new InvalidOperationException("This method requires that an HttpContext be active"); - return attempt.Result; - } /// /// Logs the current user out @@ -296,6 +330,59 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK); } + + /// + /// This is used when the user is auth'd successfully and we need to return an OK with user details along with setting the current Principal in the request + /// + /// + /// + private HttpResponseMessage SetPrincipalAndReturnUserDetail(IUser user) + { + if (user == null) throw new ArgumentNullException("user"); + + var userDetail = Mapper.Map(user); + //update the userDetail and set their remaining seconds + userDetail.SecondsUntilTimeout = TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes).TotalSeconds; + + //create a response with the userDetail object + var response = Request.CreateResponse(HttpStatusCode.OK, userDetail); + + //ensure the user is set for the current request + Request.SetPrincipalForRequest(user); + + return response; + } + + private string ConstructCallbackUrl(int userId, string code) + { + // Get an mvc helper to get the url + var http = EnsureHttpContext(); + var urlHelper = new UrlHelper(http.Request.RequestContext); + var action = urlHelper.Action("ValidatePasswordResetCode", "BackOffice", + new + { + area = GlobalSettings.UmbracoMvcArea, + u = userId, + r = code + }); + + // Construct full URL using configured application URL (which will fall back to request) + var applicationUri = new Uri(ApplicationContext.UmbracoApplicationUrl); + var callbackUri = new Uri(applicationUri, action); + return callbackUri.ToString(); + } + + + private HttpContextBase EnsureHttpContext() + { + var attempt = this.TryGetHttpContext(); + if (attempt.Success == false) + throw new InvalidOperationException("This method requires that an HttpContext be active"); + return attempt.Result; + } + + + private void AddModelErrors(IdentityResult result, string prefix = "") { foreach (var error in result.Errors) diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 199ffd9bed..376606ff5c 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -332,6 +332,10 @@ namespace Umbraco.Web.Editors "tagApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( controller => controller.GetAllTags(null)) }, + { + "templateApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetById(0)) + }, { "memberTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( controller => controller.GetNodes("-1", null)) @@ -359,6 +363,14 @@ namespace Umbraco.Web.Editors { "healthCheckBaseUrl", Url.GetUmbracoApiServiceBaseUrl( controller => controller.GetAllHealthChecks()) + }, + { + "templateQueryApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.PostTemplateQuery(null)) + }, + { + "codeFileApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetByPath("", "")) } } }, diff --git a/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs index f4350bc596..0910ec936e 100644 --- a/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs @@ -1,3 +1,4 @@ +using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Editors @@ -8,6 +9,7 @@ namespace Umbraco.Web.Editors /// currently in the request. /// [AppendCurrentEventMessages] + [PrefixlessBodyModelValidator] public abstract class BackOfficeNotificationsController : UmbracoAuthorizedJsonController { protected BackOfficeNotificationsController() diff --git a/src/Umbraco.Web/Editors/CodeFileController.cs b/src/Umbraco.Web/Editors/CodeFileController.cs new file mode 100644 index 0000000000..b8b2898c26 --- /dev/null +++ b/src/Umbraco.Web/Editors/CodeFileController.cs @@ -0,0 +1,477 @@ +using AutoMapper; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Web.Http; +using ClientDependency.Core; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Editors +{ + [PluginController("UmbracoApi")] + [PrefixlessBodyModelValidator] + [UmbracoApplicationAuthorizeAttribute(Core.Constants.Applications.Settings)] + public class CodeFileController : BackOfficeNotificationsController + { + + /// + /// Used to create a brand new file + /// + /// This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' + /// + /// Will return a simple 200 if file creation succeeds + [ValidationFilter] + public HttpResponseMessage PostCreate(string type, CodeFileDisplay display) + { + switch (type) + { + case Core.Constants.Trees.PartialViews: + var view = new PartialView(display.VirtualPath); + view.Content = display.Content; + var result = Services.FileService.CreatePartialView(view, display.Snippet, Security.CurrentUser.Id); + return result.Success == true ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + + case Core.Constants.Trees.PartialViewMacros: + var viewMacro = new PartialView(display.VirtualPath); + viewMacro.Content = display.Content; + var resultMacro = Services.FileService.CreatePartialViewMacro(viewMacro, display.Snippet, Security.CurrentUser.Id); + return resultMacro.Success == true ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateNotificationValidationErrorResponse(resultMacro.Exception.Message); + + case Core.Constants.Trees.Scripts: + var script = new Script(display.VirtualPath); + Services.FileService.SaveScript(script, Security.CurrentUser.Id); + return Request.CreateResponse(HttpStatusCode.OK); + + default: + return Request.CreateResponse(HttpStatusCode.NotFound); + } + } + + /// + /// Used to create a container/folder in 'partialViews', 'partialViewMacros' or 'scripts' + /// + /// 'partialViews', 'partialViewMacros' or 'scripts' + /// The virtual path of the parent. + /// The name of the container/folder + /// + [HttpPost] + public CodeFileDisplay PostCreateContainer(string type, string parentId, string name) + { + if (string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(name)) + { + throw new HttpResponseException(HttpStatusCode.BadRequest); + } + + // if the parentId is root (-1) then we just need an empty string as we are + // creating the path below and we don't wan't -1 in the path + if (parentId == Core.Constants.System.Root.ToInvariantString()) + { + parentId = string.Empty; + } + + name = System.Web.HttpUtility.UrlDecode(name); + + if (parentId.IsNullOrWhiteSpace() == false) + { + parentId = System.Web.HttpUtility.UrlDecode(parentId); + name = parentId.EnsureEndsWith("/") + name; + } + + var virtualPath = string.Empty; + switch (type) + { + case Core.Constants.Trees.PartialViews: + virtualPath = NormalizeVirtualPath(name, SystemDirectories.PartialViews); + Services.FileService.CreatePartialViewFolder(virtualPath); + break; + case Core.Constants.Trees.PartialViewMacros: + virtualPath = NormalizeVirtualPath(name, SystemDirectories.MacroPartials); + Services.FileService.CreatePartialViewMacroFolder(virtualPath); + break; + case Core.Constants.Trees.Scripts: + virtualPath = NormalizeVirtualPath(name, SystemDirectories.Scripts); + Services.FileService.CreateScriptFolder(virtualPath); + break; + + } + + return new CodeFileDisplay + { + VirtualPath = virtualPath, + Path = Url.GetTreePathFromFilePath(virtualPath) + }; + } + + /// + /// Used to get a specific file from disk via the FileService + /// + /// This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' + /// The filename or urlencoded path of the file to open + /// The file and its contents from the virtualPath + public CodeFileDisplay GetByPath(string type, string virtualPath) + { + if (string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(virtualPath)) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath); + + switch (type) + { + case Core.Constants.Trees.PartialViews: + var view = Services.FileService.GetPartialView(virtualPath); + if (view != null) + { + var display = Mapper.Map(view); + display.FileType = Core.Constants.Trees.PartialViews; + display.Path = Url.GetTreePathFromFilePath(view.Path); + display.Id = System.Web.HttpUtility.UrlEncode(view.Path); + return display; + } + return null; + + case Core.Constants.Trees.PartialViewMacros: + var viewMacro = Services.FileService.GetPartialViewMacro(virtualPath); + if (viewMacro != null) + { + var display = Mapper.Map(viewMacro); + display.FileType = Core.Constants.Trees.PartialViewMacros; + display.Path = Url.GetTreePathFromFilePath(viewMacro.Path); + display.Id = System.Web.HttpUtility.UrlEncode(viewMacro.Path); + return display; + } + return null; + + case Core.Constants.Trees.Scripts: + var script = Services.FileService.GetScriptByName(virtualPath); + if (script != null) + { + var display = Mapper.Map(script); + display.FileType = Core.Constants.Trees.Scripts; + display.Path = Url.GetTreePathFromFilePath(script.Path); + display.Id = System.Web.HttpUtility.UrlEncode(script.Path); + return display; + } + return null; + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Used to get a list of available templates/snippets to base a new Partial View og Partial View Macro from + /// + /// This is a string but will be 'partialViews', 'partialViewMacros' + /// Returns a list of if a correct type is sent + public IEnumerable GetSnippets(string type) + { + if (string.IsNullOrWhiteSpace(type)) + { + throw new HttpResponseException(HttpStatusCode.BadRequest); + } + + IEnumerable snippets; + switch (type) + { + case Core.Constants.Trees.PartialViews: + snippets = Services.FileService.GetPartialViewSnippetNames( + //ignore these - (this is taken from the logic in "PartialView.ascx.cs") + "Gallery", + "ListChildPagesFromChangeableSource", + "ListChildPagesOrderedByProperty", + "ListImagesFromMediaFolder"); + break; + case Core.Constants.Trees.PartialViewMacros: + snippets = Services.FileService.GetPartialViewSnippetNames(); + break; + default: + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + return snippets.Select(snippet => new SnippetDisplay() {Name = snippet.SplitPascalCasing().ToFirstUpperInvariant(), FileName = snippet}); + } + + /// + /// Used to scaffold the json object for the editors for 'scripts', 'partialViews', 'partialViewMacros' + /// + /// This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' + /// + /// + /// + public CodeFileDisplay GetScaffold(string type, string id = null, string snippetName = null) + { + if (string.IsNullOrWhiteSpace(type)) + { + throw new HttpResponseException(HttpStatusCode.BadRequest); + } + + if (id.IsNullOrWhiteSpace()) + id = string.Empty; + + CodeFileDisplay codeFileDisplay; + + switch (type) + { + case Core.Constants.Trees.PartialViews: + codeFileDisplay = Mapper.Map(new PartialView(string.Empty)); + codeFileDisplay.VirtualPath = SystemDirectories.PartialViews; + if (snippetName.IsNullOrWhiteSpace() == false) + codeFileDisplay.Content = Services.FileService.GetPartialViewSnippetContent(snippetName); + break; + case Core.Constants.Trees.PartialViewMacros: + codeFileDisplay = Mapper.Map(new PartialView(string.Empty)); + codeFileDisplay.VirtualPath = SystemDirectories.MacroPartials; + if (snippetName.IsNullOrWhiteSpace() == false) + codeFileDisplay.Content = Services.FileService.GetPartialViewMacroSnippetContent(snippetName); + break; + case Core.Constants.Trees.Scripts: + codeFileDisplay = Mapper.Map(new Script(string.Empty)); + codeFileDisplay.VirtualPath = SystemDirectories.Scripts; + break; + default: + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Unsupported editortype")); + } + + // Make sure that the root virtual path ends with '/' + codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.EnsureEndsWith("/"); + + if (id.IsNullOrWhiteSpace() == false && id != Core.Constants.System.Root.ToInvariantString()) + { + codeFileDisplay.VirtualPath += id.TrimStart("/").EnsureEndsWith("/"); + } + + codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.TrimStart("~"); + codeFileDisplay.Path = Url.GetTreePathFromFilePath(id); + codeFileDisplay.FileType = type; + + return codeFileDisplay; + } + + /// + /// Used to delete a specific file from disk via the FileService + /// + /// This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' + /// The filename or urlencoded path of the file to delete + /// Will return a simple 200 if file deletion succeeds + [HttpDelete] + [HttpPost] + public HttpResponseMessage Delete(string type, string virtualPath) + { + if (string.IsNullOrWhiteSpace(type) == false && string.IsNullOrWhiteSpace(virtualPath) == false) + { + virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath); + + switch (type) + { + case Core.Constants.Trees.PartialViews: + if (IsDirectory(virtualPath, SystemDirectories.PartialViews)) + { + Services.FileService.DeletePartialViewFolder(virtualPath); + return Request.CreateResponse(HttpStatusCode.OK); + } + if (Services.FileService.DeletePartialView(virtualPath, Security.CurrentUser.Id)) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View or folder found with the specified path"); + + case Core.Constants.Trees.PartialViewMacros: + if (IsDirectory(virtualPath, SystemDirectories.MacroPartials)) + { + Services.FileService.DeletePartialViewMacroFolder(virtualPath); + return Request.CreateResponse(HttpStatusCode.OK); + } + if (Services.FileService.DeletePartialViewMacro(virtualPath, Security.CurrentUser.Id)) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View Macro or folder found with the specified path"); + + case Core.Constants.Trees.Scripts: + if (IsDirectory(virtualPath, SystemDirectories.Scripts)) + { + Services.FileService.DeleteScriptFolder(virtualPath); + return Request.CreateResponse(HttpStatusCode.OK); + } + if (Services.FileService.GetScriptByName(virtualPath) != null) + { + Services.FileService.DeleteScript(virtualPath, Security.CurrentUser.Id); + return Request.CreateResponse(HttpStatusCode.OK); + } + return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Script or folder found with the specified path"); + + default: + return Request.CreateResponse(HttpStatusCode.NotFound); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Used to create or update a 'partialview', 'partialviewmacro' or 'script' file + /// + /// + /// The updated CodeFileDisplay model + public CodeFileDisplay PostSave(CodeFileDisplay display) + { + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + if (display == null || string.IsNullOrWhiteSpace(display.FileType)) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + switch (display.FileType) + { + case Core.Constants.Trees.PartialViews: + var partialViewResult = CreateOrUpdatePartialView(display); + if (partialViewResult.Success) + { + display = Mapper.Map(partialViewResult.Result, display); + display.Path = Url.GetTreePathFromFilePath(partialViewResult.Result.Path); + display.Id = System.Web.HttpUtility.UrlEncode(partialViewResult.Result.Path); + return display; + } + + display.AddErrorNotification( + Services.TextService.Localize("speechBubbles/partialViewErrorHeader"), + Services.TextService.Localize("speechBubbles/partialViewErrorText")); + break; + + case Core.Constants.Trees.PartialViewMacros: + var partialViewMacroResult = CreateOrUpdatePartialViewMacro(display); + if (partialViewMacroResult.Success) + { + display = Mapper.Map(partialViewMacroResult.Result, display); + display.Path = Url.GetTreePathFromFilePath(partialViewMacroResult.Result.Path); + display.Id = System.Web.HttpUtility.UrlEncode(partialViewMacroResult.Result.Path); + return display; + } + + display.AddErrorNotification( + Services.TextService.Localize("speechBubbles/partialViewErrorHeader"), + Services.TextService.Localize("speechBubbles/partialViewErrorText")); + break; + + case Core.Constants.Trees.Scripts: + var virtualPath = display.VirtualPath; + var script = Services.FileService.GetScriptByName(display.VirtualPath); + if (script != null) + { + script.Path = display.Name; + display = Mapper.Map(script, display); + display.Path = Url.GetTreePathFromFilePath(script.Path); + display.Id = System.Web.HttpUtility.UrlEncode(script.Path); + return display; + + } + else + { + var fileName = EnsurePartialViewExtension(display.Name, ".js"); + script = new Script(virtualPath + fileName); + } + + script.Content = display.Content; + + Services.FileService.SaveScript(script, Security.CurrentUser.Id); + break; + + default: + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + return display; + } + + private Attempt CreateOrUpdatePartialView(CodeFileDisplay display) + { + Attempt partialViewResult; + string virtualPath = NormalizeVirtualPath(display.VirtualPath, SystemDirectories.PartialViews); + var view = Services.FileService.GetPartialView(virtualPath); + if (view != null) + { + // might need to find the path + var orgPath = view.OriginalPath.Substring(0, view.OriginalPath.IndexOf(view.Name)); + view.Path = orgPath + display.Name; + + view.Content = display.Content; + partialViewResult = Services.FileService.SavePartialView(view, Security.CurrentUser.Id); + } + else + { + var fileName = EnsurePartialViewExtension(display.Name, ".cshtml"); + view = new PartialView(virtualPath + fileName); + view.Content = display.Content; + partialViewResult = Services.FileService.CreatePartialView(view, display.Snippet, Security.CurrentUser.Id); + } + + return partialViewResult; + } + + private string NormalizeVirtualPath(string virtualPath, string systemDirectory) + { + if (virtualPath.IsNullOrWhiteSpace()) + return string.Empty; + + systemDirectory = systemDirectory.TrimStart("~"); + systemDirectory = systemDirectory.Replace('\\', '/'); + virtualPath = virtualPath.TrimStart("~"); + virtualPath = virtualPath.Replace('\\', '/'); + virtualPath = virtualPath.ReplaceFirst(systemDirectory, string.Empty); + + return virtualPath; + } + + private Attempt CreateOrUpdatePartialViewMacro(CodeFileDisplay display) + { + Attempt partialViewMacroResult; + var virtualPath = display.VirtualPath ?? string.Empty; + var viewMacro = Services.FileService.GetPartialViewMacro(virtualPath); + if (viewMacro != null) + { + viewMacro.Content = display.Content; + viewMacro.Path = display.Name; + partialViewMacroResult = Services.FileService.SavePartialViewMacro(viewMacro, Security.CurrentUser.Id); + } + else + { + var fileName = EnsurePartialViewExtension(display.Name, ".cshtml"); + viewMacro = new PartialView(virtualPath + fileName); + viewMacro.Content = display.Content; + partialViewMacroResult = Services.FileService.CreatePartialViewMacro(viewMacro, display.Snippet, Security.CurrentUser.Id); + } + + return partialViewMacroResult; + } + + private string EnsurePartialViewExtension(string value, string extension) + { + if (value.EndsWith(extension) == false) + value += extension; + + return value; + } + + private bool IsDirectory(string virtualPath, string systemDirectory) + { + var path = IOHelper.MapPath(systemDirectory + "/" + virtualPath); + var dirInfo = new DirectoryInfo(path); + return dirInfo.Attributes == FileAttributes.Directory; + } + } +} diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 58494369f5..ed0ac95a2b 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -4,11 +4,9 @@ using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Formatting; using System.Text; using System.Web.Http; using System.Web.Http.ModelBinding; -using System.Web.Http.ModelBinding.Binders; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Logging; @@ -17,24 +15,15 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Publishing; using Umbraco.Core.Services; -using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; -using Umbraco.Web.Security; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; -using umbraco; -using Umbraco.Core.Models; -using Umbraco.Core.Dynamics; -using umbraco.BusinessLogic.Actions; using umbraco.cms.businesslogic.web; using umbraco.presentation.preview; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.UI; using Constants = Umbraco.Core.Constants; -using Notification = Umbraco.Web.Models.ContentEditing.Notification; namespace Umbraco.Web.Editors { diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 006705c48a..6b8d570e5f 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -88,38 +88,41 @@ namespace Umbraco.Web.Editors where TPersisted : IContentBase { //Map the property values - foreach (var p in contentItem.ContentDto.Properties) + foreach (var property in contentItem.ContentDto.Properties) { //get the dbo property - var dboProperty = contentItem.PersistedContent.Properties[p.Alias]; + var dboProperty = contentItem.PersistedContent.Properties[property.Alias]; //create the property data to send to the property editor - var d = new Dictionary(); + var dictionary = new Dictionary(); //add the files if any - var files = contentItem.UploadedFiles.Where(x => x.PropertyAlias == p.Alias).ToArray(); + var files = contentItem.UploadedFiles.Where(x => x.PropertyAlias == property.Alias).ToArray(); if (files.Length > 0) { - d.Add("files", files); - // add extra things needed to figure out where to put the files - d.Add("cuid", contentItem.PersistedContent.Key); - d.Add("puid", dboProperty.PropertyType.Key); + dictionary.Add("files", files); } - - var data = new ContentPropertyData(p.Value, p.PreValues, d); + foreach (var file in files) + file.FileName = file.FileName.ToSafeFileName(); + + // add extra things needed to figure out where to put the files + dictionary.Add("cuid", contentItem.PersistedContent.Key); + dictionary.Add("puid", dboProperty.PropertyType.Key); + + var data = new ContentPropertyData(property.Value, property.PreValues, dictionary); //get the deserialized value from the property editor - if (p.PropertyEditor == null) + if (property.PropertyEditor == null) { - LogHelper.Warn("No property editor found for property " + p.Alias); + LogHelper.Warn("No property editor found for property " + property.Alias); } else { - var valueEditor = p.PropertyEditor.ValueEditor; + var valueEditor = property.PropertyEditor.ValueEditor; //don't persist any bound value if the editor is readonly if (valueEditor.IsReadOnly == false) { - var propVal = p.PropertyEditor.ValueEditor.ConvertEditorToDb(data, dboProperty.Value); - var supportTagsAttribute = TagExtractor.GetAttribute(p.PropertyEditor); + var propVal = property.PropertyEditor.ValueEditor.ConvertEditorToDb(data, dboProperty.Value); + var supportTagsAttribute = TagExtractor.GetAttribute(property.PropertyEditor); if (supportTagsAttribute != null) { TagExtractor.SetPropertyTags(dboProperty, data, propVal, supportTagsAttribute); diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 45a2821130..d6e6cbccf4 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -106,16 +106,7 @@ namespace Umbraco.Web.Editors /// Returns the avilable compositions for this content type /// This has been wrapped in a dto instead of simple parameters to support having multiple parameters in post request body /// - /// - /// - /// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out - /// along with any content types that have matching property types that are included in the filtered content types - /// - /// - /// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out. - /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot - /// be looked up via the db, they need to be passed in. - /// + /// /// [HttpPost] public HttpResponseMessage GetAvailableCompositeContentTypes(GetAvailableCompositionsFilter filter) diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 931d00968b..b3e306a48c 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes)] [EnableOverrideAuthorization] - public class DataTypeController : UmbracoAuthorizedJsonController + public class DataTypeController : BackOfficeNotificationsController { /// /// Gets data type by name diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 596e27e3a5..f5af30a57f 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.Net; using System.Text; @@ -17,6 +18,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; @@ -28,6 +30,8 @@ using Examine.SearchCriteria; using Umbraco.Web.Dynamics; using umbraco; using System.Text.RegularExpressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using System.Web.Http.Controllers; using Umbraco.Core.Xml; namespace Umbraco.Web.Editors @@ -42,6 +46,25 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class EntityController : UmbracoAuthorizedJsonController { + + /// + /// Configures this controller with a custom action selector + /// + private class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + + //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" + //id is passed in eventually we'll probably want to support GUID + Udi too + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), + + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); + } + } + /// /// Returns an Umbraco alias given a string /// @@ -143,13 +166,48 @@ namespace Umbraco.Web.Editors return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); } - + /// - /// Gets an entity by it's unique id if the entity supports that + /// 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) + }; + } + + [Obsolete("Use GetyById instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public EntityBasic GetByKey(Guid id, UmbracoEntityTypes type) { return GetResultForKey(id, type); @@ -164,7 +222,7 @@ namespace Umbraco.Web.Editors /// public EntityBasic GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) { - //TODO: Rename this!!! It's a bit misleading, it should be GetByXPath + //TODO: Rename this!!! It's misleading, it should be GetByXPath if (type != UmbracoEntityTypes.Document) @@ -193,13 +251,61 @@ namespace Umbraco.Web.Editors }, publishedContentExists: i => Umbraco.TypedContent(i) != null); } - + + #region GetById + + /// + /// Gets an entity by it's id + /// + /// + /// + /// public EntityBasic GetById(int id, UmbracoEntityTypes type) { return GetResultForId(id, type); } - public IEnumerable GetByIds([FromUri]int[] ids, UmbracoEntityTypes type) + /// + /// Gets an entity by it's key + /// + /// + /// + /// + public EntityBasic GetById(Guid id, UmbracoEntityTypes type) + { + return GetResultForKey(id, type); + } + + /// + /// Gets an entity by it's UDI + /// + /// + /// + /// + public EntityBasic GetById(Udi id, UmbracoEntityTypes type) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetResultForKey(guidUdi.Guid, type); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + #region GetByIds + /// + /// Get entities by integer ids + /// + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) { if (ids == null) { @@ -208,6 +314,66 @@ namespace Umbraco.Web.Editors return GetResultForIds(ids, type); } + /// + /// Get entities by GUID ids + /// + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForKeys(ids, type); + } + + /// + /// Get entities by UDIs + /// + /// + /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type! + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids. + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromUri]UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + if (ids.Length == 0) + { + return Enumerable.Empty(); + } + + //all udi types will need to be the same in this list so we'll determine by the first + //currently we only support GuidIdi for this method + + var guidUdi = ids[0] as GuidUdi; + if (guidUdi != null) + { + return GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type); + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + [Obsolete("Use GetyByIds instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetByKeys([FromUri]Guid[] ids, UmbracoEntityTypes type) { if (ids == null) @@ -222,6 +388,176 @@ namespace Umbraco.Web.Editors return GetResultForChildren(id, type); } + /// + /// Get paged child entities by id + /// + /// + /// + /// + /// + /// + /// + /// + /// + public PagedResult GetPagedChildren( + string id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + int intId; + + if (int.TryParse(id, out intId)) + { + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + } + + Guid guidId; + if (Guid.TryParse(id, out guidId)) + { + //Not supported currently + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + Udi udiId; + if (Udi.TryParse(id, out udiId)) + { + //Not supported currently + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //so we don't have an INT, GUID or UDI, it's just a string, so now need to check if it's a special id or a member type + if (id == Constants.Conventions.MemberTypes.AllMembersListId) + { + //the EntityService can search paged members from the root + + intId = -1; + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + } + + //the EntityService cannot search members of a certain type, this is currently not supported and would require + //quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search + + int total; + var searchResult = ExamineSearch(filter ?? "", type, pageSize, pageNumber - 1, out total, id); + + return new PagedResult(total, pageNumber, pageSize) + { + Items = searchResult + }; + } + + /// + /// Get paged child entities by id + /// + /// + /// + /// + /// + /// + /// + /// + /// + public PagedResult GetPagedChildren( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + if (pageNumber <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + if (pageSize <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + long totalRecords; + var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = entities.Select(Mapper.Map) + }; + + return pagedResult; + } + + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } + } + + public PagedResult GetPagedDescendants( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + if (pageNumber <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + if (pageSize <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + long totalRecords; + //if it's from root, don't return recycled + var entities = id == Constants.System.Root + ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed:false) + : Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = entities.Select(Mapper.Map) + }; + + return pagedResult; + } + + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } + } + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) { return GetResultForAncestors(id, type); @@ -237,12 +573,30 @@ namespace Umbraco.Web.Editors /// /// /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// + /// /// private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null) { + int total; + return ExamineSearch(query, entityType, 200, 0, out total, searchFrom); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// + /// + /// + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, int pageSize, int pageIndex, out int totalFound, string searchFrom = null) + { + //TODO: We need to update this to support paging + var sb = new StringBuilder(); string type; @@ -318,91 +672,113 @@ namespace Umbraco.Web.Editors query = Lucene.Net.QueryParsers.QueryParser.Escape(query); - if (query.IsNullOrWhiteSpace()) + //nothing to search + if (searchFrom.IsNullOrWhiteSpace() && query.IsNullOrWhiteSpace()) { + totalFound = 0; return new List(); } - //add back the surrounding quotes - query = string.Format("{0}{1}{0}", "\"", query); - - //node name exactly boost x 10 - sb.Append("+(__nodeName: ("); - sb.Append(query.ToLower()); - sb.Append(")^10.0 "); - - foreach (var f in fields) + //update the query with the query term + if (query.IsNullOrWhiteSpace() == false) { - //additional fields normally - sb.Append(f); - sb.Append(": ("); - sb.Append(query); + //add back the surrounding quotes + query = string.Format("{0}{1}{0}", "\"", query); + + //node name exactly boost x 10 + sb.Append("+(__nodeName: ("); + sb.Append(query.ToLower()); + sb.Append(")^10.0 "); + + foreach (var f in fields) + { + //additional fields normally + sb.Append(f); + sb.Append(": ("); + sb.Append(query); + sb.Append(") "); + } + sb.Append(") "); } } else { - if (query.Trim(new[] { '\"', '\'' }).IsNullOrWhiteSpace()) + var trimmed = query.Trim(new[] {'\"', '\''}); + + //nothing to search + if (searchFrom.IsNullOrWhiteSpace() && trimmed.IsNullOrWhiteSpace()) { + totalFound = 0; return new List(); } - - query = Lucene.Net.QueryParsers.QueryParser.Escape(query); - var querywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - - //node name exactly boost x 10 - sb.Append("+(__nodeName:"); - sb.Append("\""); - sb.Append(query.ToLower()); - sb.Append("\""); - sb.Append("^10.0 "); - - //node name normally with wildcards - sb.Append(" __nodeName:"); - sb.Append("("); - foreach (var w in querywords) + //update the query with the query term + if (trimmed.IsNullOrWhiteSpace() == false) { - sb.Append(w.ToLower()); - sb.Append("* "); - } - sb.Append(") "); + query = Lucene.Net.QueryParsers.QueryParser.Escape(query); + var querywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - foreach (var f in fields) - { - //additional fields normally - sb.Append(f); - sb.Append(":"); + //node name exactly boost x 10 + sb.Append("+(__nodeName:"); + sb.Append("\""); + sb.Append(query.ToLower()); + sb.Append("\""); + sb.Append("^10.0 "); + + //node name normally with wildcards + sb.Append(" __nodeName:"); sb.Append("("); foreach (var w in querywords) { sb.Append(w.ToLower()); sb.Append("* "); } - sb.Append(")"); - sb.Append(" "); + sb.Append(") "); + + + foreach (var f in fields) + { + //additional fields normally + sb.Append(f); + sb.Append(":"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + sb.Append(")"); + sb.Append(" "); + } + + sb.Append(") "); } } //must match index type - sb.Append(") +__IndexType:"); + sb.Append("+__IndexType:"); sb.Append(type); - var raw = internalSearcher.CreateSearchCriteria().RawQuery(sb.ToString()); - //limit results to 200 to avoid huge over processing (CPU) - var result = internalSearcher.Search(raw, 200); + var result = internalSearcher + //only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested + .Search(raw, pageSize * (pageIndex + 1)); + totalFound = result.TotalItemCount; + + var pagedResult = result.Skip(pageIndex); + switch (entityType) { case UmbracoEntityTypes.Member: - return MemberFromSearchResults(result); + return MemberFromSearchResults(pagedResult.ToArray()); case UmbracoEntityTypes.Media: - return MediaFromSearchResults(result); + return MediaFromSearchResults(pagedResult); case UmbracoEntityTypes.Document: - return ContentFromSearchResults(result); + return ContentFromSearchResults(pagedResult); default: throw new NotSupportedException("The " + typeof(EntityController) + " currently does not support searching against object type " + entityType); } @@ -413,7 +789,7 @@ namespace Umbraco.Web.Editors /// /// /// - private IEnumerable MemberFromSearchResults(ISearchResults results) + private IEnumerable MemberFromSearchResults(SearchResult[] results) { var mapped = Mapper.Map>(results).ToArray(); //add additional data @@ -447,7 +823,7 @@ namespace Umbraco.Web.Editors /// /// /// - private IEnumerable MediaFromSearchResults(ISearchResults results) + private IEnumerable MediaFromSearchResults(IEnumerable results) { var mapped = Mapper.Map>(results).ToArray(); //add additional data @@ -467,9 +843,9 @@ namespace Umbraco.Web.Editors /// /// /// - private IEnumerable ContentFromSearchResults(ISearchResults results) + private IEnumerable ContentFromSearchResults(IEnumerable results) { - var mapped = Mapper.Map>(results).ToArray(); + var mapped = Mapper.Map>(results).ToArray(); //add additional data foreach (var m in mapped) { @@ -601,21 +977,21 @@ namespace Umbraco.Web.Editors } } - private IEnumerable GetResultForKeys(IEnumerable keys, UmbracoEntityTypes entityType) + private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType) { - var keysArray = keys.ToArray(); - if (keysArray.Any() == false) return Enumerable.Empty(); + if (keys.Length == 0) + return Enumerable.Empty(); var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { - var entities = Services.EntityService.GetAll(objectType.Value, keysArray) + var entities = Services.EntityService.GetAll(objectType.Value, keys) .WhereNotNull() .Select(Mapper.Map); // entities are in "some" order, put them back in order - var xref = entities.ToDictionary(x => x.Id); - var result = keysArray.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + var xref = entities.ToDictionary(x => x.Key); + var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); return result; } @@ -633,21 +1009,21 @@ namespace Umbraco.Web.Editors } } - private IEnumerable GetResultForIds(IEnumerable ids, UmbracoEntityTypes entityType) + private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType) { - var idsArray = ids.ToArray(); - if (idsArray.Any() == false) return Enumerable.Empty(); + if (ids.Length == 0) + return Enumerable.Empty(); var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { - var entities = Services.EntityService.GetAll(objectType.Value, idsArray) + var entities = Services.EntityService.GetAll(objectType.Value, ids) .WhereNotNull() .Select(Mapper.Map); // entities are in "some" order, put them back in order var xref = entities.ToDictionary(x => x.Id); - var result = idsArray.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); return result; } diff --git a/src/Umbraco.Web/Editors/EntityControllerActionSelector.cs b/src/Umbraco.Web/Editors/EntityControllerActionSelector.cs deleted file mode 100644 index 9cfab1bdcd..0000000000 --- a/src/Umbraco.Web/Editors/EntityControllerActionSelector.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Web; -using System.Web.Http.Controllers; -using Umbraco.Core; - -namespace Umbraco.Web.Editors -{ - /// - /// This allows for calling GetById/GetByIds with a GUID... so it will automatically route to GetByKey/GetByKeys - /// - internal class EntityControllerActionSelector : ApiControllerActionSelector - { - - public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext) - { - if (controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Path).InvariantEndsWith("GetById")) - { - var id = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get("id"); - - if (id != null) - { - Guid parsed; - if (Guid.TryParse(id, out parsed)) - { - var controllerType = controllerContext.Controller.GetType(); - var method = controllerType.GetMethod("GetByKey"); - if (method != null) - { - return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, method); - } - } - } - } - - if (controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Path).InvariantEndsWith("GetByIds")) - { - var ids = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).GetValues("ids"); - - if (ids != null) - { - var allmatched = true; - foreach (var id in ids) - { - Guid parsed; - if (Guid.TryParse(id, out parsed) == false) - { - allmatched = false; - } - } - if (allmatched) - { - var controllerType = controllerContext.Controller.GetType(); - var method = controllerType.GetMethod("GetByKeys"); - if (method != null) - { - return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, method); - } - } - } - } - - - - return base.SelectAction(controllerContext); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs b/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs index 7ef4ef206a..571b29ed01 100644 --- a/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs +++ b/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs @@ -4,18 +4,5 @@ using Umbraco.Web.WebApi; namespace Umbraco.Web.Editors { - /// - /// This get's applied to the EntityController in order to have a custom IHttpActionSelector assigned to it - /// - /// - /// NOTE: It is SOOOO important to remember that you cannot just assign this in the 'initialize' method of a webapi - /// controller as it will assign it GLOBALLY which is what you def do not want to do. - /// - internal class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new EntityControllerActionSelector()); - } - } + } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs b/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs new file mode 100644 index 0000000000..bb275f8fa2 --- /dev/null +++ b/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.ModelBinding; +using System.Web.Http.ValueProviders; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; + +namespace Umbraco.Web.Editors +{ + /// + /// Used to bind a value from an inner json property + /// + /// + /// An example would be if you had json like: + /// { ids: [1,2,3,4] } + /// + /// And you had an action like: GetByIds(int[] ids, UmbracoEntityTypes type) + /// + /// The ids array will not bind because the object being sent up is an object and not an array so the + /// normal json formatter will not figure this out. + /// + /// This would also let you bind sub levels of the JSON being sent up too if you wanted with any jsonpath + /// + internal class FromJsonPathAttribute : ModelBinderAttribute + { + private readonly string _jsonPath; + private readonly FromUriAttribute _fromUriAttribute = new FromUriAttribute(); + + public FromJsonPathAttribute() + { + } + + public FromJsonPathAttribute(string jsonPath) : base(typeof(JsonPathBinder)) + { + _jsonPath = jsonPath; + } + + public override IEnumerable GetValueProviderFactories(HttpConfiguration configuration) + { + return _fromUriAttribute.GetValueProviderFactories(configuration); + } + + public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter) + { + var config = parameter.Configuration; + //get the default binder, we'll use that if it's a GET or if the body is empty + var underlyingBinder = base.GetModelBinder(config, parameter.ParameterType); + var binder = new JsonPathBinder(underlyingBinder, _jsonPath); + var valueProviderFactories = GetValueProviderFactories(config); + + return new ModelBinderParameterBinding(parameter, binder, valueProviderFactories); + } + + private class JsonPathBinder : IModelBinder + { + private readonly IModelBinder _underlyingBinder; + private readonly string _jsonPath; + + public JsonPathBinder(IModelBinder underlyingBinder, string jsonPath) + { + _underlyingBinder = underlyingBinder; + _jsonPath = jsonPath; + } + + public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) + { + if (actionContext.Request.Method == HttpMethod.Get) + { + return _underlyingBinder.BindModel(actionContext, bindingContext); + } + + var requestContent = new HttpMessageContent(actionContext.Request); + var strJson = requestContent.HttpRequestMessage.Content.ReadAsStringAsync().Result; + + if (strJson.IsNullOrWhiteSpace()) + { + return _underlyingBinder.BindModel(actionContext, bindingContext); + } + + var json = JsonConvert.DeserializeObject(strJson); + + //if no explicit json path then use the model name + var match = json.SelectToken(_jsonPath ?? bindingContext.ModelName); + + if (match == null) + { + return false; + } + + bindingContext.Model = match.ToObject(bindingContext.ModelType); + + return true; + } + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MacroController.cs b/src/Umbraco.Web/Editors/MacroController.cs index aed6cec602..b0b2cb4bf2 100644 --- a/src/Umbraco.Web/Editors/MacroController.cs +++ b/src/Umbraco.Web/Editors/MacroController.cs @@ -4,21 +4,26 @@ using System.Net; using System.Net.Http; using System.Text; using System.Web.Http; +using System.Web.SessionState; using AutoMapper; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using umbraco; +using Umbraco.Core; namespace Umbraco.Web.Editors { /// /// API controller to deal with Macro data /// + /// + /// Note that this implements IRequiresSessionState which will enable HttpContext.Session - generally speaking we don't normally + /// enable this for webapi controllers, however since this controller is used to render macro content and macros can access + /// Session, we don't want it to throw null reference exceptions. + /// [PluginController("UmbracoApi")] - public class MacroController : UmbracoAuthorizedJsonController + public class MacroController : UmbracoAuthorizedJsonController, IRequiresSessionState { - - /// /// Gets the macro parameters to be filled in for a particular macro /// @@ -124,6 +129,6 @@ namespace Umbraco.Web.Editors "text/html"); return result; } - + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 7a7e349a3c..3db1656c34 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; using System.IO; using System.Net; using System.Net.Http; @@ -27,6 +29,9 @@ using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using System.Linq; using System.Runtime.Serialization; +using System.Text.RegularExpressions; +using System.Web.Http.Controllers; +using Examine; using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; using umbraco; @@ -36,6 +41,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 { @@ -44,9 +50,22 @@ namespace Umbraco.Web.Editors /// access to ALL of the methods on this controller will need access to the media application. /// [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Media)] + [UmbracoApplicationAuthorize(Constants.Applications.Media)] + [MediaControllerControllerConfiguration] public class MediaController : ContentControllerBase { + /// + /// Configures this controller with a custom action selector + /// + private class MediaControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(Udi), typeof(string)))); + } + } + /// /// Constructor /// @@ -171,8 +190,9 @@ namespace Umbraco.Web.Editors .Select(Mapper.Map>); } + #region GetChildren /// - /// Returns the child media objects + /// Returns the child media objects - using the entity INT id /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(int id, @@ -209,6 +229,92 @@ namespace Umbraco.Web.Editors return pagedResult; } + /// + /// Returns the child media objects - using the entity GUID id + /// + /// + /// + /// + /// + /// + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Guid id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + var entity = Services.EntityService.GetByKey(id); + if (entity != null) + { + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Returns the child media objects - using the entity UDI id + /// + /// + /// + /// + /// + /// + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Udi id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + var entity = Services.EntityService.GetByKey(guidUdi.Guid); + if (entity != null) + { + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public PagedResult> GetChildren(string id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + foreach (var type in new[] { typeof(int), typeof(Guid) }) + { + var parsed = id.TryConvertTo(type); + if (parsed) + { + //oooh magic! will auto select the right overload + return GetChildren((dynamic)parsed.Result); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + /// /// Moves an item to the recycle bin, if it is already there then it will permanently delete it /// @@ -448,12 +554,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(), @@ -525,7 +656,8 @@ namespace Umbraco.Web.Editors foreach (var file in result.FileData) { var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); - var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); + var safeFileName = fileName.ToSafeFileName(); + var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) { diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index a423e638b9..879ffd3d0a 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -11,6 +11,11 @@ using System.Net; using System.Net.Http; using Umbraco.Web.WebApi; using Umbraco.Core.Services; +using Umbraco.Core.Models.EntityBase; +using System; +using System.ComponentModel; +using System.Web.Http.Controllers; +using Umbraco.Core; namespace Umbraco.Web.Editors { @@ -24,8 +29,21 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] [EnableOverrideAuthorization] + [MediaTypeControllerControllerConfigurationAttribute] public class MediaTypeController : ContentTypeControllerBase { + /// + /// Configures this controller with a custom action selector + /// + private class MediaTypeControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetAllowedChildren", "contentId", typeof(int), typeof(Guid), typeof(Udi), typeof(string)))); + } + } + /// /// Constructor /// @@ -49,6 +67,7 @@ namespace Umbraco.Web.Editors return Services.ContentTypeService.CountContentTypes(); } + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] public MediaTypeDisplay GetById(int id) { var ct = Services.ContentTypeService.GetMediaType(id); @@ -168,8 +187,9 @@ namespace Umbraco.Web.Editors } + #region GetAllowedChildren /// - /// Returns the allowed child content type objects for the content item id passed in + /// Returns the allowed child content type objects for the content item id passed in - based on an INT id /// /// [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] @@ -213,6 +233,61 @@ namespace Umbraco.Web.Editors return basics; } + /// + /// Returns the allowed child content type objects for the content item id passed in - based on a GUID id + /// + /// + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public IEnumerable GetAllowedChildren(Guid contentId) + { + var entity = ApplicationContext.Services.EntityService.GetByKey(contentId); + if (entity != null) + { + return GetAllowedChildren(entity.Id); + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Returns the allowed child content type objects for the content item id passed in - based on a UDI id + /// + /// + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public IEnumerable GetAllowedChildren(Udi contentId) + { + var guidUdi = contentId as GuidUdi; + if (guidUdi != null) + { + var entity = ApplicationContext.Services.EntityService.GetByKey(guidUdi.Guid); + if (entity != null) + { + return GetAllowedChildren(entity.Id); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [Obsolete("Do not use this method, use either the overload with INT, GUID or UDI instead, this will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public IEnumerable GetAllowedChildren(string contentId) + { + foreach (var type in new[] { typeof(int), typeof(Guid) }) + { + var parsed = contentId.TryConvertTo(type); + if (parsed) + { + //oooh magic! will auto select the right overload + return GetAllowedChildren((dynamic)parsed.Result); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + /// /// Move the media type /// diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 3f56087926..ec28a9408c 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -92,8 +92,7 @@ namespace Umbraco.Web.Editors { long totalRecords; var members = Services.MemberService - .GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField - , memberTypeAlias, filter).ToArray(); + .GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter).ToArray(); if (totalRecords == 0) { return new PagedResult(0, 0, 0); diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index f984eeb468..b15bcd767e 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -15,9 +15,11 @@ using umbraco.cms.presentation.Trees; using umbraco.presentation.developer.packages; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Packaging.Models; using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; @@ -81,7 +83,14 @@ namespace Umbraco.Web.Editors if (pack == null) throw new ArgumentNullException("pack"); var refreshCache = false; - + + var removedTemplates = new List(); + var removedMacros = new List(); + var removedContentTypes = new List(); + var removedDictionaryItems = new List(); + var removedDataTypes = new List(); + var removedFiles = new List(); + //Uninstall templates foreach (var item in pack.Data.Templates.ToArray()) { @@ -90,6 +99,7 @@ namespace Umbraco.Web.Editors var found = Services.FileService.GetTemplate(nId); if (found != null) { + removedTemplates.Add(found); ApplicationContext.Services.FileService.DeleteTemplate(found.Alias, Security.GetUserId()); } pack.Data.Templates.Remove(nId.ToString()); @@ -103,8 +113,9 @@ namespace Umbraco.Web.Editors var macro = Services.MacroService.GetById(nId); if (macro != null) { + removedMacros.Add(macro); Services.MacroService.Delete(macro); - } + } pack.Data.Macros.Remove(nId.ToString()); } @@ -126,13 +137,12 @@ namespace Umbraco.Web.Editors //Order the DocumentTypes before removing them if (contentTypes.Any()) { + //TODO: I don't think this ordering is necessary var orderedTypes = from contentType in contentTypes - orderby contentType.ParentId descending, contentType.Id descending - select contentType; - foreach (var contentType in orderedTypes) - { - contentTypeService.Delete(contentType); - } + orderby contentType.ParentId descending, contentType.Id descending + select contentType; + removedContentTypes.AddRange(orderedTypes); + contentTypeService.Delete(orderedTypes); } //Remove Dictionary items @@ -143,8 +153,9 @@ namespace Umbraco.Web.Editors var di = Services.LocalizationService.GetDictionaryItemById(nId); if (di != null) { + removedDictionaryItems.Add(di); Services.LocalizationService.Delete(di); - } + } pack.Data.DictionaryItems.Remove(nId.ToString()); } @@ -156,8 +167,9 @@ namespace Umbraco.Web.Editors var dtd = Services.DataTypeService.GetDataTypeDefinitionById(nId); if (dtd != null) { + removedDataTypes.Add(dtd); Services.DataTypeService.Delete(dtd); - } + } pack.Data.DataTypes.Remove(nId.ToString()); } @@ -198,30 +210,45 @@ namespace Umbraco.Web.Editors //Remove files foreach (var item in pack.Data.Files.ToArray()) { + removedFiles.Add(item.GetRelativePath()); + //here we need to try to find the file in question as most packages does not support the tilde char var file = IOHelper.FindFile(item); if (file != null) { if (file.StartsWith("/") == false) file = string.Format("/{0}", file); - var filePath = IOHelper.MapPath(file); + if (File.Exists(filePath)) - { File.Delete(filePath); - - } } pack.Data.Files.Remove(file); } pack.Save(); pack.Delete(Security.GetUserId()); - + + // create a summary of what was actually removed, for PackagingService.UninstalledPackage + var summary = new UninstallationSummary + { + MetaData = pack.GetMetaData(), + TemplatesUninstalled = removedTemplates, + MacrosUninstalled = removedMacros, + ContentTypesUninstalled = removedContentTypes, + DictionaryItemsUninstalled = removedDictionaryItems, + DataTypesUninstalled = removedDataTypes, + FilesUninstalled = removedFiles, + PackageUninstalled = true + }; + + // trigger the UninstalledPackage event + PackagingService.OnUninstalledPackage(new UninstallPackageEventArgs(summary, false)); + //TODO: Legacy - probably not needed if (refreshCache) { library.RefreshContent(); - } + } TreeDefinitionCollection.Instance.ReRegisterTrees(); global::umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers(); } @@ -241,8 +268,8 @@ namespace Umbraco.Web.Editors { Version pckVersion; return Version.TryParse(pck.Data.Version, out pckVersion) - ? new {package = pck, version = pckVersion} - : new {package = pck, version = new Version(0, 0, 0)}; + ? new { package = pck, version = pckVersion } + : new { package = pck, version = new Version(0, 0, 0) }; }) .Select(grouping => { @@ -313,7 +340,7 @@ namespace Umbraco.Web.Editors model.UmbracoVersion = ins.RequirementsType == RequirementsType.Strict ? string.Format("{0}.{1}.{2}", ins.RequirementsMajor, ins.RequirementsMinor, ins.RequirementsPatch) : string.Empty; - + //now we need to check for version comparison model.IsCompatible = true; if (ins.RequirementsType == RequirementsType.Strict) @@ -393,7 +420,7 @@ namespace Umbraco.Web.Editors { //TODO: Currently it has to be here, it's not ideal but that's the way it is right now var packageTempDir = IOHelper.MapPath(SystemDirectories.Data); - + //ensure it's there Directory.CreateDirectory(packageTempDir); @@ -409,14 +436,14 @@ namespace Umbraco.Web.Editors PopulateFromPackageData(model); var validate = ValidateInstalledInternal(model.Name, model.Version); - + if (validate == false) { //this package is already installed throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("packager/packageAlreadyInstalled"))); + Services.TextService.Localize("packager/packageAlreadyInstalled"))); } - + } else { @@ -425,7 +452,7 @@ namespace Umbraco.Web.Editors Services.TextService.Localize("media/disallowedFileType"), SpeechBubbleIcon.Warning)); } - + } return model; @@ -447,7 +474,7 @@ namespace Umbraco.Web.Editors //our repo guid using (var our = Repository.getByGuid("65194810-1f85-11dd-bd0b-0800200c9a66")) { - path = our.fetch(packageGuid, Security.CurrentUser.Id); + path = our.fetch(packageGuid, Security.CurrentUser.Id); } } @@ -491,12 +518,12 @@ namespace Umbraco.Web.Editors if (UmbracoVersion.Current < packageMinVersion) { throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("packager/targetVersionMismatch", new[] {packageMinVersion.ToString()}))); + Services.TextService.Localize("packager/targetVersionMismatch", new[] { packageMinVersion.ToString() }))); } } model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, tempPath); - model.Id = ins.CreateManifest( IOHelper.MapPath(model.TemporaryDirectoryPath), model.PackageGuid.ToString(), model.RepositoryGuid.ToString()); + model.Id = ins.CreateManifest(IOHelper.MapPath(model.TemporaryDirectoryPath), model.PackageGuid.ToString(), model.RepositoryGuid.ToString()); return model; } diff --git a/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs new file mode 100644 index 0000000000..53e25c146a --- /dev/null +++ b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Web; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Validation; +using System.Web.Http.ValueProviders; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; + +namespace Umbraco.Web.Editors +{ + /// + /// This is used to auto-select specific actions on controllers that would otherwise be ambiguous based on a single parameter type + /// + /// + /// As an example, lets say we have 2 methods: GetChildren(int id) and GetChildren(Guid id), by default Web Api won't allow this since + /// it won't know what to select, but if this Tuple is passed in new Tuple{string, string}("GetChildren", "id") + /// + /// This supports POST values too however only for JSON values + /// + internal class ParameterSwapControllerActionSelector : ApiControllerActionSelector + { + private readonly ParameterSwapInfo[] _actions; + + /// + /// Constructor accepting a list of action name + parameter name + /// + /// + public ParameterSwapControllerActionSelector(params ParameterSwapInfo[] actions) + { + _actions = actions; + } + + public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext) + { + var found = _actions.FirstOrDefault(x => controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Path).InvariantEndsWith(x.ActionName)); + + if (found != null) + { + HttpActionDescriptor method; + if (TryBindFromUri(controllerContext, found, out method)) + { + return method; + } + + //if it's a post we can try to read from the body and bind from the json value + if (controllerContext.Request.Method == HttpMethod.Post) + { + var requestContent = new HttpMessageContent(controllerContext.Request); + var strJson = requestContent.HttpRequestMessage.Content.ReadAsStringAsync().Result; + var json = JsonConvert.DeserializeObject(strJson); + + if (json == null) + { + return base.SelectAction(controllerContext); + } + + var requestParam = json[found.ParamName]; + + if (requestParam != null) + { + var paramTypes = found.SupportedTypes; + + foreach (var paramType in paramTypes) + { + try + { + var converted = requestParam.ToObject(paramType); + if (converted != null) + { + method = MatchByType(paramType, controllerContext, found); + if (method != null) + return method; + } + } + catch (JsonReaderException) + { + //can't convert + } + catch (JsonSerializationException) + { + //can't convert + } + } + } + } + } + return base.SelectAction(controllerContext); + } + + private bool TryBindFromUri(HttpControllerContext controllerContext, ParameterSwapInfo found, out HttpActionDescriptor method) + { + var requestParam = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get(found.ParamName); + + requestParam = (requestParam == null) ? null : requestParam.Trim(); + var paramTypes = found.SupportedTypes; + + if (requestParam == string.Empty && paramTypes.Length > 0) + { + //if it's empty then in theory we can select any of the actions since they'll all need to deal with empty or null parameters + //so we'll try to use the first one available + method = MatchByType(paramTypes[0], controllerContext, found); + if (method != null) + return true; + } + + if (requestParam != null) + { + foreach (var paramType in paramTypes) + { + //check if this is IEnumerable and if so this will get it's type + //we need to know this since the requestParam will always just be a string + var enumType = paramType.GetEnumeratedType(); + + var converted = requestParam.TryConvertTo(enumType ?? paramType); + if (converted) + { + method = MatchByType(paramType, controllerContext, found); + if (method != null) + return true; + } + } + } + + method = null; + return false; + } + + private static ReflectedHttpActionDescriptor MatchByType(Type idType, HttpControllerContext controllerContext, ParameterSwapInfo found) + { + var controllerType = controllerContext.Controller.GetType(); + var methods = controllerType.GetMethods().Where(info => info.Name == found.ActionName).ToArray(); + if (methods.Length > 1) + { + //choose the one that has the parameter with the T type + var method = methods.FirstOrDefault(x => x.GetParameters().FirstOrDefault(p => p.Name == found.ParamName && p.ParameterType == idType) != null); + + return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, method); + } + return null; + } + + internal class ParameterSwapInfo + { + public string ActionName { get; private set; } + public string ParamName { get; private set; } + public Type[] SupportedTypes { get; private set; } + + public ParameterSwapInfo(string actionName, string paramName, params Type[] supportedTypes) + { + ActionName = actionName; + ParamName = paramName; + SupportedTypes = supportedTypes; + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/TemplateController.cs b/src/Umbraco.Web/Editors/TemplateController.cs new file mode 100644 index 0000000000..40effc10fa --- /dev/null +++ b/src/Umbraco.Web/Editors/TemplateController.cs @@ -0,0 +1,172 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Web.Http; +using AutoMapper; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Editors +{ + [PluginController("UmbracoApi")] + [UmbracoTreeAuthorize(Constants.Trees.Templates)] + public class TemplateController : BackOfficeNotificationsController + { + /// + /// Gets data type by alias + /// + /// + /// + public TemplateDisplay GetByAlias(string alias) + { + var template = Services.FileService.GetTemplate(alias); + return template == null ? null : Mapper.Map(template); + } + + /// + /// Get all templates + /// + /// + public IEnumerable GetAll() + { + return Services.FileService.GetTemplates().Select(Mapper.Map); + } + + /// + /// Gets the content json for the content id + /// + /// + /// + public TemplateDisplay GetById(int id) + { + var template = Services.FileService.GetTemplate(id); + if (template == null) + throw new HttpResponseException(HttpStatusCode.NotFound); + + return Mapper.Map(template); + } + + /// + /// Deletes a template wth a given ID + /// + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var template = Services.FileService.GetTemplate(id); + if (template == null) + throw new HttpResponseException(HttpStatusCode.NotFound); + + Services.FileService.DeleteTemplate(template.Alias); + return Request.CreateResponse(HttpStatusCode.OK); + } + + public TemplateDisplay GetScaffold(int id) + { + //empty default + var dt = new Template("", ""); + dt.Path = "-1"; + + if (id > 0) + { + var master = Services.FileService.GetTemplate(id); + if(master != null) + { + dt.SetMasterTemplate(master); + } + } + + var content = ViewHelper.GetDefaultFileContent( layoutPageAlias: dt.MasterTemplateAlias ); + var scaffold = Mapper.Map(dt); + + scaffold.Content = content + "\r\n\r\n@* the fun starts here *@\r\n\r\n"; + return scaffold; + } + + /// + /// Saves the data type + /// + /// + /// + public TemplateDisplay PostSave(TemplateDisplay display) + { + + //Checking the submitted is valid with the Required attributes decorated on the ViewModel + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + + if (display.Id > 0) + { + // update + var template = Services.FileService.GetTemplate(display.Id); + if (template == null) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var changeMaster = template.MasterTemplateAlias != display.MasterTemplateAlias; + var changeAlias = template.Alias != display.Alias; + + Mapper.Map(display, template); + + if (changeMaster) + { + if (string.IsNullOrEmpty(display.MasterTemplateAlias) == false) + { + + var master = Services.FileService.GetTemplate(display.MasterTemplateAlias); + if(master == null || master.Id == display.Id) + { + template.SetMasterTemplate(null); + }else + { + template.SetMasterTemplate(master); + } + + } + else + { + //remove the master + template.SetMasterTemplate(null); + } + } + + Services.FileService.SaveTemplate(template); + + if (changeAlias) + { + template = Services.FileService.GetTemplate(template.Id); + } + + Mapper.Map(template, display); + } + else + { + //create + ITemplate master = null; + if (string.IsNullOrEmpty(display.MasterTemplateAlias) == false) + { + master = Services.FileService.GetTemplate(display.MasterTemplateAlias); + if (master == null) + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var template = Services.FileService.CreateTemplateWithIdentity(display.Name, display.Content, master); + //template = Services.FileService.GetTemplate(template.Id); + Mapper.Map(template, display); + } + + return display; + } + } +} diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 2eaf63f159..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) { @@ -64,9 +76,9 @@ namespace Umbraco.Web.Editors var queryResult = new QueryResultModel(); var sb = new StringBuilder(); - - sb.Append("CurrentPage.Site()"); - + var indention = Environment.NewLine + "\t\t\t\t\t\t"; + + sb.Append("Model.Content.Site()"); var timer = new Stopwatch(); timer.Start(); @@ -103,7 +115,7 @@ namespace Umbraco.Web.Editors { // we did not find the path sb.Clear(); - sb.AppendFormat("Umbraco.Content({0})", model.Source.Id); + sb.AppendFormat("Umbraco.TypedContent({0})", model.Source.Id); pointerNode = targetNode; } } @@ -126,10 +138,12 @@ namespace Umbraco.Web.Editors timer.Start(); contents = pointerNode.Children; timer.Stop(); - sb.Append(".Children"); + sb.Append(".Children()"); } + //setup 2 clauses, 1 for returning, 1 for testing var clause = string.Empty; + var tokenizedClause = string.Empty; // WHERE var token = 0; @@ -141,12 +155,13 @@ namespace Umbraco.Web.Editors foreach (var condition in model.Filters) { if(string.IsNullOrEmpty( condition.ConstraintValue)) continue; - - - - var operation = condition.BuildCondition(token); + + //x is passed in as the parameter alias for the linq where statement clause + var operation = condition.BuildCondition("x"); + var tokenizedOperation = condition.BuildTokenizedCondition(token); clause = string.IsNullOrEmpty(clause) ? operation : string.Concat(new[] { clause, " && ", operation }); + tokenizedClause = string.IsNullOrEmpty(tokenizedClause) ? tokenizedOperation : string.Concat(new[] { tokenizedClause, " && ", tokenizedOperation }); token++; } @@ -156,19 +171,21 @@ namespace Umbraco.Web.Editors timer.Start(); - //clause = "Visible && " + clause; - - contents = contents.AsQueryable().Where(clause, model.Filters.Select(this.GetConstraintValue).ToArray()); - // contents = contents.Where(clause, values.ToArray()); + //trial-run the tokenized clause to time the execution + //for review - this uses a tonized query rather then the normal linq query. + contents = contents.AsQueryable().Where(tokenizedClause, model.Filters.Select(this.GetConstraintValue).ToArray()); contents = contents.Where(x => x.IsVisible()); timer.Stop(); - clause = string.Format("\"Visible && {0}\",{1}", clause, - string.Join(",", model.Filters.Select(x => x.Property.Type == "string" ? - string.Format("\"{0}\"", x.ConstraintValue) : x.ConstraintValue).ToArray())); + + //the query to output to the editor + sb.Append(indention); + sb.Append(".Where(x => x.IsVisible())"); + + sb.Append(indention); + sb.AppendFormat(".Where(x => {0})", clause); - sb.AppendFormat(".Where({0})", clause); } else { @@ -178,7 +195,8 @@ namespace Umbraco.Web.Editors timer.Stop(); - sb.Append(".Where(\"Visible\")"); + sb.Append(indention); + sb.Append(".Where(x => x.IsVisible())"); } @@ -192,6 +210,7 @@ namespace Umbraco.Web.Editors var direction = model.Sort.Direction == "ascending" ? string.Empty : " desc"; + sb.Append(indention); sb.AppendFormat(".OrderBy(\"{0}{1}\")", model.Sort.Property.Alias, direction); } @@ -203,6 +222,7 @@ namespace Umbraco.Web.Editors timer.Stop(); + sb.Append(indention); sb.AppendFormat(".Take({0})", model.Take); } } @@ -217,7 +237,7 @@ namespace Umbraco.Web.Editors }); - return queryResult; + return queryResult; } private object GetConstraintValue(QueryCondition condition) @@ -289,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/ExamineExtensions.cs b/src/Umbraco.Web/ExamineExtensions.cs index 89f6fbe7cf..e75f9200c6 100644 --- a/src/Umbraco.Web/ExamineExtensions.cs +++ b/src/Umbraco.Web/ExamineExtensions.cs @@ -9,12 +9,12 @@ using Umbraco.Web.PublishedCache; namespace Umbraco.Web { - /// - /// Extension methods for Examine - /// - internal static class ExamineExtensions + /// + /// Extension methods for Examine + /// + public static class ExamineExtensions { - internal static PublishedContentSet ConvertSearchResultToPublishedContent(this IEnumerable results, + public static PublishedContentSet ConvertSearchResultToPublishedContent(this IEnumerable results, ContextualPublishedCache cache) { //TODO: The search result has already returned a result which SHOULD include all of the data to create an IPublishedContent, 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/HttpUrlHelperExtensions.cs b/src/Umbraco.Web/HttpUrlHelperExtensions.cs index 1b04a7dd8f..d3e87a6bf7 100644 --- a/src/Umbraco.Web/HttpUrlHelperExtensions.cs +++ b/src/Umbraco.Web/HttpUrlHelperExtensions.cs @@ -122,5 +122,29 @@ namespace Umbraco.Web } } } + + /// + /// Return the Base Url (not including the action) for a Web Api service + /// + /// + /// + /// + /// + public static string GetUmbracoApiServiceBaseUrl(this UrlHelper url, string actionName) + where T : UmbracoApiController + { + return url.GetUmbracoApiService(actionName).TrimEnd(actionName); + } + + public static string GetUmbracoApiServiceBaseUrl(this UrlHelper url, Expression> methodSelector) + where T : UmbracoApiController + { + var method = Core.ExpressionHelper.GetMethodInfo(methodSelector); + if (method == null) + { + throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result "); + } + return url.GetUmbracoApiService(method.Name).TrimEnd(method.Name); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/IHttpContextAccessor.cs b/src/Umbraco.Web/IHttpContextAccessor.cs index 068783725a..4b5a8ed884 100644 --- a/src/Umbraco.Web/IHttpContextAccessor.cs +++ b/src/Umbraco.Web/IHttpContextAccessor.cs @@ -1,5 +1,3 @@ -using System.Web; - namespace Umbraco.Web { /// @@ -8,8 +6,6 @@ namespace Umbraco.Web /// /// NOTE: This has a singleton lifespan /// - public interface IHttpContextAccessor - { - HttpContextBase Value { get; } - } + public interface IHttpContextAccessor : Core.IHttpContextAccessor + { } } \ No newline at end of file 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/Install/InstallHelper.cs b/src/Umbraco.Web/Install/InstallHelper.cs index d39b16f6ac..de101394c1 100644 --- a/src/Umbraco.Web/Install/InstallHelper.cs +++ b/src/Umbraco.Web/Install/InstallHelper.cs @@ -146,7 +146,7 @@ namespace Umbraco.Web.Install { get { - var databaseSettings = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName]; + var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; if (GlobalSettings.ConfigurationStatus.IsNullOrWhiteSpace() && _umbContext.Application.DatabaseContext.IsConnectionStringConfigured(databaseSettings) == false) { diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs index 4854919efb..ce28a26176 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs @@ -83,7 +83,7 @@ namespace Umbraco.Web.Install.InstallSteps private bool ShouldDisplayView() { //If the connection string is already present in web.config we don't need to show the settings page and we jump to installing/upgrading. - var databaseSettings = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName]; + var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; if (_applicationContext.DatabaseContext.IsConnectionStringConfigured(databaseSettings)) { diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs index 57c8e67465..b75a310546 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs @@ -48,13 +48,13 @@ namespace Umbraco.Web.Install.InstallSteps internal static void HandleConnectionStrings() { // Remove legacy umbracoDbDsn configuration setting if it exists and connectionstring also exists - if (ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName] != null) + if (ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName] != null) { - GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName); + GlobalSettings.RemoveSetting(Constants.System.UmbracoConnectionName); } else { - var ex = new ArgumentNullException(string.Format("ConfigurationManager.ConnectionStrings[{0}]", GlobalSettings.UmbracoConnectionName), "Install / upgrade did not complete successfully, umbracoDbDSN was not set in the connectionStrings section"); + var ex = new ArgumentNullException(string.Format("ConfigurationManager.ConnectionStrings[{0}]", Constants.System.UmbracoConnectionName), "Install / upgrade did not complete successfully, umbracoDbDSN was not set in the connectionStrings section"); LogHelper.Error("", ex); throw ex; } diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs index 678ae427e5..c3ae313898 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs @@ -58,7 +58,7 @@ namespace Umbraco.Web.Install.InstallSteps return false; } - var databaseSettings = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName]; + var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; if (_applicationContext.DatabaseContext.IsConnectionStringConfigured(databaseSettings)) { diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index 4d8e9c5a4b..7a6a54f9e7 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -81,7 +81,7 @@ namespace Umbraco.Web.Install.InstallSteps { var client = new System.Net.WebClient(); var values = new NameValueCollection { { "name", admin.Name }, { "email", admin.Email} }; - client.UploadValues("http://umbraco.org/base/Ecom/SubmitEmail/installer.aspx", values); + client.UploadValues("https://shop.umbraco.com/base/Ecom/SubmitEmail/installer.aspx", values); } catch { /* fail in silence */ } } @@ -118,7 +118,7 @@ namespace Umbraco.Web.Install.InstallSteps public override bool RequiresExecution(UserModel model) { //now we have to check if this is really a new install, the db might be configured and might contain data - var databaseSettings = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName]; + var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; //if there's already a version then there should def be a user but in some cases someone may have // left a version number in there but cleared out their db conn string, in that case, it's really a new install. @@ -147,4 +147,4 @@ namespace Umbraco.Web.Install.InstallSteps } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs b/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs index 51622ed504..2f8ec85075 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs @@ -1,8 +1,10 @@ -using System.Text; +using System; +using System.Text; using System.Xml; using System.Collections.Generic; using System.Net; using System.Net.Http; +using System.Web; using Newtonsoft.Json; using Umbraco.Core.Media; @@ -27,10 +29,13 @@ namespace Umbraco.Web.Media.EmbedProviders public virtual string BuildFullUrl(string url, int maxWidth, int maxHeight) { + if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) == false) + throw new ArgumentException("Not a valid Url"); + var fullUrl = new StringBuilder(); fullUrl.Append(APIEndpoint); - fullUrl.Append("?url=" + url); + fullUrl.Append("?url=" + HttpUtility.UrlEncode(url)); foreach (var p in RequestParams) fullUrl.Append(string.Format("&{0}={1}", p.Key, p.Value)); diff --git a/src/Umbraco.Web/Models/ContentEditing/CodeFileDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/CodeFileDisplay.cs new file mode 100644 index 0000000000..178124f044 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/CodeFileDisplay.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "scriptFile", Namespace = "")] + public class CodeFileDisplay : INotificationModel + { + /// + /// VirtualPath is the path to the file on disk + /// /views/partials/file.cshtml + /// + [DataMember(Name = "virtualPath", IsRequired = true)] + public string VirtualPath { get; set; } + + /// + /// Path represents the path used by the backoffice tree + /// For files stored on disk, this is a urlencoded, comma seperated + /// path to the file, always starting with -1. + /// + /// -1,Partials,Parials%2FFolder,Partials%2FFolder%2FFile.cshtml + /// + [DataMember(Name = "path")] + [ReadOnly(true)] + public string Path { get; set; } + + [DataMember(Name = "name", IsRequired = true)] + public string Name { get; set; } + + [DataMember(Name = "content", IsRequired = true)] + public string Content { get; set; } + + [DataMember(Name = "fileType", IsRequired = true)] + public string FileType { get; set; } + + [DataMember(Name = "snippet")] + [ReadOnly(true)] + public string Snippet { get; set; } + + [DataMember(Name = "id")] + [ReadOnly(true)] + public string Id { get; set; } + + public List Notifications { get; private set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index 87eff89b58..74c6a3ea7e 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -84,17 +84,21 @@ namespace Umbraco.Web.Models.ContentEditing } /// - /// The real persisted content object + /// The real persisted content object - used during inbound model binding /// + /// + /// This is not used for outgoing model information. + /// [JsonIgnore] internal TPersisted PersistedContent { get; set; } /// - /// The DTO object used to gather all required content data including data type information etc... for use with validation + /// The DTO object used to gather all required content data including data type information etc... for use with validation - used during inbound model binding /// /// /// We basically use this object to hydrate all required data from the database into one object so we can validate everything we need /// instead of having to look up all the data individually. + /// This is not used for outgoing model information. /// [JsonIgnore] internal ContentItemDto ContentDto { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs index 40d884d653..9f6e5b28da 100644 --- a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs @@ -6,7 +6,10 @@ using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json; +using Umbraco.Core; using Umbraco.Core.Models.Validation; +using Umbraco.Core.Serialization; namespace Umbraco.Web.Models.ContentEditing { @@ -25,7 +28,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "id", IsRequired = true)] [Required] public object Id { get; set; } - + + [DataMember(Name = "udi")] + [ReadOnly(true)] + [JsonConverter(typeof(UdiJsonConverter))] + public Udi Udi { get; set; } + [DataMember(Name = "icon")] public string Icon { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs b/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs index d9bc169c8f..61d61f2108 100644 --- a/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs +++ b/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs @@ -3,7 +3,18 @@ namespace Umbraco.Web.Models.ContentEditing public class GetAvailableCompositionsFilter { public int ContentTypeId { get; set; } + + /// + /// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out. + /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot + /// be looked up via the db, they need to be passed in. + /// public string[] FilterPropertyTypes { get; set; } + + /// + /// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out + /// along with any content types that have matching property types that are included in the filtered content types + /// public string[] FilterContentTypes { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs index 1e8a7ba088..6879701bde 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs @@ -1,4 +1,5 @@ -using System.Runtime.Serialization; +using System.Collections.Generic; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { @@ -33,5 +34,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "view", IsRequired = true)] public string View { get; set; } + /// + /// This allows for custom configuration to be injected into the pre-value editor + /// + [DataMember(Name = "config")] + public IDictionary Config { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/SnippetDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/SnippetDisplay.cs new file mode 100644 index 0000000000..e05f8c5c89 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/SnippetDisplay.cs @@ -0,0 +1,14 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "scriptFile", Namespace = "")] + public class SnippetDisplay + { + [DataMember(Name = "name", IsRequired = true)] + public string Name { get; set; } + + [DataMember(Name = "fileName", IsRequired = true)] + public string FileName { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs new file mode 100644 index 0000000000..91c1aefdb0 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Models.Validation; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "template", Namespace = "")] + public class TemplateDisplay : INotificationModel + { + + [DataMember(Name = "id")] + public int Id { get; set; } + + [Required] + [DataMember(Name = "name")] + public string Name { get; set; } + + [Required] + [DataMember(Name = "alias")] + public string Alias { get; set; } + + [DataMember(Name = "content")] + public string Content { get; set; } + + [DataMember(Name = "path")] + public string Path { get; set; } + + [DataMember(Name = "virtualPath")] + public string VirtualPath { get; set; } + + [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. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UmbracoEntityTypes.cs b/src/Umbraco.Web/Models/ContentEditing/UmbracoEntityTypes.cs index 824dcb2b91..c68ee3c655 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UmbracoEntityTypes.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UmbracoEntityTypes.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Web.Models.ContentEditing +using System; +using System.ComponentModel; + +namespace Umbraco.Web.Models.ContentEditing { /// /// Represents the type's of Umbraco entities that can be resolved from the EntityController @@ -53,6 +56,8 @@ /// /// Content Item /// + [Obsolete("This is not used and will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] ContentItem, /// diff --git a/src/Umbraco.Web/Models/ContentExtensions.cs b/src/Umbraco.Web/Models/ContentExtensions.cs index cfee14a2a8..d1f153125e 100644 --- a/src/Umbraco.Web/Models/ContentExtensions.cs +++ b/src/Umbraco.Web/Models/ContentExtensions.cs @@ -46,9 +46,7 @@ namespace Umbraco.Web.Models { var route = umbracoContext == null ? null // for tests only - : umbracoContext.ContentCache.GetRouteById(contentId); // cached - - if (route != null && route.StartsWith("err/")) route = null; + : umbracoContext.ContentCache.GetRouteById(contentId); // may be cached var domainHelper = new DomainHelper(domainService); IDomain domain; @@ -73,7 +71,7 @@ namespace Umbraco.Web.Models } else { - // if content is published then we have a (cached) route + // if content is published then we have a route // from which we can figure out the domain var pos = route.IndexOf('/'); diff --git a/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs b/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs index 624c641d3b..2b9047c284 100644 --- a/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; @@ -10,9 +11,22 @@ namespace Umbraco.Web.Models.Mapping { internal class AvailablePropertyEditorsResolver : ValueResolver> { + private readonly IContentSection _contentSection; + + public AvailablePropertyEditorsResolver(IContentSection contentSection) + { + _contentSection = contentSection; + } + protected override IEnumerable ResolveCore(IDataTypeDefinition source) { return PropertyEditorResolver.Current.PropertyEditors + .Where(x => + { + if (_contentSection.ShowDeprecatedPropertyEditors) + return true; + return source.PropertyEditorAlias == x.Alias || x.IsDeprecated == false; + }) .OrderBy(x => x.Name) .Select(Mapper.Map); } diff --git a/src/Umbraco.Web/Models/Mapping/CodeFileDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/CodeFileDisplayMapper.cs new file mode 100644 index 0000000000..aa033c91b0 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/CodeFileDisplayMapper.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models.Mapping; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + public class CodeFileDisplayMapper : MapperConfiguration + { + public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) + { + config.CreateMap() + .ForMember(x => x.FileType, exp => exp.Ignore()) + .ForMember(x => x.Notifications, exp => exp.Ignore()) + .ForMember(x => x.Path, exp => exp.Ignore()) + .ForMember(x => x.Snippet, exp => exp.Ignore()); + + config.CreateMap() + .ForMember(x => x.FileType, exp => exp.Ignore()) + .ForMember(x => x.Notifications, exp => exp.Ignore()) + .ForMember(x => x.Path, exp => exp.Ignore()) + .ForMember(x => x.Snippet, exp => exp.Ignore()); + + config.CreateMap() + .ForMember(x => x.Id, exp => exp.Ignore()) + .ForMember(x => x.Key, exp => exp.Ignore()) + .ForMember(x => x.Path, exp => exp.Ignore()) + .ForMember(x => x.CreateDate, exp => exp.Ignore()) + .ForMember(x => x.UpdateDate, exp => exp.Ignore()) + .ForMember(x => x.Path, exp => exp.Ignore()) + .ForMember(x => x.Alias, exp => exp.Ignore()) + .ForMember(x => x.Name, exp => exp.Ignore()) + .ForMember(x => x.OriginalPath, exp => exp.Ignore()) + .ForMember(x => x.HasIdentity, exp => exp.Ignore()); + + config.CreateMap() + .ForMember(x => x.Id, exp => exp.Ignore()) + .ForMember(x => x.Key, exp => exp.Ignore()) + .ForMember(x => x.Path, exp => exp.Ignore()) + .ForMember(x => x.CreateDate, exp => exp.Ignore()) + .ForMember(x => x.UpdateDate, exp => exp.Ignore()) + .ForMember(x => x.Path, exp => exp.Ignore()) + .ForMember(x => x.Alias, exp => exp.Ignore()) + .ForMember(x => x.Name, exp => exp.Ignore()) + .ForMember(x => x.OriginalPath, exp => exp.Ignore()) + .ForMember(x => x.HasIdentity, exp => exp.Ignore()); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index e179159e7c..6f23ecee0f 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -28,6 +28,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IContent TO ContentItemDisplay config.CreateMap() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Document, content.Key))) .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(display => display.Updater, expression => expression.ResolveUsing(new CreatorResolver())) .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) @@ -58,6 +59,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IContent TO ContentItemBasic config.CreateMap>() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Document, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.Updater, expression => expression.ResolveUsing(new CreatorResolver())) .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) @@ -68,6 +70,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IContent TO ContentItemDto config.CreateMap>() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Document, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion)) .ForMember(dto => dto.Updater, expression => expression.Ignore()) @@ -115,7 +118,7 @@ namespace Umbraco.Web.Models.Mapping //map the tree node url if (HttpContext.Current != null) { - var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null)); display.TreeNodeUrl = url; } @@ -178,16 +181,10 @@ namespace Umbraco.Web.Models.Mapping { {"items", templateItemConfig} } - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}urls", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("content/urls"), - Value = string.Join(",", display.Urls), - View = "urllist" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor } }; + TabsAndPropertiesResolver.MapGenericProperties(content, display, localizedText, properties.ToArray(), genericProperties => { @@ -219,6 +216,15 @@ namespace Umbraco.Web.Models.Mapping //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor docTypeProperty.View = "urllist"; } + + // inject 'Link to document' as the first generic property + genericProperties.Insert(0, new ContentPropertyDisplay + { + Alias = string.Format("{0}urls", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = localizedText.Localize("content/urls"), + Value = string.Join(",", display.Urls), + View = "urllist" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor + }); }); } diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs index bf0ec1f457..de5b5a14fd 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs @@ -161,9 +161,12 @@ namespace Umbraco.Web.Models.Mapping }); - config.CreateMap(); - config.CreateMap(); - config.CreateMap(); + config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MemberType, content.Key))); + config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MediaType, content.Key))); + config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DocumentType, content.Key))); config.CreateMap() diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs index 52f2dbad4b..cd42c87a56 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs @@ -126,6 +126,7 @@ namespace Umbraco.Web.Models.Mapping where TPropertyTypeDisplay : PropertyTypeDisplay, new() { return mapping + .ForMember(x => x.Udi, expression => expression.ResolveUsing(new ContentTypeUdiResolver())) .ForMember(display => display.Notifications, expression => expression.Ignore()) .ForMember(display => display.Errors, expression => expression.Ignore()) .ForMember(display => display.AllowAsRoot, expression => expression.MapFrom(type => type.AllowedAsRoot)) diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs new file mode 100644 index 0000000000..2d33b17155 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs @@ -0,0 +1,23 @@ +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Resolves a UDI for a content type based on it's type + /// + internal class ContentTypeUdiResolver : ValueResolver + { + protected override Udi ResolveCore(IContentTypeComposition source) + { + if (source == null) return null; + + return Udi.Create( + source.GetType() == typeof(IMemberType) + ? Constants.UdiEntityType.MemberType + : source.GetType() == typeof(IMediaType) + ? Constants.UdiEntityType.MediaType : Constants.UdiEntityType.DocumentType, source.Key); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index ac2faa82dc..e78aeaf6a3 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using AutoMapper; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.Mapping; using Umbraco.Core.PropertyEditors; @@ -34,6 +35,7 @@ namespace Umbraco.Web.Models.Mapping }; config.CreateMap() + .ForMember(x => x.Udi, expression => expression.Ignore()) .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) .ForMember(x => x.IsSystemDataType, expression => expression.Ignore()) .ForMember(x => x.Id, expression => expression.Ignore()) @@ -44,6 +46,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.AdditionalData, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DataType, content.Key))) .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) .ForMember(x => x.Icon, expression => expression.Ignore()) .ForMember(x => x.Alias, expression => expression.Ignore()) @@ -61,7 +64,8 @@ namespace Umbraco.Web.Models.Mapping }); config.CreateMap() - .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing(new AvailablePropertyEditorsResolver())) + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DataType, content.Key))) + .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing(new AvailablePropertyEditorsResolver(UmbracoConfig.For.UmbracoSettings().Content))) .ForMember(display => display.PreValues, expression => expression.ResolveUsing( new PreValueDisplayResolver(lazyDataTypeService))) .ForMember(display => display.SelectedEditor, expression => expression.MapFrom( diff --git a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs index 5610a70008..5be9d550e5 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; using Examine; +using Examine.LuceneEngine.Providers; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Mapping; @@ -17,11 +18,20 @@ namespace Umbraco.Web.Models.Mapping public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(x => Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(x.NodeObjectTypeId), x.Key))) .ForMember(basic => basic.Icon, expression => expression.MapFrom(entity => entity.ContentTypeIcon)) .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()); + .ForMember(x => x.Alias, expression => expression.Ignore()) + .AfterMap((entity, basic) => + { + if (entity.NodeObjectTypeId == Constants.ObjectTypes.MemberGuid && basic.Icon.IsNullOrWhiteSpace()) + { + basic.Icon = "icon-user"; + } + }); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.Ignore()) .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-box")) .ForMember(basic => basic.Path, expression => expression.UseValue("")) .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) @@ -29,6 +39,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.AdditionalData, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.Ignore()) .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-tab")) .ForMember(basic => basic.Path, expression => expression.UseValue("")) .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) @@ -38,6 +49,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.AdditionalData, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.Ignore()) .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-user")) .ForMember(basic => basic.Path, expression => expression.UseValue("")) .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) @@ -46,6 +58,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.AdditionalData, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(x => Udi.Create(Constants.UdiEntityType.Template, x.Key))) .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-layout")) .ForMember(basic => basic.Path, expression => expression.MapFrom(template => template.Path)) .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) @@ -70,6 +83,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.SortOrder, expression => expression.Ignore()); config.CreateMap() + .ForMember(x => x.Udi, expression => expression.ResolveUsing(new ContentTypeUdiResolver())) .ForMember(basic => basic.Path, expression => expression.MapFrom(x => x.Path)) .ForMember(basic => basic.ParentId, expression => expression.MapFrom(x => x.ParentId)) .ForMember(dto => dto.Trashed, expression => expression.Ignore()) @@ -77,6 +91,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() //default to document icon + .ForMember(x => x.Udi, expression => expression.Ignore()) .ForMember(x => x.Icon, expression => expression.Ignore()) .ForMember(x => x.Id, expression => expression.MapFrom(result => result.Id)) .ForMember(x => x.Name, expression => expression.Ignore()) @@ -87,21 +102,40 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.Trashed, expression => expression.Ignore()) .ForMember(x => x.AdditionalData, expression => expression.Ignore()) .AfterMap((result, basic) => - { + { + //get the icon if there is one basic.Icon = result.Fields.ContainsKey(UmbracoContentIndexer.IconFieldName) ? result.Fields[UmbracoContentIndexer.IconFieldName] : "icon-document"; basic.Name = result.Fields.ContainsKey("nodeName") ? result.Fields["nodeName"] : "[no name]"; - if (result.Fields.ContainsKey("__NodeKey")) + if (result.Fields.ContainsKey(UmbracoContentIndexer.NodeKeyFieldName)) { Guid key; - if (Guid.TryParse(result.Fields["__NodeKey"], out key)) + if (Guid.TryParse(result.Fields[UmbracoContentIndexer.NodeKeyFieldName], out key)) { basic.Key = key; + + //need to set the UDI + if (result.Fields.ContainsKey(LuceneIndexer.IndexTypeFieldName)) + { + switch (result.Fields[LuceneIndexer.IndexTypeFieldName]) + { + case IndexTypes.Member: + basic.Udi = new GuidUdi(Constants.UdiEntityType.Member, basic.Key); + break; + case IndexTypes.Content: + basic.Udi = new GuidUdi(Constants.UdiEntityType.Document, basic.Key); + break; + case IndexTypes.Media: + basic.Udi = new GuidUdi(Constants.UdiEntityType.Media, basic.Key); + break; + } + } } } + if (result.Fields.ContainsKey("parentID")) { int parentId; @@ -114,7 +148,7 @@ namespace Umbraco.Web.Models.Mapping basic.ParentId = -1; } } - basic.Path = result.Fields.ContainsKey("__Path") ? result.Fields["__Path"] : ""; + basic.Path = result.Fields.ContainsKey(UmbracoContentIndexer.IndexPathFieldName) ? result.Fields[UmbracoContentIndexer.IndexPathFieldName] : ""; if (result.Fields.ContainsKey(UmbracoContentIndexer.NodeTypeAliasFieldName)) { @@ -124,6 +158,9 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap>() .ConvertUsing(results => results.Select(Mapper.Map).ToList()); + + config.CreateMap, IEnumerable>() + .ConvertUsing(results => results.Select(Mapper.Map).ToList()); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs index 54ebca6e68..3dc86e61c9 100644 --- a/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs @@ -20,6 +20,7 @@ namespace Umbraco.Web.Models.Mapping { //FROM IMacro TO EntityBasic config.CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Macro, content.Key))) .ForMember(entityBasic => entityBasic.Icon, expression => expression.UseValue("icon-settings-alt")) .ForMember(dto => dto.ParentId, expression => expression.UseValue(-1)) .ForMember(dto => dto.Path, expression => expression.ResolveUsing(macro => "-1," + macro.Id)) diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index c3f9412401..453e1567ab 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -25,6 +25,7 @@ namespace Umbraco.Web.Models.Mapping { //FROM IMedia TO MediaItemDisplay config.CreateMap() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) @@ -45,6 +46,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IMedia TO ContentItemBasic config.CreateMap>() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) .ForMember(dto => dto.Trashed, expression => expression.MapFrom(content => content.Trashed)) @@ -56,6 +58,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IMedia TO ContentItemDto config.CreateMap>() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.Published, expression => expression.Ignore()) .ForMember(dto => dto.Updater, expression => expression.Ignore()) @@ -66,8 +69,8 @@ namespace Umbraco.Web.Models.Mapping private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, ILogger logger) { - // Adapted from ContentModelMapper - //map the IsChildOfListView (this is actually if it is a descendant of a list view!) + // Adapted from ContentModelMapper + //map the IsChildOfListView (this is actually if it is a descendant of a list view!) //TODO: Fix this shorthand .Ancestors() lookup, at least have an overload to use the current if (media.HasIdentity) { @@ -92,20 +95,20 @@ namespace Umbraco.Web.Models.Mapping display.IsChildOfListView = ancesctorListView != null; } } - + //map the tree node url if (HttpContext.Current != null) { - var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null)); display.TreeNodeUrl = url; } - + if (media.ContentType.IsContainer) { TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, localizedText); } - + var genericProperties = new List { new ContentPropertyDisplay @@ -117,20 +120,6 @@ namespace Umbraco.Web.Models.Mapping } }; - var links = media.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, logger); - - if (links.Any()) - { - var link = new ContentPropertyDisplay - { - Alias = string.Format("{0}urls", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("media/urls"), - Value = string.Join(",", links), - View = "urllist" - }; - genericProperties.Add(link); - } - TabsAndPropertiesResolver.MapGenericProperties(media, display, localizedText, genericProperties, properties => { if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null @@ -152,6 +141,20 @@ namespace Umbraco.Web.Models.Mapping }; docTypeProperty.View = "urllist"; } + + // inject 'Link to media' as the first generic property + var links = media.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, logger); + if (links.Any()) + { + var link = new ContentPropertyDisplay + { + Alias = string.Format("{0}urls", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = localizedText.Localize("media/urls"), + Value = string.Join(",", links), + View = "urllist" + }; + properties.Insert(0, link); + } }); } } diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index edb44d36ce..3982302906 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -61,6 +61,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IMember TO MediaItemDisplay config.CreateMap() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) @@ -84,6 +85,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IMember TO MemberBasic config.CreateMap() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) .ForMember(dto => dto.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) @@ -96,9 +98,10 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); //FROM MembershipUser TO MemberBasic - config.CreateMap() + config.CreateMap() //we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity .ForMember(member => member.Id, expression => expression.MapFrom(user => int.MaxValue)) + .ForMember(display => display.Udi, expression => expression.Ignore()) .ForMember(member => member.CreateDate, expression => expression.MapFrom(user => user.CreationDate)) .ForMember(member => member.UpdateDate, expression => expression.MapFrom(user => user.LastActivityDate)) .ForMember(member => member.Key, expression => expression.MapFrom(user => user.ProviderUserKey.TryConvertTo().Result.ToString("N"))) @@ -121,6 +124,7 @@ namespace Umbraco.Web.Models.Mapping //FROM IMember TO ContentItemDto config.CreateMap>() + .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) .ForMember(dto => dto.Published, expression => expression.Ignore()) .ForMember(dto => dto.Updater, expression => expression.Ignore()) @@ -148,7 +152,7 @@ namespace Umbraco.Web.Models.Mapping //map the tree node url if (HttpContext.Current != null) { - var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Key.ToString("N"), null)); display.TreeNodeUrl = url; } diff --git a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs index 5f3697c3d5..067d495591 100644 --- a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs @@ -1,5 +1,6 @@ using AutoMapper; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index ff4176a944..0671f59273 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -229,7 +229,7 @@ namespace Umbraco.Web.Models.Mapping var groupsGroupsByName = content.PropertyGroups.OrderBy(x => x.SortOrder).GroupBy(x => x.Name); foreach (var groupsByName in groupsGroupsByName) { - var properties = new List(); + var properties = new List(); // merge properties for groups with the same name foreach (var group in groupsByName) @@ -237,13 +237,16 @@ namespace Umbraco.Web.Models.Mapping var groupProperties = content.GetPropertiesForGroup(group) .Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored - properties.AddRange(Mapper.Map, IEnumerable>(groupProperties)); + properties.AddRange(groupProperties); } if (properties.Count == 0) continue; - TranslateProperties(properties); + // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. + var mappedProperties = Mapper.Map, IEnumerable>(properties.OrderBy(prop => prop.PropertyType.SortOrder)); + + TranslateProperties(mappedProperties); // add the tab // we need to pick an identifier... there is no "right" way... @@ -256,7 +259,7 @@ namespace Umbraco.Web.Models.Mapping Id = groupId, Alias = groupName, Label = _localizedTextService.UmbracoDictionaryTranslate(groupName), - Properties = properties, + Properties = mappedProperties, IsActive = false }); } diff --git a/src/Umbraco.Web/Models/Mapping/TemplateModelMapper.cs b/src/Umbraco.Web/Models/Mapping/TemplateModelMapper.cs new file mode 100644 index 0000000000..d673e573a8 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/TemplateModelMapper.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Mapping; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class TemplateModelMapper : MapperConfiguration + { + public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) + { + config.CreateMap() + .ForMember(x => x.Notifications, exp => exp.Ignore()); + + config.CreateMap() + .ForMember(x => x.Key, exp => exp.Ignore()) + .ForMember(x => x.Path, exp => exp.Ignore()) + .ForMember(x => x.CreateDate, exp => exp.Ignore()) + .ForMember(x => x.UpdateDate, exp => exp.Ignore()) + .ForMember(x => x.VirtualPath, exp => exp.Ignore()) + .ForMember(x => x.Path, exp => exp.Ignore()) + .ForMember(x => x.MasterTemplateId, exp => exp.Ignore()) // ok, assigned when creating the template + .ForMember(x => x.IsMasterTemplate, exp => exp.Ignore()) + .ForMember(x => x.HasIdentity, exp => exp.Ignore()); + } + } +} diff --git a/src/Umbraco.Web/Models/SendCodeViewModel.cs b/src/Umbraco.Web/Models/SendCodeViewModel.cs new file mode 100644 index 0000000000..31c6644089 --- /dev/null +++ b/src/Umbraco.Web/Models/SendCodeViewModel.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models +{ + /// + /// Used for 2FA verification + /// + [DataContract(Name = "code", Namespace = "")] + public class Verify2FACodeModel + { + [Required] + [DataMember(Name = "code", IsRequired = true)] + public string Code { get; set; } + + [Required] + [DataMember(Name = "provider", IsRequired = true)] + public string Provider { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs index 284518bf1e..36593736d3 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs @@ -11,16 +11,48 @@ internal static class QueryConditionExtensions { - private static string MakeBinaryOperation(this QueryCondition condition, string operand, int token) + + public static string BuildTokenizedCondition(this QueryCondition condition, int token) { - return string.Format("{0}{1}@{2}", condition.Property.Name, operand, token); + return condition.BuildConditionString(string.Empty, token); } - - public static string BuildCondition(this QueryCondition condition, int token) + public static string BuildCondition(this QueryCondition condition, string parameterAlias) { + return condition.BuildConditionString(parameterAlias + "."); + } + + private static string BuildConditionString(this QueryCondition condition, string prefix, int token = -1) + { + + + var operand = string.Empty; var value = string.Empty; + var constraintValue = string.Empty; + + + //if a token is used, use a token placeholder, otherwise, use the actual value + if(token >= 0){ + constraintValue = string.Format("@{0}", token); + }else { + + //modify the format of the constraint value + switch (condition.Property.Type) + { + case "string": + constraintValue = string.Format("\"{0}\"", condition.ConstraintValue); + break; + case "datetime": + constraintValue = string.Format("DateTime.Parse(\"{0}\")", condition.ConstraintValue); + break; + default: + constraintValue = condition.ConstraintValue; + break; + } + + // constraintValue = condition.Property.Type == "string" ? string.Format("\"{0}\"", condition.ConstraintValue) : condition.ConstraintValue; + } switch (condition.Term.Operathor) { @@ -43,17 +75,23 @@ operand = " <= "; break; case Operathor.Contains: - value = string.Format("{0}.Contains(@{1})", condition.Property.Name, token); + value = string.Format("{0}{1}.Contains({2})", prefix, condition.Property.Alias, constraintValue); break; case Operathor.NotContains: - value = string.Format("!{0}.Contains(@{1})", condition.Property.Name, token); + value = string.Format("!{0}{1}.Contains({2})", prefix, condition.Property.Alias, constraintValue); break; default : operand = " == "; break; } - return string.IsNullOrEmpty(value) ? condition.MakeBinaryOperation(operand, token) : value; + + if (string.IsNullOrEmpty(value) == false) + return value; + + + + return string.Format("{0}{1}{2}{3}", prefix, condition.Property.Alias, operand, constraintValue); } } diff --git a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs index 982286c24b..f1c27ffb96 100644 --- a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs +++ b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; +using System.Web.SessionState; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Web.WebApi; namespace Umbraco.Web.Mvc { @@ -93,6 +95,13 @@ namespace Umbraco.Web.Mvc } //look in this namespace to create the controller controllerPluginRoute.DataTokens.Add("Namespaces", new[] {controllerType.Namespace}); + + //Special case! Check if the controller type implements IRequiresSessionState and if so use our + //custom webapi session handler + if (typeof(IRequiresSessionState).IsAssignableFrom(controllerType)) + { + controllerPluginRoute.RouteHandler = new SessionHttpControllerRouteHandler(); + } } //Don't look anywhere else except this namespace! @@ -100,9 +109,9 @@ namespace Umbraco.Web.Mvc //constraints: only match controllers ending with 'controllerSuffixName' and only match this controller's ID for this route if (controllerSuffixName.IsNullOrWhiteSpace() == false) - { - controllerPluginRoute.Constraints = new RouteValueDictionary( - new Dictionary + { + controllerPluginRoute.Constraints = new RouteValueDictionary( + new Dictionary { {"controller", @"(\w+)" + controllerSuffixName} }); diff --git a/src/Umbraco.Web/Mvc/Strings.Designer.cs b/src/Umbraco.Web/Mvc/Strings.Designer.cs index 02a44fbbd7..243a7f7dd9 100644 --- a/src/Umbraco.Web/Mvc/Strings.Designer.cs +++ b/src/Umbraco.Web/Mvc/Strings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34014 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -65,8 +65,8 @@ namespace Umbraco.Web.Mvc { ///<configuration> /// /// <configSections> - /// <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> - /// <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> + /// <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> + /// <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> /// <section name="page [rest of string was truncated]";. /// internal static string WebConfigTemplate { diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index 89499b833e..90c081d5b0 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -28,12 +28,16 @@ using System.Security; [assembly: System.Security.SecurityRules(System.Security.SecurityRuleSet.Level1)] [assembly: InternalsVisibleTo("Umbraco.Tests")] +[assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] [assembly: InternalsVisibleTo("umbraco.MacroEngines")] [assembly: InternalsVisibleTo("Umbraco.Web.UI")] [assembly: InternalsVisibleTo("umbraco.webservices")] [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/Properties/Settings1.Designer.cs b/src/Umbraco.Web/Properties/Settings1.Designer.cs index 3f63c8d5cd..55e957a383 100644 --- a/src/Umbraco.Web/Properties/Settings1.Designer.cs +++ b/src/Umbraco.Web/Properties/Settings1.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34003 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -12,7 +12,7 @@ namespace Umbraco.Web.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.0.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index 4d58060164..ef66f8740e 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -1,29 +1,60 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using Umbraco.Core; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.ContentPickerAlias, "Content Picker", PropertyEditorValueTypes.Integer, "contentpicker", IsParameterEditor = true, Group = "Pickers")] - public class ContentPickerPropertyEditor : PropertyEditor - { + /// + /// Legacy content property editor that stores Integer Ids + /// + [Obsolete("This editor is obsolete, use ContentPickerPropertyEditor2 instead which stores UDI")] + [PropertyEditor(Constants.PropertyEditors.ContentPickerAlias, "(Obsolete) Content Picker", PropertyEditorValueTypes.Integer, "contentpicker", IsParameterEditor = true, Group = "Pickers", IsDeprecated = true)] + public class ContentPickerPropertyEditor : ContentPickerPropertyEditor2 + { public ContentPickerPropertyEditor() { - _internalPreValues = new Dictionary + InternalPreValues["idType"] = "int"; + } + + /// + /// overridden to change the pre-value picker to use INT ids + /// + /// + protected override PreValueEditor CreatePreValueEditor() + { + var preValEditor = base.CreatePreValueEditor(); + preValEditor.Fields.Single(x => x.Key == "startNodeId").Config["idType"] = "int"; + return preValEditor; + } + } + + /// + /// Content property editor that stores UDI + /// + [PropertyEditor(Constants.PropertyEditors.ContentPicker2Alias, "Content Picker", PropertyEditorValueTypes.String, "contentpicker", IsParameterEditor = true, Group = "Pickers")] + public class ContentPickerPropertyEditor2 : PropertyEditor + { + + public ContentPickerPropertyEditor2() + { + InternalPreValues = new Dictionary { {"startNodeId", "-1"}, {"showOpenButton", "0"}, {"showEditButton", "0"}, - {"showPathOnHover", "0"} + {"showPathOnHover", "0"}, + {"idType", "udi"} }; } - private IDictionary _internalPreValues; + internal IDictionary InternalPreValues; public override IDictionary DefaultPreValues { - get { return _internalPreValues; } - set { _internalPreValues = value; } + get { return InternalPreValues; } + set { InternalPreValues = value; } } protected override PreValueEditor CreatePreValueEditor() @@ -33,17 +64,27 @@ namespace Umbraco.Web.PropertyEditors internal class ContentPickerPreValueEditor : PreValueEditor { - [PreValueField("showOpenButton", "Show open button", "boolean")] - 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; } + public ContentPickerPreValueEditor() + { + //create the fields + Fields.Add(new PreValueField() + { + Key = "showOpenButton", + View = "boolean", + Name = "Show open button (this feature is in preview!)", + Description = "Opens the node in a dialog" + }); + Fields.Add(new PreValueField() + { + Key = "startNodeId", + View = "treepicker", + Name = "Start node", + Config = new Dictionary + { + {"idType", "udi"} + } + }); + } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs index bfdbe368d1..5c89b868b8 100644 --- a/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs @@ -9,7 +9,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { [Obsolete("This is no longer used by default, use the ListViewPropertyEditor instead")] - [PropertyEditor(Constants.PropertyEditors.FolderBrowserAlias, "(Obsolete) Folder Browser", "folderbrowser", HideLabel=true, Icon="icon-folder", Group="media")] + [PropertyEditor(Constants.PropertyEditors.FolderBrowserAlias, "(Obsolete) Folder Browser", "folderbrowser", HideLabel=true, Icon="icon-folder", Group="media", IsDeprecated = true)] public class FolderBrowserPropertyEditor : PropertyEditor { diff --git a/src/Umbraco.Web/PropertyEditors/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/MacroContainerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs index 3ac5788dd2..b3e5390b64 100644 --- a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs @@ -9,7 +9,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MacroContainerAlias, "Macro container", "macrocontainer", ValueType = PropertyEditorValueTypes.Text, Group="rich content", Icon="icon-settings-alt")] + [PropertyEditor(Constants.PropertyEditors.MacroContainerAlias, "Macro Picker", "macrocontainer", ValueType = PropertyEditorValueTypes.Text, Group="rich content", Icon="icon-settings-alt", IsDeprecated = true)] public class MacroContainerPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index 7e966d31ad..5da7bf38eb 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -9,34 +9,23 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MediaPickerAlias, "Legacy Media Picker", PropertyEditorValueTypes.Integer, "mediapicker", Group="media", Icon="icon-picture")] - public class MediaPickerPropertyEditor : PropertyEditor + /// + /// Legacy media property editor that stores Integer Ids + /// + [Obsolete("This editor is obsolete, use ContentPickerPropertyEditor2 instead which stores UDI")] + [PropertyEditor(Constants.PropertyEditors.MediaPickerAlias, "(Obsolete) Media Picker", PropertyEditorValueTypes.Integer, "mediapicker", Group = "media", Icon = "icon-picture", IsDeprecated = true)] + public class MediaPickerPropertyEditor : MediaPickerPropertyEditor2 { public MediaPickerPropertyEditor() { InternalPreValues = new Dictionary { {"multiPicker", "0"}, - {"onlyImages", "0"} + {"onlyImages", "0"}, + {"idType", "int"} }; } - protected IDictionary InternalPreValues; - - protected override PropertyValueEditor CreateValueEditor() - { - //TODO: Need to add some validation to the ValueEditor to ensure that any media chosen actually exists! - return base.CreateValueEditor(); - } - - - - public override IDictionary DefaultPreValues - { - get { return InternalPreValues; } - set { InternalPreValues = value; } - } - protected override PreValueEditor CreatePreValueEditor() { return new SingleMediaPickerPreValueEditor(); @@ -48,4 +37,70 @@ namespace Umbraco.Web.PropertyEditors public int StartNodeId { get; set; } } } + + /// + /// Media picker property editors that stores UDI + /// + [PropertyEditor(Constants.PropertyEditors.MediaPicker2Alias, "Media Picker", PropertyEditorValueTypes.Text, "mediapicker", Group = "media", Icon = "icon-picture")] + public class MediaPickerPropertyEditor2 : PropertyEditor + { + public MediaPickerPropertyEditor2() + { + InternalPreValues = new Dictionary + { + {"idType", "udi"} + }; + } + + internal IDictionary InternalPreValues; + + public override IDictionary DefaultPreValues + { + get { return InternalPreValues; } + set { InternalPreValues = value; } + } + + protected override PreValueEditor CreatePreValueEditor() + { + return new MediaPickerPreValueEditor(); + } + + internal class MediaPickerPreValueEditor : PreValueEditor + { + public MediaPickerPreValueEditor() + { + //create the fields + Fields.Add(new PreValueField() + { + Key = "multiPicker", + View = "boolean", + Name = "Pick multiple items" + }); + Fields.Add(new PreValueField() + { + Key = "onlyImages", + View = "boolean", + Name = "Pick only images", + Description = "Only let the editor choose images from media." + }); + Fields.Add(new PreValueField() + { + Key = "disableFolderSelect", + View = "boolean", + Name = "Disable folder select", + Description = "Do not allow folders to be picked." + }); + Fields.Add(new PreValueField() + { + Key = "startNodeId", + View = "mediapicker", + Name = "Start node", + Config = new Dictionary + { + {"idType", "udi"} + } + }); + } + } + } } diff --git a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs index 7dadbfe24c..68f864e9b6 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs @@ -8,8 +8,33 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MemberPickerAlias, "Member Picker", PropertyEditorValueTypes.Integer, "memberpicker", Group = "People", Icon = "icon-user")] - public class MemberPickerPropertyEditor : PropertyEditor + + [Obsolete("This editor is obsolete, use MemberPickerPropertyEditor2 instead which stores UDI")] + [PropertyEditor(Constants.PropertyEditors.MemberPickerAlias, "(Obsolete) Member Picker", PropertyEditorValueTypes.Integer, "memberpicker", Group = "People", Icon = "icon-user", IsDeprecated = true)] + public class MemberPickerPropertyEditor : MemberPickerPropertyEditor2 { + public MemberPickerPropertyEditor() + { + InternalPreValues["idType"] = "int"; + } + } + + [PropertyEditor(Constants.PropertyEditors.MemberPicker2Alias, "Member Picker", PropertyEditorValueTypes.String, "memberpicker", Group = "People", Icon = "icon-user")] + public class MemberPickerPropertyEditor2 : PropertyEditor + { + public MemberPickerPropertyEditor2() + { + InternalPreValues = new Dictionary + { + {"idType", "udi"} + }; + } + + internal IDictionary InternalPreValues; + public override IDictionary DefaultPreValues + { + get { return InternalPreValues; } + set { InternalPreValues = value; } + } } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index b3d3f02448..fbba130543 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -1,21 +1,45 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "Multinode Treepicker", "contentpicker", Group="pickers", Icon="icon-page-add")] - public class MultiNodeTreePickerPropertyEditor : PropertyEditor + [Obsolete("This editor is obsolete, use MultiNodeTreePickerPropertyEditor2 instead which stores UDI")] + [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "(Obsolete) Multinode Treepicker", "contentpicker", Group = "pickers", Icon = "icon-page-add", IsDeprecated = true)] + public class MultiNodeTreePickerPropertyEditor : MultiNodeTreePickerPropertyEditor2 { public MultiNodeTreePickerPropertyEditor() { - _internalPreValues = new Dictionary + InternalPreValues["idType"] = "int"; + } + + /// + /// overridden to change the pre-value picker to use INT ids + /// + /// + protected override PreValueEditor CreatePreValueEditor() + { + var preValEditor = base.CreatePreValueEditor(); + preValEditor.Fields.Single(x => x.Key == "startNode").Config["idType"] = "int"; + return preValEditor; + } + } + + [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePicker2Alias, "Multinode Treepicker", PropertyEditorValueTypes.Text, "contentpicker", Group="pickers", Icon="icon-page-add")] + public class MultiNodeTreePickerPropertyEditor2 : PropertyEditor + { + public MultiNodeTreePickerPropertyEditor2() + { + InternalPreValues = new Dictionary { {"multiPicker", "1"}, {"showOpenButton", "0"}, {"showEditButton", "0"}, - {"showPathOnHover", "0"} + {"showPathOnHover", "0"}, + {"idType", "udi"} }; } @@ -24,36 +48,55 @@ namespace Umbraco.Web.PropertyEditors return new MultiNodePickerPreValueEditor(); } - private IDictionary _internalPreValues; + internal IDictionary InternalPreValues; public override IDictionary DefaultPreValues { - get { return _internalPreValues; } - set { _internalPreValues = value; } + get { return InternalPreValues; } + set { InternalPreValues = value; } } internal class MultiNodePickerPreValueEditor : PreValueEditor { - [PreValueField("startNode", "Node type", "treesource")] - public string StartNode { get; set; } - - [PreValueField("filter", "Allow items of type", "textstring", Description = "Separate with comma")] - public string Filter { get; set; } - - [PreValueField("minNumber", "Minimum number of items", "number")] - public string MinNumber { get; set; } - - [PreValueField("maxNumber", "Maximum number of items", "number")] - public string MaxNumber { get; set; } - - - [PreValueField("showOpenButton", "Show open button", "boolean")] - 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; } + public MultiNodePickerPreValueEditor() + { + //create the fields + Fields.Add(new PreValueField() + { + Key = "startNode", + View = "treesource", + Name = "Node type", + Config = new Dictionary + { + {"idType", "udi"} + } + }); + Fields.Add(new PreValueField() + { + Key = "filter", + View = "textstring", + Name = "Allow items of type", + Description = "Separate with comma" + }); + Fields.Add(new PreValueField() + { + Key = "minNumber", + View = "number", + Name = "Minimum number of items" + }); + Fields.Add(new PreValueField() + { + Key = "maxNumber", + View = "number", + Name = "Maximum number of items" + }); + Fields.Add(new PreValueField() + { + Key = "showOpenButton", + View = "boolean", + Name = "Show open button (this feature is in preview!)", + Description = "Opens the node in a dialog" + }); + } /// /// This ensures the multiPicker pre-val is set based on the maxNumber of nodes set diff --git a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs index 37587d1c54..12aec22e31 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs @@ -1,35 +1,29 @@ -using Umbraco.Core; +using System; +using System.Linq; +using Umbraco.Core; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "Media Picker", "mediapicker", Group = "media", Icon = "icon-pictures-alt-2")] - public class MultipleMediaPickerPropertyEditor : MediaPickerPropertyEditor - { + [Obsolete("This editor is obsolete, use MultipleMediaPickerPropertyEditor2 instead which stores UDI")] + [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "(Obsolete) Media Picker", "mediapicker", Group = "media", Icon = "icon-pictures-alt-2", IsDeprecated = true)] + public class MultipleMediaPickerPropertyEditor : MediaPickerPropertyEditor2 + { public MultipleMediaPickerPropertyEditor() { - //clear the pre-values so it defaults to a multiple picker. - InternalPreValues.Clear(); - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new MediaPickerPreValueEditor(); - } - - internal class MediaPickerPreValueEditor : PreValueEditor - { - [PreValueField("multiPicker", "Pick multiple items", "boolean")] - public bool MultiPicker { get; set; } - - [PreValueField("onlyImages", "Pick only images", "boolean", Description = "Only let the editor choose images from media.")] - public bool OnlyImages { get; set; } + //default it to multi picker + InternalPreValues["multiPicker"] = "1"; + } - [PreValueField("disableFolderSelect", "Disable folder select", "boolean", Description = "Do not allow folders to be picked.")] - public bool DisableFolderSelect { get; set; } - - [PreValueField("startNodeId", "Start node", "mediapicker")] - public int StartNodeId { get; set; } + /// + /// overridden to change the pre-value picker to use INT ids + /// + /// + protected override PreValueEditor CreatePreValueEditor() + { + var preValEditor = base.CreatePreValueEditor(); + preValEditor.Fields.Single(x => x.Key == "startNodeId").Config["idType"] = "int"; + return preValEditor; } - } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index be228bf3ef..b44c65b157 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Net; using System.Runtime.InteropServices; using Newtonsoft.Json.Linq; using Umbraco.Core; @@ -61,14 +60,7 @@ namespace Umbraco.Web.PropertyEditors public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) { var json = editorValue.Value as JArray; - return json == null - ? null - : json.Select(x => x.Value()).Where(x => x.IsNullOrWhiteSpace() == false) - //First we will decode it as html because we know that if this is not a malicious post that the value is - // already Html encoded by the tags JavaScript controller. Then we'll re-Html Encode it to ensure that in case this - // is a malicious post (i.e. someone is submitting data manually by modifying the request). - .Select(WebUtility.HtmlDecode) - .Select(WebUtility.HtmlEncode); + return json == null ? null : json.Select(x => x.Value()); } /// diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index e58bc73b25..c8ddd77afd 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -2,11 +2,9 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Runtime.CompilerServices; -using System.Text; using System.Xml; using System.Xml.XPath; using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; @@ -18,7 +16,6 @@ using umbraco.BusinessLogic; using umbraco.presentation.preview; using Umbraco.Core.Services; using GlobalSettings = umbraco.GlobalSettings; -using Task = System.Threading.Tasks.Task; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { @@ -83,7 +80,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache && DomainHelper.ExistsDomainInPath(umbracoContext.Application.Services.DomainService.GetAll(false), content.Path, domainRootNodeId) == false; if (deepest) - _routesCache.Store(content.Id, route); + _routesCache.Store(content.Id, route, true); // trusted route } public virtual string GetRouteById(UmbracoContext umbracoContext, bool preview, int contentId) @@ -102,38 +99,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (route == null) return null; - // find the content back, detect routes collisions: we should find ourselves back, - // else it means that another content with "higher priority" is sharing the same route. - // perf impact: - // - non-colliding, adds one complete "by route" lookup, only on the first time a url is computed (then it's cached anyways) - // - colliding, adds one "by route" lookup, the first time the url is computed, then one dictionary looked each time it is computed again - // assuming no collisions, the impact is one complete "by route" lookup the first time each url is computed - // - // U4-9121 - this lookup is too expensive when computing a large amount of urls on a front-end (eg menu) - // ... thinking about moving the lookup out of the path into its own async task, so we are not reporting errors - // in the back-office anymore, but at least we are not polluting the cache - // instead, refactored DeterminedIdByRoute to stop using XPath, with a 16x improvement according to benchmarks - // will it be enough? + // cache the route BUT do NOT trust it as it can be a colliding route + // meaning if we GetRouteById again, we'll get it from cache, but it + // won't be used for inbound routing + if (preview == false) + _routesCache.Store(contentId, route, false); - var loopId = preview ? 0 : _routesCache.GetNodeId(route); // might be cached already in case of collision - if (loopId == 0) - { - var content = DetermineIdByRoute(umbracoContext, preview, route, GlobalSettings.HideTopLevelNodeFromPath); - - // add the other route to cache so next time we have it already - if (content != null && preview == false) - AddToCacheIfDeepestRoute(umbracoContext, content, route); - - loopId = content == null ? 0 : content.Id; // though... 0 here would be quite weird? - } - - // cache if we have a route and not previewing and it's not a colliding route - // (the result of DetermineRouteById is always the deepest route) - if (/*route != null &&*/ preview == false && loopId == contentId) - _routesCache.Store(contentId, route); - - // return route if no collision, else report collision - return loopId == contentId ? route : ("err/" + loopId); + return route; } IPublishedContent DetermineIdByRoute(UmbracoContext umbracoContext, bool preview, string route, bool hideTopLevelNode) @@ -149,18 +121,25 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache //check if we can find the node in our xml cache var id = NavigateRoute(umbracoContext, preview, startNodeId, path, hideTopLevelNode); - if (id > 0) return GetById(umbracoContext, preview, id); + return id > 0 ? GetById(umbracoContext, preview, id) : null; + } - // if hideTopLevelNodePath is true then for url /foo we looked for /*/foo - // but maybe that was the url of a non-default top-level node, so we also - // have to look for /foo (see note in ApplyHideTopLevelNodeFromPath). - if (hideTopLevelNode && path.Length > 1 && path.IndexOf('/', 1) < 0) + private static XmlElement GetXmlElementChildWithLowestSortOrder(XmlNode element) + { + XmlElement elt = null; + var min = int.MaxValue; + foreach (var n in element.ChildNodes) { - var id2 = NavigateRoute(umbracoContext, preview, startNodeId, path, false); - if (id2 > 0) return GetById(umbracoContext, preview, id2); - } + var e = n as XmlElement; + if (e == null) continue; - return null; + var sortOrder = int.Parse(e.GetAttribute("sortOrder")); + if (sortOrder >= min) continue; + + min = sortOrder; + elt = e; + } + return elt; } private int NavigateRoute(UmbracoContext umbracoContext, bool preview, int startNodeId, string path, bool hideTopLevelNode) @@ -177,17 +156,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return elt == null ? -1 : startNodeId; } - elt = null; - var min = int.MaxValue; - foreach (var e in xml.DocumentElement.ChildNodes.OfType()) - { - var sortOrder = int.Parse(e.GetAttribute("sortOrder")); - if (sortOrder < min) - { - min = sortOrder; - elt = e; - } - } + elt = GetXmlElementChildWithLowestSortOrder(xml.DocumentElement); return elt == null ? -1 : int.Parse(elt.GetAttribute("id")); } @@ -201,12 +170,19 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (hideTopLevelNode && startNodeId <= 0) { - foreach (var e in elt.ChildNodes.OfType()) + //Don't use OfType or Cast, this is critical code, all ChildNodes are XmlElement so explicitly cast + // https://gist.github.com/Shazwazza/04e2e5642a316f4a87e52dada2901198 + foreach (var n in elt.ChildNodes) { + var e = n as XmlElement; + if (e == null) continue; + var id = NavigateElementRoute(e, urlParts); if (id > 0) return id; } - return -1; + + if (urlParts.Length > 1) + return -1; } return NavigateElementRoute(elt, urlParts); @@ -224,8 +200,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache while (found && i < urlParts.Length) { found = false; - foreach (var child in elt.ChildNodes.OfType()) + //Don't use OfType or Cast, this is critical code, all ChildNodes are XmlElement so explicitly cast + // https://gist.github.com/Shazwazza/04e2e5642a316f4a87e52dada2901198 + var sortOrder = -1; + foreach (var o in elt.ChildNodes) { + var child = o as XmlElement; + if (child == null) continue; + var noNode = UseLegacySchema ? child.Name != "node" : child.GetAttributeNode("isDoc") == null; @@ -233,8 +215,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (child.GetAttribute("urlName") != urlParts[i]) continue; found = true; + + var so = int.Parse(child.GetAttribute("sortOrder")); + if (sortOrder >= 0 && so >= sortOrder) continue; + + sortOrder = so; elt = child; - break; } i++; } @@ -243,7 +229,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache string DetermineRouteById(UmbracoContext umbracoContext, bool preview, int contentId) { - var elt = GetXml(umbracoContext, preview).GetElementById(contentId.ToString(CultureInfo.InvariantCulture)); + var xml = GetXml(umbracoContext, preview); + var elt = xml.GetElementById(contentId.ToString(CultureInfo.InvariantCulture)); if (elt == null) return null; var domainHelper = GetDomainHelper(umbracoContext.Application.Services.DomainService); @@ -270,7 +257,18 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // no domain, respect HideTopLevelNodeFromPath for legacy purposes if (hasDomains == false && GlobalSettings.HideTopLevelNodeFromPath) - ApplyHideTopLevelNodeFromPath(umbracoContext, eltId, eltParentId, pathParts); + { + if (eltParentId == -1) + { + var rootNode = GetXmlElementChildWithLowestSortOrder(xml.DocumentElement); + if (rootNode != null && rootNode == elt) + pathParts.RemoveAt(pathParts.Count - 1); + } + else + { + pathParts.RemoveAt(pathParts.Count - 1); + } + } // assemble the route pathParts.Reverse(); @@ -280,30 +278,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return route; } - static void ApplyHideTopLevelNodeFromPath(UmbracoContext umbracoContext, int nodeId, int parentId, IList pathParts) - { - // in theory if hideTopLevelNodeFromPath is true, then there should be only once - // top-level node, or else domains should be assigned. but for backward compatibility - // we add this check - we look for the document matching "/" and if it's not us, then - // we do not hide the top level path - // it has to be taken care of in GetByRoute too so if - // "/foo" fails (looking for "/*/foo") we try also "/foo". - // this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but - // that's the way it works pre-4.10 and we try to be backward compat for the time being - if (parentId == -1) - { - var rootNode = umbracoContext.ContentCache.GetByRoute("/", true); - if (rootNode == null) - throw new Exception("Failed to get node at /."); - if (rootNode.Id == nodeId) // remove only if we're the default node - pathParts.RemoveAt(pathParts.Count - 1); - } - else - { - pathParts.RemoveAt(pathParts.Count - 1); - } - } - #endregion #region XPath Strings diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs index 9c8eed322b..4e775b3253 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs @@ -83,10 +83,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// /// The node identified. /// The route. - public void Store(int nodeId, string route) + /// A value indicating whether the value can be trusted for inbound routing. + public void Store(int nodeId, string route, bool trust) { _routes.AddOrUpdate(nodeId, i => route, (i, s) => route); - _nodeIds.AddOrUpdate(route, i => nodeId, (i, s) => nodeId); + if (trust) + _nodeIds.AddOrUpdate(route, i => nodeId, (i, s) => nodeId); } /// @@ -119,15 +121,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// The node identifier. public void ClearNode(int nodeId) { - if (!_routes.ContainsKey(nodeId)) return; - - string key; - if (!_routes.TryGetValue(nodeId, out key)) return; - - int val; - _nodeIds.TryRemove(key, out val); - string val2; - _routes.TryRemove(nodeId, out val2); + string route; + if (_routes.TryRemove(nodeId, out route)) + { + int id; + _nodeIds.TryRemove(route, out id); + } } /// diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index 6d7864b874..202fe551c7 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -313,100 +313,151 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _parentInitialized = true; } - private void InitializeNode() - { - if (_xmlNode == null) return; - - if (_xmlNode.Attributes != null) - { - _id = int.Parse(_xmlNode.Attributes.GetNamedItem("id").Value); - if (_xmlNode.Attributes.GetNamedItem("key") != null) // because, migration - _key = Guid.Parse(_xmlNode.Attributes.GetNamedItem("key").Value); - if (_xmlNode.Attributes.GetNamedItem("template") != null) - _template = int.Parse(_xmlNode.Attributes.GetNamedItem("template").Value); - if (_xmlNode.Attributes.GetNamedItem("sortOrder") != null) - _sortOrder = int.Parse(_xmlNode.Attributes.GetNamedItem("sortOrder").Value); - if (_xmlNode.Attributes.GetNamedItem("nodeName") != null) - _name = _xmlNode.Attributes.GetNamedItem("nodeName").Value; - if (_xmlNode.Attributes.GetNamedItem("writerName") != null) - _writerName = _xmlNode.Attributes.GetNamedItem("writerName").Value; - if (_xmlNode.Attributes.GetNamedItem("urlName") != null) - _urlName = _xmlNode.Attributes.GetNamedItem("urlName").Value; - // Creatorname is new in 2.1, so published xml might not have it! - try - { - _creatorName = _xmlNode.Attributes.GetNamedItem("creatorName").Value; - } - catch - { - _creatorName = _writerName; - } - - //Added the actual userID, as a user cannot be looked up via full name only... - if (_xmlNode.Attributes.GetNamedItem("creatorID") != null) - _creatorId = int.Parse(_xmlNode.Attributes.GetNamedItem("creatorID").Value); - if (_xmlNode.Attributes.GetNamedItem("writerID") != null) - _writerId = int.Parse(_xmlNode.Attributes.GetNamedItem("writerID").Value); - - if (UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema) - { - if (_xmlNode.Attributes.GetNamedItem("nodeTypeAlias") != null) - _docTypeAlias = _xmlNode.Attributes.GetNamedItem("nodeTypeAlias").Value; - } - else - { - _docTypeAlias = _xmlNode.Name; - } - - if (_xmlNode.Attributes.GetNamedItem("nodeType") != null) - _docTypeId = int.Parse(_xmlNode.Attributes.GetNamedItem("nodeType").Value); - if (_xmlNode.Attributes.GetNamedItem("path") != null) - _path = _xmlNode.Attributes.GetNamedItem("path").Value; - if (_xmlNode.Attributes.GetNamedItem("version") != null) - _version = new Guid(_xmlNode.Attributes.GetNamedItem("version").Value); - if (_xmlNode.Attributes.GetNamedItem("createDate") != null) - _createDate = DateTime.Parse(_xmlNode.Attributes.GetNamedItem("createDate").Value); - if (_xmlNode.Attributes.GetNamedItem("updateDate") != null) - _updateDate = DateTime.Parse(_xmlNode.Attributes.GetNamedItem("updateDate").Value); - if (_xmlNode.Attributes.GetNamedItem("level") != null) - _level = int.Parse(_xmlNode.Attributes.GetNamedItem("level").Value); - - _isDraft = (_xmlNode.Attributes.GetNamedItem("isDraft") != null); - } - - // load data - var dataXPath = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "data" : "* [not(@isDoc)]"; - var nodes = _xmlNode.SelectNodes(dataXPath); - - _contentType = PublishedContentType.Get(PublishedItemType.Content, _docTypeAlias); - - var propertyNodes = new Dictionary(); - if (nodes != null) - foreach (XmlNode n in nodes) - { - var attrs = n.Attributes; - if (attrs == null) continue; - var alias = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema - ? attrs.GetNamedItem("alias").Value - : n.Name; - propertyNodes[alias.ToLowerInvariant()] = n; - } - - _properties = _contentType.PropertyTypes.Select(p => - { - XmlNode n; - return propertyNodes.TryGetValue(p.PropertyTypeAlias.ToLowerInvariant(), out n) - ? new XmlPublishedProperty(p, _isPreviewing, n) - : new XmlPublishedProperty(p, _isPreviewing); - }).Cast().ToDictionary( - x => x.PropertyTypeAlias, - x => x, - StringComparer.OrdinalIgnoreCase); + private void InitializeNode() + { + InitializeNode(_xmlNode, UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema, _isPreviewing, + out _id, out _key, out _template, out _sortOrder, out _name, out _writerName, + out _urlName, out _creatorName, out _creatorId, out _writerId, out _docTypeAlias, out _docTypeId, out _path, + out _version, out _createDate, out _updateDate, out _level, out _isDraft, out _contentType, out _properties, + PublishedContentType.Get); // warn: this is not thread-safe... _nodeInitialized = true; + } + + internal static void InitializeNode(XmlNode xmlNode, bool legacy, bool isPreviewing, + out int id, out Guid key, out int template, out int sortOrder, out string name, out string writerName, out string urlName, + out string creatorName, out int creatorId, out int writerId, out string docTypeAlias, out int docTypeId, out string path, + out Guid version, out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft, + out PublishedContentType contentType, out Dictionary properties, + Func getPublishedContentType) + { + //initialize the out params with defaults: + writerName = null; + docTypeAlias = null; + id = template = sortOrder = template = creatorId = writerId = docTypeId = level = default(int); + key = version = default(Guid); + name = writerName = urlName = creatorName = docTypeAlias = path = null; + createDate = updateDate = default(DateTime); + isDraft = false; + contentType = null; + properties = null; + + //return if this is null + if (xmlNode == null) + { + return; + } + + if (xmlNode.Attributes != null) + { + id = int.Parse(xmlNode.Attributes.GetNamedItem("id").Value); + if (xmlNode.Attributes.GetNamedItem("key") != null) // because, migration + key = Guid.Parse(xmlNode.Attributes.GetNamedItem("key").Value); + if (xmlNode.Attributes.GetNamedItem("template") != null) + template = int.Parse(xmlNode.Attributes.GetNamedItem("template").Value); + if (xmlNode.Attributes.GetNamedItem("sortOrder") != null) + sortOrder = int.Parse(xmlNode.Attributes.GetNamedItem("sortOrder").Value); + if (xmlNode.Attributes.GetNamedItem("nodeName") != null) + name = xmlNode.Attributes.GetNamedItem("nodeName").Value; + if (xmlNode.Attributes.GetNamedItem("writerName") != null) + writerName = xmlNode.Attributes.GetNamedItem("writerName").Value; + if (xmlNode.Attributes.GetNamedItem("urlName") != null) + urlName = xmlNode.Attributes.GetNamedItem("urlName").Value; + // Creatorname is new in 2.1, so published xml might not have it! + try + { + creatorName = xmlNode.Attributes.GetNamedItem("creatorName").Value; + } + catch + { + creatorName = writerName; + } + + //Added the actual userID, as a user cannot be looked up via full name only... + if (xmlNode.Attributes.GetNamedItem("creatorID") != null) + creatorId = int.Parse(xmlNode.Attributes.GetNamedItem("creatorID").Value); + if (xmlNode.Attributes.GetNamedItem("writerID") != null) + writerId = int.Parse(xmlNode.Attributes.GetNamedItem("writerID").Value); + + if (legacy) + { + if (xmlNode.Attributes.GetNamedItem("nodeTypeAlias") != null) + docTypeAlias = xmlNode.Attributes.GetNamedItem("nodeTypeAlias").Value; + } + else + { + docTypeAlias = xmlNode.Name; + } + + if (xmlNode.Attributes.GetNamedItem("nodeType") != null) + docTypeId = int.Parse(xmlNode.Attributes.GetNamedItem("nodeType").Value); + if (xmlNode.Attributes.GetNamedItem("path") != null) + path = xmlNode.Attributes.GetNamedItem("path").Value; + if (xmlNode.Attributes.GetNamedItem("version") != null) + version = new Guid(xmlNode.Attributes.GetNamedItem("version").Value); + if (xmlNode.Attributes.GetNamedItem("createDate") != null) + createDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("createDate").Value); + if (xmlNode.Attributes.GetNamedItem("updateDate") != null) + updateDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("updateDate").Value); + if (xmlNode.Attributes.GetNamedItem("level") != null) + level = int.Parse(xmlNode.Attributes.GetNamedItem("level").Value); + + isDraft = (xmlNode.Attributes.GetNamedItem("isDraft") != null); + } + + //dictionary to store the property node data + var propertyNodes = new Dictionary(); + + foreach (XmlNode n in xmlNode.ChildNodes) + { + var e = n as XmlElement; + if (e == null) continue; + if (legacy) + { + if (n.Name == "data") + { + PopulatePropertyNodes(propertyNodes, e, true); + } + else break; //we are not longer on property elements + } + else + { + if (e.HasAttribute("isDoc") == false) + { + PopulatePropertyNodes(propertyNodes, e, false); + } + else break; //we are not longer on property elements + } + } + + //lookup the content type and create the properties collection + contentType = getPublishedContentType(PublishedItemType.Content, docTypeAlias); + properties = new Dictionary(StringComparer.OrdinalIgnoreCase); + + //fill in the property collection + foreach (var propertyType in contentType.PropertyTypes) + { + XmlNode n; + var val = propertyNodes.TryGetValue(propertyType.PropertyTypeAlias.ToLowerInvariant(), out n) + ? new XmlPublishedProperty(propertyType, isPreviewing, n) + : new XmlPublishedProperty(propertyType, isPreviewing); + + properties[propertyType.PropertyTypeAlias] = val; + } } + private static void PopulatePropertyNodes(IDictionary propertyNodes, XmlNode n, bool legacy) + { + var attrs = n.Attributes; + if (attrs == null) return; + + var alias = legacy + ? attrs.GetNamedItem("alias").Value + : n.Name; + propertyNodes[alias.ToLowerInvariant()] = n; + } + private void InitializeChildren() { if (_xmlNode == null) return; diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 5caa08729a..7972f109f9 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1777,6 +1777,17 @@ namespace Umbraco.Web return content.Children().FirstOrDefault(); } + /// + /// Gets the first child of the content, of a given content type. + /// + /// The content. + /// The content type alias. + /// The first child of content, of the given content type. + public static IPublishedContent FirstChild(this IPublishedContent content, string alias) + { + return content.Children( alias ).FirstOrDefault(); + } + public static IPublishedContent FirstChild(this IPublishedContent content, Func predicate) { return content.Children(predicate).FirstOrDefault(); diff --git a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs deleted file mode 100644 index cf75e60755..0000000000 --- a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Runtime.Remoting.Messaging; -using Umbraco.Core.Events; - -namespace Umbraco.Web -{ - /// - /// Stores the instance of EventMessages in the current request so all events will share the same instance - /// - internal class RequestLifespanMessagesFactory : IEventMessagesFactory - { - private const string ContextKey = "Umbraco.Web.RequestLifespanMessagesFactory"; - private readonly IHttpContextAccessor _httpAccessor; - - public RequestLifespanMessagesFactory(IHttpContextAccessor httpAccessor) - { - if (httpAccessor == null) throw new ArgumentNullException("httpAccessor"); - _httpAccessor = httpAccessor; - } - - public EventMessages Get() - { - var httpContext = _httpAccessor.Value; - if (httpContext != null) - { - var eventMessages = httpContext.Items[ContextKey] as EventMessages; - if (eventMessages == null) httpContext.Items[ContextKey] = eventMessages = new EventMessages(); - return eventMessages; - } - - var lccContext = CallContext.LogicalGetData(ContextKey) as EventMessages; - if (lccContext != null) return lccContext; - - throw new Exception("Could not get messages."); - } - - public EventMessages TryGet() - { - var httpContext = _httpAccessor.Value; - return httpContext != null - ? httpContext.Items[ContextKey] as EventMessages - : CallContext.LogicalGetData(ContextKey) as EventMessages; - } - - // Deploy wants to execute things outside of a request, where this factory would fail, - // so the factory is extended so that Deploy can Set/Clear event messages in the logical - // call context (which flows with async) - it needs to be set and cleared because, contrary - // to http context, it's not being cleared at the end of anything. - // - // to be refactored in v8! the whole IEventMessagesFactory is borked anyways - - public void SetLlc() - { - CallContext.LogicalSetData(ContextKey, new EventMessages()); - } - - public void ClearLlc() - { - CallContext.FreeNamedDataSlot(ContextKey); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index d55095d151..6b3c0fe69b 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -65,14 +65,6 @@ namespace Umbraco.Web.Routing return null; } - if (route.StartsWith("err/")) - { - LogHelper.Debug( - "Page with nodeId={0} has a colliding url with page with nodeId={1}.", - () => id, () => route.Substring(4)); - return "#err-" + route.Substring(4); - } - var domainHelper = new DomainHelper(umbracoContext.Application.Services.DomainService); // extract domainUri and path @@ -115,9 +107,6 @@ namespace Umbraco.Web.Routing return null; } - if (route.StartsWith("err/")) - return null; - var domainHelper = new DomainHelper(umbracoContext.Application.Services.DomainService); // extract domainUri and path diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 03a50a4b99..05b6b0e468 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -67,6 +67,32 @@ namespace Umbraco.Web.Routing #region Public + /// + /// Tries to route the request. + /// + internal bool TryRouteRequest() + { + // disabled - is it going to change the routing? + //_pcr.OnPreparing(); + + FindDomain(); + if (_pcr.IsRedirect) return false; + if (_pcr.HasPublishedContent) return true; + FindPublishedContent(); + if (_pcr.IsRedirect) return false; + + // don't handle anything - we just want to ensure that we find the content + //HandlePublishedContent(); + //FindTemplate(); + //FollowExternalRedirect(); + //HandleWildcardDomains(); + + // disabled - we just want to ensure that we find the content + //_pcr.OnPrepared(); + + return _pcr.HasPublishedContent; + } + /// /// Prepares the request. /// diff --git a/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs index b13cb7b296..80e4192f72 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs @@ -295,8 +295,7 @@ namespace Umbraco.Web.Routing private static bool IsNotRoute(string route) { // null if content not found - // err/- if collision or anomaly or ... - return route == null || route.StartsWith("err/"); + return route == null; } // gets a value indicating whether server is 'slave' diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index c6e08c16c5..54ef8a42b5 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Web.Security; using Umbraco.Core.Models; using umbraco; +using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; namespace Umbraco.Web.Routing @@ -62,34 +65,45 @@ namespace Umbraco.Web.Routing { urls.Add(ui.Text("content", "getUrlException", umbracoContext.Security.CurrentUser)); } - else if (url.StartsWith("#err-")) + else { - // route error, report - var id = int.Parse(url.Substring(5)); - var o = umbracoContext.ContentCache.GetById(id); - string s; - if (o == null) + // test for collisions + var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); + if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(UmbracoContext.Current.CleanedUmbracoUrl); + var pcr = new PublishedContentRequest(uri, UmbracoContext.Current.RoutingContext, UmbracoConfig.For.UmbracoSettings().WebRouting, s => Roles.Provider.GetRolesForUser(s)); + pcr.Engine.TryRouteRequest(); + + if (pcr.HasPublishedContent == false) { - s = "(unknown)"; + urls.Add(ui.Text("content", "routeError", "(error)", umbracoContext.Security.CurrentUser)); + } + else if (pcr.PublishedContent.Id != content.Id) + { + var o = pcr.PublishedContent; + string s; + if (o == null) + { + s = "(unknown)"; + } + else + { + var l = new List(); + while (o != null) + { + l.Add(o.Name); + o = o.Parent; + } + l.Reverse(); + s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; + + } + urls.Add(ui.Text("content", "routeError", s, umbracoContext.Security.CurrentUser)); } else { - var l = new List(); - while (o != null) - { - l.Add(o.Name); - o = o.Parent; - } - l.Reverse(); - s = "/" + string.Join("/", l) + " (id=" + id + ")"; - + urls.Add(url); + urls.AddRange(urlProvider.GetOtherUrls(content.Id)); } - urls.Add(ui.Text("content", "routeError", s, umbracoContext.Security.CurrentUser)); - } - else - { - urls.Add(url); - urls.AddRange(urlProvider.GetOtherUrls(content.Id)); } return urls; } diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index 794c927f97..760304574d 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -77,11 +77,13 @@ namespace Umbraco.Web.Scheduling return false; // do NOT repeat, going down } - // running on a background task, requires a safe database (see UsingSafeDatabase doc) - using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase()) + // running on a background task, and Log.CleanLogs uses the old SqlHelper, + // better wrap in a scope and ensure it's all cleaned up and nothing leaks + using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) using (DisposableTimer.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { Log.CleanLogs(GetLogScrubbingMaximumAge(_settings)); + scope.Complete(); } return true; // repeat diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 5288f2630a..d7ed241275 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -82,12 +82,14 @@ namespace Umbraco.Web.Scheduling Content = new StringContent(string.Empty) }; - // running on a background task, requires a safe database (see UsingSafeDatabase doc) + // running on a background task, requires its own (safe) scope // (GetAuthenticationHeaderValue uses UserService to load the current user, hence requires a database) - using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase()) + // (might not need a scope but we don't know really) + using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) { //pass custom the authorization header request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext); + scope.Complete(); } var result = await wc.SendAsync(request, token); diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index 80903dff90..7e4c9c45aa 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -55,10 +55,19 @@ namespace Umbraco.Web.Security.Identity app.SetLoggerFactory(new OwinLoggerFactory()); } - // TODO: Move this method in v8, it doesn't belong in this namespace/extension class - public static void ConfigureSignalR(this IAppBuilder app) + /// + /// This maps a Signal path/hub + /// + /// + /// + public static IAppBuilder UseSignalR(this IAppBuilder app) { - app.MapSignalR("/umbraco/signalr", new HubConfiguration { EnableDetailedErrors = true }); + + // TODO: Move this method in v8, it doesn't belong in this namespace/extension class + var umbracoPath = GlobalSettings.UmbracoMvcArea; + + return app.MapSignalR(HttpRuntime.AppDomainAppVirtualPath + + umbracoPath + "/BackOffice/signalr", new HubConfiguration { EnableDetailedErrors = true }); } /// diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 4f5c14723b..158910a794 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -428,7 +428,8 @@ namespace Umbraco.Web.Security var viewProperties = new List(); foreach (var prop in memberType.PropertyTypes - .Where(x => builtIns.Contains(x.Alias) == false && memberType.MemberCanEditProperty(x.Alias))) + .Where(x => builtIns.Contains(x.Alias) == false && memberType.MemberCanEditProperty(x.Alias)) + .OrderBy(p => p.SortOrder)) { var value = string.Empty; if (member != null) diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index f0c0861059..cda9f04fad 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -189,13 +189,13 @@ namespace Umbraco.Web.Security } /// - /// Returns the back office IUser instance for the username specified + /// Gets (and creates if not found) the back office instance for the username specified /// /// /// /// - /// This will return an Iuser instance no matter what membership provider is installed for the back office, it will automatically - /// create any missing Iuser accounts if one is not found and a custom membership provider is being used. + /// This will return an instance no matter what membership provider is installed for the back office, it will automatically + /// create any missing accounts if one is not found and a custom membership provider or is being used. /// internal IUser GetBackOfficeUser(string username) { diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs index 4a316fe96f..f1d7f761af 100644 --- a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Persistence.Migrations; using Umbraco.Web.WebApi.Filters; using umbraco.interfaces; +using Umbraco.Core; using Umbraco.Core.Configuration; namespace Umbraco.Web.Strategies.Migrations @@ -15,7 +16,7 @@ namespace Umbraco.Web.Strategies.Migrations { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (e.ProductName != Constants.System.UmbracoMigrationName) return; if (HttpContext.Current == null) return; diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs index cf9ddeafb2..52144ae383 100644 --- a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Strategies.Migrations { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (e.ProductName != Constants.System.UmbracoMigrationName) return; var target70 = new Version(7, 0, 0); diff --git a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs index 270fe4eace..45eb0a92b3 100644 --- a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs +++ b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Strategies.Migrations { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (e.ProductName != Constants.System.UmbracoMigrationName) return; var target720 = new Version(7, 2, 0); diff --git a/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs b/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs index 9a8b6a6044..a4e4349372 100644 --- a/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs +++ b/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Strategies.Migrations { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (e.ProductName != Constants.System.UmbracoMigrationName) return; var target73 = new Version(7, 3, 0); diff --git a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs index 7be8d818d3..70f0b58f1a 100644 --- a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs +++ b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Strategies.Migrations { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (e.ProductName != Constants.System.UmbracoMigrationName) return; var target = new Version(6, 0, 0); if (e.ConfiguredVersion < target) diff --git a/src/Umbraco.Web/Strategies/Migrations/RebuildXmlCachesAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/RebuildXmlCachesAfterUpgrade.cs index e62a738675..5f8f8b8593 100644 --- a/src/Umbraco.Web/Strategies/Migrations/RebuildXmlCachesAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/RebuildXmlCachesAfterUpgrade.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Strategies.Migrations { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (e.ProductName != Constants.System.UmbracoMigrationName) return; var v730 = new Semver.SemVersion(new Version(7, 3, 0)); diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs index 93d3aa2f87..a25deac1d9 100644 --- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs +++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs @@ -137,11 +137,9 @@ namespace Umbraco.Web.Strategies { try { - // running on a background task, requires a safe database (see UsingSafeDatabase doc) - using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase()) - { - _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); - } + // TouchServer uses a proper unit of work etc underneath so even in a + // background task it is safe to call it without dealing with any scope + _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); return true; // repeat } 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..2a5e28496e 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,23 +122,34 @@ namespace Umbraco.Web.Trees protected IEnumerable GetChildEntities(string id) { + // use helper method to ensure we support both integer and guid lookups int iid; + + // look up from GUID if it's not an integer if (int.TryParse(id, out iid) == false) { - 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; } + //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 if (iid == Constants.System.Root && UserStartNode != Constants.System.Root) { //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 +187,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 +221,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 +298,39 @@ namespace Umbraco.Web.Trees { return allowedUserOptions.Select(x => x.Action).OfType().Any(); } + + /// + /// Get an entity via an id that can be either an integer, Guid or UDI + /// + /// + /// + internal IUmbracoEntity GetEntityFromId(string id) + { + IUmbracoEntity entity; + + Guid idGuid = Guid.Empty; + int idInt; + Udi idUdi; + + if (Guid.TryParse(id, out idGuid)) + { + entity = Services.EntityService.GetByKey(idGuid, UmbracoObjectType); + } + else if (int.TryParse(id, out idInt)) + { + entity = Services.EntityService.Get(idInt, UmbracoObjectType); + } + else if (Udi.TryParse(id, out idUdi)) + { + var guidUdi = idUdi as GuidUdi; + entity = guidUdi != null ? Services.EntityService.GetByKey(guidUdi.Guid, UmbracoObjectType) : null; + } + else + { + return null; + } + + return entity; + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/FileSystemTreeController.cs b/src/Umbraco.Web/Trees/FileSystemTreeController.cs index f9ebaaf2d6..88f322971d 100644 --- a/src/Umbraco.Web/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web/Trees/FileSystemTreeController.cs @@ -1,10 +1,10 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Net.Http.Formatting; +using umbraco.BusinessLogic.Actions; +using Umbraco.Core; using Umbraco.Core.IO; +using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Trees @@ -13,6 +13,7 @@ namespace Umbraco.Web.Trees { protected abstract string FilePath { get; } protected abstract string FileSearchPattern { get; } + protected abstract string FileIcon { get; } /// /// Inheritors can override this method to modify the file node that is created. @@ -26,13 +27,13 @@ namespace Umbraco.Web.Trees /// protected virtual void OnRenderFolderNode(ref TreeNode treeNode) { } - protected override Models.Trees.TreeNodeCollection GetTreeNodes(string id, System.Net.Http.Formatting.FormDataCollection queryStrings) + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { string orgPath = ""; string path = ""; if (!string.IsNullOrEmpty(id) && id != "-1") { - orgPath = id; + orgPath = System.Web.HttpUtility.UrlDecode(id); path = IOHelper.MapPath(FilePath + "/" + orgPath); orgPath += "/"; } @@ -50,7 +51,7 @@ namespace Umbraco.Web.Trees if ((dir.Attributes & FileAttributes.Hidden) == 0) { var HasChildren = dir.GetFiles().Length > 0 || dir.GetDirectories().Length > 0; - var node = CreateTreeNode(orgPath + dir.Name, orgPath, queryStrings, dir.Name, "icon-folder", HasChildren); + var node = CreateTreeNode(System.Web.HttpUtility.UrlEncode(orgPath + dir.Name), orgPath, queryStrings, dir.Name, "icon-folder", HasChildren); OnRenderFolderNode(ref node); if(node != null) @@ -79,7 +80,7 @@ namespace Umbraco.Web.Trees if (filterByMultipleExtensions && Array.IndexOf(allowedExtensions, file.Extension.ToLower().Trim('.')) < 0) continue; - var node = CreateTreeNode(orgPath + file.Name, orgPath, queryStrings, file.Name, "icon-file", false); + var node = CreateTreeNode(System.Web.HttpUtility.UrlEncode(orgPath + file.Name), orgPath, queryStrings, file.Name, FileIcon, false); OnRenderFileNode(ref node); @@ -89,6 +90,64 @@ namespace Umbraco.Web.Trees } return nodes; - } + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + //if root node no need to visit the filesystem so lets just create the menu and return it + if (id == Constants.System.Root.ToInvariantString()) + { + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + //create action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + //refresh action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + + return menu; + } + + string path; + if (string.IsNullOrEmpty(id) == false) + { + var orgPath = System.Web.HttpUtility.UrlDecode(id); + path = IOHelper.MapPath(FilePath + "/" + orgPath); + } + else + { + path = IOHelper.MapPath(FilePath); + } + + var dirInfo = new DirectoryInfo(path); + //check if it's a directory + if (dirInfo.Attributes == FileAttributes.Directory) + { + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + //create action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + + var hasChildren = dirInfo.GetFiles().Length > 0 || dirInfo.GetDirectories().Length > 0; + //We can only delete folders if it doesn't have any children (folders or files) + if (hasChildren == false) + { + //delete action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)), true); + } + + //refresh action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + } + //if it's not a directory then we only allow to delete the item + else + { + //delete action + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + } + + return menu; + } } } diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTree.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTree.cs deleted file mode 100644 index dfc0636804..0000000000 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTree.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Text; -using Umbraco.Core.IO; -using umbraco.businesslogic; -using umbraco.cms.presentation.Trees; -using Umbraco.Core; - -namespace Umbraco.Web.Trees -{ - /// - /// Tree for displaying partial view macros in the developer app - /// - [Tree(Constants.Applications.Developer, "partialViewMacros", "Partial View Macro Files", sortOrder: 6)] - public class PartialViewMacrosTree : PartialViewsTree - { - public PartialViewMacrosTree(string application) : base(application) - { - } - - protected override string FilePath - { - get { return SystemDirectories.MvcViews + "/MacroPartials/"; } - } - - public override void RenderJS(ref StringBuilder javascript) - { - javascript.Append( - @" - function openMacroPartialView(id) { - UmbClientMgr.contentFrame('Settings/Views/EditView.aspx?treeType=partialViewMacros&file=' + id); - } - "); - - }/// - /// Ensures that no folders can be added - /// - /// - protected override void OnRenderFolderNode(ref XmlTreeNode xNode) - { - base.OnRenderFolderNode(ref xNode); - - xNode.NodeType = "partialViewMacrosFolder"; - } - - protected override void ChangeNodeAction(XmlTreeNode xNode) - { - xNode.Action = xNode.Action.Replace("openFile", "openMacroPartialView"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs new file mode 100644 index 0000000000..9643823e7b --- /dev/null +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs @@ -0,0 +1,39 @@ +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Web.Models.Trees; + +namespace Umbraco.Web.Trees +{ + /// + /// Tree for displaying partial view macros in the developer app + /// + [Tree(Constants.Applications.Developer, "partialViewMacros", "Partial View Macro Files", sortOrder: 6)] + public class PartialViewMacrosTreeController : FileSystemTreeController + { + protected override string FilePath + { + get { return SystemDirectories.MacroPartials; } + } + + protected override string FileSearchPattern + { + get { return "*.cshtml"; } + } + + protected override string FileIcon + { + get { return "icon-article"; } + } + + protected override void OnRenderFileNode(ref TreeNode treeNode) + { + base.OnRenderFileNode(ref treeNode); + } + + protected override void OnRenderFolderNode(ref TreeNode treeNode) + { + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + } + } +} diff --git a/src/Umbraco.Web/Trees/PartialViewsTree.cs b/src/Umbraco.Web/Trees/PartialViewsTree.cs deleted file mode 100644 index 11d6e3e804..0000000000 --- a/src/Umbraco.Web/Trees/PartialViewsTree.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using umbraco.BusinessLogic.Actions; -using umbraco.businesslogic; -using umbraco.cms.businesslogic.template; -using umbraco.cms.presentation.Trees; -using umbraco.interfaces; - -namespace Umbraco.Web.Trees -{ - /// - /// Tree for displaying partial views in the settings app - /// - [Tree(Constants.Applications.Settings, "partialViews", "Partial Views", sortOrder: 2)] - public class PartialViewsTree : FileSystemTree - { - public PartialViewsTree(string application) : base(application) { } - - public override void RenderJS(ref StringBuilder javascript) - { - javascript.Append( - @" - function openPartialView(id) { - UmbClientMgr.contentFrame('Settings/Views/EditView.aspx?treeType=partialViews&file=' + id); - } - "); - } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.NodeType = TreeAlias; - rootNode.NodeID = "init"; - } - - protected override string FilePath - { - get { return SystemDirectories.MvcViews + "/Partials/"; } - } - - protected override string FileSearchPattern - { - get { return "*.cshtml"; } - } - - /// - /// Ensures that no folders can be added - /// - /// - protected override void OnRenderFolderNode(ref XmlTreeNode xNode) - { - // We should allow folder hierarchy for organization in large sites. - xNode.Action = "javascript:void(0);"; - xNode.NodeType = "partialViewsFolder"; - xNode.Menu = new List(new IAction[] - { - ActionNew.Instance, - ContextMenuSeperator.Instance, - ActionDelete.Instance, - ContextMenuSeperator.Instance, - ActionRefresh.Instance - }); - - } - - protected virtual void ChangeNodeAction(XmlTreeNode xNode) - { - xNode.Action = xNode.Action.Replace("openFile", "openPartialView"); - } - - protected override void OnRenderFileNode(ref XmlTreeNode xNode) - { - ChangeNodeAction(xNode); - xNode.Icon = "icon-article"; - xNode.OpenIcon = "icon-article"; - - xNode.Text = xNode.Text.StripFileExtension(); - } - - - } -} diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs new file mode 100644 index 0000000000..07a0a557af --- /dev/null +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -0,0 +1,38 @@ +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Web.Models.Trees; + +namespace Umbraco.Web.Trees +{ + /// + /// Tree for displaying partial views in the settings app + /// + [Tree(Constants.Applications.Settings, "partialViews", "Partial Views", sortOrder: 2)] + public class PartialViewsTreeController : FileSystemTreeController + { + protected override string FilePath + { + get { return SystemDirectories.PartialViews; } + } + + protected override string FileSearchPattern + { + get { return "*.cshtml"; } + } + protected override string FileIcon + { + get { return "icon-article"; } + } + + protected override void OnRenderFileNode(ref TreeNode treeNode) + { + base.OnRenderFileNode(ref treeNode); + } + + protected override void OnRenderFolderNode(ref TreeNode treeNode) + { + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + } + } +} diff --git a/src/Umbraco.Web/Trees/ScriptTreeController.cs b/src/Umbraco.Web/Trees/ScriptTreeController.cs new file mode 100644 index 0000000000..f491989228 --- /dev/null +++ b/src/Umbraco.Web/Trees/ScriptTreeController.cs @@ -0,0 +1,35 @@ +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Web.Models.Trees; + +namespace Umbraco.Web.Trees +{ + [Tree(Constants.Applications.Settings, "scripts", "Scripts", sortOrder: 4)] + public class ScriptTreeController : FileSystemTreeController + { + protected override string FilePath + { + get { return SystemDirectories.Scripts; } + } + + protected override string FileSearchPattern + { + get { return "*.js"; } + } + protected override string FileIcon + { + get { return "icon-script"; } + } + + protected override void OnRenderFolderNode(ref TreeNode treeNode) + { + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + } + + protected override void OnRenderFileNode(ref TreeNode treeNode) + { + base.OnRenderFileNode(ref treeNode); + } + } +} diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index 61cff8f862..04458cbc20 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -48,12 +48,13 @@ namespace Umbraco.Web.Trees nodes.AddRange(found.Select(template => CreateTreeNode( template.Id.ToString(CultureInfo.InvariantCulture), //TODO: Fix parent ID stuff for templates - "-1", + "-1", queryStrings, template.Name, template.IsMasterTemplate ? "icon-newspaper" : "icon-newspaper-alt", - template.IsMasterTemplate, - GetEditorPath(template, queryStrings)))); + template.IsMasterTemplate, + GetEditorPath(template, queryStrings) + ))); return nodes; } @@ -67,15 +68,14 @@ namespace Umbraco.Web.Trees protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { var menu = new MenuItemCollection(); + //Create the normal create action + var item = menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); + item.NavigateToRoute(string.Format("{0}/templates/edit/{1}?create=true", queryStrings.GetValue("application"), id)); + if (id == Constants.System.Root.ToInvariantString()) { - //Create the normal create action - menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)) - //Since we haven't implemented anything for templates in angular, this needs to be converted to - //use the legacy format - .ConvertLegacyMenuItem(null, "inittemplates", queryStrings.GetValue("application")); - + //refresh action menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); @@ -86,12 +86,6 @@ namespace Umbraco.Web.Trees if (template == null) return new MenuItemCollection(); var entity = FromTemplate(template); - //Create the create action for creating sub layouts - menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)) - //Since we haven't implemented anything for templates in angular, this needs to be converted to - //use the legacy format - .ConvertLegacyMenuItem(entity, "templates", queryStrings.GetValue("application")); - //don't allow delete if it has child layouts if (template.IsMasterTemplate == false) { @@ -132,8 +126,7 @@ namespace Umbraco.Web.Trees return Services.FileService.DetermineTemplateRenderingEngine(template) == RenderingEngine.WebForms ? "/" + queryStrings.GetValue("application") + "/framed/" + Uri.EscapeDataString("settings/editTemplate.aspx?templateID=" + template.Id) - : "/" + queryStrings.GetValue("application") + "/framed/" + - Uri.EscapeDataString("settings/Views/EditView.aspx?treeType=" + Constants.Trees.Templates + "&templateID=" + template.Id); + : null; } } } diff --git a/src/Umbraco.Web/Trees/UrlHelperExtensions.cs b/src/Umbraco.Web/Trees/UrlHelperExtensions.cs index 0931fc0997..4b94f548ca 100644 --- a/src/Umbraco.Web/Trees/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/Trees/UrlHelperExtensions.cs @@ -1,5 +1,8 @@ using System; +using System.Linq; using System.Net.Http.Formatting; +using System.Text; +using System.Web; using System.Web.Http.Routing; using Umbraco.Core; @@ -30,5 +33,35 @@ namespace Umbraco.Web.Trees return actionUrl; } + + internal static string GetTreePathFromFilePath(this UrlHelper urlHelper, string virtualPath, string basePath = "") + { + //This reuses the Logic from umbraco.cms.helpers.DeepLink class + //to convert a filepath to a tree syncing path string. + + //removes the basepath from the path + //and normalises paths - / is used consistently between trees and editors + basePath = basePath.TrimStart("~"); + virtualPath = virtualPath.TrimStart("~"); + virtualPath = virtualPath.Substring(basePath.Length); + virtualPath = virtualPath.Replace('\\', '/'); + + //-1 is the default root id for trees + var sb = new StringBuilder("-1"); + + //split the virtual path and iterate through it + var pathPaths = virtualPath.Split('/'); + + for (var p = 0; p < pathPaths.Length; p++) + { + var path = HttpUtility.UrlEncode(string.Join("/", pathPaths.Take(p + 1))); + if (string.IsNullOrEmpty(path) == false) + { + sb.Append(","); + sb.Append(path); + } + } + return sb.ToString().TrimEnd(","); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/UI/Controls/UmbracoControl.cs b/src/Umbraco.Web/UI/Controls/UmbracoControl.cs index bc633b22a3..b84deaa232 100644 --- a/src/Umbraco.Web/UI/Controls/UmbracoControl.cs +++ b/src/Umbraco.Web/UI/Controls/UmbracoControl.cs @@ -81,7 +81,7 @@ namespace Umbraco.Web.UI.Controls /// public UrlHelper Url { - get { return _url ?? (_url = new UrlHelper(new RequestContext(new HttpContextWrapper(Context), new RouteData()))); } + get { return _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } } /// diff --git a/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs b/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs index 2a18413ace..246b9d45c2 100644 --- a/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs +++ b/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs @@ -119,7 +119,7 @@ namespace Umbraco.Web.UI.Controls /// public UrlHelper Url { - get { return _url ?? (_url = new UrlHelper(new RequestContext(new HttpContextWrapper(Context), new RouteData()))); } + get { return _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } } /// diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js index 0314c65fae..3be8a58983 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js @@ -16,6 +16,8 @@ 'lib/ng-file-upload/ng-file-upload.min.js', 'lib/angular-local-storage/angular-local-storage.min.js', + //"lib/ace-builds/src-min-noconflict/ace.js", + 'lib/bootstrap/js/bootstrap.2.3.2.min.js', 'lib/bootstrap-tabdrop/bootstrap-tabdrop.js', 'lib/umbraco/Extensions.js', diff --git a/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs b/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs index 3b8bfd9385..8c7087939d 100644 --- a/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs +++ b/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18444 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -62,21 +62,21 @@ namespace Umbraco.Web.UI.JavaScript { /// /// Looks up a localized string similar to [ - /// 'lib/jquery/jquery-2.0.3.min.js', + /// 'lib/jquery/jquery.min.js', /// 'lib/angular/1.1.5/angular.min.js', - /// 'lib/underscore/underscore.js', + /// 'lib/underscore/underscore-min.js', /// - /// 'lib/jquery/jquery-ui-1.10.3.custom.min.js', + /// 'lib/jquery-ui/jquery-ui.min.js', + /// 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', /// /// 'lib/angular/1.1.5/angular-cookies.min.js', /// 'lib/angular/1.1.5/angular-mobile.js', /// 'lib/angular/1.1.5/angular-sanitize.min.js', /// /// 'lib/angular/angular-ui-sortable.js', - /// - /// 'lib/jquery/jquery.upload/js/jquery.fileupload.js', - /// 'lib/jquery/jquery.upload/js/load-image.min.js', - /// 'lib/jquery/jquery.upload/js/ [rest of string was truncated]";. + /// + /// 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', + /// 'lib/ng-file-upload/ng-file-upload.min. [rest of string was truncated]";. /// internal static string JsInitialize { get { @@ -90,7 +90,9 @@ namespace Umbraco.Web.UI.JavaScript { /// UmbClientMgr.setUmbracoPath('"##UmbracoPath##"'); /// /// jQuery(document).ready(function () { + /// /// angular.bootstrap(document, ['umbraco']); + /// /// }); ///});. /// diff --git a/src/Umbraco.Web/UI/Pages/BasePage.cs b/src/Umbraco.Web/UI/Pages/BasePage.cs index b9eec4982b..e2c64b7ece 100644 --- a/src/Umbraco.Web/UI/Pages/BasePage.cs +++ b/src/Umbraco.Web/UI/Pages/BasePage.cs @@ -69,7 +69,7 @@ namespace Umbraco.Web.UI.Pages /// public UrlHelper Url { - get { return _url ?? (_url = new UrlHelper(new RequestContext(new HttpContextWrapper(Context), new RouteData()))); } + get { return _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } } private HtmlHelper _html; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d5a6414975..964cf3dd99 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1,5 +1,5 @@  - + 9.0.30729 @@ -36,7 +36,7 @@ 4.0 - v4.5 + v4.6.2 ..\ true @@ -99,49 +99,38 @@ ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True ..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll - True - False ..\packages\xmlrpcnet.2.5.0\lib\net20\CookComputing.XmlRpcV2.dll - + ..\packages\dotless.1.4.1.0\lib\dotless.Core.dll - - ..\packages\Examine.0.1.70.0\lib\Examine.dll - True + + ..\packages\Examine.0.1.81\lib\net45\Examine.dll - False ..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll - False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - False ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll - True - False ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll - False ..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll @@ -150,46 +139,34 @@ - False ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - False ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - False ..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll - False ..\packages\Microsoft.Owin.Security.Cookies.3.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll - False ..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - False - True - False ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - - ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - False - True + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - False ..\packages\Owin.1.0\lib\net40\Owin.dll - - False - ..\packages\semver.1.1.2\lib\net45\Semver.dll + + ..\packages\Semver.2.0.4\lib\net452\Semver.dll System @@ -208,7 +185,6 @@ - False ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll @@ -228,43 +204,30 @@ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - False - True - + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - False ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll - True ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll - False - True System.Web.Services ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll - False - True ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll - False - True ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - False - True @@ -307,8 +270,6 @@ ..\packages\UrlRewritingNet.UrlRewriter.2.0.7\lib\UrlRewritingNet.UrlRewriter.dll - False - False @@ -322,7 +283,11 @@ + + + + @@ -373,14 +338,21 @@ + + + + + + + @@ -414,7 +386,6 @@ - @@ -438,6 +409,8 @@ + + @@ -496,7 +469,6 @@ - @@ -1012,6 +984,7 @@ + @@ -1092,8 +1065,8 @@ - - + + @@ -2015,6 +1988,7 @@ Mvc\web.config + MSDiscoCodeGenerator Reference.cs @@ -2109,9 +2083,6 @@ - - Designer - Reference.map diff --git a/src/Umbraco.Web/UmbracoContextExtensions.cs b/src/Umbraco.Web/UmbracoContextExtensions.cs index 53ef92690e..3e5c9d355e 100644 --- a/src/Umbraco.Web/UmbracoContextExtensions.cs +++ b/src/Umbraco.Web/UmbracoContextExtensions.cs @@ -53,7 +53,7 @@ namespace Umbraco.Web /// public static EventMessages GetCurrentEventMessages(this UmbracoContext umbracoContext) { - var eventMessagesFactory = umbracoContext.Application.Services.EventMessagesFactory as RequestLifespanMessagesFactory; + var eventMessagesFactory = umbracoContext.Application.Services.EventMessagesFactory as ScopeLifespanMessagesFactory; return eventMessagesFactory == null ? null : eventMessagesFactory.TryGet(); } diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs index 01745ef8f0..c11252d2ca 100644 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs @@ -42,9 +42,7 @@ namespace Umbraco.Web // (EXPERT: an overload accepts a custom BackOfficeUserStore implementation) app.ConfigureUserManagerForUmbracoBackOffice( ApplicationContext, - Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider()); - - app.ConfigureSignalR(); + Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider()); } /// @@ -59,6 +57,7 @@ namespace Umbraco.Web .UseUmbracoBackOfficeCookieAuthentication(ApplicationContext, PipelineStage.Authenticate) .UseUmbracoBackOfficeExternalCookieAuthentication(ApplicationContext, PipelineStage.Authenticate) .UseUmbracoPreviewAuthentication(ApplicationContext, PipelineStage.Authorize) + .UseSignalR() .FinalizeMiddlewareConfiguration(); } 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/Web References/org.umbraco.our/Reference.cs b/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs index ad6323da86..5e753fa7f8 100644 --- a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs +++ b/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34003 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ // -// This source code was auto-generated by Microsoft.VSDesigner, Version 4.0.30319.34003. +// This source code was auto-generated by Microsoft.VSDesigner, Version 4.0.30319.42000. // #pragma warning disable 1591 @@ -23,7 +23,7 @@ namespace Umbraco.Web.org.umbraco.our { /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute(Name="RepositorySoap", Namespace="http://packages.umbraco.org/webservices/")] @@ -480,7 +480,7 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1586.0")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -549,7 +549,7 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1586.0")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -738,7 +738,7 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1586.0")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://packages.umbraco.org/webservices/")] public enum SubmitStatus { @@ -757,11 +757,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void CategoriesCompletedEventHandler(object sender, CategoriesCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CategoriesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -783,11 +783,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void ModulesCompletedEventHandler(object sender, ModulesCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ModulesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -809,11 +809,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void ModulesCategorizedCompletedEventHandler(object sender, ModulesCategorizedCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ModulesCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -835,11 +835,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void NitrosCompletedEventHandler(object sender, NitrosCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class NitrosCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -861,11 +861,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void NitrosCategorizedCompletedEventHandler(object sender, NitrosCategorizedCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class NitrosCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -887,11 +887,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void authenticateCompletedEventHandler(object sender, authenticateCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class authenticateCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -913,11 +913,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void fetchPackageCompletedEventHandler(object sender, fetchPackageCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class fetchPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -939,11 +939,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void fetchPackageByVersionCompletedEventHandler(object sender, fetchPackageByVersionCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class fetchPackageByVersionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -965,11 +965,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void fetchProtectedPackageCompletedEventHandler(object sender, fetchProtectedPackageCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class fetchProtectedPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -991,11 +991,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void SubmitPackageCompletedEventHandler(object sender, SubmitPackageCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SubmitPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -1017,11 +1017,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void PackageByGuidCompletedEventHandler(object sender, PackageByGuidCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.33440")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class PackageByGuidCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { diff --git a/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs b/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs index e68ae400f5..9e35deb713 100644 --- a/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs +++ b/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18444 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ // -// This source code was auto-generated by Microsoft.VSDesigner, Version 4.0.30319.18444. +// This source code was auto-generated by Microsoft.VSDesigner, Version 4.0.30319.42000. // #pragma warning disable 1591 @@ -23,7 +23,7 @@ namespace Umbraco.Web.org.umbraco.update { /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute(Name="CheckForUpgradeSoap", Namespace="http://update.umbraco.org/")] @@ -180,7 +180,7 @@ namespace Umbraco.Web.org.umbraco.update { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1586.0")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -225,7 +225,7 @@ namespace Umbraco.Web.org.umbraco.update { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1586.0")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://update.umbraco.org/")] public enum UpgradeType { @@ -253,15 +253,15 @@ namespace Umbraco.Web.org.umbraco.update { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void InstallCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] public delegate void CheckUpgradeCompletedEventHandler(object sender, CheckUpgradeCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CheckUpgradeCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { diff --git a/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs b/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs index b54d6102c0..de1383706e 100644 --- a/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs +++ b/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs @@ -8,7 +8,7 @@ using Newtonsoft.Json.Serialization; namespace Umbraco.Web.WebApi { /// - /// Applying this attribute to any webapi controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. + /// Applying this attribute to any webapi controller will ensure that it only contains one json formatter with a camelCase formatter /// public class JsonCamelCaseFormatter : Attribute, IControllerConfiguration { diff --git a/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs index e018881f8a..2593acfbe0 100644 --- a/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs +++ b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs @@ -6,7 +6,7 @@ using System.Web.Http.Validation; namespace Umbraco.Web.WebApi { /// - /// Applying this attribute to any webapi controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. + /// Applying this attribute to any webapi controller will ensure that the is of type /// internal class PrefixlessBodyModelValidatorAttribute : Attribute, IControllerConfiguration { diff --git a/src/Umbraco.Web/WebApi/SessionHttpControllerRouteHandler.cs b/src/Umbraco.Web/WebApi/SessionHttpControllerRouteHandler.cs new file mode 100644 index 0000000000..d4d462c5f8 --- /dev/null +++ b/src/Umbraco.Web/WebApi/SessionHttpControllerRouteHandler.cs @@ -0,0 +1,31 @@ +using System.Web; +using System.Web.Http.WebHost; +using System.Web.Routing; +using System.Web.SessionState; + +namespace Umbraco.Web.WebApi +{ + /// + /// A custom WebApi route handler that enables session on the HttpContext - use with caution! + /// + /// + /// WebApi controllers (and REST in general) shouldn't have session state enabled since it's stateless, + /// enabling session state puts additional locks on requests so only use this when absolutley needed + /// + internal class SessionHttpControllerRouteHandler : HttpControllerRouteHandler + { + protected override IHttpHandler GetHttpHandler(RequestContext requestContext) + { + return new SessionHttpControllerHandler(requestContext.RouteData); + } + + /// + /// A custom WebApi handler that enables session on the HttpContext + /// + private class SessionHttpControllerHandler : HttpControllerHandler, IRequiresSessionState + { + public SessionHttpControllerHandler(RouteData routeData) : base(routeData) + { } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index bc41c9c877..8b935b1ed6 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -42,9 +42,11 @@ using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; using Umbraco.Core.Cache; +using Umbraco.Core.Events; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Web.Editors; using Umbraco.Web.HealthCheck; @@ -86,17 +88,16 @@ namespace Umbraco.Web /// Creates and returns the service context for the app /// /// - /// + /// /// - protected override ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory) + protected override ServiceContext CreateServiceContext(DatabaseContext dbContext, IScopeProvider scopeProvider) { //use a request based messaging factory - var evtMsgs = new RequestLifespanMessagesFactory(new SingletonHttpContextAccessor()); + var evtMsgs = new ScopeLifespanMessagesFactory(new SingletonHttpContextAccessor(), scopeProvider); return new ServiceContext( new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), - new PetaPocoUnitOfWorkProvider(dbFactory), - new FileUnitOfWorkProvider(), - new PublishingStrategy(evtMsgs, ProfilingLogger.Logger), + new PetaPocoUnitOfWorkProvider(scopeProvider), + new FileUnitOfWorkProvider(scopeProvider), ApplicationCache, ProfilingLogger.Logger, evtMsgs); 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/WebServices/ScheduledPublishController.cs b/src/Umbraco.Web/WebServices/ScheduledPublishController.cs index fcf07126fb..433930dda9 100644 --- a/src/Umbraco.Web/WebServices/ScheduledPublishController.cs +++ b/src/Umbraco.Web/WebServices/ScheduledPublishController.cs @@ -44,7 +44,17 @@ namespace Umbraco.Web.WebServices } catch (Exception ee) { - LogHelper.Error("Error executing scheduled task", ee); + var errorMessage = "Error executing scheduled task"; + if (HttpContext != null && HttpContext.Request != null) + { + if (HttpContext.Request.Url != null) + errorMessage = string.Format("{0} | Request to {1}", errorMessage, HttpContext.Request.Url); + if (HttpContext.Request.UserHostAddress != null) + errorMessage = string.Format("{0} | Coming from {1}", errorMessage, HttpContext.Request.UserHostAddress); + if (HttpContext.Request.UrlReferrer != null) + errorMessage = string.Format("{0} | Referrer {1}", errorMessage, HttpContext.Request.UrlReferrer); + } + LogHelper.Error(errorMessage, ee); Response.StatusCode = 400; diff --git a/src/Umbraco.Web/WebServices/UmbracoHttpHandler.cs b/src/Umbraco.Web/WebServices/UmbracoHttpHandler.cs index d50f28b350..d315f0d20d 100644 --- a/src/Umbraco.Web/WebServices/UmbracoHttpHandler.cs +++ b/src/Umbraco.Web/WebServices/UmbracoHttpHandler.cs @@ -71,7 +71,7 @@ namespace Umbraco.Web.WebServices /// public UrlHelper Url { - get { return _url ?? (_url = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()))); } + get { return _url ?? (_url = new UrlHelper(HttpContext.Current.Request.RequestContext)); } } /// diff --git a/src/Umbraco.Web/WebServices/UmbracoWebService.cs b/src/Umbraco.Web/WebServices/UmbracoWebService.cs index e92e85de1e..55b2d54295 100644 --- a/src/Umbraco.Web/WebServices/UmbracoWebService.cs +++ b/src/Umbraco.Web/WebServices/UmbracoWebService.cs @@ -72,7 +72,7 @@ namespace Umbraco.Web.WebServices /// public UrlHelper Url { - get { return _url ?? (_url = new UrlHelper(new RequestContext(new HttpContextWrapper(Context), new RouteData()))); } + get { return _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } } /// diff --git a/src/Umbraco.Web/app.config b/src/Umbraco.Web/app.config index 2b15194775..0e2734a4bb 100644 --- a/src/Umbraco.Web/app.config +++ b/src/Umbraco.Web/app.config @@ -41,7 +41,7 @@ - + @@ -69,4 +69,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 520a6e4f76..84e8a07098 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -3,7 +3,7 @@ - + @@ -24,9 +24,9 @@ - + - + diff --git a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs new file mode 100644 index 0000000000..9ec95ae45b --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs @@ -0,0 +1,137 @@ +using System; +using System.Xml; +using Umbraco.Core; +using Umbraco.Core.Scoping; + +// ReSharper disable once CheckNamespace +namespace umbraco +{ + // provides safe access to the Xml cache + internal class SafeXmlReaderWriter : IDisposable + { + private readonly bool _scoped; + private readonly Action _apply; + private IDisposable _releaser; + private bool _isWriter; + private bool _applyChanges; + private XmlDocument _xml; + private bool _using; + private bool _registerXmlChange; + + private SafeXmlReaderWriter(IDisposable releaser, XmlDocument xml, Action apply, bool isWriter, bool scoped) + { + _releaser = releaser; + _apply = apply; + _isWriter = isWriter; + _scoped = scoped; + + // cloning for writer is not an option anymore (see XmlIsImmutable) + _xml = _isWriter ? Clone(xml) : xml; + } + + public static SafeXmlReaderWriter Get(IScopeProviderInternal scopeProvider, AsyncLock xmlLock, XmlDocument xml, Action apply, bool writer) + { + var scopeContext = scopeProvider.Context; + + // no real scope = just create a reader/writer instance + if (scopeContext == null) + { + // obtain exclusive access to xml and create reader/writer + var releaser = xmlLock.Lock(); + return new SafeXmlReaderWriter(releaser, xml, apply, writer, false); + } + + // get or create an enlisted reader/writer + var rw = scopeContext.Enlist("safeXmlReaderWriter", + () => // creator + { + // obtain exclusive access to xml and create reader/writer + var releaser = xmlLock.Lock(); + return new SafeXmlReaderWriter(releaser, xml, apply, writer, true); + }, + (completed, item) => // action + { + item.DisposeForReal(completed); + }); + + // ensure it's not already in-use - should never happen, just being super safe + if (rw._using) + throw new InvalidOperationException(); + rw._using = true; + + return rw; + } + + public bool IsWriter { get { return _isWriter; } } + + public void UpgradeToWriter() + { + if (_isWriter) + throw new InvalidOperationException("Already a writer."); + _isWriter = true; + + // cloning for writer is not an option anymore (see XmlIsImmutable) + //fixme: But XmlIsImmutable is not actually used! + _xml = Clone(_xml); + } + + internal static Action Cloning { get; set; } + + private static XmlDocument Clone(XmlDocument xmlDoc) + { + if (Cloning != null) Cloning(); + return xmlDoc == null ? null : (XmlDocument) xmlDoc.CloneNode(true); + } + + public XmlDocument Xml + { + get { return _xml; } + set + { + if (_isWriter == false) + throw new InvalidOperationException("Not a writer."); + _xml = value; + } + } + + // registerXmlChange indicates whether to do what should be done when Xml changes, + // that is, to request that the file be written to disk - something we don't want + // to do if we're committing Xml precisely after we've read from disk! + public void AcceptChanges(bool registerXmlChange = true) + { + if (_isWriter == false) + throw new InvalidOperationException("Not a writer."); + + _applyChanges = true; + _registerXmlChange |= registerXmlChange; + + // fixme - what about context cache? + // just 'clearing' here is not enough because it would be re-assigned to the + // un-modified _xmlContent, so we'd need to *change* it somehow to point to + // our temp _xml (and then maybe restore if the scope does not complete). + // not doing it means that the 'current' xml cache does *not* update during the + // scope but only once the scope has completed... might be an issue with + // GetUrl... would that impact Deploy? don't think so - so for the time being + // we do nothing *but* we need to deal with it at some point! + } + + private void DisposeForReal(bool completed) + { + // apply changes! + if (_isWriter && _applyChanges && completed) + _apply(_xml, _registerXmlChange); + + // release the lock + _releaser.Dispose(); + _releaser = null; + } + + public void Dispose() + { + _using = false; + + if (_scoped == false) + DisposeForReal(true); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 00d1984d12..edfffe03eb 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -4,12 +4,8 @@ using System.ComponentModel; using System.Globalization; using System.IO; using System.Text; -using System.Threading; -using System.Threading.Tasks; using System.Web; using System.Xml; -using System.Xml.Linq; -using System.Xml.XPath; using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.web; @@ -22,12 +18,12 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Profiling; +using Umbraco.Core.Scoping; using Umbraco.Web; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Scheduling; using File = System.IO.File; using Node = umbraco.NodeFactory.Node; -using Task = System.Threading.Tasks.Task; namespace umbraco { @@ -36,6 +32,7 @@ namespace umbraco /// public class content { + private readonly IScopeProviderInternal _scopeProvider = (IScopeProviderInternal) ApplicationContext.Current.ScopeProvider; private XmlCacheFilePersister _persisterTask; private volatile bool _released; @@ -84,7 +81,7 @@ namespace umbraco } // initialize content - populate the cache - using (var safeXml = GetSafeXmlWriter(false)) + using (var safeXml = GetSafeXmlWriter()) { bool registerXmlChange; @@ -93,7 +90,7 @@ namespace umbraco LoadXmlLocked(safeXml, out registerXmlChange); // if we use the file and registerXmlChange is true this will // write to file, else it will not - safeXml.Commit(registerXmlChange); + safeXml.AcceptChanges(registerXmlChange); } } @@ -120,9 +117,9 @@ namespace umbraco private static readonly object DbReadSyncLock = new object(); private const string XmlContextContentItemKey = "UmbracoXmlContextContent"; - private const string XmlContextClonedContentItemKey = "UmbracoXmlContextContent.cloned"; private static string _umbracoXmlDiskCacheFileName = string.Empty; - private volatile XmlDocument _xmlContent; + // internal for SafeXmlReaderWriter + internal volatile XmlDocument _xmlContent; /// /// Gets the path of the umbraco XML disk cache file. @@ -149,7 +146,11 @@ namespace umbraco // not work as expected for a double check lock because properties are treated differently in the clr. public virtual bool isInitializing { - get { return _xmlContent == null; } + get + { + // ok to access _xmlContent here + return _xmlContent == null; + } } /// @@ -188,6 +189,8 @@ namespace umbraco } } + internal static bool TestingUpdateSitemapProvider = true; + /// /// Used by all overloaded publish methods to do the actual "noderepresentation to xml" /// @@ -196,6 +199,8 @@ namespace umbraco /// public static XmlDocument PublishNodeDo(Document d, XmlDocument xmlContentCopy, bool updateSitemapProvider) { + updateSitemapProvider &= TestingUpdateSitemapProvider; + // check if document *is* published, it could be unpublished by an event if (d.Published) { @@ -251,7 +256,7 @@ namespace umbraco /// The parent node identifier. public void SortNodes(int parentId) { - using (var safeXml = GetSafeXmlWriter(false)) + using (var safeXml = GetSafeXmlWriter()) { var parentNode = parentId == -1 ? safeXml.Xml.DocumentElement @@ -266,7 +271,7 @@ namespace umbraco if (sorted == false) return; - safeXml.Commit(); + safeXml.AcceptChanges(); } } @@ -296,10 +301,9 @@ namespace umbraco using (var safeXml = GetSafeXmlWriter()) { safeXml.Xml = PublishNodeDo(d, safeXml.Xml, true); + safeXml.AcceptChanges(); } - ClearContextCache(); - var cachedFieldKeyStart = string.Format("{0}{1}_", CacheKeys.ContentItemCacheKey, d.Id); ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(cachedFieldKeyStart); @@ -339,7 +343,7 @@ namespace umbraco if (c.HasPublishedVersion == false) return; if (c.WasPropertyDirty("SortOrder") == false) return; - using (var safeXml = GetSafeXmlWriter(false)) + using (var safeXml = GetSafeXmlWriter()) { //TODO: This can be null: safeXml.Xml!!!! @@ -354,7 +358,7 @@ namespace umbraco // only if node was actually modified attr.Value = sortOrder; - safeXml.Commit(); + safeXml.AcceptChanges(); } } @@ -376,9 +380,8 @@ namespace umbraco { safeXml.Xml = PublishNodeDo(d, safeXml.Xml, true); } + safeXml.AcceptChanges(); } - - ClearContextCache(); } [Obsolete("Method obsolete in version 4.1 and later, please use UpdateDocumentCache", true)] @@ -394,6 +397,11 @@ namespace umbraco } public virtual void ClearDocumentCache(int documentId) + { + ClearDocumentCache(documentId, true); + } + + internal virtual void ClearDocumentCache(int documentId, bool removeDbXmlEntry) { // Get the document Document d; @@ -408,7 +416,7 @@ namespace umbraco ClearDocumentXmlCache(documentId); return; } - ClearDocumentCache(d); + ClearDocumentCache(d, removeDbXmlEntry); } /// @@ -416,7 +424,8 @@ namespace umbraco /// This means the node gets unpublished from the website. /// /// The document - internal void ClearDocumentCache(Document doc) + /// + internal void ClearDocumentCache(Document doc, bool removeDbXmlEntry) { var e = new DocumentCacheEventArgs(); FireBeforeClearDocumentCache(doc, e); @@ -425,14 +434,17 @@ namespace umbraco { XmlNode x; - // remove from xml db cache - doc.XmlRemoveFromDB(); + //Hack: this is here purely for backwards compat if someone for some reason is using the + // ClearDocumentCache(int documentId) method and expecting it to remove the xml + if (removeDbXmlEntry) + { + // remove from xml db cache + doc.XmlRemoveFromDB(); + } // clear xml cache ClearDocumentXmlCache(doc.Id); - ClearContextCache(); - //SD: changed to fire event BEFORE running the sitemap!! argh. FireAfterClearDocumentCache(doc, e); @@ -456,7 +468,8 @@ namespace umbraco if (x == null) return; - safeXml.UpgradeToWriter(false); + if (safeXml.IsWriter == false) + safeXml.UpgradeToWriter(); // Find the document in the xml cache x = safeXml.Xml.GetElementById(id.ToString()); @@ -464,7 +477,7 @@ namespace umbraco { // The document already exists in cache, so repopulate it x.ParentNode.RemoveChild(x); - safeXml.Commit(); + safeXml.AcceptChanges(); } } } @@ -483,12 +496,11 @@ namespace umbraco #region Protected & Private methods - /// - /// Clear HTTPContext cache if any - /// - private void ClearContextCache() + // Clear HTTPContext cache if any + // internal for SafeXmlReaderWriter + internal void ClearContextCache() { - // If running in a context very important to reset context cache orelse new nodes are missing + // If running in a context very important to reset context cache or else new nodes are missing if (UmbracoContext.Current != null && UmbracoContext.Current.HttpContext != null && UmbracoContext.Current.HttpContext.Items.Contains(XmlContextContentItemKey)) UmbracoContext.Current.HttpContext.Items.Remove(XmlContextContentItemKey); } @@ -561,7 +573,8 @@ namespace umbraco { get { return XmlFileEnabled && UmbracoConfig.For.UmbracoSettings().Content.XmlContentCheckForDiskChanges; } } - + + //fixme: this is not used? // whether _xml is immutable or not (achieved by cloning before changing anything) private static bool XmlIsImmutable { @@ -578,7 +591,8 @@ namespace umbraco #region Xml - private readonly AsyncLock _xmlLock = new AsyncLock(); // protects _xml + // internal for SafeXmlReaderWriter + internal readonly AsyncLock _xmlLock = new AsyncLock(); // protects _xml /// /// Get content. First call to this property will initialize xmldoc @@ -609,6 +623,7 @@ namespace umbraco } // to be used by content.Instance + // ok to access _xmlContent here - just capturing protected internal virtual XmlDocument XmlContentInternal { get @@ -619,7 +634,9 @@ namespace umbraco } // assumes xml lock - private void SetXmlLocked(XmlDocument xml, bool registerXmlChange) + // ok to access _xmlContent here since this is called from the safe reader/writer + // internal for SafeXmlReaderWriter + internal void SetXmlLocked(XmlDocument xml, bool registerXmlChange) { // this is the ONLY place where we write to _xmlContent _xmlContent = xml; @@ -631,11 +648,6 @@ namespace umbraco _persisterTask = _persisterTask.Touch(); // _persisterTask != null because SyncToXmlFile == true } - private static XmlDocument Clone(XmlDocument xmlDoc) - { - return xmlDoc == null ? null : (XmlDocument)xmlDoc.CloneNode(true); - } - private static XmlDocument EnsureSchema(string contentTypeAlias, XmlDocument xml) { string subset = null; @@ -692,91 +704,21 @@ namespace umbraco // gets a locked safe read access to the main xml private SafeXmlReaderWriter GetSafeXmlReader() { - var releaser = _xmlLock.Lock(); - return SafeXmlReaderWriter.GetReader(this, releaser); + return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xmlContent, (xml, registerXmlChange) => + { + SetXmlLocked(xml, registerXmlChange); + ClearContextCache(); + }, false); } // gets a locked safe write access to the main xml (cloned) - private SafeXmlReaderWriter GetSafeXmlWriter(bool auto = true) + private SafeXmlReaderWriter GetSafeXmlWriter() { - var releaser = _xmlLock.Lock(); - return SafeXmlReaderWriter.GetWriter(this, releaser, auto); - } - - private class SafeXmlReaderWriter : IDisposable - { - private readonly content _instance; - private IDisposable _releaser; - private bool _isWriter; - private bool _auto; - private bool _committed; - private XmlDocument _xml; - - private SafeXmlReaderWriter(content instance, IDisposable releaser, bool isWriter, bool auto) + return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xmlContent, (xml, registerXmlChange) => { - _instance = instance; - _releaser = releaser; - _isWriter = isWriter; - _auto = auto; - - // cloning for writer is not an option anymore (see XmlIsImmutable) - _xml = _isWriter ? Clone(instance._xmlContent) : instance._xmlContent; - } - - public static SafeXmlReaderWriter GetReader(content instance, IDisposable releaser) - { - return new SafeXmlReaderWriter(instance, releaser, false, false); - } - - public static SafeXmlReaderWriter GetWriter(content instance, IDisposable releaser, bool auto) - { - return new SafeXmlReaderWriter(instance, releaser, true, auto); - } - - public void UpgradeToWriter(bool auto) - { - if (_isWriter) - throw new InvalidOperationException("Already writing."); - _isWriter = true; - _auto = auto; - _xml = Clone(_xml); // cloning for writer is not an option anymore (see XmlIsImmutable) - } - - public XmlDocument Xml - { - get - { - return _xml; - } - set - { - if (_isWriter == false) - throw new InvalidOperationException("Not writing."); - _xml = value; - } - } - - // registerXmlChange indicates whether to do what should be done when Xml changes, - // that is, to request that the file be written to disk - something we don't want - // to do if we're committing Xml precisely after we've read from disk! - public void Commit(bool registerXmlChange = true) - { - if (_isWriter == false) - throw new InvalidOperationException("Not writing."); - _instance.SetXmlLocked(Xml, registerXmlChange); - _committed = true; - } - - public void Dispose() - { - if (_releaser == null) - return; - if (_isWriter && _auto && _committed == false) - Commit(); - _releaser.Dispose(); - _releaser = null; - } - + SetXmlLocked(xml, registerXmlChange); + ClearContextCache(); + }, true); } private static string ChildNodesXPath @@ -836,7 +778,8 @@ namespace umbraco try { - var xml = _xmlContent; // capture (atomic + volatile), immutable anyway + // ok to access _xmlContent here - capture (atomic + volatile), immutable anyway + var xml = _xmlContent; if (xml == null) return; // delete existing file, if any @@ -950,11 +893,11 @@ namespace umbraco // time to read - using (var safeXml = GetSafeXmlWriter(false)) + using (var safeXml = GetSafeXmlWriter()) { bool registerXmlChange; LoadXmlLocked(safeXml, out registerXmlChange); // updates _lastFileRead - safeXml.Commit(registerXmlChange); + safeXml.AcceptChanges(registerXmlChange); } } 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/create/templateTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/templateTasks.cs index bbddcd9de8..8c1cab8e78 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/templateTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/templateTasks.cs @@ -13,8 +13,7 @@ using umbraco.cms.businesslogic.member; namespace umbraco { public class templateTasks : LegacyDialogTask - { - + { public override bool PerformSave() { var masterId = ParentID; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs index 0c375893d6..d3c5b11330 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs @@ -233,7 +233,7 @@ namespace umbraco.cms.presentation.developer var macroPropertyAliasNew = (TextBox)((Control)sender).Parent.FindControl("macroPropertyAliasNew"); var macroPropertyNameNew = (TextBox)((Control)sender).Parent.FindControl("macroPropertyNameNew"); var macroPropertyTypeNew = (DropDownList)((Control)sender).Parent.FindControl("macroPropertyTypeNew"); - + if (macroPropertyAliasNew.Text != ui.Text("general", "new", UmbracoUser) + " " + ui.Text("general", "alias", UmbracoUser)) { if (_macro.Properties.ContainsKey(macroPropertyAliasNew.Text.Trim())) @@ -287,7 +287,7 @@ namespace umbraco.cms.presentation.developer } protected override void OnInit(EventArgs e) - { + { base.OnInit(e); EnsureChildControls(); } @@ -332,7 +332,9 @@ namespace umbraco.cms.presentation.developer SetMacroValuesFromPostBack(_macro, Convert.ToInt32(tempCachePeriod), tempMacroAssembly, tempMacroType); - // Save elements + // save elements + // this is oh so completely broken + var aliases = new Dictionary(); foreach (RepeaterItem item in macroProperties.Items) { var macroPropertyId = (HtmlInputHidden)item.FindControl("macroPropertyID"); @@ -345,15 +347,27 @@ namespace umbraco.cms.presentation.developer var sortOrder = 0; int.TryParse(macroElementSortOrder.Text, out sortOrder); + var alias = macroElementAlias.Text.Trim(); + if (prop.Alias != alias) // changing the alias + { + // use a temp alias to avoid collision if eg swapping aliases + var tempAlias = Guid.NewGuid().ToString("N").Substring(0, 8); + aliases[tempAlias] = alias; + alias = tempAlias; + } + _macro.Properties.UpdateProperty( prop.Alias, macroElementName.Text.Trim(), sortOrder, - macroElementType.SelectedValue, - macroElementAlias.Text.Trim()); - + macroElementType.SelectedValue, + alias); } + // now apply the real aliases, should not collide + foreach (var kvp in aliases) + _macro.Properties.UpdateProperty(kvp.Key, newAlias: kvp.Value); + Services.MacroService.Save(_macro); ClientTools.ShowSpeechBubble(speechBubbleIcon.save, "Macro saved", ""); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs index 2473a4cb5b..a75b378cd2 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs @@ -452,14 +452,11 @@ namespace umbraco.presentation.developer.packages //Order the DocumentTypes before removing them if (contentTypes.Any()) { + //TODO: I don't think this ordering is necessary var orderedTypes = (from contentType in contentTypes - orderby contentType.ParentId descending, contentType.Id descending - select contentType); - - foreach (var contentType in orderedTypes) - { - contentTypeService.Delete(contentType); - } + orderby contentType.ParentId descending, contentType.Id descending + select contentType); + contentTypeService.Delete(orderedTypes); } //Remove Dictionary items diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Resources.Designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Resources.Designer.cs index 9aa180110b..3adaf973ca 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Resources.Designer.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Resources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18034 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. 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 ebd0db8b1c..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 @@ -168,7 +182,12 @@ namespace UmbracoExamine base.Initialize(name, config); - if (config["useTempStorage"] != null) + //NOTES: useTempStorage is obsolete, tempStorageDirectory is obsolete, both have been superceded by Examine Core's IDirectoryFactory + // tempStorageDirectory never actually got finished in Umbraco Core but accidentally got shipped (it's only enabled on the searcher + // and not the indexer). So this whole block is just legacy + + //detect if a dir factory has been specified, if so then useTempStorage will not be used (deprecated) + if (config["directoryFactory"] == null && config["useTempStorage"] != null) { var fsDir = base.GetLuceneDirectory() as FSDirectory; if (fsDir != null) diff --git a/src/UmbracoExamine/Config/IndexSetExtensions.cs b/src/UmbracoExamine/Config/IndexSetExtensions.cs index 1255f50a3c..f4bd2e24b2 100644 --- a/src/UmbracoExamine/Config/IndexSetExtensions.cs +++ b/src/UmbracoExamine/Config/IndexSetExtensions.cs @@ -14,7 +14,7 @@ namespace UmbracoExamine.Config public static class IndexSetExtensions { internal static IIndexCriteria ToIndexCriteria(this IndexSet set, IDataService svc, - IEnumerable indexFieldPolicies) + StaticFieldCollection indexFieldPolicies) { return new LazyIndexCriteria(set, svc, indexFieldPolicies); } @@ -29,7 +29,7 @@ namespace UmbracoExamine.Config /// public static IIndexCriteria ToIndexCriteria(this IndexSet set, IDataService svc) { - return set.ToIndexCriteria(svc, Enumerable.Empty()); + return set.ToIndexCriteria(svc, new StaticFieldCollection()); } } diff --git a/src/UmbracoExamine/Config/LazyIndexCriteria.cs b/src/UmbracoExamine/Config/LazyIndexCriteria.cs index 72ab3f31ba..ee58431930 100644 --- a/src/UmbracoExamine/Config/LazyIndexCriteria.cs +++ b/src/UmbracoExamine/Config/LazyIndexCriteria.cs @@ -12,7 +12,7 @@ namespace UmbracoExamine.Config public LazyIndexCriteria( IndexSet set, IDataService svc, - IEnumerable indexFieldPolicies) + StaticFieldCollection indexFieldPolicies) { if (set == null) throw new ArgumentNullException("set"); if (indexFieldPolicies == null) throw new ArgumentNullException("indexFieldPolicies"); @@ -35,8 +35,9 @@ namespace UmbracoExamine.Config foreach (var u in userProps) { var field = new IndexField() { Name = u }; - var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == u); - if (policy != null) + + StaticField policy; + if (indexFieldPolicies.TryGetValue(u, out policy)) { field.Type = policy.Type; field.EnableSorting = policy.EnableSorting; @@ -55,8 +56,9 @@ namespace UmbracoExamine.Config foreach (var s in sysProps) { var field = new IndexField() { Name = s }; - var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == s); - if (policy != null) + + StaticField policy; + if (indexFieldPolicies.TryGetValue(s, out policy)) { field.Type = policy.Type; field.EnableSorting = policy.EnableSorting; diff --git a/src/UmbracoExamine/DataServices/UmbracoContentService.cs b/src/UmbracoExamine/DataServices/UmbracoContentService.cs index 4d265bf127..b52f60a2f2 100644 --- a/src/UmbracoExamine/DataServices/UmbracoContentService.cs +++ b/src/UmbracoExamine/DataServices/UmbracoContentService.cs @@ -57,7 +57,7 @@ namespace UmbracoExamine.DataServices [Obsolete("This should no longer be used, latest content will be indexed by using the IContentService directly")] public XDocument GetLatestContentByXPath(string xpath) { - using (_applicationContext.DatabaseContext.UseSafeDatabase()) + using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) { var xmlContent = XDocument.Parse(""); var rootContent = _applicationContext.Services.ContentService.GetRootContent(); @@ -67,6 +67,7 @@ namespace UmbracoExamine.DataServices xmlContent.Root.Add(c.ToDeepXml(_applicationContext.Services.PackagingService)); } var result = ((IEnumerable)xmlContent.XPathEvaluate(xpath)).Cast(); + scope.Complete(); return result.ToXDocument(); } } @@ -79,9 +80,11 @@ namespace UmbracoExamine.DataServices /// public bool IsProtected(int nodeId, string path) { - using (_applicationContext.DatabaseContext.UseSafeDatabase()) + using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) { - return _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId)); + var ret = _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId)); + scope.Complete(); + return ret; } } @@ -92,11 +95,12 @@ namespace UmbracoExamine.DataServices public IEnumerable GetAllUserPropertyNames() { - using (_applicationContext.DatabaseContext.UseSafeDatabase()) + using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) { - try + try { var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias"); + scope.Complete(); return result; } catch (Exception ex) diff --git a/src/UmbracoExamine/LocalStorage/AzureLocalStorageDirectory.cs b/src/UmbracoExamine/LocalStorage/AzureLocalStorageDirectory.cs index 6d60d26079..56d1b414c5 100644 --- a/src/UmbracoExamine/LocalStorage/AzureLocalStorageDirectory.cs +++ b/src/UmbracoExamine/LocalStorage/AzureLocalStorageDirectory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Specialized; +using System.ComponentModel; using System.IO; using System.Web; using Umbraco.Core; @@ -9,6 +10,8 @@ namespace UmbracoExamine.LocalStorage /// /// When running on Azure websites, we can use the local user's storage space /// + [Obsolete("This has been superceded by IDirectoryFactory in Examine Core and should not be used")] + [EditorBrowsable(EditorBrowsableState.Never)] public sealed class AzureLocalStorageDirectory : ILocalStorageDirectory { public DirectoryInfo GetLocalStorageDirectory(NameValueCollection config, string configuredPath) diff --git a/src/UmbracoExamine/LocalStorage/CodeGenLocalStorageDirectory.cs b/src/UmbracoExamine/LocalStorage/CodeGenLocalStorageDirectory.cs index eb7bb9a8b3..4aa92f0e53 100644 --- a/src/UmbracoExamine/LocalStorage/CodeGenLocalStorageDirectory.cs +++ b/src/UmbracoExamine/LocalStorage/CodeGenLocalStorageDirectory.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Specialized; +using System.ComponentModel; using System.IO; using System.Web; @@ -11,6 +13,8 @@ namespace UmbracoExamine.LocalStorage /// This is the default implementation - but it comes with it's own limitations - the CodeGen folder is cleared whenever new /// DLLs are changed in the /bin folder (among other circumstances) which means the index would be re-synced (or rebuilt) there. /// + [Obsolete("This has been superceded by IDirectoryFactory in Examine Core and should not be used")] + [EditorBrowsable(EditorBrowsableState.Never)] public sealed class CodeGenLocalStorageDirectory : ILocalStorageDirectory { public DirectoryInfo GetLocalStorageDirectory(NameValueCollection config, string configuredPath) diff --git a/src/UmbracoExamine/StaticFieldCollection.cs b/src/UmbracoExamine/StaticFieldCollection.cs new file mode 100644 index 0000000000..909271e0b5 --- /dev/null +++ b/src/UmbracoExamine/StaticFieldCollection.cs @@ -0,0 +1,28 @@ +using System.Collections.ObjectModel; + +namespace UmbracoExamine +{ + internal class StaticFieldCollection : KeyedCollection + { + protected override string GetKeyForItem(StaticField item) + { + return item.Name; + } + + /// + /// Implements TryGetValue using the underlying dictionary + /// + /// + /// + /// + public bool TryGetValue(string key, out StaticField field) + { + if (Dictionary == null) + { + field = null; + return false; + } + return Dictionary.TryGetValue(key, out field); + } + } +} \ No newline at end of file diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index 5acd5a077b..c86118b475 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; @@ -32,6 +35,7 @@ namespace UmbracoExamine private readonly IDataTypeService _dataTypeService; private readonly IUserService _userService; private readonly IContentTypeService _contentTypeService; + private readonly EntityXmlSerializer _serializer = new EntityXmlSerializer(); #region Constructors @@ -143,16 +147,43 @@ 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 - - + #region Constants & Fields /// /// Used to store the path of a content object /// public const string IndexPathFieldName = "__Path"; + public const string NodeKeyFieldName = "__Key"; public const string NodeTypeAliasFieldName = "__NodeTypeAlias"; public const string IconFieldName = "__Icon"; @@ -166,27 +197,27 @@ namespace UmbracoExamine /// Alot of standard umbraco fields shouldn't be tokenized or even indexed, just stored into lucene /// for retreival after searching. /// - internal static readonly List IndexFieldPolicies - = new List + internal static readonly StaticFieldCollection IndexFieldPolicies + = new StaticFieldCollection { new StaticField("id", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), new StaticField("key", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), - new StaticField( "version", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), - new StaticField( "parentID", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), - new StaticField( "level", FieldIndexTypes.NOT_ANALYZED, true, "NUMBER"), - new StaticField( "writerID", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), - new StaticField( "creatorID", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), - new StaticField( "nodeType", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), - new StaticField( "template", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), - new StaticField( "sortOrder", FieldIndexTypes.NOT_ANALYZED, true, "NUMBER"), - new StaticField( "createDate", FieldIndexTypes.NOT_ANALYZED, false, "DATETIME"), - new StaticField( "updateDate", FieldIndexTypes.NOT_ANALYZED, false, "DATETIME"), - new StaticField( "nodeName", FieldIndexTypes.ANALYZED, false, string.Empty), - new StaticField( "urlName", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), - new StaticField( "writerName", FieldIndexTypes.ANALYZED, false, string.Empty), - new StaticField( "creatorName", FieldIndexTypes.ANALYZED, false, string.Empty), - new StaticField( "nodeTypeAlias", FieldIndexTypes.ANALYZED, false, string.Empty), - new StaticField( "path", FieldIndexTypes.NOT_ANALYZED, false, string.Empty) + new StaticField("version", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), + new StaticField("parentID", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), + new StaticField("level", FieldIndexTypes.NOT_ANALYZED, true, "NUMBER"), + new StaticField("writerID", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), + new StaticField("creatorID", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), + new StaticField("nodeType", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), + new StaticField("template", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), + new StaticField("sortOrder", FieldIndexTypes.NOT_ANALYZED, true, "NUMBER"), + new StaticField("createDate", FieldIndexTypes.NOT_ANALYZED, false, "DATETIME"), + new StaticField("updateDate", FieldIndexTypes.NOT_ANALYZED, false, "DATETIME"), + new StaticField("nodeName", FieldIndexTypes.ANALYZED, false, string.Empty), + new StaticField("urlName", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), + new StaticField("writerName", FieldIndexTypes.ANALYZED, false, string.Empty), + new StaticField("creatorName", FieldIndexTypes.ANALYZED, false, string.Empty), + new StaticField("nodeTypeAlias", FieldIndexTypes.ANALYZED, false, string.Empty), + new StaticField("path", FieldIndexTypes.NOT_ANALYZED, false, string.Empty) }; #endregion @@ -230,7 +261,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); } @@ -238,6 +275,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. @@ -360,119 +402,262 @@ namespace UmbracoExamine } #endregion - #region Protected - - /// - /// This is a static query, it's parameters don't change so store statically - /// - private static readonly IQuery PublishedQuery = Query.Builder.Where(x => x.Published == true); + #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; - - 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 - { - //add the published filter - descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "Path", Direction.Ascending, true, PublishedQuery); - } - - //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), type); - pageIndex++; - } while (content.Length == pageSize); - - 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) + /// + /// 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 { - var serializer = new EntityXmlSerializer(); + 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) { - var xml = serializer.Serialize( - _contentService, - _dataTypeService, - _userService, - c); + 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 + //unpublished content and if so return null; + if (notPublished.Any(path => c.Path.StartsWith(string.Format("{0},", path)))) + { + yield return null; + continue; + } + } + + var xml = serializer(c); //add a custom 'icon' attribute xml.Add(new XAttribute("icon", c.ContentType.Icon)); @@ -494,7 +679,7 @@ namespace UmbracoExamine public override void RebuildIndex() { - DataService.LogService.AddVerboseLog(-1, "Rebuilding index"); + DataService.LogService.AddInfoLog(-1, "Rebuilding index"); base.RebuildIndex(); } @@ -525,15 +710,16 @@ namespace UmbracoExamine // Get all user data that we want to index and store into a dictionary foreach (var field in IndexerData.UserFields) { - if (e.Fields.ContainsKey(field.Name)) + string fieldVal; + if (e.Fields.TryGetValue(field.Name, out fieldVal)) { //check if the field value has html - if (XmlHelper.CouldItBeXml(e.Fields[field.Name])) + if (XmlHelper.CouldItBeXml(fieldVal)) { //First save the raw value to a raw field, we will change the policy of this field by detecting the prefix later - e.Fields[RawFieldPrefix + field.Name] = e.Fields[field.Name]; + e.Fields[RawFieldPrefix + field.Name] = fieldVal; //now replace the original value with the stripped html - e.Fields[field.Name] = DataService.ContentService.StripHtml(e.Fields[field.Name]); + e.Fields[field.Name] = DataService.ContentService.StripHtml(fieldVal); } } } @@ -542,18 +728,23 @@ namespace UmbracoExamine //ensure the special path and node type alias fields is added to the dictionary to be saved to file var path = e.Node.Attribute("path").Value; - if (!e.Fields.ContainsKey(IndexPathFieldName)) + if (e.Fields.ContainsKey(IndexPathFieldName) == false) e.Fields.Add(IndexPathFieldName, path); //this needs to support both schema's so get the nodeTypeAlias if it exists, otherwise the name var nodeTypeAlias = e.Node.Attribute("nodeTypeAlias") == null ? e.Node.Name.LocalName : e.Node.Attribute("nodeTypeAlias").Value; - if (!e.Fields.ContainsKey(NodeTypeAliasFieldName)) + if (e.Fields.ContainsKey(NodeTypeAliasFieldName) == false) e.Fields.Add(NodeTypeAliasFieldName, nodeTypeAlias); //add icon var icon = (string)e.Node.Attribute("icon"); - if (!e.Fields.ContainsKey(IconFieldName)) - e.Fields.Add(IconFieldName, icon); + if (e.Fields.ContainsKey(IconFieldName) == false) + e.Fields.Add(IconFieldName, icon); + + //add guid + var guid = (string)e.Node.Attribute("key"); + if (e.Fields.ContainsKey(NodeKeyFieldName) == false) + e.Fields.Add(NodeKeyFieldName, guid); } /// @@ -584,10 +775,18 @@ namespace UmbracoExamine //adds the special node type alias property to the index fields.Add(NodeTypeAliasFieldName, allValuesForIndexing[NodeTypeAliasFieldName]); - //icon - if (allValuesForIndexing[IconFieldName].IsNullOrWhiteSpace() == false) + //guid + string guidVal; + if (allValuesForIndexing.TryGetValue(NodeKeyFieldName, out guidVal) && guidVal.IsNullOrWhiteSpace() == false) { - fields.Add(IconFieldName, allValuesForIndexing[IconFieldName]); + fields.Add(NodeKeyFieldName, guidVal); + } + + //icon + string iconVal; + if (allValuesForIndexing.TryGetValue(IconFieldName, out iconVal) && iconVal.IsNullOrWhiteSpace() == false) + { + fields.Add(IconFieldName, iconVal); } return fields; @@ -619,9 +818,13 @@ namespace UmbracoExamine /// /// protected override FieldIndexTypes GetPolicy(string fieldName) - { - var def = IndexFieldPolicies.Where(x => x.Name == fieldName).ToArray(); - return (def.Any() == false ? FieldIndexTypes.ANALYZED : def.Single().IndexType); + { + StaticField def; + if (IndexFieldPolicies.TryGetValue(fieldName, out def)) + { + return def.IndexType; + } + return FieldIndexTypes.ANALYZED; } /// @@ -631,14 +834,18 @@ namespace UmbracoExamine /// protected override bool ValidateDocument(XElement node) { - var nodeId = int.Parse(node.Attribute("id").Value); // Test for access if we're only indexing published content // return nothing if we're not supporting protected content and it is protected, and we're not supporting unpublished content - if (!SupportUnpublishedContent - && (!SupportProtectedContent - && DataService.ContentService.IsProtected(nodeId, node.Attribute("path").Value))) + if (SupportUnpublishedContent == false + && SupportProtectedContent == false) { - return false; + + var nodeId = int.Parse(node.Attribute("id").Value); + + if (DataService.ContentService.IsProtected(nodeId, node.Attribute("path").Value)) + { + return false; + } } return base.ValidateDocument(node); } diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj index 0ebbdc1a51..efa5eeed41 100644 --- a/src/UmbracoExamine/UmbracoExamine.csproj +++ b/src/UmbracoExamine/UmbracoExamine.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,7 +10,7 @@ Properties UmbracoExamine UmbracoExamine - v4.5 + v4.6.2 512 @@ -82,16 +82,13 @@ ..\Solution Items\TheFARM-Public.snk - - ..\packages\Examine.0.1.70.0\lib\Examine.dll - True + + ..\packages\Examine.0.1.81\lib\net45\Examine.dll - False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - False ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll @@ -136,6 +133,7 @@ + diff --git a/src/UmbracoExamine/UmbracoExamineSearcher.cs b/src/UmbracoExamine/UmbracoExamineSearcher.cs index acbfefb180..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) { @@ -73,7 +102,12 @@ namespace UmbracoExamine base.Initialize(name, config); - if (config != null && config["useTempStorage"] != null) + //NOTES: useTempStorage is obsolete, tempStorageDirectory is obsolete, both have been superceded by Examine Core's IDirectoryFactory + // tempStorageDirectory never actually got finished in Umbraco Core but accidentally got shipped (it's only enabled on the searcher + // and not the indexer). So this whole block is just legacy + + //detect if a dir factory has been specified, if so then useTempStorage will not be used (deprecated) + if (config != null && config["directoryFactory"] == null && config["useTempStorage"] != null) { //Use the temp storage directory which will store the index in the local/codegen folder, this is useful // for websites that are running from a remove file server and file IO latency becomes an issue @@ -110,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..9ca61164eb 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 @@ -88,8 +117,9 @@ namespace UmbracoExamine if (indexerData.UserFields.Any(x => x.Name == "_searchEmail") == false) { var field = new IndexField { Name = "_searchEmail" }; - var policy = IndexFieldPolicies.FirstOrDefault(x => x.Name == "_searchEmail"); - if (policy != null) + + StaticField policy; + if (IndexFieldPolicies.TryGetValue("_searchEmail", out policy)) { field.Type = policy.Type; field.EnableSorting = policy.EnableSorting; @@ -129,40 +159,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) @@ -174,19 +232,8 @@ namespace UmbracoExamine protected override XDocument GetXDocument(string xPath, string type) { throw new NotSupportedException(); - } - - protected override Dictionary GetSpecialFieldsToIndex(Dictionary allValuesForIndexing) - { - var fields = base.GetSpecialFieldsToIndex(allValuesForIndexing); - - //adds the special path property to the index - fields.Add("__key", allValuesForIndexing["__key"]); - - return fields; - - } - + } + /// /// Add the special __key and _searchEmail fields /// @@ -197,8 +244,8 @@ namespace UmbracoExamine if (e.Node.Attribute("key") != null) { - if (e.Fields.ContainsKey("__key") == false) - e.Fields.Add("__key", e.Node.Attribute("key").Value); + if (e.Fields.ContainsKey(NodeKeyFieldName) == false) + e.Fields.Add(NodeKeyFieldName, e.Node.Attribute("key").Value); } if (e.Node.Attribute("email") != null) diff --git a/src/UmbracoExamine/app.config b/src/UmbracoExamine/app.config index a0794caa99..c79842ade3 100644 --- a/src/UmbracoExamine/app.config +++ b/src/UmbracoExamine/app.config @@ -12,7 +12,7 @@ - + @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/src/UmbracoExamine/packages.config b/src/UmbracoExamine/packages.config index 04734b9fb8..81a5d495c4 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/Resources/Strings.Designer.cs b/src/umbraco.MacroEngines/Resources/Strings.Designer.cs index 236db870a0..1acc353daf 100644 --- a/src/umbraco.MacroEngines/Resources/Strings.Designer.cs +++ b/src/umbraco.MacroEngines/Resources/Strings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34014 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/src/umbraco.MacroEngines/app.config b/src/umbraco.MacroEngines/app.config index cc98223bd1..6863fed4c8 100644 --- a/src/umbraco.MacroEngines/app.config +++ b/src/umbraco.MacroEngines/app.config @@ -4,7 +4,7 @@ - + @@ -44,4 +44,4 @@ - \ No newline at end of file + diff --git a/src/umbraco.MacroEngines/packages.config b/src/umbraco.MacroEngines/packages.config index 9a785e35c3..bc7e6fe4c1 100644 --- a/src/umbraco.MacroEngines/packages.config +++ b/src/umbraco.MacroEngines/packages.config @@ -1,6 +1,6 @@  - + @@ -11,6 +11,6 @@ - + \ No newline at end of file diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj index 62180a280d..bdc6f91e0b 100644 --- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,7 +10,7 @@ Properties umbraco.MacroEngines umbraco.MacroEngines - v4.5 + v4.6.2 512 @@ -45,29 +45,23 @@ false - - ..\packages\Examine.0.1.70.0\lib\Examine.dll - True + + ..\packages\Examine.0.1.81\lib\net45\Examine.dll - False ..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll - False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - False ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - True + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll @@ -75,42 +69,33 @@ - False ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - True - False ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - False ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll - True ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll - True ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll - True ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll - True ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - True @@ -219,10 +204,10 @@ - Designer + diff --git a/src/umbraco.businesslogic/Application.cs b/src/umbraco.businesslogic/Application.cs index 2fd7b3cc02..ad6822019b 100644 --- a/src/umbraco.businesslogic/Application.cs +++ b/src/umbraco.businesslogic/Application.cs @@ -44,7 +44,7 @@ namespace umbraco.BusinessLogic try { - const string umbracoDsn = Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName; + const string umbracoDsn = Constants.System.UmbracoConnectionName; var databaseSettings = ConfigurationManager.ConnectionStrings[umbracoDsn]; if (databaseSettings != null) diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index 6af70e21a9..b1f58d0745 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -113,7 +113,7 @@ namespace umbraco.BasePages /// public UrlHelper Url { - get { return _url ?? (_url = new UrlHelper(new RequestContext(new HttpContextWrapper(Context), new RouteData()))); } + get { return _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } } /// diff --git a/src/umbraco.businesslogic/GlobalSettings.cs b/src/umbraco.businesslogic/GlobalSettings.cs index 3d3721f783..9bc725c7c4 100644 --- a/src/umbraco.businesslogic/GlobalSettings.cs +++ b/src/umbraco.businesslogic/GlobalSettings.cs @@ -79,7 +79,7 @@ namespace umbraco /// Gets the database connection string /// /// The database connection string. - [Obsolete("Use System.ConfigurationManager.ConnectionStrings to get the connection with the key Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName instead")] + [Obsolete("Use System.ConfigurationManager.ConnectionStrings to get the connection with the key Constants.System.UmbracoConnectionName instead")] public static string DbDSN { get { return Umbraco.Core.Configuration.GlobalSettings.DbDsn; } @@ -359,7 +359,7 @@ namespace umbraco { get { - var databaseSettings = ConfigurationManager.ConnectionStrings[Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName]; + var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; var dataHelper = DataLayerHelper.CreateSqlHelper(databaseSettings.ConnectionString, false); if (HttpContext.Current != null) diff --git a/src/umbraco.businesslogic/app.config b/src/umbraco.businesslogic/app.config index a0794caa99..c79842ade3 100644 --- a/src/umbraco.businesslogic/app.config +++ b/src/umbraco.businesslogic/app.config @@ -12,7 +12,7 @@ - + @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/src/umbraco.businesslogic/packages.config b/src/umbraco.businesslogic/packages.config index 65ef682839..29ca6a8471 100644 --- a/src/umbraco.businesslogic/packages.config +++ b/src/umbraco.businesslogic/packages.config @@ -5,6 +5,6 @@ - + \ No newline at end of file diff --git a/src/umbraco.businesslogic/ui.cs b/src/umbraco.businesslogic/ui.cs index b289b6d6c5..18c6412bf2 100644 --- a/src/umbraco.businesslogic/ui.cs +++ b/src/umbraco.businesslogic/ui.cs @@ -56,8 +56,8 @@ namespace umbraco private static string GetLanguage() { - var user = UmbracoEnsuredPage.CurrentUser; - return GetLanguage(user); + //Return the current user's language which is based on the current thread culture + return GetLanguage(""); } private static string GetLanguage(User u) @@ -84,7 +84,7 @@ namespace umbraco { return userLanguage; } - var language = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName; + var language = Thread.CurrentThread.CurrentCulture.Name; if (string.IsNullOrEmpty(language)) language = UmbracoDefaultUiLanguage; return language; diff --git a/src/umbraco.businesslogic/umbraco.businesslogic.csproj b/src/umbraco.businesslogic/umbraco.businesslogic.csproj index 1483523caf..e8b090e26f 100644 --- a/src/umbraco.businesslogic/umbraco.businesslogic.csproj +++ b/src/umbraco.businesslogic/umbraco.businesslogic.csproj @@ -1,5 +1,5 @@  - + Local 9.0.30729 @@ -37,7 +37,7 @@ 3.5 true - v4.5 + v4.6.2 http://localhost/businesslogic/ true Web @@ -108,21 +108,17 @@ ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True - False ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - + ..\packages\Owin.1.0\lib\net40\Owin.dll @@ -142,27 +138,21 @@ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - True ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll - True ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll - True ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll - True ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll - True ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - True System.XML diff --git a/src/umbraco.cms/app.config b/src/umbraco.cms/app.config index a0794caa99..c79842ade3 100644 --- a/src/umbraco.cms/app.config +++ b/src/umbraco.cms/app.config @@ -12,7 +12,7 @@ - + @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/src/umbraco.cms/businesslogic/Content.cs b/src/umbraco.cms/businesslogic/Content.cs index 4b942d453f..b64e4cf678 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -149,7 +149,7 @@ namespace umbraco.cms.businesslogic { _contentType = new ContentType(contentTypeId); } - catch + catch (Exception e) { return null; } diff --git a/src/umbraco.cms/businesslogic/Packager/FileResources/PackageFiles.Designer.cs b/src/umbraco.cms/businesslogic/Packager/FileResources/PackageFiles.Designer.cs index 81909bb6a5..0d0b9c03e2 100644 --- a/src/umbraco.cms/businesslogic/Packager/FileResources/PackageFiles.Designer.cs +++ b/src/umbraco.cms/businesslogic/Packager/FileResources/PackageFiles.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18034 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index 1f17da3922..e97b9317d8 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -14,16 +14,18 @@ using Umbraco.Core.Packaging; using umbraco.cms.businesslogic.web; using umbraco.BusinessLogic; using System.Diagnostics; -using umbraco.cms.businesslogic.macro; using umbraco.cms.businesslogic.template; using umbraco.interfaces; +using Umbraco.Core.Events; +using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Services; namespace umbraco.cms.businesslogic.packager { /// /// The packager is a component which enables sharing of both data and functionality components between different umbraco installations. /// - /// The output is a .umb (a zip compressed file) which contains the exported documents/medias/macroes/documenttypes (etc.) + /// The output is a .umb (a zip compressed file) which contains the exported documents/medias/macros/documenttypes (etc.) /// in a Xml document, along with the physical files used (images/usercontrols/xsl documents etc.) /// /// Partly implemented, import of packages is done, the export is *under construction*. @@ -507,6 +509,7 @@ namespace umbraco.cms.businesslogic.packager } OnPackageBusinessLogicInstalled(insPack); + OnPackageInstalled(insPack); } } @@ -517,6 +520,7 @@ namespace umbraco.cms.businesslogic.packager /// public void InstallCleanUp(int packageId, string tempDir) { + if (Directory.Exists(tempDir)) { Directory.Delete(tempDir, true); @@ -814,5 +818,21 @@ namespace umbraco.cms.businesslogic.packager EventHandler handler = PackageBusinessLogicInstalled; if (handler != null) handler(null, e); } + + private void OnPackageInstalled(InstalledPackage insPack) + { + // getting an InstallationSummary for sending to the PackagingService.ImportedPackage event + var fileService = ApplicationContext.Current.Services.FileService; + var macroService = ApplicationContext.Current.Services.MacroService; + var contentTypeService = ApplicationContext.Current.Services.ContentTypeService; + var dataTypeService = ApplicationContext.Current.Services.DataTypeService; + var localizationService = ApplicationContext.Current.Services.LocalizationService; + + var installationSummary = insPack.GetInstallationSummary(contentTypeService, dataTypeService, fileService, localizationService, macroService); + installationSummary.PackageInstalled = true; + + var args = new ImportPackageEventArgs(installationSummary, false); + PackagingService.OnImportedPackage(args); + } } } diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs index 974f398ca7..172a9d6443 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; using Umbraco.Core.Auditing; using Umbraco.Core.Logging; using Umbraco.Core.IO; +using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Services; namespace umbraco.cms.businesslogic.packager { public class InstalledPackage @@ -124,5 +127,70 @@ namespace umbraco.cms.businesslogic.packager { if (AfterDelete != null) AfterDelete(this, e); } + + /// + /// Used internally for creating an InstallationSummary (used in new PackagingService) representation of this InstalledPackage object. + /// + /// + /// + /// + /// + /// + /// + internal InstallationSummary GetInstallationSummary(IContentTypeService contentTypeService, IDataTypeService dataTypeService, IFileService fileService, ILocalizationService localizationService, IMacroService macroService) + { + var macros = TryGetIntegerIds(Data.Macros).Select(macroService.GetById).ToList(); + var templates = TryGetIntegerIds(Data.Templates).Select(fileService.GetTemplate).ToList(); + var contentTypes = TryGetIntegerIds(Data.Documenttypes).Select(contentTypeService.GetContentType).ToList(); + var dataTypes = TryGetIntegerIds(Data.DataTypes).Select(dataTypeService.GetDataTypeDefinitionById).ToList(); + var dictionaryItems = TryGetIntegerIds(Data.DictionaryItems).Select(localizationService.GetDictionaryItemById).ToList(); + var languages = TryGetIntegerIds(Data.Languages).Select(localizationService.GetLanguageById).ToList(); + + for (var i = 0; i < Data.Files.Count; i++) + { + var filePath = Data.Files[i]; + Data.Files[i] = filePath.GetRelativePath(); + } + + return new InstallationSummary + { + ContentTypesInstalled = contentTypes, + DataTypesInstalled = dataTypes, + DictionaryItemsInstalled = dictionaryItems, + FilesInstalled = Data.Files, + LanguagesInstalled = languages, + MacrosInstalled = macros, + MetaData = GetMetaData(), + TemplatesInstalled = templates, + }; + } + + internal MetaData GetMetaData() + { + return new MetaData() + { + AuthorName = Data.Author, + AuthorUrl = Data.AuthorUrl, + Control = Data.LoadControl, + License = Data.License, + LicenseUrl = Data.LicenseUrl, + Name = Data.Name, + Readme = Data.Readme, + Url = Data.Url, + Version = Data.Version + }; + } + + private static IEnumerable TryGetIntegerIds(IEnumerable ids) + { + var intIds = new List(); + foreach (var id in ids) + { + int parsed; + if (int.TryParse(id, out parsed)) + intIds.Add(parsed); + } + return intIds; + } } } 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/datatype/DataEditorSettingsStorage.cs b/src/umbraco.cms/businesslogic/datatype/DataEditorSettingsStorage.cs index 5d2434164f..ddebb2ee2d 100644 --- a/src/umbraco.cms/businesslogic/datatype/DataEditorSettingsStorage.cs +++ b/src/umbraco.cms/businesslogic/datatype/DataEditorSettingsStorage.cs @@ -4,6 +4,7 @@ using System.Configuration; using System.Linq; using System.Text; using umbraco.DataLayer; +using Umbraco.Core; namespace umbraco.cms.businesslogic.datatype { @@ -14,7 +15,7 @@ namespace umbraco.cms.businesslogic.datatype public DataEditorSettingsStorage() { - var databaseSettings = ConfigurationManager.ConnectionStrings[Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName]; + var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; var dataHelper = DataLayerHelper.CreateSqlHelper(databaseSettings.ConnectionString, false); init(DataLayerHelper.CreateSqlHelper(dataHelper.ConnectionString, false)); diff --git a/src/umbraco.cms/businesslogic/macro/Macro.cs b/src/umbraco.cms/businesslogic/macro/Macro.cs index 0b043a90a3..0ce2c45c6b 100644 --- a/src/umbraco.cms/businesslogic/macro/Macro.cs +++ b/src/umbraco.cms/businesslogic/macro/Macro.cs @@ -48,6 +48,14 @@ namespace umbraco.cms.businesslogic.macro { get { return MacroEntity.Id; } } + + /// + /// key + /// + public Guid Key + { + get { return MacroEntity.Key; } + } /// /// If set to true, the macro can be inserted on documents using the richtexteditor. @@ -178,6 +186,7 @@ namespace umbraco.cms.businesslogic.macro { return MacroEntity.Properties.Select(x => new MacroProperty { + Key = x.Key, Alias = x.Alias, Name = x.Name, SortOrder = x.SortOrder, diff --git a/src/umbraco.cms/businesslogic/macro/MacroProperty.cs b/src/umbraco.cms/businesslogic/macro/MacroProperty.cs index ff3a58d46a..0944324275 100644 --- a/src/umbraco.cms/businesslogic/macro/MacroProperty.cs +++ b/src/umbraco.cms/businesslogic/macro/MacroProperty.cs @@ -36,6 +36,7 @@ namespace umbraco.cms.businesslogic.macro /// public MacroProperty() { + Key = Guid.NewGuid(); } /// @@ -75,6 +76,11 @@ namespace umbraco.cms.businesslogic.macro /// The id. public int Id { get; private set; } + /// + /// Gets the key. + /// + public Guid Key { get; set; } + /// /// Gets or sets the macro. /// @@ -147,10 +153,11 @@ namespace umbraco.cms.businesslogic.macro private void Setup() { using (var sqlHelper = Application.SqlHelper) - using (var dr = sqlHelper.ExecuteReader("select macro, editorAlias, macroPropertySortOrder, macroPropertyAlias, macroPropertyName from cmsMacroProperty where id = @id", sqlHelper.CreateParameter("@id", Id))) + using (var dr = sqlHelper.ExecuteReader("select uniqueId, macro, editorAlias, macroPropertySortOrder, macroPropertyAlias, macroPropertyName from cmsMacroProperty where id = @id", sqlHelper.CreateParameter("@id", Id))) { if (dr.Read()) { + Key = dr.GetGuid("uniqueId"); Macro = new Macro(dr.GetInt("macro")); SortOrder = (int)dr.GetByte("macroPropertySortOrder"); Alias = dr.GetString("macroPropertyAlias"); diff --git a/src/umbraco.cms/businesslogic/propertytype/propertytype.cs b/src/umbraco.cms/businesslogic/propertytype/propertytype.cs index b9622d7c91..8d5f538fd9 100644 --- a/src/umbraco.cms/businesslogic/propertytype/propertytype.cs +++ b/src/umbraco.cms/businesslogic/propertytype/propertytype.cs @@ -54,32 +54,32 @@ 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; } + + //Fixed issue U4-9493 Case issues + _sortOrder = found.sortOrder; + _alias = found.Alias; + _name = found.Name; + _validationRegExp = found.validationRegExp; + _DataTypeId = found.dataTypeId; + _contenttypeid = found.contentTypeId; + _description = found.Description; } #endregion @@ -92,7 +92,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 +118,6 @@ namespace umbraco.cms.businesslogic.propertytype { _tabId = value; PropertyTypeGroup = value; - InvalidateCache(); } } @@ -148,7 +146,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 +159,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 +195,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 +208,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 +221,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 +257,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 +323,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 +345,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 +367,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 +400,6 @@ namespace umbraco.cms.businesslogic.propertytype // delete cache from either master (via tabid) or current contentype FlushCacheBasedOnTab(); - InvalidateCache(); } public void FlushCacheBasedOnTab() @@ -478,8 +466,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 +482,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.cms/packages.config b/src/umbraco.cms/packages.config index 46e6d733a1..400a4dea20 100644 --- a/src/umbraco.cms/packages.config +++ b/src/umbraco.cms/packages.config @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 3ed531db8e..aefb57e24a 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -1,5 +1,5 @@  - + Local 9.0.30729 @@ -36,7 +36,7 @@ 3.5 - v4.5 + v4.6.2 publish\ true Disk @@ -108,20 +108,16 @@ ..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll - True - False ..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll - False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - True + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll System @@ -168,7 +164,6 @@ - False ..\packages\Tidy.Net.1.0.0\lib\TidyNet.dll diff --git a/src/umbraco.controls/TreePicker/BaseTreePickerScripts.Designer.cs b/src/umbraco.controls/TreePicker/BaseTreePickerScripts.Designer.cs index 75268145d4..cf53229e77 100644 --- a/src/umbraco.controls/TreePicker/BaseTreePickerScripts.Designer.cs +++ b/src/umbraco.controls/TreePicker/BaseTreePickerScripts.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18034 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -62,16 +62,17 @@ namespace umbraco.uicontrols.TreePicker { /// /// Looks up a localized string similar to /// <reference path="/umbraco_client/Application/NamespaceManager.js" /> - /// - ///Umbraco.Sys.registerNamespace("Umbraco.Controls"); - /// - ///(function($) { - /// Umbraco.Controls.TreePicker = function(clientId, label, itemIdValueClientID, itemTitleClientID, itemPickerUrl, width, height, showHeader, umbracoPath) { - /// var obj = { - /// _itemPickerUrl: itemPickerUrl, - /// _webServiceUrl: umbracoPath + "/webservices/legacyAjaxCalls.asmx/GetNodeBreadcrumbs", - /// _label: label, - /// _wid [rest of string was truncated]";. + ///(function ($) { + /// $(document).ready(function () { + /// // Tooltip only Text + /// $('.umb-tree-picker a.choose').click(function () { + /// var that = this; + /// var s = $(that).data("section"); + /// UmbClientMgr.openAngularModalWindow({ + /// template: 'views/common/dialogs/treepicker.html', + /// section: s, + /// callback: function (data) { + /// //this [rest of string was truncated]";. /// internal static string BaseTreePicker { get { diff --git a/src/umbraco.controls/app.config b/src/umbraco.controls/app.config index a0794caa99..c79842ade3 100644 --- a/src/umbraco.controls/app.config +++ b/src/umbraco.controls/app.config @@ -12,7 +12,7 @@ - + @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/src/umbraco.controls/umbraco.controls.csproj b/src/umbraco.controls/umbraco.controls.csproj index 49fd505856..3cff539619 100644 --- a/src/umbraco.controls/umbraco.controls.csproj +++ b/src/umbraco.controls/umbraco.controls.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -23,7 +23,7 @@ 3.5 - v4.5 + v4.6.2 publish\ true Disk @@ -70,7 +70,6 @@ ..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll - True diff --git a/src/umbraco.datalayer/DataLayerHelper.cs b/src/umbraco.datalayer/DataLayerHelper.cs index 7952b737ec..f79a90a0f1 100644 --- a/src/umbraco.datalayer/DataLayerHelper.cs +++ b/src/umbraco.datalayer/DataLayerHelper.cs @@ -10,6 +10,7 @@ using System; using System.Configuration; using System.Data.Common; using System.Reflection; +using Umbraco.Core; namespace umbraco.DataLayer { @@ -72,7 +73,7 @@ namespace umbraco.DataLayer throw new ArgumentException("Bad connection string.", "connectionString", ex); } - var connectionStringSettings = ConfigurationManager.ConnectionStrings[Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName]; + var connectionStringSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; if (forceLegacyConnection == false && connectionStringSettings != null) SetDataHelperNames(connectionStringSettings); diff --git a/src/umbraco.datalayer/SqlHelper.cs b/src/umbraco.datalayer/SqlHelper.cs index 4420e8bde0..8eadd01d35 100644 --- a/src/umbraco.datalayer/SqlHelper.cs +++ b/src/umbraco.datalayer/SqlHelper.cs @@ -359,11 +359,12 @@ namespace umbraco.DataLayer internal class CurrentConnectionUsing : IDisposable { - public static MethodInfo OpenMethod { get; private set; } - public static MethodInfo CloseMethod { get; private set; } + private static MethodInfo OpenMethod { get; set; } + //private static MethodInfo CloseMethod { get; set; } - private static readonly object Factory; - private static readonly MethodInfo CreateMethod; + private static readonly object ScopeProvider; + private static readonly MethodInfo ScopeProviderAmbientOrNoScopeMethod; + private static readonly PropertyInfo ScopeDatabaseProperty; private static readonly PropertyInfo ConnectionProperty; private static readonly FieldInfo TransactionField; private static readonly PropertyInfo InnerConnectionProperty; @@ -378,23 +379,30 @@ namespace umbraco.DataLayer var applicationContextType = coreAssembly.GetType("Umbraco.Core.ApplicationContext"); var databaseContextType = coreAssembly.GetType("Umbraco.Core.DatabaseContext"); - var defaultDatabaseFactoryType = coreAssembly.GetType("Umbraco.Core.Persistence.DefaultDatabaseFactory"); var umbracoDatabaseType = coreAssembly.GetType("Umbraco.Core.Persistence.UmbracoDatabase"); var databaseType = coreAssembly.GetType("Umbraco.Core.Persistence.Database"); + var scopeProviderType = coreAssembly.GetType("Umbraco.Core.Scoping.IScopeProviderInternal"); + var scopeType = coreAssembly.GetType("Umbraco.Core.Scoping.IScope"); - var currentProperty = applicationContextType.GetProperty("Current", BindingFlags.Static | BindingFlags.Public); - var applicationContext = currentProperty.GetValue(null, NoArgs); + var applicationContextCurrentProperty = applicationContextType.GetProperty("Current", BindingFlags.Static | BindingFlags.Public); + if (applicationContextCurrentProperty == null) throw new Exception("oops: applicationContextCurrentProperty."); + var applicationContext = applicationContextCurrentProperty.GetValue(null, NoArgs); - var databaseContextProperty = applicationContextType.GetProperty("DatabaseContext", BindingFlags.Instance | BindingFlags.Public); - var databaseContext = databaseContextProperty.GetValue(applicationContext, NoArgs); + var applicationContextDatabaseContextProperty = applicationContextType.GetProperty("DatabaseContext", BindingFlags.Instance | BindingFlags.Public); + if (applicationContextDatabaseContextProperty == null) throw new Exception("oops: applicationContextDatabaseContextProperty."); + var databaseContext = applicationContextDatabaseContextProperty.GetValue(applicationContext, NoArgs); - var factoryField = databaseContextType.GetField("_factory", BindingFlags.Instance | BindingFlags.NonPublic); - Factory = factoryField.GetValue(databaseContext); + var databaseContextScopeProviderField = databaseContextType.GetField("ScopeProvider", BindingFlags.Instance | BindingFlags.NonPublic); + if (databaseContextScopeProviderField == null) throw new Exception("oops: databaseContextScopeProviderField."); + ScopeProvider = databaseContextScopeProviderField.GetValue(databaseContext); - CreateMethod = defaultDatabaseFactoryType.GetMethod("CreateDatabase", BindingFlags.Instance | BindingFlags.Public); + ScopeProviderAmbientOrNoScopeMethod = scopeProviderType.GetMethod("GetAmbientOrNoScope", BindingFlags.Instance | BindingFlags.Public); + if (ScopeProviderAmbientOrNoScopeMethod == null) throw new Exception("oops: ScopeProviderAmbientOrNoScopeMethod."); + ScopeDatabaseProperty = scopeType.GetProperty("Database", BindingFlags.Instance | BindingFlags.Public); + if (ScopeDatabaseProperty == null) throw new Exception("oops: ScopeDatabaseProperty."); OpenMethod = databaseType.GetMethod("OpenSharedConnection", BindingFlags.Instance | BindingFlags.Public); - CloseMethod = databaseType.GetMethod("CloseSharedConnection", BindingFlags.Instance | BindingFlags.Public); + //CloseMethod = databaseType.GetMethod("CloseSharedConnection", BindingFlags.Instance | BindingFlags.Public); ConnectionProperty = umbracoDatabaseType.GetProperty("Connection", BindingFlags.Instance | BindingFlags.Public); TransactionField = databaseType.GetField("_transaction", BindingFlags.Instance | BindingFlags.NonPublic); @@ -408,7 +416,8 @@ namespace umbraco.DataLayer public CurrentConnectionUsing() { - _database = CreateMethod.Invoke(Factory, NoArgs); + var scope = ScopeProviderAmbientOrNoScopeMethod.Invoke(ScopeProvider, NoArgs); + _database = ScopeDatabaseProperty.GetValue(scope); var connection = ConnectionProperty.GetValue(_database, NoArgs); // we have to open to make sure that we *do* have a connection diff --git a/src/umbraco.datalayer/SqlHelpers/MySql/MySqlHelper.cs b/src/umbraco.datalayer/SqlHelpers/MySql/MySqlHelper.cs index 2e76941c21..c1a530f8f4 100644 --- a/src/umbraco.datalayer/SqlHelpers/MySql/MySqlHelper.cs +++ b/src/umbraco.datalayer/SqlHelpers/MySql/MySqlHelper.cs @@ -122,12 +122,12 @@ namespace umbraco.DataLayer.SqlHelpers.MySql { using (var cc = UseCurrentConnection) { - return new MySqlDataReader(ExecuteReader((MSC.MySqlConnection) cc.Connection, (MSC.MySqlTransaction) cc.Transaction, commandText, parameters, true)); + return new MySqlDataReader(ExecuteReader((MSC.MySqlConnection) cc.Connection, (MSC.MySqlTransaction) cc.Transaction, commandText, parameters)); } } // copied & adapted from MySqlHelper - private static MSC.MySqlDataReader ExecuteReader(MSC.MySqlConnection connection, MSC.MySqlTransaction trx, string commandText, MSC.MySqlParameter[] commandParameters, bool externalConn) + private static MSC.MySqlDataReader ExecuteReader(MSC.MySqlConnection connection, MSC.MySqlTransaction trx, string commandText, MSC.MySqlParameter[] commandParameters) { MSC.MySqlCommand mySqlCommand = new MSC.MySqlCommand(); mySqlCommand.Connection = connection; @@ -139,7 +139,7 @@ namespace umbraco.DataLayer.SqlHelpers.MySql foreach (var commandParameter in commandParameters) mySqlCommand.Parameters.Add(commandParameter); } - MSC.MySqlDataReader mySqlDataReader = !externalConn ? mySqlCommand.ExecuteReader(CommandBehavior.CloseConnection) : mySqlCommand.ExecuteReader(); + MSC.MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader(); mySqlCommand.Parameters.Clear(); return mySqlDataReader; } diff --git a/src/umbraco.datalayer/app.config b/src/umbraco.datalayer/app.config index 1f5a6442ad..6d9f461c6f 100644 --- a/src/umbraco.datalayer/app.config +++ b/src/umbraco.datalayer/app.config @@ -4,7 +4,7 @@ - + @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/src/umbraco.datalayer/packages.config b/src/umbraco.datalayer/packages.config index 2a222078f5..3537761ce9 100644 --- a/src/umbraco.datalayer/packages.config +++ b/src/umbraco.datalayer/packages.config @@ -1,6 +1,5 @@  - - + \ No newline at end of file diff --git a/src/umbraco.datalayer/umbraco.datalayer.csproj b/src/umbraco.datalayer/umbraco.datalayer.csproj index 682062248d..9452e2f39d 100644 --- a/src/umbraco.datalayer/umbraco.datalayer.csproj +++ b/src/umbraco.datalayer/umbraco.datalayer.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -23,7 +23,7 @@ 3.5 - v4.5 + v4.6.2 publish\ true Disk @@ -68,16 +68,10 @@ false - - False - ..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll - - False ..\packages\Microsoft.ApplicationBlocks.Data.1.0.1559.20655\lib\Microsoft.ApplicationBlocks.Data.dll - - False + ..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll 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(); + } + } /// diff --git a/src/umbraco.editorControls/app.config b/src/umbraco.editorControls/app.config index 1d7a37c980..554506d233 100644 --- a/src/umbraco.editorControls/app.config +++ b/src/umbraco.editorControls/app.config @@ -16,7 +16,7 @@ - + @@ -53,4 +53,4 @@ - \ No newline at end of file + diff --git a/src/umbraco.editorControls/mediapicker/MediaChooserScripts.Designer.cs b/src/umbraco.editorControls/mediapicker/MediaChooserScripts.Designer.cs index a7743a55be..30cc94f1de 100644 --- a/src/umbraco.editorControls/mediapicker/MediaChooserScripts.Designer.cs +++ b/src/umbraco.editorControls/mediapicker/MediaChooserScripts.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18034 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/src/umbraco.editorControls/umbraco.editorControls.csproj b/src/umbraco.editorControls/umbraco.editorControls.csproj index 3bccb0b5f8..56d7e7ea86 100644 --- a/src/umbraco.editorControls/umbraco.editorControls.csproj +++ b/src/umbraco.editorControls/umbraco.editorControls.csproj @@ -1,5 +1,5 @@  - + Local 9.0.30729 @@ -37,7 +37,7 @@ 3.5 true - v4.5 + v4.6.2 http://localhost/umbraco.editorControls/ true Web @@ -116,7 +116,6 @@ ..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll - True System diff --git a/src/umbraco.interfaces/umbraco.interfaces.csproj b/src/umbraco.interfaces/umbraco.interfaces.csproj index 28bd27603f..85070859f7 100644 --- a/src/umbraco.interfaces/umbraco.interfaces.csproj +++ b/src/umbraco.interfaces/umbraco.interfaces.csproj @@ -1,5 +1,5 @@  - + Local 9.0.30729 @@ -36,7 +36,7 @@ 3.5 - v4.0 + v4.6.2 publish\ true Disk @@ -77,6 +77,7 @@ full prompt AllRules.ruleset + false bin\Release\ @@ -100,6 +101,7 @@ pdbonly prompt AllRules.ruleset + false diff --git a/src/umbraco.providers/app.config b/src/umbraco.providers/app.config index a0794caa99..c79842ade3 100644 --- a/src/umbraco.providers/app.config +++ b/src/umbraco.providers/app.config @@ -12,7 +12,7 @@ - + @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/src/umbraco.providers/umbraco.providers.csproj b/src/umbraco.providers/umbraco.providers.csproj index 102e9098da..0e07ce6328 100644 --- a/src/umbraco.providers/umbraco.providers.csproj +++ b/src/umbraco.providers/umbraco.providers.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -23,7 +23,7 @@ 3.5 - v4.5 + v4.6.2 publish\ true Disk