diff --git a/build/Build.proj b/build/Build.proj index 051e02de5c..e937df15bd 100644 --- a/build/Build.proj +++ b/build/Build.proj @@ -97,15 +97,8 @@ - - - - - - - @@ -151,14 +144,14 @@ DestinationFiles="@(WebPiFiles->'$(WebPiFolder)%(RecursiveDir)%(Filename)%(Extension)')" /> - + - + @@ -225,6 +218,7 @@ + @@ -233,6 +227,11 @@ OverwriteReadOnlyFiles="true" SkipUnchangedFiles="false" /> + + en-US umbraco - + - - - + + @@ -27,16 +26,18 @@ - + - - + + - - + + + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 76d72935e9..61a8188804 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -26,7 +26,6 @@ - @@ -41,6 +40,7 @@ + \ No newline at end of file diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt index 1f5bbb0164..e4d621f693 100644 --- a/build/NuSpecs/tools/Dashboard.config.install.xdt +++ b/build/NuSpecs/tools/Dashboard.config.install.xdt @@ -35,6 +35,11 @@ views/dashboard/developer/examinemanagement.html + + + views/dashboard/developer/xmldataintegrityreport.html + +
diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt new file mode 100644 index 0000000000..c34963f2b0 --- /dev/null +++ b/build/NuSpecs/tools/Views.Web.config.install.xdt @@ -0,0 +1,37 @@ + + + + +
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 75eb8dc3cf..fbe2cbe439 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -20,9 +20,11 @@ - + + + @@ -144,7 +146,12 @@ - + + + + + + @@ -160,12 +167,32 @@ - + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1 index b479ecf340..1fa9352be7 100644 --- a/build/NuSpecs/tools/install.core.ps1 +++ b/build/NuSpecs/tools/install.core.ps1 @@ -58,6 +58,7 @@ if ($project) { 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.Web.Infrastructure.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Infrastructure.dll -Force -Confirm:$false } + if(Test-Path $umbracoBinFolder\Microsoft.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Helpers.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\MiniProfiler.dll) { Remove-Item $umbracoBinFolder\MiniProfiler.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\MySql.Data.dll) { Remove-Item $umbracoBinFolder\MySql.Data.dll -Force -Confirm:$false } diff --git a/build/NuSpecs/tools/log4net.config.install.xdt b/build/NuSpecs/tools/log4net.config.install.xdt new file mode 100644 index 0000000000..f4d3caf7fc --- /dev/null +++ b/build/NuSpecs/tools/log4net.config.install.xdt @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt index 6acc793e05..45f43206fe 100644 --- a/build/NuSpecs/tools/trees.config.install.xdt +++ b/build/NuSpecs/tools/trees.config.install.xdt @@ -7,12 +7,12 @@ - + - + diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index f767faaa60..b31e79056f 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.3.0 -beta2 \ No newline at end of file +beta3 \ No newline at end of file diff --git a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index e4258e943d..dbc0f2d41d 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.3.0")] -[assembly: AssemblyInformationalVersion("7.3.0-beta2")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.3.0-beta3")] \ No newline at end of file diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index b48d4eb5e9..7e306a6361 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -1,18 +1,14 @@ using System; using System.Configuration; using System.Threading; -using System.Web; -using System.Web.Caching; -using Umbraco.Core.Cache; +using System.Threading.Tasks; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Profiling; using Umbraco.Core.Services; using Umbraco.Core.Sync; - namespace Umbraco.Core { /// @@ -35,6 +31,7 @@ namespace Umbraco.Core if (dbContext == null) throw new ArgumentNullException("dbContext"); if (serviceContext == null) throw new ArgumentNullException("serviceContext"); if (cache == null) throw new ArgumentNullException("cache"); + if (logger == null) throw new ArgumentNullException("logger"); _databaseContext = dbContext; _services = serviceContext; ApplicationCache = cache; @@ -49,7 +46,7 @@ namespace Umbraco.Core /// /// /// - [Obsolete("Use the other constructor specifying an ILogger instead")] + [Obsolete("Use the other constructor specifying a ProfilingLogger instead")] public ApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache) : this(dbContext, serviceContext, cache, new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler)) @@ -60,10 +57,26 @@ namespace Umbraco.Core /// Creates a basic app context /// /// + [Obsolete("Use the other constructor specifying a ProfilingLogger instead")] public ApplicationContext(CacheHelper cache) { + if (cache == null) throw new ArgumentNullException("cache"); ApplicationCache = cache; + ProfilingLogger = new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler); + Init(); + } + /// + /// Creates a basic app context + /// + /// + /// + public ApplicationContext(CacheHelper cache, ProfilingLogger logger) + { + if (cache == null) throw new ArgumentNullException("cache"); + if (logger == null) throw new ArgumentNullException("logger"); + ApplicationCache = cache; + ProfilingLogger = logger; Init(); } @@ -80,13 +93,13 @@ namespace Umbraco.Core /// public static ApplicationContext EnsureContext(ApplicationContext appContext, bool replaceContext) { - if (ApplicationContext.Current != null) + if (Current != null) { if (!replaceContext) - return ApplicationContext.Current; + return Current; } - ApplicationContext.Current = appContext; - return ApplicationContext.Current; + Current = appContext; + return Current; } /// @@ -106,14 +119,14 @@ namespace Umbraco.Core [Obsolete("Use the other method specifying an ProfilingLogger instead")] public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, bool replaceContext) { - if (ApplicationContext.Current != null) + if (Current != null) { if (!replaceContext) - return ApplicationContext.Current; + return Current; } var ctx = new ApplicationContext(dbContext, serviceContext, cache); - ApplicationContext.Current = ctx; - return ApplicationContext.Current; + Current = ctx; + return Current; } /// @@ -133,14 +146,14 @@ namespace Umbraco.Core /// public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, ProfilingLogger logger, bool replaceContext) { - if (ApplicationContext.Current != null) + if (Current != null) { if (!replaceContext) - return ApplicationContext.Current; + return Current; } var ctx = new ApplicationContext(dbContext, serviceContext, cache, logger); - ApplicationContext.Current = ctx; - return ApplicationContext.Current; + Current = ctx; + return Current; } /// @@ -232,7 +245,7 @@ namespace Umbraco.Core /// - has the SystemDirectories.Umbraco path /// - does not end with a slash /// It is initialized on the first request made to the server, by UmbracoModule.EnsureApplicationUrl: - /// - if umbracoSettings:settings/web.routing/@appUrl is set, use the value (new setting) + /// - if umbracoSettings:settings/web.routing/@umbracoApplicationUrl is set, use the value (new setting) /// - if umbracoSettings:settings/scheduledTasks/@baseUrl is set, use the value (backward compatibility) /// - otherwise, use the url of the (first) request. /// Not locking, does not matter if several threads write to this. @@ -242,9 +255,10 @@ namespace Umbraco.Core /// - http://issues.umbraco.org/issue/U4-5728 /// - http://issues.umbraco.org/issue/U4-5391 /// - internal string UmbracoApplicationUrl { - get - { + internal string UmbracoApplicationUrl + { + get + { // if initialized, return if (_umbracoApplicationUrl != null) return _umbracoApplicationUrl; @@ -261,11 +275,15 @@ namespace Umbraco.Core } internal string _umbracoApplicationUrl; // internal for tests - private Lazy _configured; + private Lazy _configured; + internal MainDom MainDom { get; private set; } + private void Init() - { - + { + MainDom = new MainDom(ProfilingLogger.Logger); + MainDom.Acquire(); + //Create the lazy value to resolve whether or not the application is 'configured' _configured = new Lazy(() => { @@ -304,7 +322,7 @@ namespace Umbraco.Core } }); - } + } private string ConfigurationStatus { @@ -321,12 +339,6 @@ namespace Umbraco.Core } } - private void AssertIsReady() - { - if (!this.IsReady) - throw new Exception("ApplicationContext is not ready yet."); - } - private void AssertIsNotReady() { if (this.IsReady) diff --git a/src/Umbraco.Core/ApplicationEventHandler.cs b/src/Umbraco.Core/ApplicationEventHandler.cs index a725a08a8e..8d97baac95 100644 --- a/src/Umbraco.Core/ApplicationEventHandler.cs +++ b/src/Umbraco.Core/ApplicationEventHandler.cs @@ -74,7 +74,7 @@ namespace Umbraco.Core /// private bool ShouldExecute(ApplicationContext applicationContext) { - if (applicationContext.IsConfigured && applicationContext.DatabaseContext.CanConnect) + if (applicationContext.IsConfigured && applicationContext.DatabaseContext.IsDatabaseConfigured) { return true; } @@ -84,7 +84,7 @@ namespace Umbraco.Core return true; } - if (!applicationContext.DatabaseContext.CanConnect && ExecuteWhenDatabaseNotConfigured) + if (!applicationContext.DatabaseContext.IsDatabaseConfigured && ExecuteWhenDatabaseNotConfigured) { return true; } diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs index 608b19a700..b4e8e64312 100644 --- a/src/Umbraco.Core/AsyncLock.cs +++ b/src/Umbraco.Core/AsyncLock.cs @@ -70,7 +70,7 @@ namespace Umbraco.Core { var wait = _semaphore != null ? _semaphore.WaitAsync() - : WaitOneAsync(_semaphore2); + : _semaphore2.WaitOneAsync(); return wait.IsCompleted ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named @@ -83,7 +83,7 @@ namespace Umbraco.Core { var wait = _semaphore != null ? _semaphore.WaitAsync(millisecondsTimeout) - : WaitOneAsync(_semaphore2, millisecondsTimeout); + : _semaphore2.WaitOneAsync(millisecondsTimeout); return wait.IsCompleted ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named @@ -181,40 +181,5 @@ namespace Umbraco.Core Dispose(false); } } - - // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100 - // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html - // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex... - // version below should be OK - - private static Task WaitOneAsync(WaitHandle handle, int millisecondsTimeout = Timeout.Infinite) - { - var tcs = new TaskCompletionSource(); - var callbackHandleInitLock = new object(); - lock (callbackHandleInitLock) - { - RegisteredWaitHandle callbackHandle = null; - // ReSharper disable once RedundantAssignment - callbackHandle = ThreadPool.RegisterWaitForSingleObject( - handle, - (state, timedOut) => - { - tcs.SetResult(null); - - // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. - lock (callbackHandleInitLock) - { - // ReSharper disable once PossibleNullReferenceException - // ReSharper disable once AccessToModifiedClosure - callbackHandle.Unregister(null); - } - }, - /*state:*/ null, - /*millisecondsTimeOutInterval:*/ millisecondsTimeout, - /*executeOnlyOnce:*/ true); - } - - return tcs.Task; - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs index a4a6938fc0..ca1f4e85a0 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -102,7 +102,7 @@ namespace Umbraco.Core.Cache // cannot create value within the lock, so if result.IsValueCreated is false, just // do nothing here - means that if creation throws, a race condition could cause // more than one thread to reach the return statement below and throw - accepted. - + if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null { result = GetSafeLazy(getCacheItem); @@ -122,7 +122,7 @@ namespace Umbraco.Core.Cache if (eh != null) throw eh.Exception; // throw once! return value; } - + #endregion #region Insert diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs index 88fee8ef3a..e7f5d17b83 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs @@ -35,7 +35,7 @@ namespace Umbraco.Core.Cache { const string prefix = CacheItemPrefix + "-"; return _cache.Cast() - .Where(x => x.Key is string && ((string) x.Key).StartsWith(prefix)); + .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); } protected override void RemoveEntry(string key) @@ -191,7 +191,7 @@ namespace Umbraco.Core.Cache var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache if (value == null) return; // do not store null values (backward compat) - cacheKey = GetCacheKey(cacheKey); + cacheKey = GetCacheKey(cacheKey); var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index 76519a6722..8afa5639f1 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -49,7 +49,7 @@ namespace Umbraco.Core.Cache { if (MemoryCache[key] == null) return; MemoryCache.Remove(key); - } + } } public virtual void ClearCacheObjectTypes(string typeName) @@ -137,7 +137,7 @@ namespace Umbraco.Core.Cache .Select(x => x.Key) .ToArray()) // ToArray required to remove MemoryCache.Remove(key); - } + } } public virtual void ClearCacheByKeyExpression(string regexString) @@ -149,7 +149,7 @@ namespace Umbraco.Core.Cache .Select(x => x.Key) .ToArray()) // ToArray required to remove MemoryCache.Remove(key); - } + } } #endregion @@ -201,7 +201,7 @@ namespace Umbraco.Core.Cache return GetCacheItem(cacheKey, getCacheItem, null); } - public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal,CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { // see notes in HttpRuntimeCacheProvider @@ -264,7 +264,7 @@ namespace Umbraco.Core.Cache { policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependentFiles.ToList())); } - + if (removedCallback != null) { policy.RemovedCallback = arguments => @@ -295,6 +295,5 @@ namespace Umbraco.Core.Cache } return policy; } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 49bea68719..ed5265446f 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 "beta2"; } } + public static string CurrentComment { get { return "beta3"; } } // 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/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 998bda32f2..80f5a14662 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -2,10 +2,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Web; using AutoMapper; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.Mapping; @@ -39,15 +42,15 @@ namespace Umbraco.Core /// public class CoreBootManager : IBootManager { - private ProfilingLogger _profilingLogger; + protected ProfilingLogger ProfilingLogger { get; private set; } private DisposableTimer _timer; private bool _isInitialized = false; private bool _isStarted = false; private bool _isComplete = false; private readonly IServiceProvider _serviceProvider = new ActivatorServiceProvider(); private readonly UmbracoApplicationBase _umbracoApplication; - protected ApplicationContext ApplicationContext { get; set; } - protected CacheHelper ApplicationCache { get; set; } + protected ApplicationContext ApplicationContext { get; private set; } + protected CacheHelper ApplicationCache { get; private set; } protected PluginManager PluginManager { get; private set; } protected UmbracoApplicationBase UmbracoApplication @@ -66,6 +69,14 @@ namespace Umbraco.Core _umbracoApplication = umbracoApplication; } + internal CoreBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger) + { + if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); + if (logger == null) throw new ArgumentNullException("logger"); + _umbracoApplication = umbracoApplication; + ProfilingLogger = logger; + } + public virtual IBootManager Initialize() { if (_isInitialized) @@ -74,17 +85,17 @@ namespace Umbraco.Core InitializeLoggerResolver(); InitializeProfilerResolver(); - _profilingLogger = new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler); + ProfilingLogger = ProfilingLogger?? new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler); - _timer = _profilingLogger.TraceDuration( - string.Format("Umbraco application ({0}) starting", UmbracoVersion.GetSemanticVersion().ToSemanticString()), + _timer = ProfilingLogger.TraceDuration( + string.Format("Umbraco {0} application starting on {1}", UmbracoVersion.GetSemanticVersion().ToSemanticString(), NetworkHelper.MachineName), "Umbraco application startup complete"); - CreateApplicationCache(); + ApplicationCache = CreateApplicationCache(); //create and set the plugin manager (I'd much prefer to not use this singleton anymore but many things are using it unfortunately and // the way that it is setup, there must only ever be one per app so without IoC it would be hard to make this not a singleton) - PluginManager = new PluginManager(ServiceProvider, ApplicationCache.RuntimeCache, _profilingLogger); + PluginManager = new PluginManager(ServiceProvider, ApplicationCache.RuntimeCache, ProfilingLogger); PluginManager.Current = PluginManager; //Create the legacy prop-eds mapping @@ -92,38 +103,48 @@ namespace Umbraco.Core LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors(); //create database and service contexts for the app context - var dbFactory = new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, LoggerResolver.Current.Logger); + var dbFactory = new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, ProfilingLogger.Logger); Database.Mapper = new PetaPocoMapper(); var dbContext = new DatabaseContext( dbFactory, - LoggerResolver.Current.Logger, - SqlSyntaxProviders.CreateDefault(LoggerResolver.Current.Logger)); + ProfilingLogger.Logger, + SqlSyntaxProviders.CreateDefault(ProfilingLogger.Logger)); //initialize the DatabaseContext dbContext.Initialize(); - - var serviceContext = new ServiceContext( - new RepositoryFactory(ApplicationCache, LoggerResolver.Current.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), - new PetaPocoUnitOfWorkProvider(dbFactory), - new FileUnitOfWorkProvider(), - new PublishingStrategy(), - ApplicationCache, - LoggerResolver.Current.Logger); - CreateApplicationContext(dbContext, serviceContext); + //get the service context + var serviceContext = CreateServiceContext(dbContext, dbFactory); + + //set property and singleton from response + ApplicationContext.Current = ApplicationContext = CreateApplicationContext(dbContext, serviceContext); InitializeApplicationEventsResolver(); InitializeResolvers(); - - InitializeModelMappers(); - //now we need to call the initialize methods - ApplicationEventsResolver.Current.ApplicationEventHandlers - .ForEach(x => x.OnApplicationInitialized(UmbracoApplication, ApplicationContext)); + using (ProfilingLogger.DebugDuration( + string.Format("Executing {0} IApplicationEventHandler.OnApplicationInitialized", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()), + "Finished executing IApplicationEventHandler.OnApplicationInitialized")) + { + //now we need to call the initialize methods + ApplicationEventsResolver.Current.ApplicationEventHandlers + .ForEach(x => + { + try + { + x.OnApplicationInitialized(UmbracoApplication, ApplicationContext); + } + catch (Exception ex) + { + ProfilingLogger.Logger.Error("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex); + throw; + } + }); + } _isInitialized = true; @@ -131,28 +152,48 @@ namespace Umbraco.Core } /// - /// Creates and assigns the application context singleton + /// Creates and returns the service context for the app /// /// - /// - protected virtual void CreateApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext) + /// + /// + protected virtual ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory) { - //create the ApplicationContext - ApplicationContext = ApplicationContext.Current = new ApplicationContext(dbContext, serviceContext, ApplicationCache, _profilingLogger); + //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), + ApplicationCache, + ProfilingLogger.Logger, + msgFactory); } /// - /// Creates and assigns the ApplicationCache based on a new instance of System.Web.Caching.Cache + /// Creates and returns the application context for the app /// - protected virtual void CreateApplicationCache() + /// + /// + protected virtual ApplicationContext CreateApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext) + { + //create the ApplicationContext + return new ApplicationContext(dbContext, serviceContext, ApplicationCache, ProfilingLogger); + } + + /// + /// Creates and returns the CacheHelper for the app + /// + protected virtual CacheHelper CreateApplicationCache() { var cacheHelper = new CacheHelper( - new ObjectCacheRuntimeCacheProvider(), - new StaticCacheProvider(), + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), //we have no request based cache when not running in web-based context - new NullCacheProvider()); + new NullCacheProvider()); - ApplicationCache = cacheHelper; + return cacheHelper; } /// @@ -178,7 +219,7 @@ namespace Umbraco.Core /// protected virtual void InitializeLoggerResolver() { - LoggerResolver.Current = new LoggerResolver(Logger.CreateWithDefaultLog4NetConfiguration()) + LoggerResolver.Current = new LoggerResolver(ProfilingLogger == null ? Logger.CreateWithDefaultLog4NetConfiguration() : ProfilingLogger.Logger) { //This is another special resolver that needs to be resolvable before resolution is frozen //since it is used for profiling the application startup @@ -192,7 +233,7 @@ namespace Umbraco.Core protected virtual void InitializeProfilerResolver() { //By default we'll initialize the Log profiler (in the web project, we'll override with the web profiler) - ProfilerResolver.Current = new ProfilerResolver(new LogProfiler(LoggerResolver.Current.Logger)) + ProfilerResolver.Current = new ProfilerResolver(ProfilingLogger == null ? new LogProfiler(LoggerResolver.Current.Logger) : ProfilingLogger.Profiler) { //This is another special resolver that needs to be resolvable before resolution is frozen //since it is used for profiling the application startup @@ -213,7 +254,7 @@ namespace Umbraco.Core //... and set the special flag to let us resolve before frozen resolution ApplicationEventsResolver.Current = new ApplicationEventsResolver( ServiceProvider, - LoggerResolver.Current.Logger, + ProfilingLogger.Logger, PluginManager.ResolveApplicationStartupHandlers()) { CanResolveBeforeFrozen = true @@ -228,7 +269,7 @@ namespace Umbraco.Core /// Absolute protected virtual void InitializeApplicationRootPath(string rootPath) { - Umbraco.Core.IO.IOHelper.SetRootDirectory(rootPath); + IOHelper.SetRootDirectory(rootPath); } /// @@ -242,11 +283,27 @@ namespace Umbraco.Core if (_isStarted) throw new InvalidOperationException("The boot manager has already been initialized"); - //call OnApplicationStarting of each application events handler - ApplicationEventsResolver.Current.ApplicationEventHandlers - .ForEach(x => x.OnApplicationStarting(UmbracoApplication, ApplicationContext)); + using (ProfilingLogger.DebugDuration( + string.Format("Executing {0} IApplicationEventHandler.OnApplicationStarting", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()), + "Finished executing IApplicationEventHandler.OnApplicationStarting")) + { + //call OnApplicationStarting of each application events handler + ApplicationEventsResolver.Current.ApplicationEventHandlers + .ForEach(x => + { + try + { + x.OnApplicationStarting(UmbracoApplication, ApplicationContext); + } + catch (Exception ex) + { + ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex); + throw; + } + }); + } - if (afterStartup != null) + if (afterStartup != null) { afterStartup(ApplicationContext.Current); } @@ -265,12 +322,37 @@ namespace Umbraco.Core { if (_isComplete) throw new InvalidOperationException("The boot manager has already been completed"); - + FreezeResolution(); - //call OnApplicationStarting of each application events handler - ApplicationEventsResolver.Current.ApplicationEventHandlers - .ForEach(x => x.OnApplicationStarted(UmbracoApplication, ApplicationContext)); + //Here we need to make sure the db can be connected to + EnsureDatabaseConnection(); + + + //This is a special case for the user service, we need to tell it if it's an upgrade, if so we need to ensure that + // exceptions are bubbled up if a user is attempted to be persisted during an upgrade (i.e. when they auth to login) + ((UserService) ApplicationContext.Services.UserService).IsUpgrading = true; + + + using (ProfilingLogger.DebugDuration( + string.Format("Executing {0} IApplicationEventHandler.OnApplicationStarted", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()), + "Finished executing IApplicationEventHandler.OnApplicationStarted")) + { + //call OnApplicationStarting of each application events handler + ApplicationEventsResolver.Current.ApplicationEventHandlers + .ForEach(x => + { + try + { + x.OnApplicationStarted(UmbracoApplication, ApplicationContext); + } + catch (Exception ex) + { + ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex); + throw; + } + }); + } //Now, startup all of our legacy startup handler ApplicationEventsResolver.Current.InstantiateLegacyStartupHandlers(); @@ -288,6 +370,32 @@ namespace Umbraco.Core //stop the timer and log the output _timer.Dispose(); return this; + } + + /// + /// We cannot continue if the db cannot be connected to + /// + private void EnsureDatabaseConnection() + { + if (ApplicationContext.IsConfigured == false) return; + if (ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) return; + + var currentTry = 0; + while (currentTry < 5) + { + if (ApplicationContext.DatabaseContext.CanConnect) + break; + + //wait and retry + Thread.Sleep(1000); + currentTry++; + } + + if (currentTry == 5) + { + throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database."); + } + } /// @@ -307,12 +415,12 @@ namespace Umbraco.Core ApplicationCache.RuntimeCache, new ManifestParser(new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), ApplicationCache.RuntimeCache)); - PropertyEditorResolver.Current = new PropertyEditorResolver(ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolvePropertyEditors(), builder); - ParameterEditorResolver.Current = new ParameterEditorResolver(ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolveParameterEditors(), builder); + PropertyEditorResolver.Current = new PropertyEditorResolver(ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolvePropertyEditors(), builder); + ParameterEditorResolver.Current = new ParameterEditorResolver(ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveParameterEditors(), builder); //setup the validators resolver with our predefined validators ValidatorsResolver.Current = new ValidatorsResolver( - ServiceProvider, LoggerResolver.Current.Logger, new[] + ServiceProvider, ProfilingLogger.Logger, new[] { new Lazy(() => typeof (RequiredManifestValueValidator)), new Lazy(() => typeof (RegexValidator)), @@ -321,67 +429,76 @@ namespace Umbraco.Core new Lazy(() => typeof (IntegerValidator)), }); - //by default we'll use the standard configuration based sync - ServerRegistrarResolver.Current = new ServerRegistrarResolver( - new ConfigServerRegistrar()); + //by default we'll use the db server registrar unless the developer has the legacy + // dist calls enabled, in which case we'll use the config server registrar + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) + { + ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar()); + } + else + { + ServerRegistrarResolver.Current = new ServerRegistrarResolver( + new DatabaseServerRegistrar( + new Lazy(() => ApplicationContext.Services.ServerRegistrationService), + new DatabaseServerRegistrarOptions())); + } + - //by default (outside of the web) we'll use the default server messenger without - //supplying a username/password, this will automatically disable distributed calls - // .. we'll override this in the WebBootManager + //by default we'll use the database server messenger with default options (no callbacks), + // this will be overridden in the web startup ServerMessengerResolver.Current = new ServerMessengerResolver( - new WebServiceServerMessenger()); + new DatabaseServerMessenger(ApplicationContext, true, new DatabaseServerMessengerOptions())); MappingResolver.Current = new MappingResolver( - ServiceProvider, LoggerResolver.Current.Logger, + ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveAssignedMapperTypes()); - //RepositoryResolver.Current = new RepositoryResolver( // new RepositoryFactory(ApplicationCache)); CacheRefreshersResolver.Current = new CacheRefreshersResolver( - ServiceProvider, LoggerResolver.Current.Logger, + ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveCacheRefreshers()); DataTypesResolver.Current = new DataTypesResolver( - ServiceProvider, LoggerResolver.Current.Logger, + ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveDataTypes()); MacroFieldEditorsResolver.Current = new MacroFieldEditorsResolver( - ServiceProvider, LoggerResolver.Current.Logger, + ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveMacroRenderings()); PackageActionsResolver.Current = new PackageActionsResolver( - ServiceProvider, LoggerResolver.Current.Logger, + ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolvePackageActions()); ActionsResolver.Current = new ActionsResolver( - ServiceProvider, LoggerResolver.Current.Logger, + ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveActions()); //the database migration objects MigrationResolver.Current = new MigrationResolver( - LoggerResolver.Current.Logger, + ProfilingLogger.Logger, () => PluginManager.ResolveTypes()); // todo: remove once we drop IPropertyEditorValueConverter support. PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( - ServiceProvider, LoggerResolver.Current.Logger, + ServiceProvider, ProfilingLogger.Logger, PluginManager.ResolvePropertyEditorValueConverters()); // need to filter out the ones we dont want!! PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver( - ServiceProvider, LoggerResolver.Current.Logger, + ServiceProvider, ProfilingLogger.Logger, PluginManager.ResolveTypes()); // use the new DefaultShortStringHelper ShortStringHelperResolver.Current = new ShortStringHelperResolver( //new LegacyShortStringHelper()); - new DefaultShortStringHelper().WithDefaultConfig()); + new DefaultShortStringHelper(UmbracoConfig.For.UmbracoSettings()).WithDefaultConfig()); UrlSegmentProviderResolver.Current = new UrlSegmentProviderResolver( - ServiceProvider, LoggerResolver.Current.Logger, + ServiceProvider, ProfilingLogger.Logger, typeof(DefaultUrlSegmentProvider)); // by default, no factory is activated diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index f3665ae0b8..7f8cbbf0f7 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -30,8 +30,6 @@ namespace Umbraco.Core private readonly ILogger _logger; private readonly SqlSyntaxProviders _syntaxProviders; private bool _configured; - private bool _canConnect; - private volatile bool _connectCheck = false; private readonly object _locker = new object(); private string _connectionString; private string _providerName; @@ -113,21 +111,9 @@ namespace Umbraco.Core get { if (IsDatabaseConfigured == false) return false; - - //double check lock so that it is only checked once and is fast - if (_connectCheck == false) - { - lock (_locker) - { - if (_canConnect == false) - { - _canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider); - _connectCheck = true; - } - } - } - - return _canConnect; + var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider); + LogHelper.Info("CanConnect = " + canConnect); + return canConnect; } } diff --git a/src/Umbraco.Core/DisposableObject.cs b/src/Umbraco.Core/DisposableObject.cs index f054b53c98..516a9712e5 100644 --- a/src/Umbraco.Core/DisposableObject.cs +++ b/src/Umbraco.Core/DisposableObject.cs @@ -4,69 +4,58 @@ using System.Threading; namespace Umbraco.Core { /// - /// Abstract implementation of logic commonly required to safely handle disposable unmanaged resources. + /// Abstract implementation of IDisposable. /// + /// + /// Can also be used as a pattern for when inheriting is not possible. + /// + /// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx + /// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ + /// + /// Note: if an object's ctor throws, it will never be disposed, and so if that ctor + /// has allocated disposable objects, it should take care of disposing them. + /// public abstract class DisposableObject : IDisposable { private bool _disposed; - private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim(); + private readonly object _locko = new object(); - /// - /// Gets a value indicating whether this instance is disposed. - /// - /// - /// true if this instance is disposed; otherwise, false. - /// - public bool IsDisposed - { - get { return _disposed; } - } + // gets a value indicating whether this instance is disposed. + // for internal tests only (not thread safe) + //TODO make this internal + rename "Disposed" when we can break compatibility + public bool IsDisposed { get { return _disposed; } } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 + // implements IDisposable public void Dispose() { Dispose(true); - - // Use SupressFinalize in case a subclass of this type implements a finalizer. GC.SuppressFinalize(this); } + // finalizer ~DisposableObject() { - // Run dispose but let the class know it was due to the finalizer running. Dispose(false); } - protected virtual void Dispose(bool disposing) + //TODO make this private, non-virtual when we can break compatibility + protected virtual void Dispose(bool disposing) { - // Only operate if we haven't already disposed - if (IsDisposed || !disposing) return; + lock (_locko) + { + if (_disposed) return; + _disposed = true; + } - using (new WriteLock(_disposalLocker)) - { - // Check again now we're inside the lock - if (IsDisposed) return; + DisposeUnmanagedResources(); - // Call to actually release resources. This method is only - // kept separate so that the entire disposal logic can be used as a VS snippet - DisposeResources(); - - // Indicate that the instance has been disposed. - _disposed = true; - } + if (disposing) + DisposeResources(); } - /// - /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. - /// protected abstract void DisposeResources(); protected virtual void DisposeUnmanagedResources() - { - - } + { } } } \ No newline at end of file diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index 79b48af3ab..58d4d453b7 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -101,6 +101,7 @@ namespace Umbraco.Core /// The select child. /// Item type /// list of TItem + [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) { return e.SelectMany(c => f(c).FlattenList(f)).Concat(e); diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs index 80e69ae353..506ba1c22e 100644 --- a/src/Umbraco.Core/Events/CancellableEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs @@ -3,7 +3,7 @@ using System.Security.Permissions; namespace Umbraco.Core.Events { - /// + /// /// Event args for that can support cancellation /// [HostProtection(SecurityAction.LinkDemand, SharedState = true)] @@ -11,15 +11,30 @@ namespace Umbraco.Core.Events { private bool _cancel; - public CancellableEventArgs(bool canCancel) + public CancellableEventArgs(bool canCancel, EventMessages eventMessages) + { + if (eventMessages == null) throw new ArgumentNullException("eventMessages"); + CanCancel = canCancel; + Messages = eventMessages; + } + + public CancellableEventArgs(bool canCancel) { CanCancel = canCancel; - } + //create a standalone messages + Messages = new EventMessages(); + } - public CancellableEventArgs() + public CancellableEventArgs(EventMessages eventMessages) + : this(true, eventMessages) + { + } + + public CancellableEventArgs() : this(true) { } + /// /// Flag to determine if this instance will support being cancellable /// @@ -32,7 +47,7 @@ namespace Umbraco.Core.Events { get { - if (!CanCancel) + if (CanCancel == false) { throw new InvalidOperationException("This event argument class does not support cancelling."); } @@ -40,12 +55,28 @@ namespace Umbraco.Core.Events } set { - if (!CanCancel) + if (CanCancel == false) { throw new InvalidOperationException("This event argument class does not support cancelling."); } _cancel = value; } } - } + + /// + /// if this instance supports cancellation, this will set Cancel to true with an affiliated cancellation message + /// + /// + public void CancelOperation(EventMessage cancelationMessage) + { + Cancel = true; + cancelationMessage.IsDefaultEventMessage = true; + Messages.Add(cancelationMessage); + } + + /// + /// Returns the EventMessages object which is used to add messages to the message collection for this event + /// + public EventMessages Messages { get; private set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs index a51da5652e..726d32d7b0 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs @@ -12,7 +12,18 @@ namespace Umbraco.Core.Events public class CancellableObjectEventArgs : CancellableEventArgs { - public CancellableObjectEventArgs(T eventObject, bool canCancel) + public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages) + : base(canCancel, eventMessages) + { + EventObject = eventObject; + } + + public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages) + : this(eventObject, true, eventMessages) + { + } + + public CancellableObjectEventArgs(T eventObject, bool canCancel) : base(canCancel) { EventObject = eventObject; diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index 4edee846cf..1025066bcc 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -4,12 +4,56 @@ namespace Umbraco.Core.Events { public class DeleteEventArgs : CancellableObjectEventArgs> { - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List { eventObject }, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) { MediaFilesToDelete = new List(); } @@ -60,7 +104,13 @@ namespace Umbraco.Core.Events public class DeleteEventArgs : CancellableEventArgs { - public DeleteEventArgs(int id, bool canCancel) + public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages) + : base(canCancel, eventMessages) + { + Id = id; + } + + public DeleteEventArgs(int id, bool canCancel) : base(canCancel) { Id = id; diff --git a/src/Umbraco.Core/Events/EventExtensions.cs b/src/Umbraco.Core/Events/EventExtensions.cs index 99cf145fe0..8c645aead6 100644 --- a/src/Umbraco.Core/Events/EventExtensions.cs +++ b/src/Umbraco.Core/Events/EventExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace Umbraco.Core.Events { @@ -7,16 +8,16 @@ 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( + /// + /// 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) @@ -27,16 +28,16 @@ namespace Umbraco.Core.Events return args.Cancel; } - - /// - /// Raises the event - /// - /// - /// - /// - /// - /// - public static void RaiseEvent( + + /// + /// Raises the event + /// + /// + /// + /// + /// + /// + public static void RaiseEvent( this TypedEventHandler eventHandler, TArgs args, TSender sender) diff --git a/src/Umbraco.Core/Events/EventMessage.cs b/src/Umbraco.Core/Events/EventMessage.cs new file mode 100644 index 0000000000..473f955d45 --- /dev/null +++ b/src/Umbraco.Core/Events/EventMessage.cs @@ -0,0 +1,27 @@ +namespace Umbraco.Core.Events +{ + /// + /// An event message + /// + public sealed class EventMessage + { + /// + /// Initializes a new instance of the class. + /// + public EventMessage(string category, string message, EventMessageType messageType = EventMessageType.Default) + { + Category = category; + Message = message; + MessageType = messageType; + } + + public string Category { get; private set; } + public string Message { get; private set; } + public EventMessageType MessageType { get; private set; } + + /// + /// This is used to track if this message should be used as a default message so that Umbraco doesn't also append it's own default messages + /// + internal bool IsDefaultEventMessage { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/EventMessages.cs b/src/Umbraco.Core/Events/EventMessages.cs new file mode 100644 index 0000000000..2900f3d471 --- /dev/null +++ b/src/Umbraco.Core/Events/EventMessages.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + /// + /// Event messages collection + /// + public sealed class EventMessages : DisposableObject + { + private readonly List _msgs = new List(); + + public void Add(EventMessage msg) + { + _msgs.Add(msg); + } + + public int Count + { + get { return _msgs.Count; } + } + + public IEnumerable GetAll() + { + return _msgs; + } + + protected override void DisposeResources() + { + _msgs.Clear(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/IEventMessagesFactory.cs b/src/Umbraco.Core/Events/IEventMessagesFactory.cs new file mode 100644 index 0000000000..cb2391186d --- /dev/null +++ b/src/Umbraco.Core/Events/IEventMessagesFactory.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + /// + /// Event messages factory + /// + public interface IEventMessagesFactory + { + EventMessages Get(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MessageType.cs b/src/Umbraco.Core/Events/MessageType.cs new file mode 100644 index 0000000000..6299a0e7d7 --- /dev/null +++ b/src/Umbraco.Core/Events/MessageType.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Events +{ + /// + /// The type of event message + /// + public enum EventMessageType + { + Default = 0, + Info = 1, + Error = 2, + Success = 3, + Warning = 4 + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MigrationEventArgs.cs b/src/Umbraco.Core/Events/MigrationEventArgs.cs index 61fa016332..7eed61484b 100644 --- a/src/Umbraco.Core/Events/MigrationEventArgs.cs +++ b/src/Umbraco.Core/Events/MigrationEventArgs.cs @@ -77,7 +77,7 @@ namespace Umbraco.Core.Events get { return EventObject; } } - [Obsolete("Use ConfiguredUmbracoVersion instead")] + [Obsolete("Use ConfiguredSemVersion instead")] [EditorBrowsable(EditorBrowsableState.Never)] public Version ConfiguredVersion { diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs index 107629ff19..0f0a5183a9 100644 --- a/src/Umbraco.Core/Events/MoveEventArgs.cs +++ b/src/Umbraco.Core/Events/MoveEventArgs.cs @@ -4,22 +4,51 @@ using System.Linq; namespace Umbraco.Core.Events { - public class MoveEventInfo - { - public MoveEventInfo(TEntity entity, string originalPath, int newParentId) - { - Entity = entity; - OriginalPath = originalPath; - NewParentId = newParentId; - } - - public TEntity Entity { get; set; } - public string OriginalPath { get; set; } - public int NewParentId { get; set; } - } - public class MoveEventArgs : CancellableObjectEventArgs { + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// + /// + /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(bool canCancel, EventMessages eventMessages, params MoveEventInfo[] moveInfo) + : base(default(TEntity), canCancel, eventMessages) + { + if (moveInfo.FirstOrDefault() == null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); + } + + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + ParentId = moveInfo.First().NewParentId; + } + + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// + /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(EventMessages eventMessages, params MoveEventInfo[] moveInfo) + : base(default(TEntity), eventMessages) + { + if (moveInfo.FirstOrDefault() == null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); + } + + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + ParentId = moveInfo.First().NewParentId; + } + /// /// Constructor accepting a collection of MoveEventInfo objects /// diff --git a/src/Umbraco.Core/Events/MoveEventInfo.cs b/src/Umbraco.Core/Events/MoveEventInfo.cs new file mode 100644 index 0000000000..a74db7f36e --- /dev/null +++ b/src/Umbraco.Core/Events/MoveEventInfo.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Core.Events +{ + public class MoveEventInfo + { + public MoveEventInfo(TEntity entity, string originalPath, int newParentId) + { + Entity = entity; + OriginalPath = originalPath; + NewParentId = newParentId; + } + + public TEntity Entity { get; set; } + public string OriginalPath { get; set; } + public int NewParentId { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/NewEventArgs.cs b/src/Umbraco.Core/Events/NewEventArgs.cs index 8e6cd64db1..acfd64e60d 100644 --- a/src/Umbraco.Core/Events/NewEventArgs.cs +++ b/src/Umbraco.Core/Events/NewEventArgs.cs @@ -4,7 +4,39 @@ namespace Umbraco.Core.Events { public class NewEventArgs : CancellableObjectEventArgs { - public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId) : base(eventObject, canCancel) + + + public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + Alias = alias; + ParentId = parentId; + } + + public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, TEntity parent, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + Alias = alias; + Parent = parent; + } + + public NewEventArgs(TEntity eventObject, string @alias, int parentId, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + Alias = alias; + ParentId = parentId; + } + + public NewEventArgs(TEntity eventObject, string @alias, TEntity parent, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + Alias = alias; + Parent = parent; + } + + + + public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId) : base(eventObject, canCancel) { Alias = alias; ParentId = parentId; diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs index 2355e250ef..a791781617 100644 --- a/src/Umbraco.Core/Events/PublishEventArgs.cs +++ b/src/Umbraco.Core/Events/PublishEventArgs.cs @@ -4,6 +4,52 @@ namespace Umbraco.Core.Events { public class PublishEventArgs : CancellableObjectEventArgs> { + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + /// + /// + /// + public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + IsAllRepublished = isAllPublished; + } + + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + /// + public PublishEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public PublishEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List { eventObject }, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + /// + public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) + { + IsAllRepublished = isAllPublished; + } + /// /// Constructor accepting multiple entities that are used in the publish operation /// diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs index d3ac8b22e5..b84e28285e 100644 --- a/src/Umbraco.Core/Events/SaveEventArgs.cs +++ b/src/Umbraco.Core/Events/SaveEventArgs.cs @@ -4,12 +4,55 @@ namespace Umbraco.Core.Events { public class SaveEventArgs : CancellableObjectEventArgs> { - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel) + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + } + + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public SaveEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List { eventObject }, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) + { + } + + + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) { } diff --git a/src/Umbraco.Core/Events/TransientMessagesFactory.cs b/src/Umbraco.Core/Events/TransientMessagesFactory.cs new file mode 100644 index 0000000000..5cd291a37f --- /dev/null +++ b/src/Umbraco.Core/Events/TransientMessagesFactory.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Events +{ + /// + /// A simple/default transient messages factory + /// + internal class TransientMessagesFactory : IEventMessagesFactory + { + public EventMessages Get() + { + return new EventMessages(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs b/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs new file mode 100644 index 0000000000..d27d38de9a --- /dev/null +++ b/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs @@ -0,0 +1,18 @@ +using System; + +namespace Umbraco.Core.Exceptions +{ + /// + /// An exception that is thrown if the umbraco application cannnot boot + /// + public class UmbracoStartupFailedException : Exception + { + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public UmbracoStartupFailedException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/xslt/Web.config b/src/Umbraco.Core/FileResources/BlockingWeb.config similarity index 96% rename from src/Umbraco.Web.UI/xslt/Web.config rename to src/Umbraco.Core/FileResources/BlockingWeb.config index fd6e3a816a..80182af9a1 100644 --- a/src/Umbraco.Web.UI/xslt/Web.config +++ b/src/Umbraco.Core/FileResources/BlockingWeb.config @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Core/FileResources/Files.Designer.cs b/src/Umbraco.Core/FileResources/Files.Designer.cs new file mode 100644 index 0000000000..456dae221f --- /dev/null +++ b/src/Umbraco.Core/FileResources/Files.Designer.cs @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Umbraco.Core.FileResources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Files { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Files() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Umbraco.Core.FileResources.Files", typeof(Files).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0"?> + /// + ///<!-- Blocks public downloading of anything in this folder and sub folders --> + /// + ///<configuration> + /// <system.web> + /// <httpHandlers> + /// <add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/> + /// </httpHandlers> + /// </system.web> + /// <system.webServer> + /// <validation validateIntegratedModeConfiguration="false" /> + /// <handlers> + /// <remove name="BlockViewHandler"/> + /// <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.H [rest of string was truncated]";. + /// + internal static string BlockingWebConfig { + get { + return ResourceManager.GetString("BlockingWebConfig", resourceCulture); + } + } + } +} diff --git a/src/Umbraco.Core/FileResources/Files.resx b/src/Umbraco.Core/FileResources/Files.resx new file mode 100644 index 0000000000..823aacefb7 --- /dev/null +++ b/src/Umbraco.Core/FileResources/Files.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + blockingweb.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + \ No newline at end of file diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index dc5e278970..2d87f634f4 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -6,6 +6,7 @@ using System.IO; using System.Configuration; using System.Web; using System.Text.RegularExpressions; +using System.Web.Hosting; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; @@ -51,7 +52,23 @@ namespace Umbraco.Core.IO return VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root); } - [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")] + public static Attempt TryResolveUrl(string virtualPath) + { + try + { + if (virtualPath.StartsWith("~")) + return Attempt.Succeed(virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/")); + if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) + return Attempt.Succeed(virtualPath); + return Attempt.Succeed(VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root)); + } + catch (Exception ex) + { + return Attempt.Fail(virtualPath, ex); + } + } + + [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")] internal static string ResolveUrlsFromTextString(string text) { if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString) @@ -67,7 +84,7 @@ namespace Umbraco.Core.IO if (tag.Groups[1].Success) url = tag.Groups[1].Value; - if (string.IsNullOrEmpty(url) == false) + if (String.IsNullOrEmpty(url) == false) { string resolvedUrl = (url.Substring(0, 1) == "/") ? ResolveUrl(url.Substring(1)) : ResolveUrl(url); text = text.Replace(url, resolvedUrl); @@ -92,10 +109,10 @@ namespace Umbraco.Core.IO if (useHttpContext && HttpContext.Current != null) { //string retval; - if (string.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root))) - return System.Web.Hosting.HostingEnvironment.MapPath(path); + if (String.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root))) + return HostingEnvironment.MapPath(path); else - return System.Web.Hosting.HostingEnvironment.MapPath("~/" + path.TrimStart('/')); + return HostingEnvironment.MapPath("~/" + path.TrimStart('/')); } var root = GetRootDirectorySafe(); @@ -115,7 +132,7 @@ namespace Umbraco.Core.IO { string retval = ConfigurationManager.AppSettings[settingsKey]; - if (string.IsNullOrEmpty(retval)) + if (String.IsNullOrEmpty(retval)) retval = standardPath; return retval.TrimEnd('/'); @@ -232,7 +249,7 @@ namespace Umbraco.Core.IO /// internal static string GetRootDirectorySafe() { - if (string.IsNullOrEmpty(_rootDir) == false) + if (String.IsNullOrEmpty(_rootDir) == false) { return _rootDir; } @@ -241,7 +258,7 @@ namespace Umbraco.Core.IO var uri = new Uri(codeBase); var path = uri.LocalPath; var baseDirectory = Path.GetDirectoryName(path); - if (string.IsNullOrEmpty(baseDirectory)) + if (String.IsNullOrEmpty(baseDirectory)) throw new Exception("No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured."); _rootDir = baseDirectory.Contains("bin") @@ -253,8 +270,8 @@ namespace Umbraco.Core.IO internal static string GetRootDirectoryBinFolder() { - string binFolder = string.Empty; - if (string.IsNullOrEmpty(_rootDir)) + string binFolder = String.Empty; + if (String.IsNullOrEmpty(_rootDir)) { binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory.FullName; return binFolder; @@ -298,5 +315,25 @@ namespace Umbraco.Core.IO // use string extensions return filePath.ToSafeFileName(); } + + public static void EnsurePathExists(string path) + { + var absolutePath = IOHelper.MapPath(path); + if (Directory.Exists(absolutePath) == false) + Directory.CreateDirectory(absolutePath); + } + + public static void EnsureFileExists(string path, string contents) + { + var absolutePath = IOHelper.MapPath(path); + if (File.Exists(absolutePath) == false) + { + using (var writer = File.CreateText(absolutePath)) + { + writer.Write(contents); + } + } + + } } } diff --git a/src/Umbraco.Core/Macros/MacroTagParser.cs b/src/Umbraco.Core/Macros/MacroTagParser.cs index 7da2e1d3fc..850e3845af 100644 --- a/src/Umbraco.Core/Macros/MacroTagParser.cs +++ b/src/Umbraco.Core/Macros/MacroTagParser.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Macros internal class MacroTagParser { private static readonly Regex MacroRteContent = new Regex(@"()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); - private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO(?:.+?)?macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO (?:.+?)?macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// /// This formats the persisted string to something useful for the rte so that the macro renders properly since we diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs new file mode 100644 index 0000000000..6f4a539194 --- /dev/null +++ b/src/Umbraco.Core/MainDom.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.MemoryMappedFiles; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Hosting; +using Umbraco.Core.Logging; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Core +{ + // represents the main domain + class MainDom : IRegisteredObject + { + #region Vars + + private readonly ILogger _logger; + + // our own lock for local consistency + private readonly object _locko = new object(); + + // async lock representing the main domain lock + private readonly AsyncLock _asyncLock; + private IDisposable _asyncLocker; + + // event wait handle used to notify current main domain that it should + // release the lock because a new domain wants to be the main domain + private readonly EventWaitHandle _signal; + + // indicates whether... + private volatile bool _isMainDom; // we are the main domain + private volatile bool _signaled; // we have been signaled + + // actions to run before releasing the main domain + private readonly SortedList _callbacks = new SortedList(); + + private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds + + #endregion + + #region Ctor + + // initializes a new instance of MainDom + public MainDom(ILogger logger) + { + _logger = logger; + + var appId = string.Empty; + // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail + if (HostingEnvironment.ApplicationID != null) + appId = HostingEnvironment.ApplicationID.ReplaceNonAlphanumericChars(string.Empty); + + var lockName = "UMBRACO-" + appId + "-MAINDOM-LCK"; + _asyncLock = new AsyncLock(lockName); + + var eventName = "UMBRACO-" + appId + "-MAINDOM-EVT"; + _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + } + + #endregion + + // register a main domain consumer + public bool Register(Action release, int weight = 100) + { + return Register(null, release, weight); + } + + // register a main domain consumer + public bool Register(Action install, Action release, int weight = 100) + { + lock (_locko) + { + if (_signaled) return false; + if (install != null) + install(); + if (release != null) + _callbacks.Add(weight, release); + return true; + } + } + + // handles the signal requesting that the main domain is released + private void OnSignal(string source) + { + // once signaled, we stop waiting, but then there is the hosting environment + // so we have to make sure that we only enter that method once + + lock (_locko) + { + _logger.Debug("Signaled" + (_signaled ? " (again)" : "") + " (" + source + ")."); + if (_signaled) return; + if (_isMainDom == false) return; // probably not needed + _signaled = true; + } + + try + { + _logger.Debug("Stopping..."); + foreach (var callback in _callbacks.Values) + { + try + { + callback(); // no timeout on callbacks + } + catch (Exception e) + { + _logger.Error("Error while running callback, remaining callbacks will not run.", e); + throw; + } + + } + _logger.Debug("Stopped."); + } + finally + { + // in any case... + _isMainDom = false; + _asyncLocker.Dispose(); + _logger.Debug("Released MainDom."); + } + } + + // acquires the main domain + public bool Acquire() + { + lock (_locko) // we don't want the hosting environment to interfere by signaling + { + // if signaled, too late to acquire, give up + // the handler is not installed so that would be the hosting environment + if (_signaled) + { + _logger.Debug("Cannot acquire MainDom (signaled)."); + return false; + } + + _logger.Debug("Acquiring MainDom..."); + + // signal other instances that we want the lock, then wait one the lock, + // which may timeout, and this is accepted - see comments below + + // signal, then wait for the lock, then make sure the event is + // resetted (maybe there was noone listening..) + _signal.Set(); + + // if more than 1 instance reach that point, one will get the lock + // and the other one will timeout, which is accepted + + _asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds); + _isMainDom = true; + + // we need to reset the event, because otherwise we would end up + // signaling ourselves and commiting suicide immediately. + // only 1 instance can reach that point, but other instances may + // have started and be trying to get the lock - they will timeout, + // which is accepted + + _signal.Reset(); + _signal.WaitOneAsync() + .ContinueWith(_ => OnSignal("signal")); + + HostingEnvironment.RegisterObject(this); + + _logger.Debug("Acquired MainDom."); + return true; + } + } + + // gets a value indicating whether we are the main domain + public bool IsMainDom + { + get { return _isMainDom; } + } + + // IRegisteredObject + public void Stop(bool immediate) + { + try + { + OnSignal("environment"); // will run once + } + finally + { + HostingEnvironment.UnregisterObject(this); + } + } + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index aeb4da888c..571c916123 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -96,11 +96,15 @@ namespace Umbraco.Core.Manifest if (depth < 1) { - var dirs = currDir.GetDirectories(); var result = new List(); - foreach (var d in dirs) + if (currDir.Exists) { - result.AddRange(GetAllManifestFileContents(d)); + var dirs = currDir.GetDirectories(); + + foreach (var d in dirs) + { + result.AddRange(GetAllManifestFileContents(d)); + } } return result; } diff --git a/src/Umbraco.Core/Models/ContentTypeExtensions.cs b/src/Umbraco.Core/Models/ContentTypeExtensions.cs index df8c09b3d1..a47b430979 100644 --- a/src/Umbraco.Core/Models/ContentTypeExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeExtensions.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Models { var contentTypeService = ApplicationContext.Current.Services.ContentTypeService; var descendants = contentTypeService.GetContentTypeChildren(contentType.Id) - .FlattenList(type => contentTypeService.GetContentTypeChildren(type.Id)); + .SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id)); return descendants; } diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index 660d6674ca..eeacb771a9 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -101,6 +101,7 @@ namespace Umbraco.Core.Models.EntityBase /// the new api, which also needs to take effect in the legacy api. /// [IgnoreDataMember] + [Obsolete("Anytime there's a cancellable method it needs to return an Attempt so we know the outcome instead of this hack, not all services have been updated to use this though yet.")] internal bool WasCancelled { get { return _wasCancelled; } diff --git a/src/Umbraco.Core/Models/IDomain.cs b/src/Umbraco.Core/Models/IDomain.cs index 9fb3e8019d..b9f6bc65ec 100644 --- a/src/Umbraco.Core/Models/IDomain.cs +++ b/src/Umbraco.Core/Models/IDomain.cs @@ -4,9 +4,14 @@ namespace Umbraco.Core.Models { public interface IDomain : IAggregateRoot, IRememberBeingDirty, ICanBeDirty { - ILanguage Language { get; set; } + int? LanguageId { get; set; } string DomainName { get; set; } - IContent RootContent { get; set; } + int? RootContentId { get; set; } bool IsWildcard { get; } + + /// + /// Readonly value of the language ISO code for the domain + /// + string LanguageIsoCode { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DomainDto.cs b/src/Umbraco.Core/Models/Rdbms/DomainDto.cs index bd0bbc859c..e43c1bdeae 100644 --- a/src/Umbraco.Core/Models/Rdbms/DomainDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DomainDto.cs @@ -23,5 +23,11 @@ namespace Umbraco.Core.Models.Rdbms [Column("domainName")] public string DomainName { get; set; } + + /// + /// Used for a result on the query to get the associated language for a domain if there is one + /// + [ResultColumn("languageISOCode")] + public string IsoCode { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index 7381da0930..586c5915b2 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -33,11 +33,11 @@ namespace Umbraco.Core.Models //re-parse it so we can check what properties are different and adjust the event handlers var parsed = StylesheetHelper.ParseRules(Content).ToArray(); var names = parsed.Select(x => x.Name).ToArray(); - var existing = _properties.Value.Where(x => names.Contains(x.Name)).ToArray(); + var existing = _properties.Value.Where(x => names.InvariantContains(x.Name)).ToArray(); //update existing foreach (var stylesheetProperty in existing) { - var updateFrom = parsed.Single(x => x.Name == stylesheetProperty.Name); + var updateFrom = parsed.Single(x => x.Name.InvariantEquals(stylesheetProperty.Name)); //remove current event handler while we update, we'll reset it after stylesheetProperty.PropertyChanged -= Property_PropertyChanged; stylesheetProperty.Alias = updateFrom.Selector; @@ -46,14 +46,14 @@ namespace Umbraco.Core.Models stylesheetProperty.PropertyChanged += Property_PropertyChanged; } //remove no longer existing - var nonExisting = _properties.Value.Where(x => names.Contains(x.Name) == false).ToArray(); + var nonExisting = _properties.Value.Where(x => names.InvariantContains(x.Name) == false).ToArray(); foreach (var stylesheetProperty in nonExisting) { stylesheetProperty.PropertyChanged -= Property_PropertyChanged; _properties.Value.Remove(stylesheetProperty); } //add new ones - var newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).Contains(x.Name) == false); + var newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).InvariantContains(x.Name) == false); foreach (var stylesheetRule in newItems) { var prop = new StylesheetProperty(stylesheetRule.Name, stylesheetRule.Selector, stylesheetRule.Styles); @@ -128,7 +128,7 @@ namespace Umbraco.Core.Models /// public void AddProperty(StylesheetProperty property) { - if (Properties.Any(x => x.Name == property.Name)) + if (Properties.Any(x => x.Name.InvariantEquals(property.Name))) { throw new DuplicateNameException("The property with the name " + property.Name + " already exists in the collection"); } @@ -151,7 +151,7 @@ namespace Umbraco.Core.Models /// public void RemoveProperty(string name) { - if (Properties.Any(x => x.Name == name)) + if (Properties.Any(x => x.Name.InvariantEquals(name))) { Content = StylesheetHelper.ReplaceRule(Content, name, null); } diff --git a/src/Umbraco.Core/Models/UmbracoDomain.cs b/src/Umbraco.Core/Models/UmbracoDomain.cs index 963535729c..943f96c9f9 100644 --- a/src/Umbraco.Core/Models/UmbracoDomain.cs +++ b/src/Umbraco.Core/Models/UmbracoDomain.cs @@ -5,8 +5,6 @@ using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { - //TODO: Need to custom serialize this - [Serializable] [DataContract(IsReference = true)] public class UmbracoDomain : Entity, IDomain @@ -16,25 +14,32 @@ namespace Umbraco.Core.Models _domainName = domainName; } - private IContent _content; - private ILanguage _language; + public UmbracoDomain(string domainName, string languageIsoCode) + : this(domainName) + { + LanguageIsoCode = languageIsoCode; + } + + private int? _contentId; + private int? _languageId; private string _domainName; - private static readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContentId); + private static readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.LanguageId); private static readonly PropertyInfo DomainNameSelector = ExpressionHelper.GetPropertyInfo(x => x.DomainName); - private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContent); + [DataMember] - public ILanguage Language + public int? LanguageId { - get { return _language; } + get { return _languageId; } set { SetPropertyValueAndDetectChanges(o => { - _language = value; - return _language; - }, _language, DefaultLanguageSelector); + _languageId = value; + return _languageId; + }, _languageId, DefaultLanguageSelector); } } @@ -53,16 +58,16 @@ namespace Umbraco.Core.Models } [DataMember] - public IContent RootContent + public int? RootContentId { - get { return _content; } + get { return _contentId; } set { SetPropertyValueAndDetectChanges(o => { - _content = value; - return _content; - }, _content, ContentSelector); + _contentId = value; + return _contentId; + }, _contentId, ContentSelector); } } @@ -70,5 +75,10 @@ namespace Umbraco.Core.Models { get { return string.IsNullOrWhiteSpace(DomainName) || DomainName.StartsWith("*"); } } + + /// + /// Readonly value of the language ISO code for the domain + /// + public string LanguageIsoCode { get; internal set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index 1b55a89a9c..78c8f483f2 100644 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -29,6 +29,7 @@ namespace Umbraco.Core.ObjectResolution { //create the legacy resolver and only include the legacy types _legacyResolver = new LegacyStartupHandlerResolver( + serviceProvider, logger, applicationEventHandlers.Where(x => !TypeHelper.IsTypeAssignableFrom(x))); } @@ -70,8 +71,8 @@ namespace Umbraco.Core.ObjectResolution private class LegacyStartupHandlerResolver : ManyObjectsResolverBase, IDisposable { - internal LegacyStartupHandlerResolver(IEnumerable legacyStartupHandlers) - : base(legacyStartupHandlers) + internal LegacyStartupHandlerResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable legacyStartupHandlers) + : base(serviceProvider, logger, legacyStartupHandlers) { } diff --git a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs index c4ca875eb2..5145ec95c5 100644 --- a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs +++ b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs @@ -7,6 +7,7 @@ using System.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core.Logging; namespace Umbraco.Core.Persistence { @@ -75,8 +76,10 @@ namespace Umbraco.Core.Persistence connection.Open(); connection.Close(); } - catch (DbException) + 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); return false; } diff --git a/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs index 63ae627c62..bc38f72dab 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs @@ -5,7 +5,8 @@ using Umbraco.Core.Persistence.Repositories; namespace Umbraco.Core.Persistence.Mappers { - [MapperFor(typeof(DomainRepository.CacheableDomain))] + [MapperFor(typeof(IDomain))] + [MapperFor(typeof(UmbracoDomain))] public sealed class DomainMapper : BaseMapper { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); @@ -22,10 +23,10 @@ namespace Umbraco.Core.Persistence.Mappers internal override void BuildMap() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.RootContentId, dto => dto.RootStructureId); - CacheMap(src => src.DefaultLanguageId, dto => dto.DefaultLanguage); - CacheMap(src => src.DomainName, dto => dto.DomainName); + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.RootContentId, dto => dto.RootStructureId); + CacheMap(src => src.LanguageId, dto => dto.DefaultLanguage); + CacheMap(src => src.DomainName, dto => dto.DomainName); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs index 44757aed9a..00f968941d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs @@ -30,7 +30,8 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe Create.Table("umbracoCacheInstruction") .WithColumn("id").AsInt32().Identity().NotNullable() .WithColumn("utcStamp").AsDateTime().NotNullable() - .WithColumn("jsonInstruction").AsCustom(textType).NotNullable(); + .WithColumn("jsonInstruction").AsCustom(textType).NotNullable() + .WithColumn("originated").AsString(500).NotNullable(); Create.PrimaryKey("PK_umbracoCacheInstruction") .OnTable("umbracoCacheInstruction") diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 3395b38b3d..bb474a3440 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -820,12 +820,13 @@ namespace Umbraco.Core.Persistence.Repositories var contentTypes = _contentTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()) .ToArray(); + + var ids = dtos + .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + .Select(x => x.TemplateId.Value).ToArray(); + //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types - var templates = _templateRepository.GetAll( - dtos - .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - .Select(x => x.TemplateId.Value).ToArray()) - .ToArray(); + var templates = ids.Length == 0 ? Enumerable.Empty() : _templateRepository.GetAll(ids).ToArray(); var dtosWithContentTypes = dtos //This select into and null check are required because we don't have a foreign damn key on the contentType column diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index fd4577faa5..9ac905cf03 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -773,7 +773,7 @@ AND umbracoNode.id <> @id", var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, - ParentTypes.parentContentTypeId as chtParentId, + ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, umbracoNode.uniqueID as nUniqueId @@ -787,7 +787,12 @@ AND umbracoNode.id <> @id", ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId ) AllowedTypes ON AllowedTypes.Id = cmsContentType.nodeId - LEFT JOIN cmsContentType2ContentType as ParentTypes + LEFT JOIN ( + SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId + FROM cmsContentType2ContentType + INNER JOIN umbracoNode + ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" + ) ParentTypes ON ParentTypes.childContentTypeId = cmsContentType.nodeId WHERE (umbracoNode.nodeObjectType = @nodeObjectType)"; @@ -902,7 +907,7 @@ AND umbracoNode.id <> @id", cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, - ParentTypes.parentContentTypeId as chtParentId, + ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, umbracoNode.uniqueID as nUniqueId, @@ -925,7 +930,12 @@ AND umbracoNode.id <> @id", ON cmsTemplate.nodeId = umbracoNode.id ) as Template ON Template.nodeId = cmsDocumentType.templateNodeId - LEFT JOIN cmsContentType2ContentType as ParentTypes + LEFT JOIN ( + SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId + FROM cmsContentType2ContentType + INNER JOIN umbracoNode + ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" + ) ParentTypes ON ParentTypes.childContentTypeId = cmsContentType.nodeId WHERE (umbracoNode.nodeObjectType = @nodeObjectType)"; @@ -1103,7 +1113,14 @@ AND umbracoNode.id <> @id", ? x.ctId == currentCtId : x.nUniqueId == currentCtId; }) - .Select(x => (TId?)x.chtParentId) + .Select(x => + { + //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is + // how it is for now. + return (typeof(TId) == typeof(int)) + ? (TId?)x.chtParentId + : (TId?)x.chtParentKey; + }) .Where(x => x.HasValue) .Distinct() .Select(x => x.Value).ToList()); diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs index 1e84814d1b..1985875717 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -33,13 +33,16 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new {Id = id}) - .OrderBy(x => x.UniqueId); + .OrderBy(x => x.UniqueId, SqlSyntax); var dto = Database.Fetch(new DictionaryLanguageTextRelator().Map, sql).FirstOrDefault(); if (dto == null) return null; - var entity = ConvertFromDto(dto); + //This will be cached + var allLanguages = _languageRepository.GetAll().ToArray(); + + var entity = ConvertFromDto(dto, allLanguages); //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -56,8 +59,11 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("cmsDictionary.pk in (@ids)", new { ids = ids }); } + //This will be cached + var allLanguages = _languageRepository.GetAll().ToArray(); + return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(ConvertFromDto); + .Select(dto => ConvertFromDto(dto, allLanguages)); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -65,10 +71,13 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - sql.OrderBy(x => x.UniqueId); + sql.OrderBy(x => x.UniqueId, SqlSyntax); + + //This will be cached + var allLanguages = _languageRepository.GetAll().ToArray(); return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(ConvertFromDto); + .Select(x => ConvertFromDto(x, allLanguages)); } #endregion @@ -81,14 +90,14 @@ namespace Umbraco.Core.Persistence.Repositories if(isCount) { sql.Select("COUNT(*)") - .From(); + .From(SqlSyntax); } else { sql.Select("*") - .From() - .LeftJoin() - .On(left => left.UniqueId, right => right.UniqueId); + .From(SqlSyntax) + .LeftJoin(SqlSyntax) + .On(SqlSyntax, left => left.UniqueId, right => right.UniqueId); } return sql; } @@ -200,7 +209,7 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - protected IDictionaryItem ConvertFromDto(DictionaryDto dto) + protected IDictionaryItem ConvertFromDto(DictionaryDto dto, ILanguage[] allLanguages) { var factory = new DictionaryItemFactory(); var entity = factory.BuildEntity(dto); @@ -208,9 +217,11 @@ namespace Umbraco.Core.Persistence.Repositories var list = new List(); foreach (var textDto in dto.LanguageTextDtos) { - var language = _languageRepository.Get(textDto.LanguageId); + //Assuming this is cached! + var language = allLanguages.FirstOrDefault(x => x.Id == textDto.LanguageId); if (language == null) continue; + var translationFactory = new DictionaryTranslationFactory(dto.UniqueId, language); list.Add(translationFactory.BuildEntity(textDto)); } @@ -235,6 +246,47 @@ namespace Umbraco.Core.Persistence.Repositories } } + private IEnumerable GetRootDictionaryItems() + { + var query = Query.Builder.Where(x => x.ParentId == null); + return GetByQuery(query); + } + + public IEnumerable GetDictionaryItemDescendants(Guid? parentId) + { + //This will be cached + var allLanguages = _languageRepository.GetAll().ToArray(); + + //This methods will look up children at each level, since we do not store a path for dictionary (ATM), we need to do a recursive + // lookup to get descendants. Currently this is the most efficient way to do it + + Func>> getItemsFromParents = guids => + { + //needs to be in groups of 2000 because we are doing an IN clause and there's a max parameter count that can be used. + return guids.InGroupsOf(2000) + .Select(@group => + { + var sqlClause = GetBaseQuery(false) + .Where(x => x.Parent != null) + .Where(string.Format("{0} IN (@parentIds)", SqlSyntax.GetQuotedColumnName("parent")), new { parentIds = @group }); + + var translator = new SqlTranslator(sqlClause, Query.Builder); + var sql = translator.Translate(); + sql.OrderBy(x => x.UniqueId, SqlSyntax); + + return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) + .Select(x => ConvertFromDto(x, allLanguages)); + }); + }; + + var childItems = parentId.HasValue == false + ? new[] { GetRootDictionaryItems() } + : getItemsFromParents(new[] { parentId.Value }); + + return childItems.SelectRecursive(items => getItemsFromParents(items.Select(x => x.Key).ToArray())).SelectMany(items => items); + + } + private class DictionaryByUniqueIdRepository : SimpleGetRepository { private readonly DictionaryRepository _dictionaryRepository; @@ -262,7 +314,9 @@ namespace Umbraco.Core.Persistence.Repositories protected override IDictionaryItem ConvertToEntity(DictionaryDto dto) { - return _dictionaryRepository.ConvertFromDto(dto); + //This will be cached + var allLanguages = _dictionaryRepository._languageRepository.GetAll().ToArray(); + return _dictionaryRepository.ConvertFromDto(dto, allLanguages); } protected override object GetBaseWhereClauseArguments(Guid id) @@ -303,7 +357,9 @@ namespace Umbraco.Core.Persistence.Repositories protected override IDictionaryItem ConvertToEntity(DictionaryDto dto) { - return _dictionaryRepository.ConvertFromDto(dto); + //This will be cached + var allLanguages = _dictionaryRepository._languageRepository.GetAll().ToArray(); + return _dictionaryRepository.ConvertFromDto(dto, allLanguages); } protected override object GetBaseWhereClauseArguments(string id) diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs index 0dec93e25e..21ba8b4baf 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs @@ -7,53 +7,53 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.FaultHandling; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { + //TODO: We need to get a readonly ISO code for the domain assigned + internal class DomainRepository : PetaPocoRepositoryBase, IDomainRepository { - private readonly IContentRepository _contentRepository; - private readonly ILanguageRepository _languageRepository; + private readonly RepositoryCacheOptions _cacheOptions; - public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentRepository contentRepository, ILanguageRepository languageRepository) + public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { - _contentRepository = contentRepository; - _languageRepository = languageRepository; + //Custom cache options for better performance + _cacheOptions = new RepositoryCacheOptions + { + GetAllCacheAllowZeroCount = true, + GetAllCacheValidateCount = false + }; } /// - /// Override the cache, this repo will not perform any cache, the caching is taken care of in the inner repository + /// Returns the repository cache options /// - /// - /// This is required because IDomain is a deep object and we dont' want to cache it since it contains an ILanguage and an IContent, when these - /// are deep cloned the object graph that will be cached will be huge. Instead we'll have an internal repository that caches the simple - /// Domain structure and we'll use the other repositories to resolve the entities to attach - /// - protected override IRuntimeCacheProvider RuntimeCache + protected override RepositoryCacheOptions RepositoryCacheOptions { - get { return new NullCacheProvider(); } + get { return _cacheOptions; } } protected override IDomain PerformGet(int id) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) - { - var factory = new DomainModelFactory(); - return factory.BuildDomainEntity(repo.Get(id), _contentRepository, _languageRepository); - } + //use the underlying GetAll which will force cache all domains + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) + var sql = GetBaseQuery(false).Where("umbracoDomains.id > 0"); + if (ids.Any()) { - var factory = new DomainModelFactory(); - return factory.BuildDomainEntities(repo.GetAll(ids).ToArray(), _contentRepository, _languageRepository); + sql.Where("umbracoDomains.id in (@ids)", new { ids = ids }); } + + return Database.Fetch(sql).Select(ConvertFromDto); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -64,7 +64,18 @@ namespace Umbraco.Core.Persistence.Repositories protected override Sql GetBaseQuery(bool isCount) { var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*").From(SqlSyntax); + if (isCount) + { + sql.Select("COUNT(*)").From(SqlSyntax); + } + else + { + sql.Select("umbracoDomains.*, umbracoLanguage.languageISOCode") + .From(SqlSyntax) + .LeftJoin(SqlSyntax) + .On(SqlSyntax, dto => dto.DefaultLanguage, dto => dto.Id); + } + return sql; } @@ -73,18 +84,13 @@ namespace Umbraco.Core.Persistence.Repositories return "umbracoDomains.id = @Id"; } - protected override void PersistDeletedItem(IDomain entity) - { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) - { - var factory = new DomainModelFactory(); - repo.PersistDeletedItem(factory.BuildEntity(entity)); - } - } - protected override IEnumerable GetDeleteClauses() { - throw new NotImplementedException(); + var list = new List + { + "DELETE FROM umbracoDomains WHERE id = @Id" + }; + return list; } protected override Guid NodeObjectTypeId @@ -94,245 +100,112 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistNewItem(IDomain entity) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) + var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName", new { domainName = entity.DomainName }); + if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName)); + + if (entity.RootContentId.HasValue) { - var factory = new DomainModelFactory(); - var cacheableEntity = factory.BuildEntity(entity); - repo.PersistNewItem(cacheableEntity); - //re-map the id - entity.Id = cacheableEntity.Id; - entity.ResetDirtyProperties(); + var contentExists = Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContent WHERE nodeId = @id", new { id = entity.RootContentId.Value }); + if (contentExists == 0) throw new NullReferenceException("No content exists with id " + entity.RootContentId.Value); } + + if (entity.LanguageId.HasValue) + { + var languageExists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoLanguage WHERE id = @id", new { id = entity.LanguageId.Value }); + if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value); + } + + ((UmbracoDomain)entity).AddingEntity(); + + var factory = new DomainModelFactory(); + var dto = factory.BuildDto(entity); + + var id = Convert.ToInt32(Database.Insert(dto)); + entity.Id = id; + + //if the language changed, we need to resolve the ISO code! + if (entity.LanguageId.HasValue) + { + ((UmbracoDomain)entity).LanguageIsoCode = Database.ExecuteScalar("SELECT languageISOCode FROM umbracoLanguage WHERE id=@langId", new { langId = entity.LanguageId }); + } + + entity.ResetDirtyProperties(); } protected override void PersistUpdatedItem(IDomain entity) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) + ((UmbracoDomain)entity).UpdatingEntity(); + + var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName AND umbracoDomains.id <> @id", + new { domainName = entity.DomainName, id = entity.Id }); + //ensure there is no other domain with the same name on another entity + if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName)); + + if (entity.RootContentId.HasValue) { - var factory = new DomainModelFactory(); - repo.PersistUpdatedItem(factory.BuildEntity(entity)); - entity.ResetDirtyProperties(); + var contentExists = Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContent WHERE nodeId = @id", new { id = entity.RootContentId.Value }); + if (contentExists == 0) throw new NullReferenceException("No content exists with id " + entity.RootContentId.Value); } + + if (entity.LanguageId.HasValue) + { + var languageExists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoLanguage WHERE id = @id", new { id = entity.LanguageId.Value }); + if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value); + } + + var factory = new DomainModelFactory(); + var dto = factory.BuildDto(entity); + + Database.Update(dto); + + //if the language changed, we need to resolve the ISO code! + if (entity.WasPropertyDirty("LanguageId")) + { + ((UmbracoDomain)entity).LanguageIsoCode = Database.ExecuteScalar("SELECT languageISOCode FROM umbracoLanguage WHERE id=@langId", new {langId = entity.LanguageId}); + } + + entity.ResetDirtyProperties(); } public IDomain GetByName(string domainName) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) - { - var factory = new DomainModelFactory(); - return factory.BuildDomainEntity( - repo.GetByQuery(new Query().Where(x => x.DomainName.InvariantEquals(domainName))).FirstOrDefault(), - _contentRepository, _languageRepository); - } + return GetAll().FirstOrDefault(x => x.DomainName.InvariantEquals(domainName)); } public bool Exists(string domainName) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) - { - var query = new Query().Where(x => x.DomainName.InvariantEquals(domainName)); - return repo.GetByQuery(query).Any(); - } + return GetAll().Any(x => x.DomainName.InvariantEquals(domainName)); } public IEnumerable GetAll(bool includeWildcards) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) - { - var factory = new DomainModelFactory(); - return factory.BuildDomainEntities(repo.GetAll().ToArray(), _contentRepository, _languageRepository) - .Where(x => includeWildcards || x.IsWildcard == false); - } + return GetAll().Where(x => includeWildcards || x.IsWildcard == false); } public IEnumerable GetAssignedDomains(int contentId, bool includeWildcards) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) - { - var factory = new DomainModelFactory(); - - var query = new Query().Where(x => x.RootContentId == contentId); - - return factory.BuildDomainEntities(repo.GetByQuery(query).ToArray(), _contentRepository, _languageRepository) - .Where(x => includeWildcards || x.IsWildcard == false); - } + return GetAll() + .Where(x => x.RootContentId == contentId) + .Where(x => includeWildcards || x.IsWildcard == false); } - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() + private IDomain ConvertFromDto(DomainDto dto) { - _contentRepository.Dispose(); - _languageRepository.Dispose(); - } - - /// - /// A simple domain model that is cacheable without a large object graph - /// - internal class CacheableDomain : Entity, IAggregateRoot - { - public int? DefaultLanguageId { get; set; } - public string DomainName { get; set; } - public int? RootContentId { get; set; } - } - - /// - /// Inner repository responsible for CRUD for domains that allows caching simple data - /// - private class CachedDomainRepository : PetaPocoRepositoryBase - { - private readonly DomainRepository _domainRepo; - - public CachedDomainRepository(DomainRepository domainRepo, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - _domainRepo = domainRepo; - } - - protected override CacheableDomain PerformGet(int id) - { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - - var dto = Database.FirstOrDefault(sql); - if (dto == null) - return null; - - var entity = ConvertFromDto(dto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)entity).ResetDirtyProperties(false); - - return entity; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false).Where("umbracoDomains.id > 0"); - if (ids.Any()) - { - sql.Where("umbracoDomains.id in (@ids)", new { ids = ids }); - } - - return Database.Fetch(sql).Select(ConvertFromDto); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - return Database.Fetch(sql).Select(ConvertFromDto); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _domainRepo.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return _domainRepo.GetBaseWhereClause(); - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM umbracoDomains WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - - protected override void PersistNewItem(CacheableDomain entity) - { - var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName", new { domainName = entity.DomainName }); - if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName)); - - entity.AddingEntity(); - - var factory = new DomainModelFactory(); - var dto = factory.BuildDto(entity); - - var id = Convert.ToInt32(Database.Insert(dto)); - entity.Id = id; - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(CacheableDomain entity) - { - entity.UpdatingEntity(); - - var factory = new DomainModelFactory(); - var dto = factory.BuildDto(entity); - - Database.Update(dto); - - entity.ResetDirtyProperties(); - } - - private CacheableDomain ConvertFromDto(DomainDto dto) - { - var factory = new DomainModelFactory(); - var entity = factory.BuildEntity(dto); - return entity; - } - } + var factory = new DomainModelFactory(); + var entity = factory.BuildEntity(dto); + return entity; + } internal class DomainModelFactory { - public IEnumerable BuildDomainEntities(CacheableDomain[] cacheableDomains, IContentRepository contentRepository, ILanguageRepository languageRepository) + + public IDomain BuildEntity(DomainDto dto) { - var contentIds = cacheableDomains.Select(x => x.RootContentId).Where(x => x.HasValue).Select(x => x.Value).Distinct().ToArray(); - var langIds = cacheableDomains.Select(x => x.DefaultLanguageId).Where(x => x.HasValue).Select(x => x.Value).Distinct().ToArray(); - var contentItems = contentRepository.GetAll(contentIds); - var langItems = languageRepository.GetAll(langIds); - - return cacheableDomains - .WhereNotNull() - .Select(cacheableDomain => new UmbracoDomain(cacheableDomain.DomainName) - { - Id = cacheableDomain.Id, - //lookup from repo - this will be cached - Language = cacheableDomain.DefaultLanguageId.HasValue ? langItems.FirstOrDefault(l => l.Id == cacheableDomain.DefaultLanguageId.Value) : null, - //lookup from repo - this will be cached - RootContent = cacheableDomain.RootContentId.HasValue ? contentItems.FirstOrDefault(l => l.Id == cacheableDomain.RootContentId.Value) : null, - }); - } - - public IDomain BuildDomainEntity(CacheableDomain cacheableDomain, IContentRepository contentRepository, ILanguageRepository languageRepository) - { - if (cacheableDomain == null) return null; - - return new UmbracoDomain(cacheableDomain.DomainName) + var domain = new UmbracoDomain(dto.DomainName, dto.IsoCode) { - Id = cacheableDomain.Id, - //lookup from repo - this will be cached - Language = cacheableDomain.DefaultLanguageId.HasValue ? languageRepository.Get(cacheableDomain.DefaultLanguageId.Value) : null, - //lookup from repo - this will be cached - RootContent = cacheableDomain.RootContentId.HasValue ? contentRepository.Get(cacheableDomain.RootContentId.Value) : null - }; - } - - public CacheableDomain BuildEntity(IDomain entity) - { - var domain = new CacheableDomain - { - Id = entity.Id, - DefaultLanguageId = entity.Language == null ? null : (int?)entity.Language.Id, - DomainName = entity.DomainName, - RootContentId = entity.RootContent == null ? null : (int?)entity.RootContent.Id + Id = dto.Id, + LanguageId = dto.DefaultLanguage, + RootContentId = dto.RootStructureId }; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -340,18 +213,9 @@ namespace Umbraco.Core.Persistence.Repositories return domain; } - public CacheableDomain BuildEntity(DomainDto dto) + public DomainDto BuildDto(IDomain entity) { - var domain = new CacheableDomain { Id = dto.Id, DefaultLanguageId = dto.DefaultLanguage, DomainName = dto.DomainName, RootContentId = dto.RootStructureId }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - domain.ResetDirtyProperties(false); - return domain; - } - - public DomainDto BuildDto(CacheableDomain entity) - { - var dto = new DomainDto { DefaultLanguage = entity.DefaultLanguageId, DomainName = entity.DomainName, Id = entity.Id, RootStructureId = entity.RootContentId }; + var dto = new DomainDto { DefaultLanguage = entity.LanguageId, DomainName = entity.DomainName, Id = entity.Id, RootStructureId = entity.RootContentId }; return dto; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs index 3a0328294a..d030bcda2a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories @@ -7,5 +8,6 @@ namespace Umbraco.Core.Persistence.Repositories { IDictionaryItem Get(Guid uniqueId); IDictionaryItem Get(string key); + IEnumerable GetDictionaryItemDescendants(Guid? parentId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs index 993f350ce1..afd03f4273 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Umbraco.Core.Models; @@ -5,6 +6,10 @@ namespace Umbraco.Core.Persistence.Repositories { public interface ITagRepository : IRepositoryQueryable { + + TaggedEntity GetTaggedEntityByKey(Guid key); + TaggedEntity GetTaggedEntityById(int id); + IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup); IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null); @@ -25,6 +30,14 @@ namespace Umbraco.Core.Persistence.Repositories /// IEnumerable GetTagsForEntity(int contentId, string group = null); + /// + /// Returns all tags that exist on the content item - Content/Media/Member + /// + /// The content item id to get tags for + /// Optional group + /// + IEnumerable GetTagsForEntity(Guid contentId, string group = null); + /// /// Returns all tags that exist on the content item for the property specified - Content/Media/Member /// @@ -34,6 +47,15 @@ namespace Umbraco.Core.Persistence.Repositories /// IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null); + /// + /// Returns all tags that exist on the content item for the property specified - Content/Media/Member + /// + /// The content item id to get tags for + /// The property alias to get tags for + /// Optional group + /// + IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null); + /// /// Assigns the given tags to a content item's property /// diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index 9a789ac3f6..2f379b4587 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -21,26 +21,30 @@ namespace Umbraco.Core.Persistence.Repositories public LanguageRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { + //Custom cache options for better performance + _cacheOptions = new RepositoryCacheOptions + { + GetAllCacheAllowZeroCount = true, + GetAllCacheValidateCount = false + }; + } + + private readonly RepositoryCacheOptions _cacheOptions; + + /// + /// Returns the repository cache options + /// + protected override RepositoryCacheOptions RepositoryCacheOptions + { + get { return _cacheOptions; } } #region Overrides of RepositoryBase protected override ILanguage PerformGet(int id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - - var languageDto = Database.FirstOrDefault(sql); - if (languageDto == null) - return null; - - var entity = ConvertFromDto(languageDto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)entity).ResetDirtyProperties(false); - - return entity; + //use the underlying GetAll which will force cache all domains + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -155,94 +159,16 @@ namespace Umbraco.Core.Persistence.Repositories public ILanguage GetByCultureName(string cultureName) { - var cultureNameRepo = new LanguageByCultureNameRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax); - return cultureNameRepo.Get(cultureName); + //use the underlying GetAll which will force cache all domains + return GetAll().FirstOrDefault(x => x.CultureName.InvariantEquals(cultureName)); } public ILanguage GetByIsoCode(string isoCode) { - var isoRepo = new LanguageByIsoCodeRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax); - return isoRepo.Get(isoCode); + //use the underlying GetAll which will force cache all domains + return GetAll().FirstOrDefault(x => x.IsoCode.InvariantEquals(isoCode)); } - /// - /// Inner repository for looking up languages by ISO code, this deals with caching by a string key - /// - private class LanguageByIsoCodeRepository : SimpleGetRepository - { - private readonly LanguageRepository _languageRepository; - - public LanguageByIsoCodeRepository(LanguageRepository languageRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - _languageRepository = languageRepository; - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _languageRepository.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "umbracoLanguage.languageISOCode = @Id"; - } - - protected override ILanguage ConvertToEntity(LanguageDto dto) - { - var factory = new LanguageFactory(); - return factory.BuildEntity(dto); - } - - protected override object GetBaseWhereClauseArguments(string id) - { - return new {Id = id}; - } - - protected override string GetWhereInClauseForGetAll() - { - return "umbracoLanguage.languageISOCode in (@ids)"; - } - } - - /// - /// Inner repository for looking up languages by culture name, this deals with caching by a string key - /// - private class LanguageByCultureNameRepository : SimpleGetRepository - { - private readonly LanguageRepository _languageRepository; - - public LanguageByCultureNameRepository(LanguageRepository languageRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - _languageRepository = languageRepository; - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _languageRepository.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "umbracoLanguage.languageCultureName = @Id"; - } - - protected override ILanguage ConvertToEntity(LanguageDto dto) - { - var factory = new LanguageFactory(); - return factory.BuildEntity(dto); - } - - protected override object GetBaseWhereClauseArguments(string id) - { - return new {Id = id}; - } - - protected override string GetWhereInClauseForGetAll() - { - return "umbracoLanguage.languageCultureName in (@ids)"; - } - } + } } \ 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 4a9c110359..162ad88ca0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -468,10 +468,11 @@ namespace Umbraco.Core.Persistence.Repositories { //NOTE: This doesn't allow properties to be part of the query var dtos = Database.Fetch(sql); + + var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray(); //content types - var contentTypes = _mediaTypeRepository.GetAll(dtos.Select(x => x.ContentDto.ContentTypeId).ToArray()) - .ToArray(); + var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _mediaTypeRepository.GetAll(ids).ToArray(); var dtosWithContentTypes = dtos //This select into and null check are required because we don't have a foreign damn key on the contentType column diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 02f5fb10c1..ac5529b523 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -674,8 +674,10 @@ namespace Umbraco.Core.Persistence.Repositories //NOTE: This doesn't allow properties to be part of the query var dtos = Database.Fetch(sql); + var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray(); + //content types - var contentTypes = _memberTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()).ToArray(); + var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _memberTypeRepository.GetAll(ids).ToArray(); var dtosWithContentTypes = dtos //This select into and null check are required because we don't have a foreign damn key on the contentType column diff --git a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs index 63527c7fb9..a88672ea6f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs @@ -56,7 +56,7 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformGetAll(params TId[] ids) { - var sql = new Sql().From(); + var sql = new Sql().From(SqlSyntax); if (ids.Any()) { diff --git a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs index 14e32d6f9c..a701219ef4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs @@ -96,7 +96,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = new Sql(); if (isCount) { - sql.Select("COUNT(*)").From(); + sql.Select("COUNT(*)").From(SqlSyntax); } else { @@ -105,10 +105,10 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - private static Sql GetBaseQuery() + private Sql GetBaseQuery() { var sql = new Sql(); - sql.Select("*").From(); + sql.Select("*").From(SqlSyntax); return sql; } @@ -162,19 +162,55 @@ namespace Umbraco.Core.Persistence.Repositories //TODO: We need to add lookups for parentId or path! (i.e. get content in tag group that are descendants of x) + public TaggedEntity GetTaggedEntityByKey(Guid key) + { + var sql = new Sql() + .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group")) + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.TagId, right => right.Id) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .Where(dto => dto.UniqueId == key); + + return CreateTaggedEntityCollection(Database.Fetch(sql)).FirstOrDefault(); + } + + public TaggedEntity GetTaggedEntityById(int id) + { + var sql = new Sql() + .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group")) + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.TagId, right => right.Id) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeId == id); + + return CreateTaggedEntityCollection(Database.Fetch(sql)).FirstOrDefault(); + } + public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup) { var sql = new Sql() .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group")) - .From() - .InnerJoin() - .On(left => left.TagId, right => right.Id) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.Id, right => right.PropertyTypeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.TagId, right => right.Id) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) .Where(dto => dto.Group == tagGroup); if (objectType != TaggableObjectTypes.All) @@ -185,22 +221,22 @@ namespace Umbraco.Core.Persistence.Repositories } return CreateTaggedEntityCollection( - ApplicationContext.Current.DatabaseContext.Database.Fetch(sql)); + Database.Fetch(sql)); } public IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null) { var sql = new Sql() .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group")) - .From() - .InnerJoin() - .On(left => left.TagId, right => right.Id) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.Id, right => right.PropertyTypeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.TagId, right => right.Id) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) .Where(dto => dto.Tag == tag); if (objectType != TaggableObjectTypes.All) @@ -216,12 +252,11 @@ namespace Umbraco.Core.Persistence.Repositories } return CreateTaggedEntityCollection( - ApplicationContext.Current.DatabaseContext.Database.Fetch(sql)); + Database.Fetch(sql)); } private IEnumerable CreateTaggedEntityCollection(IEnumerable dbResult) { - var list = new List(); foreach (var node in dbResult.GroupBy(x => (int)x.nodeId)) { var properties = new List(); @@ -230,9 +265,8 @@ namespace Umbraco.Core.Persistence.Repositories var tags = propertyType.Select(x => new Tag((int)x.tagId, (string)x.tag, (string)x.group)); properties.Add(new TaggedProperty(propertyType.Key.id, propertyType.Key.alias, tags)); } - list.Add(new TaggedEntity(node.Key, properties)); + yield return new TaggedEntity(node.Key, properties); } - return list; } public IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string group = null) @@ -240,13 +274,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetTagsQuerySelect(true); sql = ApplyRelationshipJoinToTagsQuery(sql); - - sql = sql - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId); - + if (objectType != TaggableObjectTypes.All) { var nodeObjectType = GetNodeObjectType(objectType); @@ -268,7 +296,21 @@ namespace Umbraco.Core.Persistence.Repositories sql = ApplyRelationshipJoinToTagsQuery(sql); sql = sql - .Where(dto => dto.NodeId == contentId); + .Where(dto => dto.NodeId == contentId); + + sql = ApplyGroupFilterToTagsQuery(sql, group); + + return ExecuteTagsQuery(sql); + } + + public IEnumerable GetTagsForEntity(Guid contentId, string group = null) + { + var sql = GetTagsQuerySelect(); + + sql = ApplyRelationshipJoinToTagsQuery(sql); + + sql = sql + .Where(dto => dto.UniqueId == contentId); sql = ApplyGroupFilterToTagsQuery(sql, group); @@ -282,9 +324,26 @@ namespace Umbraco.Core.Persistence.Repositories sql = ApplyRelationshipJoinToTagsQuery(sql); sql = sql - .InnerJoin() - .On(left => left.Id, right => right.PropertyTypeId) - .Where(dto => dto.NodeId == contentId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) + .Where(dto => dto.NodeId == contentId) + .Where(dto => dto.Alias == propertyTypeAlias); + + sql = ApplyGroupFilterToTagsQuery(sql, group); + + return ExecuteTagsQuery(sql); + } + + public IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null) + { + var sql = GetTagsQuerySelect(); + + sql = ApplyRelationshipJoinToTagsQuery(sql); + + sql = sql + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) + .Where(dto => dto.UniqueId == contentId) .Where(dto => dto.Alias == propertyTypeAlias); sql = ApplyGroupFilterToTagsQuery(sql, group); @@ -298,7 +357,7 @@ namespace Umbraco.Core.Persistence.Repositories if (withGrouping) { - sql = sql.Select("cmsTags.Id, cmsTags.Tag, cmsTags." + SqlSyntax.GetQuotedColumnName("Group") + @", Count(*) NodeCount"); + sql = sql.Select("cmsTags.id, cmsTags.tag, cmsTags." + SqlSyntax.GetQuotedColumnName("group") + @", Count(*) NodeCount"); } else { @@ -311,14 +370,18 @@ namespace Umbraco.Core.Persistence.Repositories private Sql ApplyRelationshipJoinToTagsQuery(Sql sql) { return sql - .From() - .InnerJoin() - .On(left => left.TagId, right => right.Id); + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.TagId, right => right.Id) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId); } private Sql ApplyGroupFilterToTagsQuery(Sql sql, string group) { - if (!group.IsNullOrWhiteSpace()) + if (@group.IsNullOrWhiteSpace() == false) { sql = sql.Where(dto => dto.Group == group); } @@ -328,7 +391,7 @@ namespace Umbraco.Core.Persistence.Repositories private Sql ApplyGroupByToTagsQuery(Sql sql) { - return sql.GroupBy(new string[] { "cmsTags.Id", "cmsTags.Tag", "cmsTags." + SqlSyntax.GetQuotedColumnName("Group") + @"" }); + return sql.GroupBy(new string[] { "cmsTags.id", "cmsTags.tag", "cmsTags." + SqlSyntax.GetQuotedColumnName("group") + @"" }); } private IEnumerable ExecuteTagsQuery(Sql sql) @@ -385,16 +448,16 @@ namespace Umbraco.Core.Persistence.Repositories //adds any tags found in the collection that aren't in cmsTag var insertTagsSql = string.Concat("insert into cmsTags (Tag,", - SqlSyntax.GetQuotedColumnName("Group"), + SqlSyntax.GetQuotedColumnName("group"), ") ", " select TagSet.Tag, TagSet.", - SqlSyntax.GetQuotedColumnName("Group"), + SqlSyntax.GetQuotedColumnName("group"), " from ", tagSetSql, " left outer join cmsTags on (TagSet.Tag = cmsTags.Tag and TagSet.", - SqlSyntax.GetQuotedColumnName("Group"), + SqlSyntax.GetQuotedColumnName("group"), " = cmsTags.", - SqlSyntax.GetQuotedColumnName("Group"), + SqlSyntax.GetQuotedColumnName("group"), ")", " where cmsTags.Id is null "); //insert the tags that don't exist @@ -413,9 +476,9 @@ namespace Umbraco.Core.Persistence.Repositories "select NewTags.Id from ", tagSetSql, " inner join cmsTags as NewTags on (TagSet.Tag = NewTags.Tag and TagSet.", - SqlSyntax.GetQuotedColumnName("Group"), + SqlSyntax.GetQuotedColumnName("group"), " = TagSet.", - SqlSyntax.GetQuotedColumnName("Group"), + SqlSyntax.GetQuotedColumnName("group"), ") ", ") as NewTagsSet ", "left outer join cmsTagRelationship ", @@ -451,7 +514,7 @@ namespace Umbraco.Core.Persistence.Repositories " AND tagId IN ", "(SELECT id FROM cmsTags INNER JOIN ", tagSetSql, - " ON (TagSet.Tag = cmsTags.Tag and TagSet." + SqlSyntax.GetQuotedColumnName("Group") + @" = cmsTags." + SqlSyntax.GetQuotedColumnName("Group") + @"))"); + " ON (TagSet.Tag = cmsTags.Tag and TagSet." + SqlSyntax.GetQuotedColumnName("group") + @" = cmsTags." + SqlSyntax.GetQuotedColumnName("group") + @"))"); Database.Execute(deleteSql); } @@ -503,7 +566,7 @@ namespace Umbraco.Core.Persistence.Repositories var array = tagsToInsert .Select(tag => - string.Format("select '{0}' as Tag, '{1}' as " + SqlSyntax.GetQuotedColumnName("Group") + @"", + string.Format("select '{0}' as Tag, '{1}' as " + SqlSyntax.GetQuotedColumnName("group") + @"", PetaPocoExtensions.EscapeAtSymbols(tag.Text.Replace("'", "''")), tag.Group)) .ToArray(); return "(" + string.Join(" union ", array).Replace(" ", " ") + ") as TagSet"; diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 2bbac2a280..6bd83a8ad8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -358,7 +358,8 @@ namespace Umbraco.Core.Persistence.Repositories return Enumerable.Empty(); } - var result = GetAll(pagedResult.Items.Select(x => x.Id).ToArray()); + var ids = pagedResult.Items.Select(x => x.Id).ToArray(); + var result = ids.Length == 0 ? Enumerable.Empty() : GetAll(ids); //now we need to ensure this result is also ordered by the same order by clause return result.OrderBy(orderBy.Compile()); @@ -405,7 +406,8 @@ namespace Umbraco.Core.Persistence.Repositories private IEnumerable ConvertFromDtos(IEnumerable dtos) { var userTypeIds = dtos.Select(x => Convert.ToInt32(x.Type)).ToArray(); - var allUserTypes = _userTypeRepository.GetAll(userTypeIds); + + var allUserTypes = userTypeIds.Length == 0 ? Enumerable.Empty() : _userTypeRepository.GetAll(userTypeIds); return dtos.Select(dto => { diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 19556b1f31..bb213eb50f 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -294,7 +294,7 @@ namespace Umbraco.Core.Persistence public IDomainRepository CreateDomainRepository(IDatabaseUnitOfWork uow) { - return new DomainRepository(uow, _cacheHelper, _logger, _sqlSyntax, CreateContentRepository(uow), CreateLanguageRepository(uow)); + return new DomainRepository(uow, _cacheHelper, _logger, _sqlSyntax); } public ITaskTypeRepository CreateTaskTypeRepository(IDatabaseUnitOfWork uow) diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 95c723deaa..1d60879f97 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -20,6 +20,8 @@ namespace Umbraco.Core.Persistence { private readonly ILogger _logger; private readonly Guid _instanceId = Guid.NewGuid(); + private bool _enableCount; + /// /// Used for testing /// @@ -28,6 +30,33 @@ namespace Umbraco.Core.Persistence get { return _instanceId; } } + /// + /// Generally used for testing, will output all SQL statements executed to the logger + /// + internal bool EnableSqlTrace { get; set; } + + /// + /// Used for testing + /// + internal void EnableSqlCount() + { + _enableCount = true; + } + + /// + /// Used for testing + /// + internal void DisableSqlCount() + { + _enableCount = false; + SqlCount = 0; + } + + /// + /// Used for testing + /// + internal int SqlCount { get; private set; } + [Obsolete("Use the other constructor specifying an ILogger instead")] public UmbracoDatabase(IDbConnection connection) : this(connection, LoggerResolver.Current.Logger) @@ -56,24 +85,28 @@ namespace Umbraco.Core.Persistence : base(connection) { _logger = logger; + EnableSqlTrace = false; } public UmbracoDatabase(string connectionString, string providerName, ILogger logger) : base(connectionString, providerName) { _logger = logger; + EnableSqlTrace = false; } public UmbracoDatabase(string connectionString, DbProviderFactory provider, ILogger logger) : base(connectionString, provider) { _logger = logger; + EnableSqlTrace = false; } public UmbracoDatabase(string connectionStringName, ILogger logger) : base(connectionStringName) { _logger = logger; + EnableSqlTrace = false; } public override IDbConnection OnConnectionOpened(IDbConnection connection) @@ -87,5 +120,18 @@ namespace Umbraco.Core.Persistence _logger.Info(x.StackTrace); base.OnException(x); } + + public override void OnExecutedCommand(IDbCommand cmd) + { + if (EnableSqlTrace) + { + _logger.Debug(cmd.CommandText); + } + if (_enableCount) + { + SqlCount++; + } + base.OnExecutedCommand(cmd); + } } } \ 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 249b1c696b..8c909fc554 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs @@ -46,7 +46,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork /// Constructor accepting an IDatabaseFactory instance /// /// - internal PetaPocoUnitOfWorkProvider(IDatabaseFactory dbFactory) + public PetaPocoUnitOfWorkProvider(IDatabaseFactory dbFactory) { Mandate.ParameterNotNull(dbFactory, "dbFactory"); _dbFactory = dbFactory; diff --git a/src/Umbraco.Core/Publishing/PublishStatus.cs b/src/Umbraco.Core/Publishing/PublishStatus.cs index 519046f5f9..3436e9070e 100644 --- a/src/Umbraco.Core/Publishing/PublishStatus.cs +++ b/src/Umbraco.Core/Publishing/PublishStatus.cs @@ -1,36 +1,32 @@ using System.Collections.Generic; +using Umbraco.Core.Events; using Umbraco.Core.Models; +using Umbraco.Core.Services; namespace Umbraco.Core.Publishing { /// /// The result of publishing a content item /// - public class PublishStatus + public class PublishStatus : OperationStatus { - public PublishStatus() - { - //initialize - InvalidProperties = new List(); - } - - public PublishStatus(IContent content, PublishStatusType statusType) - : this() - { - ContentItem = content; - StatusType = statusType; + public PublishStatus(IContent content, PublishStatusType statusType, EventMessages eventMessages) + : base(content, statusType, eventMessages) + { } /// /// Creates a successful publish status /// - public PublishStatus(IContent content) - : this(content, PublishStatusType.Success) + public PublishStatus(IContent content, EventMessages eventMessages) + : this(content, PublishStatusType.Success, eventMessages) { } - public IContent ContentItem { get; private set; } - public PublishStatusType StatusType { get; internal set; } + public IContent ContentItem + { + get { return Entity; } + } /// /// Gets sets the invalid properties if the status failed due to validation. diff --git a/src/Umbraco.Core/Publishing/PublishingStrategy.cs b/src/Umbraco.Core/Publishing/PublishingStrategy.cs index 1c33f87c5e..c6d4f19baf 100644 --- a/src/Umbraco.Core/Publishing/PublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/PublishingStrategy.cs @@ -5,14 +5,26 @@ using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core; +using Umbraco.Core.Services; namespace Umbraco.Core.Publishing { + //TODO: Do we need this anymore?? /// /// Currently acts as an interconnection between the new public api and the legacy api for publishing /// public class PublishingStrategy : BasePublishingStrategy { + private readonly IEventMessagesFactory _eventMessagesFactory; + private readonly ILogger _logger; + + public PublishingStrategy(IEventMessagesFactory eventMessagesFactory, ILogger logger) + { + if (eventMessagesFactory == null) throw new ArgumentNullException("eventMessagesFactory"); + if (logger == null) throw new ArgumentNullException("logger"); + _eventMessagesFactory = eventMessagesFactory; + _logger = logger; + } /// /// Publishes a single piece of Content @@ -21,48 +33,50 @@ namespace Umbraco.Core.Publishing /// Id of the User issueing the publish operation internal Attempt PublishInternal(IContent content, int userId) { - if (Publishing.IsRaisedEventCancelled(new PublishEventArgs(content), this)) + var evtMsgs = _eventMessagesFactory.Get(); + + if (Publishing.IsRaisedEventCancelled( + new PublishEventArgs(content, evtMsgs), this)) { - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", content.Name, content.Id)); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); } - //Check if the Content is Expired to verify that it can in fact be published if (content.Status == ContentStatus.Expired) { - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' has expired and could not be published.", content.Name, content.Id)); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedHasExpired)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedHasExpired, evtMsgs)); } //Check if the Content is Awaiting Release to verify that it can in fact be published if (content.Status == ContentStatus.AwaitingRelease) { - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' is awaiting release and could not be published.", content.Name, content.Id)); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedAwaitingRelease)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedAwaitingRelease, evtMsgs)); } //Check if the Content is Trashed to verify that it can in fact be published if (content.Status == ContentStatus.Trashed) { - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' is trashed and could not be published.", content.Name, content.Id)); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedIsTrashed)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedIsTrashed, evtMsgs)); } content.ChangePublishedState(PublishedState.Published); - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' has been published.", content.Name, content.Id)); - return Attempt.Succeed(new PublishStatus(content)); + return Attempt.Succeed(new PublishStatus(content, evtMsgs)); } /// @@ -121,11 +135,13 @@ namespace Umbraco.Core.Publishing // much difference because we iterate over them all anyways?? Morten? // Because we're grouping I think this will execute all the queries anyways so need to fetch it all first. var fetchedContent = content.ToArray(); - + + var evtMsgs = _eventMessagesFactory.Get(); + //We're going to populate the statuses with all content that is already published because below we are only going to iterate over // content that is not published. We'll set the status to "AlreadyPublished" statuses.AddRange(fetchedContent.Where(x => x.Published) - .Select(x => Attempt.Succeed(new PublishStatus(x, PublishStatusType.SuccessAlreadyPublished)))); + .Select(x => Attempt.Succeed(new PublishStatus(x, PublishStatusType.SuccessAlreadyPublished, evtMsgs)))); int? firstLevel = null; @@ -137,7 +153,7 @@ namespace Umbraco.Core.Publishing //be published regardless of the rules mentioned in the remarks. if (!firstLevel.HasValue) { - firstLevel = level.Key; + firstLevel = level.Key; } /* Only update content thats not already been published - we want to loop through @@ -148,7 +164,7 @@ namespace Umbraco.Core.Publishing //Check if this item should be excluded because it's parent's publishing has failed/cancelled if (parentsIdsCancelled.Contains(item.ParentId)) { - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' will not be published because it's parent's publishing action failed or was cancelled.", item.Name, item.Id)); //if this cannot be published, ensure that it's children can definitely not either! parentsIdsCancelled.Add(item.Id); @@ -164,26 +180,27 @@ namespace Umbraco.Core.Publishing } //Fire Publishing event - if (Publishing.IsRaisedEventCancelled(new PublishEventArgs(item), this)) + if (Publishing.IsRaisedEventCancelled( + new PublishEventArgs(item, evtMsgs), this)) { //the publishing has been cancelled. - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", item.Name, item.Id)); - statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedCancelledByEvent))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedCancelledByEvent, evtMsgs))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); - + continue; } //Check if the content is valid if the flag is set to check - if (!item.IsValid()) + if (item.IsValid() == false) { - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' will not be published because some of it's content is not passing validation rules.", item.Name, item.Id)); - statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedContentInvalid))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedContentInvalid, evtMsgs))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -194,24 +211,24 @@ namespace Umbraco.Core.Publishing //Check if the Content is Expired to verify that it can in fact be published if (item.Status == ContentStatus.Expired) { - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' has expired and could not be published.", item.Name, item.Id)); - statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedHasExpired))); - + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedHasExpired, evtMsgs))); + //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); - + continue; } //Check if the Content is Awaiting Release to verify that it can in fact be published if (item.Status == ContentStatus.AwaitingRelease) { - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' is awaiting release and could not be published.", item.Name, item.Id)); - statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedAwaitingRelease))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedAwaitingRelease, evtMsgs))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -222,10 +239,10 @@ namespace Umbraco.Core.Publishing //Check if the Content is Trashed to verify that it can in fact be published if (item.Status == ContentStatus.Trashed) { - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' is trashed and could not be published.", item.Name, item.Id)); - statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedIsTrashed))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedIsTrashed, evtMsgs))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -235,13 +252,13 @@ namespace Umbraco.Core.Publishing item.ChangePublishedState(PublishedState.Published); - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' has been published.", item.Name, item.Id)); - statuses.Add(Attempt.Succeed(new PublishStatus(item))); + statuses.Add(Attempt.Succeed(new PublishStatus(item, evtMsgs))); } - + } return statuses; @@ -262,7 +279,7 @@ namespace Umbraco.Core.Publishing //TODO: We're going back to the service layer here... not sure how to avoid this? And this will add extra overhead to // any document that fails to publish... var hasPublishedVersion = ApplicationContext.Current.Services.ContentService.HasPublishedVersion(content.Id); - + if (hasPublishedVersion && !includeUnpublishedDocuments) { //it has a published version but our flag tells us to not include un-published documents and therefore we should @@ -272,7 +289,7 @@ namespace Umbraco.Core.Publishing else if (!hasPublishedVersion) { //it doesn't have a published version so we certainly cannot publish it's children. - parentsIdsCancelled.Add(content.Id); + parentsIdsCancelled.Add(content.Id); } } @@ -325,12 +342,15 @@ namespace Umbraco.Core.Publishing // if published != newest, then the published flags need to be reseted by whoever is calling that method // at the moment it's done by the content service + var evtMsgs = _eventMessagesFactory.Get(); + //Fire UnPublishing event - if (UnPublishing.IsRaisedEventCancelled(new PublishEventArgs(content), this)) + if (UnPublishing.IsRaisedEventCancelled( + new PublishEventArgs(content, evtMsgs), this)) { - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' will not be unpublished, the event was cancelled.", content.Name, content.Id)); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); } //If Content has a release date set to before now, it should be removed so it doesn't interrupt an unpublish @@ -339,7 +359,7 @@ namespace Umbraco.Core.Publishing { content.ReleaseDate = null; - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' had its release date removed, because it was unpublished.", content.Name, content.Id)); } @@ -348,11 +368,11 @@ namespace Umbraco.Core.Publishing if (content.Published) content.ChangePublishedState(PublishedState.Unpublished); - LogHelper.Info( + _logger.Info( string.Format("Content '{0}' with Id '{1}' has been unpublished.", content.Name, content.Id)); - return Attempt.Succeed(new PublishStatus(content)); + return Attempt.Succeed(new PublishStatus(content, evtMsgs)); } /// @@ -384,7 +404,9 @@ namespace Umbraco.Core.Publishing /// thats being published public override void PublishingFinalized(IContent content) { - Published.RaiseEvent(new PublishEventArgs(content, false, false), this); + var evtMsgs = _eventMessagesFactory.Get(); + Published.RaiseEvent( + new PublishEventArgs(content, false, false, evtMsgs), this); } /// @@ -394,7 +416,9 @@ namespace Umbraco.Core.Publishing /// Boolean indicating whether its all content that is republished public override void PublishingFinalized(IEnumerable content, bool isAllRepublished) { - Published.RaiseEvent(new PublishEventArgs(content, false, isAllRepublished), this); + var evtMsgs = _eventMessagesFactory.Get(); + Published.RaiseEvent( + new PublishEventArgs(content, false, isAllRepublished, evtMsgs), this); } @@ -404,7 +428,9 @@ namespace Umbraco.Core.Publishing /// thats being unpublished public override void UnPublishingFinalized(IContent content) { - UnPublished.RaiseEvent(new PublishEventArgs(content, false, false), this); + var evtMsgs = _eventMessagesFactory.Get(); + UnPublished.RaiseEvent( + new PublishEventArgs(content, false, false, evtMsgs), this); } /// @@ -413,7 +439,9 @@ namespace Umbraco.Core.Publishing /// An enumerable list of thats being unpublished public override void UnPublishingFinalized(IEnumerable content) { - UnPublished.RaiseEvent(new PublishEventArgs(content, false, false), this); + var evtMsgs = _eventMessagesFactory.Get(); + UnPublished.RaiseEvent( + new PublishEventArgs(content, false, false, evtMsgs), this); } /// diff --git a/src/Umbraco.Core/Publishing/UnPublishStatus.cs b/src/Umbraco.Core/Publishing/UnPublishStatus.cs new file mode 100644 index 0000000000..80ab786e1a --- /dev/null +++ b/src/Umbraco.Core/Publishing/UnPublishStatus.cs @@ -0,0 +1,30 @@ +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Publishing +{ + /// + /// The result of unpublishing a content item + /// + public class UnPublishStatus : OperationStatus + { + public UnPublishStatus(IContent content, UnPublishedStatusType statusType, EventMessages eventMessages) + : base(content, statusType, eventMessages) + { + } + + /// + /// Creates a successful unpublish status + /// + public UnPublishStatus(IContent content, EventMessages eventMessages) + : this(content, UnPublishedStatusType.Success, eventMessages) + { + } + + public IContent ContentItem + { + get { return Entity; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/UnPublishedStatusType.cs b/src/Umbraco.Core/Publishing/UnPublishedStatusType.cs new file mode 100644 index 0000000000..515e98daf4 --- /dev/null +++ b/src/Umbraco.Core/Publishing/UnPublishedStatusType.cs @@ -0,0 +1,26 @@ +namespace Umbraco.Core.Publishing +{ + /// + /// A status type of the result of unpublishing a content item + /// + /// + /// Anything less than 10 = Success! + /// + public enum UnPublishedStatusType + { + /// + /// The unpublishing was successful. + /// + Success = 0, + + /// + /// The item was already unpublished + /// + SuccessAlreadyUnPublished = 1, + + /// + /// The publish action has been cancelled by an event handler + /// + FailedCancelledByEvent = 14, + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index fc7ecf5330..85d6a0c715 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; +using Microsoft.Owin.Logging; using Microsoft.Owin.Security; using Umbraco.Core.Configuration; using Umbraco.Core.Models.Identity; @@ -12,9 +14,16 @@ namespace Umbraco.Core.Security { public class BackOfficeSignInManager : SignInManager { - public BackOfficeSignInManager(BackOfficeUserManager userManager, IAuthenticationManager authenticationManager) + private readonly ILogger _logger; + private readonly IOwinRequest _request; + + public BackOfficeSignInManager(BackOfficeUserManager userManager, IAuthenticationManager authenticationManager, ILogger logger, IOwinRequest request) : base(userManager, authenticationManager) { + if (logger == null) throw new ArgumentNullException("logger"); + if (request == null) throw new ArgumentNullException("request"); + _logger = logger; + _request = request; AuthenticationType = Constants.Security.BackOfficeAuthenticationType; } @@ -23,9 +32,54 @@ namespace Umbraco.Core.Security return user.GenerateUserIdentityAsync((BackOfficeUserManager)UserManager); } - public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context) + public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context, ILogger logger) { - return new BackOfficeSignInManager(context.GetUserManager(), context.Authentication); + return new BackOfficeSignInManager( + context.GetUserManager(), + context.Authentication, + logger, + context.Request); + } + + /// + /// Sign in the user in using the user name and password + /// + /// + /// + public async override Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) + { + var result = await base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout); + + switch (result) + { + case SignInStatus.Success: + break; + case SignInStatus.LockedOut: + _logger.WriteCore(TraceEventType.Information, 0, + string.Format( + "Login attempt failed for username {0} from IP address {1}, the user is locked", + userName, + _request.RemoteIpAddress), null, null); + break; + case SignInStatus.RequiresVerification: + _logger.WriteCore(TraceEventType.Information, 0, + string.Format( + "Login attempt failed for username {0} from IP address {1}, the user requires verification", + userName, + _request.RemoteIpAddress), null, null); + break; + case SignInStatus.Failure: + _logger.WriteCore(TraceEventType.Information, 0, + string.Format( + "Login attempt failed for username {0} from IP address {1}", + userName, + _request.RemoteIpAddress), null, null); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return result; } /// @@ -67,6 +121,12 @@ namespace Umbraco.Core.Security ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes) }, userIdentity); } + + _logger.WriteCore(TraceEventType.Information, 0, + string.Format( + "Login attempt succeeded for username {0} from IP address {1}", + user.UserName, + _request.RemoteIpAddress), null, null); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/AuditService.cs b/src/Umbraco.Core/Services/AuditService.cs index 032516421c..3e076084f2 100644 --- a/src/Umbraco.Core/Services/AuditService.cs +++ b/src/Umbraco.Core/Services/AuditService.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; @@ -8,8 +9,8 @@ namespace Umbraco.Core.Services { public sealed class AuditService : RepositoryService, IAuditService { - public AuditService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public AuditService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index b7ad8c4d03..c9df0d685a 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -26,9 +26,8 @@ namespace Umbraco.Core.Services /// /// Represents the Content Service, which is an easy access to operations involving /// - public class ContentService : RepositoryService, IContentService + public class ContentService : RepositoryService, IContentService, IContentServiceOperations { - private readonly IPublishingStrategy _publishingStrategy; private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; @@ -38,37 +37,15 @@ namespace Umbraco.Core.Services //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); - [Obsolete("Use the constructors that specify all dependencies instead")] - public ContentService() - : this(new RepositoryFactory(ApplicationContext.Current.ApplicationCache, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider, UmbracoConfig.For.UmbracoSettings())) - { } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public ContentService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(LoggerResolver.Current.Logger), repositoryFactory, new PublishingStrategy()) - { } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public ContentService(IDatabaseUnitOfWorkProvider provider) - : this(provider, new RepositoryFactory(ApplicationContext.Current.ApplicationCache, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider, UmbracoConfig.For.UmbracoSettings()), new PublishingStrategy()) - { } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public ContentService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) - : this(provider, repositoryFactory, new PublishingStrategy()) - { } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public ContentService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IPublishingStrategy publishingStrategy) - : base(provider, repositoryFactory, LoggerResolver.Current.Logger) - { - if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy"); - _dataTypeService = new DataTypeService(UowProvider, RepositoryFactory); - _userService = new UserService(UowProvider, RepositoryFactory); - } - - public ContentService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IPublishingStrategy publishingStrategy, IDataTypeService dataTypeService, IUserService userService) - : base(provider, repositoryFactory, logger) + public ContentService( + IDatabaseUnitOfWorkProvider provider, + 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"); @@ -356,6 +333,8 @@ namespace Umbraco.Core.Services /// public IEnumerable GetByIds(IEnumerable ids) { + if (ids.Any() == false) return Enumerable.Empty(); + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) { return repository.GetAll(ids.ToArray()); @@ -768,6 +747,8 @@ namespace Umbraco.Core.Services } } + + /// /// Checks whether an item has any children /// @@ -876,6 +857,110 @@ namespace Umbraco.Core.Services return result.Success; } + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// + /// The list of statuses for all published items + IEnumerable> IContentServiceOperations.PublishWithChildren(IContent content, int userId, bool includeUnpublished) + { + return PublishWithChildrenDo(content, userId, includeUnpublished); + } + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + Attempt IContentServiceOperations.SaveAndPublish(IContent content, int userId, bool raiseEvents) + { + return SaveAndPublishDo(content, userId, raiseEvents); + } + + /// + /// 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 + Attempt IContentServiceOperations.MoveToRecycleBin(IContent content, int userId) + { + 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)) + { + return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + } + + var moveInfo = new List> + { + new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent) + }; + + //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)) + { + content.WriterId = userId; + content.ChangeTrashedState(true); + repository.AddOrUpdate(content); + + //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId + foreach (var descendant in descendants) + { + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + + descendant.WriterId = userId; + descendant.ChangeTrashedState(true, descendant.ParentId); + repository.AddOrUpdate(descendant); + } + + uow.Commit(); + } + + Trashed.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); + + Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); + + return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + } + } + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + Attempt IContentServiceOperations.UnPublish(IContent content, int userId) + { + return UnPublishDo(content, false, userId); + } + /// /// Publishes a single object /// @@ -884,7 +969,7 @@ namespace Umbraco.Core.Services /// True if publishing succeeded, otherwise False public Attempt PublishWithStatus(IContent content, int userId = 0) { - return SaveAndPublishDo(content, userId); + return ((IContentServiceOperations)this).Publish(content, userId); } /// @@ -900,7 +985,7 @@ namespace Umbraco.Core.Services //This used to just return false only when the parent content failed, otherwise would always return true so we'll // do the same thing for the moment - if (!result.Any(x => x.Result.ContentItem.Id == content.Id)) + if (result.All(x => x.Result.ContentItem.Id != content.Id)) return false; return result.Single(x => x.Result.ContentItem.Id == content.Id).Success; @@ -915,7 +1000,7 @@ namespace Umbraco.Core.Services /// True if publishing succeeded, otherwise False public IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false) { - return PublishWithChildrenDo(content, userId, includeUnpublished); + return ((IContentServiceOperations)this).PublishWithChildren(content, userId, includeUnpublished); } /// @@ -926,7 +1011,7 @@ namespace Umbraco.Core.Services /// True if unpublishing succeeded, otherwise False public bool UnPublish(IContent content, int userId = 0) { - return UnPublishDo(content, false, userId); + return ((IContentServiceOperations) this).UnPublish(content, userId).Success; } /// @@ -952,7 +1037,7 @@ namespace Umbraco.Core.Services /// True if publishing succeeded, otherwise False public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) { - return SaveAndPublishDo(content, userId, raiseEvents); + return ((IContentServiceOperations)this).SaveAndPublish(content, userId, raiseEvents); } /// @@ -963,27 +1048,29 @@ namespace Umbraco.Core.Services /// Optional boolean indicating whether or not to raise events. public void Save(IContent content, int userId = 0, bool raiseEvents = true) { - Save(content, true, userId, raiseEvents); + ((IContentServiceOperations)this).Save(content, userId, raiseEvents); } /// /// Saves a collection of objects. - /// - /// - /// If the collection of content contains new objects that references eachother by Id or ParentId, - /// then use the overload Save method with a collection of Lazy . - /// + /// /// Collection of to save /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true) + /// 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) { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return; + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(asArray, evtMsgs), + this)) + { + return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + } } using (new WriteLock(Locker)) { @@ -1022,12 +1109,105 @@ namespace Umbraco.Core.Services } if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); + Saved.RaiseEvent(new SaveEventArgs(asArray, false, evtMsgs), this); Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); + + return Attempt.Succeed(OperationStatus.Success(evtMsgs)); } } + /// + /// Permanently deletes an object. + /// + /// + /// This method will also delete associated media files, child content and possibly associated domains. + /// + /// Please note that this method will completely remove the Content from the database + /// The to delete + /// Optional Id of the User deleting the Content + Attempt IContentServiceOperations.Delete(IContent content, int userId) + { + var evtMsgs = EventMessagesFactory.Get(); + + using (new WriteLock(Locker)) + { + if (Deleting.IsRaisedEventCancelled( + new DeleteEventArgs(content, evtMsgs), + this)) + { + return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + } + + //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); + } + + 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); + } + + Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); + + return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + } + } + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt IContentServiceOperations.Publish(IContent content, int userId) + { + return SaveAndPublishDo(content, userId); + } + + /// + /// Saves a single object + /// + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + Attempt IContentServiceOperations.Save(IContent content, int userId, bool raiseEvents) + { + return Save(content, true, userId, raiseEvents); + } + + /// + /// Saves a collection of objects. + /// + /// + /// If the collection of content contains new objects that references eachother by Id or ParentId, + /// then use the overload Save method with a collection of Lazy . + /// + /// Collection of to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + public void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true) + { + ((IContentServiceOperations)this).Save(contents, userId, raiseEvents); + } + /// /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. /// @@ -1083,39 +1263,7 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting the Content public void Delete(IContent content, int userId = 0) { - using (new WriteLock(Locker)) - { - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(content), this)) - return; - - //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); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.Delete(content); - uow.Commit(); - - var args = new DeleteEventArgs(content, false); - Deleted.RaiseEvent(args, this); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); - } - - Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); - } + ((IContentServiceOperations)this).Delete(content, userId); } /// @@ -1184,59 +1332,7 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting the Content public void MoveToRecycleBin(IContent content, int userId = 0) { - using (new WriteLock(Locker)) - { - var originalPath = content.Path; - - if (Trashing.IsRaisedEventCancelled( - new MoveEventArgs( - new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), this)) - { - return; - } - - var moveInfo = new List> - { - new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent) - }; - - //Make sure that published content is unpublished before being moved to the Recycle Bin - if (HasPublishedVersion(content.Id)) - { - 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) - { - UnPublish(descendant, userId); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - content.WriterId = userId; - content.ChangeTrashedState(true); - repository.AddOrUpdate(content); - - //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId - foreach (var descendant in descendants) - { - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - - descendant.WriterId = userId; - descendant.ChangeTrashedState(true, descendant.ParentId); - repository.AddOrUpdate(descendant); - } - - uow.Commit(); - } - - Trashed.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this); - - Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); - } + ((IContentServiceOperations) this).MoveToRecycleBin(content, userId); } /// @@ -1395,7 +1491,6 @@ 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; @@ -1406,7 +1501,6 @@ namespace Umbraco.Core.Services Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); - //TODO: will this ever be false?? return true; } @@ -1496,6 +1590,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); shouldBePublished.Add(content); } @@ -1521,7 +1616,11 @@ namespace Umbraco.Core.Services 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); + } + Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); @@ -1675,6 +1774,8 @@ namespace Umbraco.Core.Services { if (content == null) throw new ArgumentNullException("content"); + var evtMsgs = EventMessagesFactory.Get(); + using (new WriteLock(Locker)) { var result = new List>(); @@ -1686,7 +1787,7 @@ namespace Umbraco.Core.Services string.Format( "Content '{0}' with Id '{1}' could not be published because its parent or one of its ancestors is not published.", content.Name, content.Id)); - result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished))); + result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished, evtMsgs))); return result; } @@ -1698,10 +1799,10 @@ namespace Umbraco.Core.Services content.Name, content.Id)); result.Add( Attempt.Fail( - new PublishStatus(content, PublishStatusType.FailedContentInvalid) - { - InvalidProperties = ((ContentBase)content).LastInvalidProperties - })); + new PublishStatus(content, PublishStatusType.FailedContentInvalid, evtMsgs) + { + InvalidProperties = ((ContentBase)content).LastInvalidProperties + })); return result; } @@ -1755,38 +1856,42 @@ namespace Umbraco.Core.Services /// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache. /// Optional Id of the User issueing the publishing /// True if unpublishing succeeded, otherwise False - private bool UnPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0) + private Attempt UnPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0) { var newest = GetById(content.Id); // ensure we have the newest version if (content.Version != newest.Version) // but use the original object if it's already the newest version content = newest; + + var evtMsgs = EventMessagesFactory.Get(); + var published = content.Published ? content : GetPublishedVersion(content.Id); // get the published version if (published == null) - return false; // already unpublished - - var unpublished = _publishingStrategy.UnPublish(content, userId); - if (unpublished) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - content.WriterId = userId; - repository.AddOrUpdate(content); - // is published is not newest, reset the published flag on published version - if (published.Version != content.Version) - repository.ClearPublished(published); - 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(content); - - Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id); + 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)); - return unpublished; + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + content.WriterId = userId; + repository.AddOrUpdate(content); + // is published is not newest, reset the published flag on published version + if (published.Version != content.Version) + repository.ClearPublished(published); + 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(content); + + Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id); + + return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.Success, evtMsgs)); } /// @@ -1798,11 +1903,14 @@ namespace Umbraco.Core.Services /// True if publishing succeeded, otherwise False private Attempt SaveAndPublishDo(IContent content, int userId = 0, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + if (raiseEvents) { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(content, evtMsgs), this)) { - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); } } @@ -1810,7 +1918,7 @@ namespace Umbraco.Core.Services { //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); //initially set to success + 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); @@ -1860,7 +1968,7 @@ namespace Umbraco.Core.Services } if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(content, false), this); + 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) @@ -1889,12 +1997,18 @@ namespace Umbraco.Core.Services /// Boolean indicating whether or not to change the Published state upon saving /// Optional Id of the User saving the Content /// Optional boolean indicating whether or not to raise events. - private void Save(IContent content, bool changeState, int userId = 0, bool raiseEvents = true) + private Attempt Save(IContent content, bool changeState, int userId = 0, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + if (raiseEvents) { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) - return; + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(content, evtMsgs), + this)) + { + return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + } } using (new WriteLock(Locker)) @@ -1921,9 +2035,11 @@ namespace Umbraco.Core.Services } if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(content, false), this); + Saved.RaiseEvent(new SaveEventArgs(content, false, evtMsgs), this); Audit(AuditType.Save, "Save Content performed by user", userId, content.Id); + + return Attempt.Succeed(OperationStatus.Success(evtMsgs)); } } diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 194871f252..f144fa51e4 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -32,28 +32,8 @@ namespace Umbraco.Core.Services //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); - [Obsolete("Use the constructors that specify all dependencies instead")] - public ContentTypeService(IContentService contentService, IMediaService mediaService) - : this(new PetaPocoUnitOfWorkProvider(LoggerResolver.Current.Logger), new RepositoryFactory(), contentService, mediaService) - {} - - [Obsolete("Use the constructors that specify all dependencies instead")] - public ContentTypeService( RepositoryFactory repositoryFactory, IContentService contentService, IMediaService mediaService) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory, contentService, mediaService) - { } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public ContentTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IContentService contentService, IMediaService mediaService) - : base(provider, repositoryFactory, LoggerResolver.Current.Logger) - { - if (contentService == null) throw new ArgumentNullException("contentService"); - if (mediaService == null) throw new ArgumentNullException("mediaService"); - _contentService = contentService; - _mediaService = mediaService; - } - - public ContentTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IContentService contentService, IMediaService mediaService) - : base(provider, repositoryFactory, logger) + public ContentTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService, IMediaService mediaService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { if (contentService == null) throw new ArgumentNullException("contentService"); if (mediaService == null) throw new ArgumentNullException("mediaService"); diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs index d22ba90d5d..c0f4a8cdf6 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -10,8 +11,8 @@ namespace Umbraco.Core.Services { public class ContentTypeServiceBase : RepositoryService { - public ContentTypeServiceBase(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public ContentTypeServiceBase(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index a30945e5c2..f6dd53020c 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -21,31 +21,8 @@ namespace Umbraco.Core.Services public class DataTypeService : RepositoryService, IDataTypeService { - [Obsolete("Use the constructors that specify all dependencies instead")] - public DataTypeService() - : this(new RepositoryFactory()) - {} - - [Obsolete("Use the constructors that specify all dependencies instead")] - public DataTypeService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) - { - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public DataTypeService(IDatabaseUnitOfWorkProvider provider) - : this(provider, new RepositoryFactory()) - { - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) - : this(provider, repositoryFactory, LoggerResolver.Current.Logger) - { - } - - public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs index 71429cfb59..2fdf3edb93 100644 --- a/src/Umbraco.Core/Services/DomainService.cs +++ b/src/Umbraco.Core/Services/DomainService.cs @@ -12,8 +12,8 @@ namespace Umbraco.Core.Services { public class DomainService : RepositoryService, IDomainService { - public DomainService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public DomainService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 1d249aa6d1..257416d054 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Cache; using Umbraco.Core.CodeAnnotations; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -19,10 +20,10 @@ namespace Umbraco.Core.Services private readonly Dictionary>> _supportedObjectTypes; - public EntityService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, + public EntityService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IDataTypeService dataTypeService, IMemberService memberService, IMemberTypeService memberTypeService, IRuntimeCacheProvider runtimeCache) - : base(provider, repositoryFactory, logger) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { _runtimeCache = runtimeCache; IContentTypeService contentTypeService1 = contentTypeService; diff --git a/src/Umbraco.Core/Services/ExternalLoginService.cs b/src/Umbraco.Core/Services/ExternalLoginService.cs index f33f1c492b..91ca77872d 100644 --- a/src/Umbraco.Core/Services/ExternalLoginService.cs +++ b/src/Umbraco.Core/Services/ExternalLoginService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.Identity; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models.Identity; using Umbraco.Core.Persistence; @@ -11,8 +12,8 @@ namespace Umbraco.Core.Services { public class ExternalLoginService : RepositoryService, IExternalLoginService { - public ExternalLoginService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public ExternalLoginService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index f6c2625e23..8b308e5f21 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -8,6 +8,86 @@ using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { + /// + /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented + /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. + /// + public interface IContentServiceOperations + { + //TODO: Remove this class in v8 + + //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... + + /// + /// Saves a single object + /// + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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. + Attempt Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); + + /// + /// Permanently deletes an object. + /// + /// + /// This method will also delete associated media files, child content and possibly associated domains. + /// + /// Please note that this method will completely remove the Content from the database + /// The to delete + /// Optional Id of the User deleting the Content + Attempt Delete(IContent content, int userId = 0); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt Publish(IContent content, int userId = 0); + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// + /// The list of statuses for all published items + IEnumerable> PublishWithChildren(IContent content, int userId = 0, bool includeUnpublished = false); + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + Attempt SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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 + Attempt MoveToRecycleBin(IContent content, int userId = 0); + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + Attempt UnPublish(IContent content, int userId = 0); + } + /// /// Defines the ContentService, which is an easy access to operations involving /// @@ -188,7 +268,7 @@ namespace Umbraco.Core.Services /// /// An Enumerable list of objects IEnumerable GetContentInRecycleBin(); - + /// /// Saves a single object /// @@ -347,6 +427,7 @@ namespace Umbraco.Core.Services /// The to publish along with its children /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False + [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] bool PublishWithChildren(IContent content, int userId = 0); @@ -375,6 +456,7 @@ namespace Umbraco.Core.Services /// Optional boolean indicating whether or not to raise save events. /// True if publishing succeeded, otherwise False [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] + [EditorBrowsable(EditorBrowsableState.Never)] bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); /// @@ -385,7 +467,7 @@ namespace Umbraco.Core.Services /// Optional boolean indicating whether or not to raise save events. /// True if publishing succeeded, otherwise False Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true); - + /// /// Permanently deletes an object. /// @@ -394,7 +476,7 @@ namespace Umbraco.Core.Services /// /// Please note that this method will completely remove the Content from the database /// The to delete - /// Optional Id of the User deleting the Content + /// Optional Id of the User deleting the Content void Delete(IContent content, int userId = 0); /// diff --git a/src/Umbraco.Core/Services/ILocalizationService.cs b/src/Umbraco.Core/Services/ILocalizationService.cs index 49eb8f266d..e6563b7ee9 100644 --- a/src/Umbraco.Core/Services/ILocalizationService.cs +++ b/src/Umbraco.Core/Services/ILocalizationService.cs @@ -61,6 +61,13 @@ namespace Umbraco.Core.Services /// An enumerable list of objects IEnumerable GetDictionaryItemChildren(Guid parentId); + /// + /// Gets a list of descendants for a + /// + /// Id of the parent, null will return all dictionary items + /// An enumerable list of objects + IEnumerable GetDictionaryItemDescendants(Guid? parentId); + /// /// Gets the root/top objects /// diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 1de23b5b0a..d32acdd6b1 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -6,6 +6,51 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Services { + /// + /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented + /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. + /// + public interface IMediaServiceOperations + { + //TODO: Remove this class in v8 + + //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + Attempt MoveToRecycleBin(IMedia media, int userId = 0); + + /// + /// Permanently deletes an object + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// but current not from the file system. + /// + /// The to delete + /// Id of the User deleting the Media + Attempt Delete(IMedia media, int userId = 0); + + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IMedia media, int userId = 0, bool raiseEvents = true); + + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IEnumerable medias, int userId = 0, bool raiseEvents = true); + } + /// /// Defines the Media Service, which is an easy access to operations involving /// @@ -174,7 +219,7 @@ namespace Umbraco.Core.Services /// The to delete /// Id of the User deleting the Media void Delete(IMedia media, int userId = 0); - + /// /// Saves a single object /// diff --git a/src/Umbraco.Core/Services/IMemberTypeService.cs b/src/Umbraco.Core/Services/IMemberTypeService.cs index b2f298d822..51133d5afd 100644 --- a/src/Umbraco.Core/Services/IMemberTypeService.cs +++ b/src/Umbraco.Core/Services/IMemberTypeService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Umbraco.Core.Models; @@ -15,15 +16,22 @@ namespace Umbraco.Core.Services /// /// Gets an object by its Id /// - /// Id of the to retrieve - /// + /// Id of the to retrieve + /// IMemberType Get(int id); + /// + /// Gets an object by its Key + /// + /// Key of the to retrieve + /// + IMemberType Get(Guid key); + /// /// Gets an object by its Alias /// - /// Alias of the to retrieve - /// + /// Alias of the to retrieve + /// IMemberType Get(string alias); /// diff --git a/src/Umbraco.Core/Services/IService.cs b/src/Umbraco.Core/Services/IService.cs index 448e09aac4..e80576c493 100644 --- a/src/Umbraco.Core/Services/IService.cs +++ b/src/Umbraco.Core/Services/IService.cs @@ -1,3 +1,5 @@ +using Umbraco.Core.Logging; + namespace Umbraco.Core.Services { /// @@ -5,6 +7,6 @@ namespace Umbraco.Core.Services /// public interface IService { - + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ITagService.cs b/src/Umbraco.Core/Services/ITagService.cs index 02ac6f79ff..2454066d5f 100644 --- a/src/Umbraco.Core/Services/ITagService.cs +++ b/src/Umbraco.Core/Services/ITagService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Umbraco.Core.Models; @@ -14,6 +15,10 @@ namespace Umbraco.Core.Services /// public interface ITagService : IService { + + TaggedEntity GetTaggedEntityById(int id); + TaggedEntity GetTaggedEntityByKey(Guid key); + /// /// Gets tagged Content by a specific 'Tag Group'. /// @@ -119,5 +124,26 @@ namespace Umbraco.Core.Services /// Optional name of the 'Tag Group' /// An enumerable list of IEnumerable GetTagsForEntity(int contentId, string tagGroup = null); + + /// + /// Gets all tags attached to a property by entity id + /// + /// Use the optional tagGroup parameter to limit the + /// result to a specific 'Tag Group'. + /// The content item id to get tags for + /// Property type alias + /// Optional name of the 'Tag Group' + /// An enumerable list of + IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string tagGroup = null); + + /// + /// Gets all tags attached to an entity (content, media or member) by entity id + /// + /// Use the optional tagGroup parameter to limit the + /// result to a specific 'Tag Group'. + /// The content item id to get tags for + /// Optional name of the 'Tag Group' + /// An enumerable list of + IEnumerable GetTagsForEntity(Guid contentId, string tagGroup = null); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs index 59b77085af..6d6cf0c101 100644 --- a/src/Umbraco.Core/Services/LocalizationService.cs +++ b/src/Umbraco.Core/Services/LocalizationService.cs @@ -18,26 +18,10 @@ namespace Umbraco.Core.Services /// public class LocalizationService : RepositoryService, ILocalizationService { + - [Obsolete("Use the constructors that specify all dependencies instead")] - public LocalizationService() - : this(new RepositoryFactory(ApplicationContext.Current.ApplicationCache, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider, UmbracoConfig.For.UmbracoSettings())) - { } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public LocalizationService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(LoggerResolver.Current.Logger), repositoryFactory, LoggerResolver.Current.Logger) - { - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public LocalizationService(IDatabaseUnitOfWorkProvider provider) - : this(provider, new RepositoryFactory(ApplicationContext.Current.ApplicationCache, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider, UmbracoConfig.For.UmbracoSettings()), LoggerResolver.Current.Logger) - { - } - - public LocalizationService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public LocalizationService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } @@ -139,10 +123,6 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { return repository.Get(id); - //var query = Query.Builder.Where(x => x.Key == id); - //var items = repository.GetByQuery(query); - - //return items.FirstOrDefault(); } } @@ -156,10 +136,6 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { return repository.Get(key); - //var query = Query.Builder.Where(x => x.ItemKey == key); - //var items = repository.GetByQuery(query); - - //return items.FirstOrDefault(); } } @@ -179,6 +155,19 @@ namespace Umbraco.Core.Services } } + /// + /// Gets a list of descendants for a + /// + /// Id of the parent, null will return all dictionary items + /// An enumerable list of objects + public IEnumerable GetDictionaryItemDescendants(Guid? parentId) + { + using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetDictionaryItemDescendants(parentId); + } + } + /// /// Gets the root/top objects /// @@ -204,10 +193,6 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { return repository.Get(key) != null; - //var query = Query.Builder.Where(x => x.ItemKey == key); - //var items = repository.GetByQuery(query); - - //return items.Any(); } } diff --git a/src/Umbraco.Core/Services/MacroService.cs b/src/Umbraco.Core/Services/MacroService.cs index bfa201e718..49853eab81 100644 --- a/src/Umbraco.Core/Services/MacroService.cs +++ b/src/Umbraco.Core/Services/MacroService.cs @@ -20,26 +20,8 @@ namespace Umbraco.Core.Services public class MacroService : RepositoryService, IMacroService { - [Obsolete("Use the constructors that specify all dependencies instead")] - public MacroService() - : this(new PetaPocoUnitOfWorkProvider(), new RepositoryFactory()) - { - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MacroService(IDatabaseUnitOfWorkProvider provider) - : this(provider, new RepositoryFactory()) - { - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MacroService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) - : this(provider, repositoryFactory, LoggerResolver.Current.Logger) - { - } - - public MacroService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public MacroService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 299d089319..9e5c206650 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Services /// /// Represents the Media Service, which is an easy access to operations involving /// - public class MediaService : RepositoryService, IMediaService + public class MediaService : RepositoryService, IMediaService, IMediaServiceOperations { //Support recursive locks because some of the methods that require locking call other methods that require locking. @@ -35,29 +35,9 @@ namespace Umbraco.Core.Services private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; private readonly IUserService _userService; - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MediaService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) - { - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) - : base(provider, repositoryFactory, LoggerResolver.Current.Logger) - { - _dataTypeService = new DataTypeService(provider, repositoryFactory); - _userService = new UserService(provider, repositoryFactory); - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IDataTypeService dataTypeService, IUserService userService) - : this(provider, repositoryFactory, LoggerResolver.Current.Logger, dataTypeService, userService) - { - } - - public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IDataTypeService dataTypeService, IUserService userService) - : base(provider, repositoryFactory, logger) + + public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, IUserService userService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); if (userService == null) throw new ArgumentNullException("userService"); @@ -293,6 +273,8 @@ namespace Umbraco.Core.Services /// public IEnumerable GetByIds(IEnumerable ids) { + if (ids.Any() == false) return Enumerable.Empty(); + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { return repository.GetAll(ids.ToArray()); @@ -744,54 +726,144 @@ namespace Umbraco.Core.Services /// Id of the User deleting the Media public void MoveToRecycleBin(IMedia media, int userId = 0) { - if (media == null) throw new ArgumentNullException("media"); + ((IMediaServiceOperations) this).MoveToRecycleBin(media, userId); + } - var originalPath = media.Path; + /// + /// Permanently deletes an object + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// but current not from the file system. + /// + /// The to delete + /// Id of the User deleting the Media + Attempt IMediaServiceOperations.Delete(IMedia media, int userId) + { + //TODO: IT would be much nicer to mass delete all in one trans in the repo level! + var evtMsgs = EventMessagesFactory.Get(); - if (Trashing.IsRaisedEventCancelled( - new MoveEventArgs( - new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) + if (Deleting.IsRaisedEventCancelled( + new DeleteEventArgs(media, evtMsgs), this)) { - return; + return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); } - var moveInfo = new List> + //Delete children before deleting the 'possible parent' + var children = GetChildren(media.Id); + foreach (var child in children) { - new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia) - }; - - //Find Descendants, which will be moved to the recycle bin along with the parent/grandparent. - var descendants = GetDescendants(media).OrderBy(x => x.Level).ToList(); + Delete(child, userId); + } var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { - //TODO: This should be part of the repo! + repository.Delete(media); + uow.Commit(); - //Remove 'published' xml from the cmsContentXml table for the unpublished media - uow.Database.Delete("WHERE nodeId = @Id", new { Id = media.Id }); + var args = new DeleteEventArgs(media, false, evtMsgs); + Deleted.RaiseEvent(args, this); - media.ChangeTrashedState(true, Constants.System.RecycleBinMedia); - repository.AddOrUpdate(media); + //remove any flagged media files + repository.DeleteMediaFiles(args.MediaFilesToDelete); + } - //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId - foreach (var descendant in descendants) + Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); + + return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + } + + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt IMediaServiceOperations.Save(IMedia media, int userId, bool raiseEvents) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(media, evtMsgs), + this)) { - //Remove 'published' xml from the cmsContentXml table for the unpublished media - uow.Database.Delete("WHERE nodeId = @Id", new { Id = descendant.Id }); + return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + } - descendant.ChangeTrashedState(true, descendant.ParentId); - repository.AddOrUpdate(descendant); + } - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); } uow.Commit(); } - Trashed.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this); + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(media, false, evtMsgs), this); - Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); + Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); + + return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + } + + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt IMediaServiceOperations.Save(IEnumerable medias, int userId, bool raiseEvents) + { + var asArray = medias.ToArray(); + var evtMsgs = EventMessagesFactory.Get(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(asArray, evtMsgs), + this)) + { + return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + } + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + foreach (var media in asArray) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + } + + //commit the whole lot in one go + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(asArray, false, evtMsgs), this); + + Audit(AuditType.Save, "Save Media items performed by user", userId, -1); + + return Attempt.Succeed(OperationStatus.Success(evtMsgs)); } /// @@ -877,6 +949,67 @@ namespace Umbraco.Core.Services } } + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + Attempt IMediaServiceOperations.MoveToRecycleBin(IMedia media, int userId) + { + if (media == null) throw new ArgumentNullException("media"); + + var originalPath = media.Path; + + var evtMsgs = EventMessagesFactory.Get(); + + if (Trashing.IsRaisedEventCancelled( + new MoveEventArgs(new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) + { + return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + } + + var moveInfo = new List> + { + new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia) + }; + + //Find Descendants, which will be moved to the recycle bin along with the parent/grandparent. + var descendants = GetDescendants(media).OrderBy(x => x.Level).ToList(); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + //TODO: This should be part of the repo! + + //Remove 'published' xml from the cmsContentXml table for the unpublished media + uow.Database.Delete("WHERE nodeId = @Id", new { Id = media.Id }); + + media.ChangeTrashedState(true, Constants.System.RecycleBinMedia); + repository.AddOrUpdate(media); + + //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId + foreach (var descendant in descendants) + { + //Remove 'published' xml from the cmsContentXml table for the unpublished media + uow.Database.Delete("WHERE nodeId = @Id", new { Id = descendant.Id }); + + 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); + + Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); + + return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + } + /// /// Permanently deletes an object as well as all of its Children. /// @@ -888,34 +1021,11 @@ namespace Umbraco.Core.Services /// Id of the User deleting the Media public void Delete(IMedia media, int userId = 0) { - //TODO: IT would be much nicer to mass delete all in one trans in the repo level! - - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(media), this)) - return; - - //Delete children before deleting the 'possible parent' - var children = GetChildren(media.Id); - foreach (var child in children) - { - Delete(child, userId); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - repository.Delete(media); - uow.Commit(); - - var args = new DeleteEventArgs(media, false); - Deleted.RaiseEvent(args, this); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); - } - - Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); + } + + /// /// Permanently deletes versions from an object prior to a specific date. /// This method will never delete the latest version of a content item. @@ -970,7 +1080,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Delete, "Delete Media by version performed by user", userId, -1); } - + /// /// Saves a single object /// @@ -979,31 +1089,7 @@ namespace Umbraco.Core.Services /// Optional boolean indicating whether or not to raise events. public void Save(IMedia media, int userId = 0, bool raiseEvents = true) { - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) - return; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(media, false), this); - - Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); + ((IMediaServiceOperations)this).Save (media, userId, raiseEvents); } /// @@ -1014,37 +1100,7 @@ namespace Umbraco.Core.Services /// Optional boolean indicating whether or not to raise events. public void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true) { - var asArray = medias.ToArray(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - foreach (var media in asArray) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - } - - //commit the whole lot in one go - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); - - Audit(AuditType.Save, "Save Media items performed by user", userId, -1); + ((IMediaServiceOperations)this).Save(medias, userId, raiseEvents); } /// diff --git a/src/Umbraco.Core/Services/MemberGroupService.cs b/src/Umbraco.Core/Services/MemberGroupService.cs index 0f75bec93e..47b62917fc 100644 --- a/src/Umbraco.Core/Services/MemberGroupService.cs +++ b/src/Umbraco.Core/Services/MemberGroupService.cs @@ -14,26 +14,8 @@ namespace Umbraco.Core.Services public class MemberGroupService : RepositoryService, IMemberGroupService { - [Obsolete("Use the constructors that specify all dependencies instead")] - public MemberGroupService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) - { - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MemberGroupService(IDatabaseUnitOfWorkProvider provider) - : this(provider, new RepositoryFactory()) - { - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MemberGroupService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) - : this(provider, repositoryFactory, LoggerResolver.Current.Logger) - { - } - - public MemberGroupService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public MemberGroupService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { //Proxy events! MemberGroupRepository.SavedMemberGroup += MemberGroupRepository_SavedMemberGroup; diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index c6c49da7e9..fdfe401ca9 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -30,37 +30,9 @@ namespace Umbraco.Core.Services private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MemberService(RepositoryFactory repositoryFactory, IMemberGroupService memberGroupService) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory, memberGroupService) - { - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MemberService(IDatabaseUnitOfWorkProvider provider, IMemberGroupService memberGroupService) - : this(provider, new RepositoryFactory(), memberGroupService) - { - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IMemberGroupService memberGroupService) - : base(provider, repositoryFactory, LoggerResolver.Current.Logger) - { - if (memberGroupService == null) throw new ArgumentNullException("memberGroupService"); - _memberGroupService = memberGroupService; - _dataTypeService = new DataTypeService(provider, repositoryFactory); - } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MemberService(IDatabaseUnitOfWorkProvider provider, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) - : this(provider, new RepositoryFactory(), LoggerResolver.Current.Logger, memberGroupService, dataTypeService) - { - - } - - public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) - : base(provider, repositoryFactory, logger) + + public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { if (memberGroupService == null) throw new ArgumentNullException("memberGroupService"); if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs index 022b8721ef..73061be4d0 100644 --- a/src/Umbraco.Core/Services/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -17,25 +17,10 @@ namespace Umbraco.Core.Services private readonly IMemberService _memberService; private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + - [Obsolete("Use the constructors that specify all dependencies instead")] - public MemberTypeService(IMemberService memberService) - : this(new PetaPocoUnitOfWorkProvider(), new RepositoryFactory(), memberService) - {} - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MemberTypeService(RepositoryFactory repositoryFactory, IMemberService memberService) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory, memberService) - { } - - [Obsolete("Use the constructors that specify all dependencies instead")] - public MemberTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IMemberService memberService) - : this(provider, repositoryFactory, LoggerResolver.Current.Logger, memberService) - { - } - - public MemberTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IMemberService memberService) - : base(provider, repositoryFactory, logger) + public MemberTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberService memberService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { if (memberService == null) throw new ArgumentNullException("memberService"); _memberService = memberService; @@ -62,6 +47,19 @@ namespace Umbraco.Core.Services } } + /// + /// Gets an object by its Key + /// + /// Key of the to retrieve + /// + public IMemberType Get(Guid key) + { + using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) + { + return repository.Get(key); + } + } + /// /// Gets an object by its Alias /// diff --git a/src/Umbraco.Core/Services/MigrationEntryService.cs b/src/Umbraco.Core/Services/MigrationEntryService.cs index 6d441b5271..2bd463c6d0 100644 --- a/src/Umbraco.Core/Services/MigrationEntryService.cs +++ b/src/Umbraco.Core/Services/MigrationEntryService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Semver; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; @@ -15,8 +16,8 @@ namespace Umbraco.Core.Services /// public sealed class MigrationEntryService : RepositoryService, IMigrationEntryService { - public MigrationEntryService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public MigrationEntryService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } diff --git a/src/Umbraco.Core/Services/OperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus.cs new file mode 100644 index 0000000000..31b684c4f1 --- /dev/null +++ b/src/Umbraco.Core/Services/OperationStatus.cs @@ -0,0 +1,61 @@ +using System; +using Umbraco.Core.Events; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + /// + /// The status returned by many of the service methods + /// + public class OperationStatus : OperationStatus + where TStatus : struct + { + public OperationStatus(TEntity entity, TStatus statusType, EventMessages eventMessages) : base(statusType, eventMessages) + { + Entity = entity; + } + + public TEntity Entity { get; private set; } + + } + + public class OperationStatus + where TStatus : struct + { + public OperationStatus(TStatus statusType, EventMessages eventMessages) + { + if (eventMessages == null) throw new ArgumentNullException("eventMessages"); + StatusType = statusType; + EventMessages = eventMessages; + } + + public TStatus StatusType { get; internal set; } + public EventMessages EventMessages { get; private set; } + } + + /// + /// The default operation status + /// + public class OperationStatus : OperationStatus + { + public OperationStatus(OperationStatusType statusType, EventMessages eventMessages) : base(statusType, eventMessages) + { + } + + + #region Static Helper methods + + internal static OperationStatus Cancelled(EventMessages eventMessages) + { + return new OperationStatus(OperationStatusType.FailedCancelledByEvent, eventMessages); + } + + internal static OperationStatus Success(EventMessages eventMessages) + { + return new OperationStatus(OperationStatusType.Success, eventMessages); + } + + #endregion + } + +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/OperationStatusType.cs b/src/Umbraco.Core/Services/OperationStatusType.cs new file mode 100644 index 0000000000..7398572810 --- /dev/null +++ b/src/Umbraco.Core/Services/OperationStatusType.cs @@ -0,0 +1,23 @@ +namespace Umbraco.Core.Services +{ + /// + /// A status type of the result of publishing a content item + /// + /// + /// Anything less than 10 = Success! + /// + public enum OperationStatusType + { + /// + /// The saving was successful. + /// + Success = 0, + + /// + /// The saving has been cancelled by a 3rd party add-in + /// + FailedCancelledByEvent = 14 + + //TODO: In the future, we might need to add more operations statuses, potentially like 'FailedByPermissions', etc... + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 5b3c8bc7c6..8ca9d3e3da 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -1219,7 +1219,7 @@ namespace Umbraco.Core.Services sortOrder = int.Parse(sortOrderAttribute.Value); } - if (macro.Properties.Any(x => x.Alias == propertyAlias)) continue; + if (macro.Properties.Any(x => string.Equals(x.Alias, propertyAlias, StringComparison.OrdinalIgnoreCase))) continue; macro.Properties.Add(new MacroProperty(propertyAlias, propertyName, sortOrder, editorAlias)); sortOrder++; } diff --git a/src/Umbraco.Core/Services/PublicAccessService.cs b/src/Umbraco.Core/Services/PublicAccessService.cs index b8e95a33a9..6ca6660895 100644 --- a/src/Umbraco.Core/Services/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/PublicAccessService.cs @@ -12,8 +12,8 @@ namespace Umbraco.Core.Services { public class PublicAccessService : RepositoryService, IPublicAccessService { - public PublicAccessService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public PublicAccessService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs index db2328047d..b4a37fc1d1 100644 --- a/src/Umbraco.Core/Services/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -14,15 +14,9 @@ namespace Umbraco.Core.Services public class RelationService : RepositoryService, IRelationService { private readonly IEntityService _entityService; - - [Obsolete("Use the constructors that specify all dependencies instead")] - public RelationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, IEntityService entityService) - : this(uowProvider, repositoryFactory, LoggerResolver.Current.Logger, entityService) - { - } - - public RelationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, ILogger logger, IEntityService entityService) - : base(uowProvider, repositoryFactory, logger) + + public RelationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IEntityService entityService) + : base(uowProvider, repositoryFactory, logger, eventMessagesFactory) { if (entityService == null) throw new ArgumentNullException("entityService"); _entityService = entityService; diff --git a/src/Umbraco.Core/Services/RepositoryService.cs b/src/Umbraco.Core/Services/RepositoryService.cs index e1c70ab580..88807f0291 100644 --- a/src/Umbraco.Core/Services/RepositoryService.cs +++ b/src/Umbraco.Core/Services/RepositoryService.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; @@ -11,15 +12,18 @@ namespace Umbraco.Core.Services public abstract class RepositoryService : IService { protected ILogger Logger { get; private set; } + protected IEventMessagesFactory EventMessagesFactory { get; private set; } protected RepositoryFactory RepositoryFactory { get; private set; } protected IDatabaseUnitOfWorkProvider UowProvider { get; private set; } - protected RepositoryService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) + protected RepositoryService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) { if (provider == null) throw new ArgumentNullException("provider"); if (repositoryFactory == null) throw new ArgumentNullException("repositoryFactory"); if (logger == null) throw new ArgumentNullException("logger"); + if (eventMessagesFactory == null) throw new ArgumentNullException("eventMessagesFactory"); Logger = logger; + EventMessagesFactory = eventMessagesFactory; RepositoryFactory = repositoryFactory; UowProvider = provider; } diff --git a/src/Umbraco.Core/Services/ServerRegistrationService.cs b/src/Umbraco.Core/Services/ServerRegistrationService.cs index 2812a9d1b8..fac56499fd 100644 --- a/src/Umbraco.Core/Services/ServerRegistrationService.cs +++ b/src/Umbraco.Core/Services/ServerRegistrationService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; @@ -20,8 +21,9 @@ namespace Umbraco.Core.Services /// A UnitOfWork provider. /// A repository factory. /// A logger. - public ServerRegistrationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, ILogger logger) - : base(uowProvider, repositoryFactory, logger) + /// + public ServerRegistrationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(uowProvider, repositoryFactory, logger, eventMessagesFactory) { } /// diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index c1aacb550e..f8a78aed3d 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -8,9 +8,29 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; using umbraco.interfaces; +using Umbraco.Core.Events; namespace Umbraco.Core.Services { + /// + /// These are used currently to return the temporary 'operation' interfaces for services + /// which are used to return a status from operational methods so we can determine if things are + /// cancelled, etc... + /// + /// These will be obsoleted in v8 since all real services methods will be changed to have the correct result. + /// + public static class ServiceWithResultExtensions + { + public static IContentServiceOperations WithResult(this IContentService contentService) + { + return (IContentServiceOperations)contentService; + } + public static IMediaServiceOperations WithResult(this IMediaService mediaService) + { + return (IMediaServiceOperations)mediaService; + } + } + /// /// The Umbraco ServiceContext, which provides access to the following services: /// , , , @@ -127,17 +147,36 @@ namespace Umbraco.Core.Services if (publicAccessService != null) _publicAccessService = new Lazy(() => publicAccessService); } - internal ServiceContext( + /// + /// Creates a service context with a RepositoryFactory which is used to construct Services + /// + /// + /// + /// + /// + /// + /// + /// + public ServiceContext( RepositoryFactory repositoryFactory, IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider, BasePublishingStrategy publishingStrategy, CacheHelper cache, - ILogger logger) + ILogger logger, + IEventMessagesFactory eventMessagesFactory) { + if (repositoryFactory == null) throw new ArgumentNullException("repositoryFactory"); + if (dbUnitOfWorkProvider == null) throw new ArgumentNullException("dbUnitOfWorkProvider"); + if (fileUnitOfWorkProvider == null) throw new ArgumentNullException("fileUnitOfWorkProvider"); + if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy"); + if (cache == null) throw new ArgumentNullException("cache"); + if (logger == null) throw new ArgumentNullException("logger"); + if (eventMessagesFactory == null) throw new ArgumentNullException("eventMessagesFactory"); + BuildServiceCache(dbUnitOfWorkProvider, fileUnitOfWorkProvider, publishingStrategy, cache, repositoryFactory, - logger); + logger, eventMessagesFactory); } /// @@ -149,28 +188,29 @@ namespace Umbraco.Core.Services BasePublishingStrategy publishingStrategy, CacheHelper cache, RepositoryFactory repositoryFactory, - ILogger logger) + ILogger logger, + IEventMessagesFactory eventMessagesFactory) { var provider = dbUnitOfWorkProvider; var fileProvider = fileUnitOfWorkProvider; if (_migrationEntryService == null) - _migrationEntryService = new Lazy(() => new MigrationEntryService(provider, repositoryFactory, logger)); + _migrationEntryService = new Lazy(() => new MigrationEntryService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_externalLoginService == null) - _externalLoginService = new Lazy(() => new ExternalLoginService(provider, repositoryFactory, logger)); + _externalLoginService = new Lazy(() => new ExternalLoginService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_publicAccessService == null) - _publicAccessService = new Lazy(() => new PublicAccessService(provider, repositoryFactory, logger)); + _publicAccessService = new Lazy(() => new PublicAccessService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_taskService == null) - _taskService = new Lazy(() => new TaskService(provider, repositoryFactory, logger)); + _taskService = new Lazy(() => new TaskService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_domainService == null) - _domainService = new Lazy(() => new DomainService(provider, repositoryFactory, logger)); + _domainService = new Lazy(() => new DomainService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_auditService == null) - _auditService = new Lazy(() => new AuditService(provider, repositoryFactory, logger)); + _auditService = new Lazy(() => new AuditService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_localizedTextService == null) { @@ -213,43 +253,43 @@ namespace Umbraco.Core.Services _notificationService = new Lazy(() => new NotificationService(provider, _userService.Value, _contentService.Value, logger)); if (_serverRegistrationService == null) - _serverRegistrationService = new Lazy(() => new ServerRegistrationService(provider, repositoryFactory, logger)); + _serverRegistrationService = new Lazy(() => new ServerRegistrationService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_userService == null) - _userService = new Lazy(() => new UserService(provider, repositoryFactory, logger)); + _userService = new Lazy(() => new UserService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_memberService == null) - _memberService = new Lazy(() => new MemberService(provider, repositoryFactory, logger, _memberGroupService.Value, _dataTypeService.Value)); + _memberService = new Lazy(() => new MemberService(provider, repositoryFactory, logger, eventMessagesFactory, _memberGroupService.Value, _dataTypeService.Value)); if (_contentService == null) - _contentService = new Lazy(() => new ContentService(provider, repositoryFactory, logger, publishingStrategy, _dataTypeService.Value, _userService.Value)); + _contentService = new Lazy(() => new ContentService(provider, repositoryFactory, logger, eventMessagesFactory, publishingStrategy, _dataTypeService.Value, _userService.Value)); if (_mediaService == null) - _mediaService = new Lazy(() => new MediaService(provider, repositoryFactory, logger, _dataTypeService.Value, _userService.Value)); + _mediaService = new Lazy(() => new MediaService(provider, repositoryFactory, logger, eventMessagesFactory, _dataTypeService.Value, _userService.Value)); if (_contentTypeService == null) - _contentTypeService = new Lazy(() => new ContentTypeService(provider, repositoryFactory, logger, _contentService.Value, _mediaService.Value)); + _contentTypeService = new Lazy(() => new ContentTypeService(provider, repositoryFactory, logger, eventMessagesFactory, _contentService.Value, _mediaService.Value)); if (_dataTypeService == null) - _dataTypeService = new Lazy(() => new DataTypeService(provider, repositoryFactory, logger)); + _dataTypeService = new Lazy(() => new DataTypeService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_fileService == null) _fileService = new Lazy(() => new FileService(fileProvider, provider, repositoryFactory)); if (_localizationService == null) - _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory, logger)); + _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_packagingService == null) _packagingService = new Lazy(() => new PackagingService(logger, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _macroService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, _userService.Value, repositoryFactory, provider)); if (_entityService == null) _entityService = new Lazy(() => new EntityService( - provider, repositoryFactory, logger, + provider, repositoryFactory, logger, eventMessagesFactory, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _memberService.Value, _memberTypeService.Value, cache.RuntimeCache)); if (_relationService == null) - _relationService = new Lazy(() => new RelationService(provider, repositoryFactory, logger, _entityService.Value)); + _relationService = new Lazy(() => new RelationService(provider, repositoryFactory, logger, eventMessagesFactory, _entityService.Value)); if (_treeService == null) _treeService = new Lazy(() => new ApplicationTreeService(logger, cache)); @@ -258,16 +298,16 @@ namespace Umbraco.Core.Services _sectionService = new Lazy(() => new SectionService(_userService.Value, _treeService.Value, provider, cache)); if (_macroService == null) - _macroService = new Lazy(() => new MacroService(provider, repositoryFactory, logger)); + _macroService = new Lazy(() => new MacroService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_memberTypeService == null) - _memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory, logger, _memberService.Value)); + _memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory, logger, eventMessagesFactory, _memberService.Value)); if (_tagService == null) - _tagService = new Lazy(() => new TagService(provider, repositoryFactory, logger)); + _tagService = new Lazy(() => new TagService(provider, repositoryFactory, logger, eventMessagesFactory)); if (_memberGroupService == null) - _memberGroupService = new Lazy(() => new MemberGroupService(provider, repositoryFactory, logger)); + _memberGroupService = new Lazy(() => new MemberGroupService(provider, repositoryFactory, logger, eventMessagesFactory)); } diff --git a/src/Umbraco.Core/Services/TagService.cs b/src/Umbraco.Core/Services/TagService.cs index 6054d29323..d3b502e14b 100644 --- a/src/Umbraco.Core/Services/TagService.cs +++ b/src/Umbraco.Core/Services/TagService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; @@ -16,33 +17,26 @@ namespace Umbraco.Core.Services /// public class TagService : RepositoryService, ITagService { - - [Obsolete("Use the constructors that specify all dependencies instead")] - public TagService() - : this(new RepositoryFactory()) - {} - - [Obsolete("Use the constructors that specify all dependencies instead")] - public TagService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) + + public TagService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } - [Obsolete("Use the constructors that specify all dependencies instead")] - public TagService(IDatabaseUnitOfWorkProvider provider) - : this(provider, new RepositoryFactory()) + public TaggedEntity GetTaggedEntityById(int id) { + using (var repository = RepositoryFactory.CreateTagRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetTaggedEntityById(id); + } } - [Obsolete("Use the constructors that specify all dependencies instead")] - public TagService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) - : this(provider, repositoryFactory, LoggerResolver.Current.Logger) - { - } - - public TagService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public TaggedEntity GetTaggedEntityByKey(Guid key) { + using (var repository = RepositoryFactory.CreateTagRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetTaggedEntityByKey(key); + } } /// @@ -222,5 +216,38 @@ namespace Umbraco.Core.Services return repository.GetTagsForEntity(contentId, tagGroup); } } + + /// + /// Gets all tags attached to a property by entity id + /// + /// Use the optional tagGroup parameter to limit the + /// result to a specific 'Tag Group'. + /// The content item id to get tags for + /// Property type alias + /// Optional name of the 'Tag Group' + /// An enumerable list of + public IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string tagGroup = null) + { + using (var repository = RepositoryFactory.CreateTagRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup); + } + } + + /// + /// Gets all tags attached to an entity (content, media or member) by entity id + /// + /// Use the optional tagGroup parameter to limit the + /// result to a specific 'Tag Group'. + /// The content item id to get tags for + /// Optional name of the 'Tag Group' + /// An enumerable list of + public IEnumerable GetTagsForEntity(Guid contentId, string tagGroup = null) + { + using (var repository = RepositoryFactory.CreateTagRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetTagsForEntity(contentId, tagGroup); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/TaskService.cs b/src/Umbraco.Core/Services/TaskService.cs index 3f51b92185..9845c5ebb9 100644 --- a/src/Umbraco.Core/Services/TaskService.cs +++ b/src/Umbraco.Core/Services/TaskService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; @@ -11,8 +12,8 @@ namespace Umbraco.Core.Services { public class TaskService : RepositoryService, ITaskService { - public TaskService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public TaskService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index dd4866c8c3..74002646da 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Data.Common; +using System.Data.SqlClient; using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Logging; @@ -17,26 +19,16 @@ namespace Umbraco.Core.Services /// public class UserService : RepositoryService, IUserService { - - [Obsolete("Use the constructors that specify all dependencies instead")] - public UserService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) - { } - [Obsolete("Use the constructors that specify all dependencies instead")] - public UserService(IDatabaseUnitOfWorkProvider provider) - : this(provider, new RepositoryFactory()) - { } + //TODO: We need to change the isUpgrading flag to use an app state enum as described here: http://issues.umbraco.org/issue/U4-6816 + // in the meantime, we will use a boolean which we are currently using during upgrades to ensure that a user object is not persisted during this phase, otherwise + // exceptions can occur if the db is not in it's correct state. + internal bool IsUpgrading { get; set; } - [Obsolete("Use the constructors that specify all dependencies instead")] - public UserService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) - : base(provider, repositoryFactory, LoggerResolver.Current.Logger) - { - } - - public UserService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) - : base(provider, repositoryFactory, logger) + public UserService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) { + IsUpgrading = false; } #region Implementation of IMembershipUserService @@ -320,7 +312,18 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateUserRepository(uow)) { repository.AddOrUpdate(entity); - uow.Commit(); + try + { + uow.Commit(); + } + catch (DbException ex) + { + //Special case, if we are upgrading and an exception occurs, just continue + if (IsUpgrading == false) throw; + + Logger.WarnWithException("An error occurred attempting to save a user instance during upgrade, normally this warning can be ignored", ex); + return; + } } if (raiseEvents) diff --git a/src/Umbraco.Core/Standalone/ServiceContextManager.cs b/src/Umbraco.Core/Standalone/ServiceContextManager.cs deleted file mode 100644 index b036359dc6..0000000000 --- a/src/Umbraco.Core/Standalone/ServiceContextManager.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Diagnostics; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Standalone -{ - internal class ServiceContextManager : IDisposable - { - private readonly string _connectionString; - private readonly string _providerName; - private readonly ILogger _logger; - private readonly ISqlSyntaxProvider _syntaxProvider; - private ServiceContext _serviceContext; - private readonly StandaloneCoreApplication _application; - - public ServiceContextManager(string connectionString, string providerName, string baseDirectory, ILogger logger, ISqlSyntaxProvider syntaxProvider) - { - _connectionString = connectionString; - _providerName = providerName; - _logger = logger; - _syntaxProvider = syntaxProvider; - - Trace.WriteLine("ServiceContextManager-Current AppDomain: " + AppDomain.CurrentDomain.FriendlyName); - Trace.WriteLine("ServiceContextManager-Current AppDomain: " + AppDomain.CurrentDomain.BaseDirectory); - - //var webAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.FullName.StartsWith("umbraco")); - //if (webAssembly != null && examineEventHandlerToRemove == null) - //{ - // var examineEventType = webAssembly.GetType("Umbraco.Web.Search.ExamineEvents"); - // examineEventHandlerToRemove = examineEventType; - //} - - _application = StandaloneCoreApplication.GetApplication(baseDirectory); - - var examineEventHandlerToRemove = Type.GetType("Umbraco.Web.Search.ExamineEvents, umbraco"); - if (examineEventHandlerToRemove != null) - _application.WithoutApplicationEventHandler(examineEventHandlerToRemove); - - _application.Start(); - } - - public ServiceContext Services - { - get - { - if (_serviceContext == null) - { - var cacheHelper = new CacheHelper( - new ObjectCacheRuntimeCacheProvider(), - new StaticCacheProvider(), - //we have no request based cache when running standalone - new NullCacheProvider()); - - var dbFactory = new DefaultDatabaseFactory(_connectionString, _providerName, _logger); - var dbContext = new DatabaseContext(dbFactory, _logger, _syntaxProvider, _providerName); - Database.Mapper = new PetaPocoMapper(); - _serviceContext = new ServiceContext( - new RepositoryFactory(cacheHelper, _logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), - new PetaPocoUnitOfWorkProvider(dbFactory), - new FileUnitOfWorkProvider(), - new PublishingStrategy(), - cacheHelper, - new DebugDiagnosticsLogger()); - - //initialize the DatabaseContext - dbContext.Initialize(_providerName); - } - - return _serviceContext; - } - } - - public void Dispose() - { - ((IDisposable)ApplicationContext.Current).Dispose(); - _application.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Standalone/StandaloneCoreApplication.cs b/src/Umbraco.Core/Standalone/StandaloneCoreApplication.cs deleted file mode 100644 index c22a648478..0000000000 --- a/src/Umbraco.Core/Standalone/StandaloneCoreApplication.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Standalone -{ - internal class StandaloneCoreApplication : UmbracoApplicationBase - { - /// - /// Initializes a new instance of the class. - /// - protected StandaloneCoreApplication(string baseDirectory) - { - _baseDirectory = baseDirectory; - } - - /// - /// Provides the application boot manager. - /// - /// An application boot manager. - protected override IBootManager GetBootManager() - { - return new StandaloneCoreBootManager(this, _handlersToAdd, _handlersToRemove, _baseDirectory); - } - - #region Application - - private readonly string _baseDirectory; - private static StandaloneCoreApplication _application; - private static bool _started; - private static readonly object AppLock = new object(); - - /// - /// Gets the instance of the standalone Umbraco application. - /// - public static StandaloneCoreApplication GetApplication(string baseDirectory) - { - lock (AppLock) - { - return _application ?? (_application = new StandaloneCoreApplication(baseDirectory)); - } - } - - /// - /// Starts the application. - /// - public void Start() - { - lock (AppLock) - { - if (_started) - throw new InvalidOperationException("Application has already started."); - Application_Start(this, EventArgs.Empty); - _started = true; - } - } - - #endregion - - #region IApplicationEventHandler management - - private readonly List _handlersToAdd = new List(); - private readonly List _handlersToRemove = new List(); - - /// - /// Associates an type with the application. - /// - /// The type to associate. - /// The application. - /// Types implementing from within - /// an executable are not automatically discovered by Umbraco and have to be - /// explicitely associated with the application using this method. - public StandaloneCoreApplication WithApplicationEventHandler() - where T : IApplicationEventHandler - { - _handlersToAdd.Add(typeof(T)); - return this; - } - - /// - /// Dissociates an type from the application. - /// - /// The type to dissociate. - /// The application. - public StandaloneCoreApplication WithoutApplicationEventHandler() - where T : IApplicationEventHandler - { - _handlersToRemove.Add(typeof(T)); - return this; - } - - /// - /// Associates an type with the application. - /// - /// The type to associate. - /// The application. - /// Types implementing from within - /// an executable are not automatically discovered by Umbraco and have to be - /// explicitely associated with the application using this method. - public StandaloneCoreApplication WithApplicationEventHandler(Type type) - { - if (type.Implements() == false) - throw new ArgumentException("Type does not implement IApplicationEventHandler.", "type"); - _handlersToAdd.Add(type); - return this; - } - - /// - /// Dissociates an type from the application. - /// - /// The type to dissociate. - /// The application. - public StandaloneCoreApplication WithoutApplicationEventHandler(Type type) - { - if (type.Implements() == false) - throw new ArgumentException("Type does not implement IApplicationEventHandler.", "type"); - _handlersToRemove.Add(type); - return this; - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Standalone/StandaloneCoreBootManager.cs b/src/Umbraco.Core/Standalone/StandaloneCoreBootManager.cs deleted file mode 100644 index 84c73f95b7..0000000000 --- a/src/Umbraco.Core/Standalone/StandaloneCoreBootManager.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Persistence.Mappers; -using umbraco.interfaces; - -namespace Umbraco.Core.Standalone -{ - internal class StandaloneCoreBootManager : CoreBootManager - { - private readonly IEnumerable _handlersToAdd; - private readonly IEnumerable _handlersToRemove; - private readonly string _baseDirectory; - - public StandaloneCoreBootManager(UmbracoApplicationBase umbracoApplication, IEnumerable handlersToAdd, IEnumerable handlersToRemove, string baseDirectory) - : base(umbracoApplication) - { - _handlersToAdd = handlersToAdd; - _handlersToRemove = handlersToRemove; - _baseDirectory = baseDirectory; - - base.InitializeApplicationRootPath(_baseDirectory); - - // this is only here to ensure references to the assemblies needed for - // the DataTypesResolver otherwise they won't be loaded into the AppDomain. - var interfacesAssemblyName = typeof(IDataType).Assembly.FullName; - } - - protected override void InitializeApplicationEventsResolver() - { - base.InitializeApplicationEventsResolver(); - - foreach (var type in _handlersToAdd) - ApplicationEventsResolver.Current.AddType(type); - - foreach (var type in _handlersToRemove) - ApplicationEventsResolver.Current.RemoveType(type); - } - - protected override void InitializeResolvers() - { - base.InitializeResolvers(); - - //Mappers are not resolved, which could be because of a known TypeMapper issue - /*MappingResolver.Reset(); - MappingResolver.Current = new MappingResolver( - () => - new List - { - typeof (ContentMapper), - typeof (ContentTypeMapper), - typeof (MediaMapper), - typeof (MediaTypeMapper), - typeof (DataTypeDefinitionMapper), - typeof (UmbracoEntityMapper) - });*/ - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 0e32991d0d..e54164bd39 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Configuration; using System.Web.Security; using Umbraco.Core.Strings; using Umbraco.Core.CodeAnnotations; +using Umbraco.Core.IO; namespace Umbraco.Core { @@ -59,6 +60,50 @@ namespace Umbraco.Core } + /// + /// Based on the input string, this will detect if the strnig is a JS path or a JS snippet. + /// If a path cannot be determined, then it is assumed to be a snippet the original text is returned + /// with an invalid attempt, otherwise a valid attempt is returned with the resolved path + /// + /// + /// + /// + /// This is only used for legacy purposes for the Action.JsSource stuff and shouldn't be needed in v8 + /// + internal static Attempt DetectIsJavaScriptPath(this string input) + { + //validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text + //block instead. + var isValid = true; + + if (Uri.IsWellFormedUriString(input, UriKind.RelativeOrAbsolute)) + { + //ok it validates, but so does alert('hello'); ! so we need to do more checks + + //here are the valid chars in a url without escaping + if (Regex.IsMatch(input, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]")) + isValid = false; + + //we'll have to be smarter and just check for certain js patterns now too! + var jsPatterns = new[] { @"\+\s*\=", @"\);", @"function\s*\(", @"!=", @"==" }; + if (jsPatterns.Any(p => Regex.IsMatch(input, p))) + isValid = false; + + if (isValid) + { + var resolvedUrlResult = IOHelper.TryResolveUrl(input); + //if the resolution was success, return it, otherwise just return the path, we've detected + // it's a path but maybe it's relative and resolution has failed, etc... in which case we're just + // returning what was given to us. + return resolvedUrlResult.Success + ? resolvedUrlResult + : Attempt.Succeed(input); + } + } + + return Attempt.Fail(input); + } + /// /// This tries to detect a json string, this is not a fail safe way but it is quicker than doing /// a try/catch when deserializing when it is not json. @@ -948,7 +993,7 @@ namespace Umbraco.Core // as the ShortStringHelper is too important, so as long as it's not there // already, we use a default one. That should never happen, but... Logging.LogHelper.Warn("ShortStringHelperResolver.HasCurrent == false, fallback to default."); - _helper = new DefaultShortStringHelper().WithDefaultConfig(); + _helper = new DefaultShortStringHelper(UmbracoConfig.For.UmbracoSettings()).WithDefaultConfig(); _helper.Freeze(); return _helper; } diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index 3414389704..96b8187e22 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Globalization; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Core.Strings { @@ -18,10 +19,20 @@ namespace Umbraco.Core.Strings /// public class DefaultShortStringHelper : IShortStringHelper { + private readonly IUmbracoSettingsSection _umbracoSettings; + #region Ctor and vars + [Obsolete("Use the other ctor that specifies all dependencies")] public DefaultShortStringHelper() { + _umbracoSettings = _umbracoSettings; + InitializeLegacyUrlReplaceCharacters(); + } + + public DefaultShortStringHelper(IUmbracoSettingsSection umbracoSettings) + { + _umbracoSettings = umbracoSettings; InitializeLegacyUrlReplaceCharacters(); } @@ -57,7 +68,7 @@ namespace Umbraco.Core.Strings private void InitializeLegacyUrlReplaceCharacters() { - foreach (var node in UmbracoConfig.For.UmbracoSettings().RequestHandler.CharCollection) + foreach (var node in _umbracoSettings.RequestHandler.CharCollection) { if(string.IsNullOrEmpty(node.Char) == false) _urlReplaceCharacters[node.Char] = node.Replacement; @@ -146,7 +157,7 @@ namespace Umbraco.Core.Strings PreFilter = ApplyUrlReplaceCharacters, PostFilter = x => CutMaxLength(x, 240), IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = (UmbracoConfig.For.UmbracoSettings().RequestHandler.ConvertUrlsToAscii ? CleanStringType.Ascii : CleanStringType.Utf8) | CleanStringType.LowerCase, + StringType = (_umbracoSettings.RequestHandler.ConvertUrlsToAscii ? CleanStringType.Ascii : CleanStringType.Utf8) | CleanStringType.LowerCase, BreakTermsOnUpper = false, Separator = '-' }).WithConfig(CleanStringType.FileName, new Config @@ -323,7 +334,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ public string GetShortStringServicesJavaScript(string controllerPath) { return string.Format(SssjsFormat, - UmbracoConfig.For.UmbracoSettings().Content.ForceSafeAliases ? "true" : "false", controllerPath); + _umbracoSettings.Content.ForceSafeAliases ? "true" : "false", controllerPath); } #endregion diff --git a/src/Umbraco.Core/Sync/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/BatchedDatabaseServerMessenger.cs deleted file mode 100644 index b16caa8779..0000000000 --- a/src/Umbraco.Core/Sync/BatchedDatabaseServerMessenger.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Umbraco.Core.Models.Rdbms; -using umbraco.interfaces; - -namespace Umbraco.Core.Sync -{ - /// - /// An that works by storing messages in the database. - /// - /// - /// abstract because it needs to be inherited by a class that will - /// - trigger FlushBatch() when appropriate - /// - trigger Boot() when appropriate - /// - trigger Sync() when appropriate - /// - public abstract class BatchedDatabaseServerMessenger : DatabaseServerMessenger - { - - protected BatchedDatabaseServerMessenger(ApplicationContext appContext, bool enableDistCalls, DatabaseServerMessengerOptions options) - : base(appContext, enableDistCalls, options) - { - - } - - protected abstract ICollection GetBatch(bool ensureHttpContext); - - public void FlushBatch() - { - var batch = GetBatch(false); - if (batch == null) return; - - var instructions = batch.SelectMany(x => x.Instructions).ToArray(); - batch.Clear(); - if (instructions.Length == 0) return; - - var dto = new CacheInstructionDto - { - UtcStamp = DateTime.UtcNow, - Instructions = JsonConvert.SerializeObject(instructions, Formatting.None), - OriginIdentity = LocalIdentity - }; - - ApplicationContext.DatabaseContext.Database.Insert(dto); - } - - protected override void DeliverRemote(IEnumerable servers, ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) - { - var idsA = ids == null ? null : ids.ToArray(); - - Type arrayType; - if (GetArrayType(idsA, out arrayType) == false) - throw new ArgumentException("All items must be of the same type, either int or Guid.", "ids"); - - BatchMessage(servers, refresher, messageType, idsA, arrayType, json); - } - - protected void BatchMessage( - IEnumerable servers, - ICacheRefresher refresher, - MessageType messageType, - IEnumerable ids = null, - Type idType = null, - string json = null) - { - var batch = GetBatch(true); - if (batch == null) - throw new Exception("Failed to get a batch."); - - batch.Add(new RefreshInstructionEnvelope(servers, refresher, - RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json))); - } - } -} diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index e9be30ab09..bf94324388 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Threading; using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -20,27 +21,27 @@ namespace Umbraco.Core.Sync /// An that works by storing messages in the database. /// // - // abstract because it needs to be inherited by a class that will - // - trigger Boot() when appropriate - // - trigger Sync() when appropriate - // // this messenger writes ALL instructions to the database, // but only processes instructions coming from remote servers, // thus ensuring that instructions run only once // - public abstract class DatabaseServerMessenger : ServerMessengerBase + public class DatabaseServerMessenger : ServerMessengerBase { private readonly ApplicationContext _appContext; private readonly DatabaseServerMessengerOptions _options; - private readonly object _lock = new object(); + private readonly ManualResetEvent _syncIdle; + private readonly object _locko = new object(); + private readonly ILogger _logger; private int _lastId = -1; - private volatile bool _syncing; private DateTime _lastSync; private bool _initialized; + private bool _syncing; + private bool _released; + private readonly ProfilingLogger _profilingLogger; protected ApplicationContext ApplicationContext { get { return _appContext; } } - protected DatabaseServerMessenger(ApplicationContext appContext, bool distributedEnabled, DatabaseServerMessengerOptions options) + public DatabaseServerMessenger(ApplicationContext appContext, bool distributedEnabled, DatabaseServerMessengerOptions options) : base(distributedEnabled) { if (appContext == null) throw new ArgumentNullException("appContext"); @@ -49,6 +50,9 @@ namespace Umbraco.Core.Sync _appContext = appContext; _options = options; _lastSync = DateTime.UtcNow; + _syncIdle = new ManualResetEvent(true); + _profilingLogger = appContext.ProfilingLogger; + _logger = appContext.ProfilingLogger.Logger; } #region Messenger @@ -98,8 +102,27 @@ namespace Umbraco.Core.Sync /// protected void Boot() { - ReadLastSynced(); - Initialize(); + // weight:10, must release *before* the facade service, because once released + // the service will *not* be able to properly handle our notifications anymore + const int weight = 10; + + var registered = ApplicationContext.MainDom.Register( + () => + { + lock (_locko) + { + _released = true; // no more syncs + } + _syncIdle.WaitOne(); // wait for pending sync + }, + weight); + + if (registered == false) + return; + + ReadLastSynced(); // get _lastId + EnsureInstructions(); // reset _lastId if instrs are missing + Initialize(); // boot } /// @@ -111,26 +134,31 @@ namespace Umbraco.Core.Sync /// private void Initialize() { - if (_lastId < 0) // never synced before + lock (_locko) { - // we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new - // server and it will need to rebuild it's own caches, eg Lucene or the xml cache file. - LogHelper.Warn("No last synced Id found, this generally means this is a new server/install. The server will rebuild its caches and indexes and then adjust it's last synced id to the latest found in the database and will start maintaining cache updates based on that id"); + if (_released) return; - // go get the last id in the db and store it - // note: do it BEFORE initializing otherwise some instructions might get lost - // when doing it before, some instructions might run twice - not an issue - var lastId = _appContext.DatabaseContext.Database.ExecuteScalar("SELECT MAX(id) FROM umbracoCacheInstruction"); - if (lastId > 0) - SaveLastSynced(lastId); + if (_lastId < 0) // never synced before + { + // we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new + // server and it will need to rebuild it's own caches, eg Lucene or the xml cache file. + _logger.Warn("No last synced Id found, this generally means this is a new server/install. The server will rebuild its caches and indexes and then adjust it's last synced id to the latest found in the database and will start maintaining cache updates based on that id"); - // execute initializing callbacks - if (_options.InitializingCallbacks != null) - foreach (var callback in _options.InitializingCallbacks) - callback(); + // go get the last id in the db and store it + // note: do it BEFORE initializing otherwise some instructions might get lost + // when doing it before, some instructions might run twice - not an issue + var lastId = _appContext.DatabaseContext.Database.ExecuteScalar("SELECT MAX(id) FROM umbracoCacheInstruction"); + if (lastId > 0) + SaveLastSynced(lastId); + + // execute initializing callbacks + if (_options.InitializingCallbacks != null) + foreach (var callback in _options.InitializingCallbacks) + callback(); + } + + _initialized = true; } - - _initialized = true; } /// @@ -138,25 +166,34 @@ namespace Umbraco.Core.Sync /// protected void Sync() { - if ((DateTime.UtcNow - _lastSync).Seconds <= _options.ThrottleSeconds) - return; - - if (_syncing) return; - - lock (_lock) + lock (_locko) { - if (_syncing) return; + if (_syncing) + return; - _syncing = true; // lock other threads out + if (_released) + return; + + if ((DateTime.UtcNow - _lastSync).Seconds <= _options.ThrottleSeconds) + return; + + _syncing = true; + _syncIdle.Reset(); _lastSync = DateTime.UtcNow; + } - using (DisposableTimer.DebugDuration("Syncing from database...")) + try + { + using (_profilingLogger.DebugDuration("Syncing from database...")) { ProcessDatabaseInstructions(); PruneOldInstructions(); } - - _syncing = false; // release + } + finally + { + _syncing = false; + _syncIdle.Set(); } } @@ -207,7 +244,7 @@ namespace Umbraco.Core.Sync } catch (JsonException ex) { - LogHelper.Error(string.Format("Failed to deserialize instructions ({0}: \"{1}\").", dto.Id, dto.Instructions), ex); + _logger.Error(string.Format("Failed to deserialize instructions ({0}: \"{1}\").", dto.Id, dto.Instructions), ex); lastId = dto.Id; // skip continue; } @@ -220,10 +257,13 @@ namespace Umbraco.Core.Sync } catch (Exception ex) { - LogHelper.Error(string.Format("Failed to execute instructions ({0}: \"{1}\").", dto.Id, dto.Instructions), ex); - LogHelper.Warn("BEWARE - DISTRIBUTED CACHE IS NOT UPDATED."); - throw; - } + _logger.Error( + string.Format("DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({0}: \"{1}\"). Instruction is being skipped/ignored", dto.Id, dto.Instructions), ex); + + //we cannot throw here because this invalid instruction will just keep getting processed over and over and errors + // will be thrown over and over. The only thing we can do is ignore and move on. + lastId = dto.Id; + } } if (lastId > 0) @@ -239,6 +279,23 @@ namespace Umbraco.Core.Sync new { pruneDate = DateTime.UtcNow.AddDays(-_options.DaysToRetainInstructions) }); } + /// + /// Ensure that the last instruction that was processed is still in the database. + /// + /// If the last instruction is not in the database anymore, then the messenger + /// should not try to process any instructions, because some instructions might be lost, + /// and it should instead cold-boot. + private void EnsureInstructions() + { + var sql = new Sql().Select("*") + .From() + .Where(dto => dto.Id == _lastId); + + var dtos = _appContext.DatabaseContext.Database.Fetch(sql); + if (dtos.Count == 0) + _lastId = -1; + } + /// /// Reads the last-synced id from file into memory. /// diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs index d621f18a3c..6323227893 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/TypeFinder.cs @@ -137,7 +137,7 @@ namespace Umbraco.Core } return _allAssemblies; - } + } } /// @@ -226,7 +226,7 @@ namespace Umbraco.Core } return LocalFilteredAssemblyCache; - } + } } /// @@ -451,7 +451,7 @@ namespace Umbraco.Core var allTypes = GetTypesWithFormattedException(a) .ToArray(); - var attributedTypes = new Type[] {}; + var attributedTypes = new Type[] { }; try { //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes but have @@ -480,7 +480,8 @@ namespace Umbraco.Core //now we need to include types that may be inheriting from sub classes of the attribute type being searched for //so we will search in assemblies that reference those types too. - foreach (var subTypesInAssembly in allAttributeTypes.GroupBy(x => x.Assembly)){ + foreach (var subTypesInAssembly in allAttributeTypes.GroupBy(x => x.Assembly)) + { //So that we are not scanning too much, we need to group the sub types: // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. @@ -610,7 +611,7 @@ namespace Umbraco.Core catch (TypeLoadException ex) { LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex); - continue; + continue; } //add the types to our list to return @@ -618,7 +619,7 @@ namespace Umbraco.Core { foundAssignableTypes.Add(t); } - + //now we need to include types that may be inheriting from sub classes of the type being searched for //so we will search in assemblies that reference those types too. foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly)) @@ -699,6 +700,10 @@ namespace Umbraco.Core #endregion + //TODO: This isn't very elegant, and will have issues since the AppDomain.CurrentDomain + // doesn't actualy load in all assemblies, only the types that have been referenced so far. + // However, in a web context, the BuildManager will have executed which will force all assemblies + // to be loaded so it's fine for now. public static Type GetTypeByName(string typeName) { var type = Type.GetType(typeName); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 24bbd61c8b..b13529f8ea 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -86,11 +86,11 @@ False - ..\packages\MySql.Data.6.9.6\lib\net45\MySql.Data.dll + ..\packages\MySql.Data.6.9.7\lib\net45\MySql.Data.dll - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True ..\packages\Owin.1.0\lib\net40\Owin.dll @@ -112,15 +112,17 @@ - - ..\packages\Microsoft.Net.Http.2.2.28\lib\net45\System.Net.Http.Extensions.dll + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll + True ..\packages\Microsoft.AspNet.WebApi.Client.4.0.30506.0\lib\net40\System.Net.Http.Formatting.dll False - - ..\packages\Microsoft.Net.Http.2.2.28\lib\net45\System.Net.Http.Primitives.dll + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll + True @@ -314,12 +316,18 @@ + + + + + + @@ -328,6 +336,12 @@ + + + True + True + Files.resx + @@ -335,6 +349,7 @@ + @@ -431,6 +446,8 @@ + + @@ -462,6 +479,8 @@ + + @@ -1230,14 +1249,10 @@ - - - - @@ -1328,6 +1343,7 @@ + @@ -1346,6 +1362,7 @@ + @@ -1357,7 +1374,12 @@ - + + + ResXFileCodeGenerator + Files.Designer.cs + + diff --git a/src/Umbraco.Core/Umbraco.Core.csproj.DotSettings b/src/Umbraco.Core/Umbraco.Core.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/Umbraco.Core/Umbraco.Core.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs index 33c3d58689..7418d4e5b2 100644 --- a/src/Umbraco.Core/UmbracoApplicationBase.cs +++ b/src/Umbraco.Core/UmbracoApplicationBase.cs @@ -45,6 +45,19 @@ namespace Umbraco.Core Logger.Error(typeof(UmbracoApplicationBase), msg, exception); }; + //take care of unhandled exceptions - there is nothing we can do to + // prevent the entire w3wp process to go down but at least we can try + // and log the exception + AppDomain.CurrentDomain.UnhandledException += (_, args) => + { + var exception = (Exception) args.ExceptionObject; + var isTerminating = args.IsTerminating; // always true? + + var msg = "Unhandled exception in AppDomain"; + if (isTerminating) msg += " (terminating)"; + LogHelper.Error(msg, exception); + }; + //boot up the application GetBootManager() .Initialize() @@ -82,7 +95,18 @@ namespace Umbraco.Core protected virtual void OnApplicationStarting(object sender, EventArgs e) { if (ApplicationStarting != null) - ApplicationStarting(sender, e); + { + try + { + ApplicationStarting(sender, e); + } + catch (Exception ex) + { + LogHelper.Error("An error occurred in an ApplicationStarting event handler", ex); + throw; + } + } + } /// @@ -93,7 +117,17 @@ namespace Umbraco.Core protected virtual void OnApplicationStarted(object sender, EventArgs e) { if (ApplicationStarted != null) - ApplicationStarted(sender, e); + { + try + { + ApplicationStarted(sender, e); + } + catch (Exception ex) + { + LogHelper.Error("An error occurred in an ApplicationStarted event handler", ex); + throw; + } + } } /// @@ -104,7 +138,17 @@ namespace Umbraco.Core private void OnApplicationInit(object sender, EventArgs e) { if (ApplicationInit != null) - ApplicationInit(sender, e); + { + try + { + ApplicationInit(sender, e); + } + catch (Exception ex) + { + LogHelper.Error("An error occurred in an ApplicationInit event handler", ex); + throw; + } + } } /// diff --git a/src/Umbraco.Core/WaitHandleExtensions.cs b/src/Umbraco.Core/WaitHandleExtensions.cs new file mode 100644 index 0000000000..0d840a2496 --- /dev/null +++ b/src/Umbraco.Core/WaitHandleExtensions.cs @@ -0,0 +1,44 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Core +{ + internal static class WaitHandleExtensions + { + + // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100 + // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html + // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex... + // version below should be OK + + public static Task WaitOneAsync(this WaitHandle handle, int millisecondsTimeout = Timeout.Infinite) + { + var tcs = new TaskCompletionSource(); + var callbackHandleInitLock = new object(); + lock (callbackHandleInitLock) + { + RegisteredWaitHandle callbackHandle = null; + // ReSharper disable once RedundantAssignment + callbackHandle = ThreadPool.RegisterWaitForSingleObject( + handle, + (state, timedOut) => + { + tcs.SetResult(null); + + // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. + lock (callbackHandleInitLock) + { + // ReSharper disable once PossibleNullReferenceException + // ReSharper disable once AccessToModifiedClosure + callbackHandle.Unregister(null); + } + }, + /*state:*/ null, + /*millisecondsTimeOutInterval:*/ millisecondsTimeout, + /*executeOnlyOnce:*/ true); + } + + return tcs.Task; + } + } +} diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index 9edfbb097e..1c1afc4d9c 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -2,22 +2,22 @@ - + - + - + - - + + - - + + \ No newline at end of file diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index d64d47b2ee..9599390dfb 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -100,7 +100,7 @@ - + @@ -108,7 +108,7 @@ - + @@ -184,6 +184,22 @@ + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs b/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs index 381f3889b5..7e8aa19880 100644 --- a/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs +++ b/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs @@ -12,11 +12,14 @@ using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Tests.TestHelpers; using umbraco.interfaces; +using Umbraco.Core.Persistence; +using Umbraco.Core.Profiling; +using Umbraco.Core.Services; namespace Umbraco.Tests.BootManagers { [TestFixture] - public class CoreBootManagerTests : BaseUmbracoApplicationTest + public class CoreBootManagerTests : BaseUmbracoConfigurationTest { private TestApp _testApp; @@ -33,14 +36,11 @@ namespace Umbraco.Tests.BootManagers { base.TearDown(); - _testApp = null; - } - - protected override void FreezeResolution() - { - //don't freeze resolution, we'll do that in the boot manager + _testApp = null; + ResolverCollection.ResetAll(); } + /// /// test application using a CoreBootManager instance to boot /// @@ -48,7 +48,7 @@ namespace Umbraco.Tests.BootManagers { protected override IBootManager GetBootManager() { - return new TestBootManager(this); + return new TestBootManager(this, new ProfilingLogger(Mock.Of(), Mock.Of())); } } @@ -57,16 +57,32 @@ namespace Umbraco.Tests.BootManagers /// public class TestBootManager : CoreBootManager { - public TestBootManager(UmbracoApplicationBase umbracoApplication) - : base(umbracoApplication) + public TestBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger) + : base(umbracoApplication, logger) { } + /// + /// Creates and returns the application context singleton + /// + /// + /// + protected override ApplicationContext CreateApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext) + { + var appContext = base.CreateApplicationContext(dbContext, serviceContext); + + var dbContextMock = new Mock(Mock.Of(), ProfilingLogger.Logger, Mock.Of(), "test"); + dbContextMock.Setup(x => x.CanConnect).Returns(true); + appContext.DatabaseContext = dbContextMock.Object; + + return appContext; + } + protected override void InitializeApplicationEventsResolver() { //create an empty resolver so we can add our own custom ones (don't type find) ApplicationEventsResolver.Current = new ApplicationEventsResolver( - new ActivatorServiceProvider(), Mock.Of(), + new ActivatorServiceProvider(), ProfilingLogger.Logger, new Type[] { typeof(LegacyStartupHandler), @@ -77,6 +93,13 @@ namespace Umbraco.Tests.BootManagers }; } + protected override void InitializeLoggerResolver() + { + } + + protected override void InitializeProfilerResolver() + { + } } /// diff --git a/src/Umbraco.Tests/BootManagers/WebBootManagerTests.cs b/src/Umbraco.Tests/BootManagers/WebBootManagerTests.cs index 9ddba6df5f..b6c2a6a553 100644 --- a/src/Umbraco.Tests/BootManagers/WebBootManagerTests.cs +++ b/src/Umbraco.Tests/BootManagers/WebBootManagerTests.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Web.Mvc; -using Microsoft.Web.Mvc; using NUnit.Framework; using Umbraco.Core.Profiling; using Umbraco.Web; @@ -16,46 +15,38 @@ namespace Umbraco.Tests.BootManagers { IList engines = new List { - new FixedWebFormViewEngine(), - new FixedRazorViewEngine(), new RenderViewEngine(), new PluginViewEngine() }; WebBootManager.WrapViewEngines(engines); - Assert.That(engines.Count, Is.EqualTo(4)); + Assert.That(engines.Count, Is.EqualTo(2)); Assert.That(engines[0], Is.InstanceOf()); Assert.That(engines[1], Is.InstanceOf()); - Assert.That(engines[2], Is.InstanceOf()); - Assert.That(engines[3], Is.InstanceOf()); } [Test] public void WrapViewEngines_HasEngines_KeepsSortOrder() { - IList engines = new List + IList engines = new List { - new FixedWebFormViewEngine(), - new FixedRazorViewEngine(), new RenderViewEngine(), new PluginViewEngine() }; WebBootManager.WrapViewEngines(engines); - Assert.That(engines.Count, Is.EqualTo(4)); - Assert.That(((ProfilingViewEngine)engines[0]).Inner, Is.InstanceOf()); - Assert.That(((ProfilingViewEngine)engines[1]).Inner, Is.InstanceOf()); - Assert.That(((ProfilingViewEngine)engines[2]).Inner, Is.InstanceOf()); - Assert.That(((ProfilingViewEngine)engines[3]).Inner, Is.InstanceOf()); + Assert.That(engines.Count, Is.EqualTo(2)); + Assert.That(((ProfilingViewEngine)engines[0]).Inner, Is.InstanceOf()); + Assert.That(((ProfilingViewEngine)engines[1]).Inner, Is.InstanceOf()); } [Test] public void WrapViewEngines_HasProfiledEngine_AddsSameInstance() { - var profiledEngine = new ProfilingViewEngine(new FixedRazorViewEngine()); + var profiledEngine = new ProfilingViewEngine(new PluginViewEngine()); IList engines = new List { profiledEngine diff --git a/src/Umbraco.Tests/Controllers/BackOfficeControllerUnitTests.cs b/src/Umbraco.Tests/Controllers/BackOfficeControllerUnitTests.cs new file mode 100644 index 0000000000..59626a1b9a --- /dev/null +++ b/src/Umbraco.Tests/Controllers/BackOfficeControllerUnitTests.cs @@ -0,0 +1,41 @@ +using System.Linq; +using NUnit.Framework; +using Umbraco.Web.Editors; + +namespace Umbraco.Tests.Controllers +{ + [TestFixture] + public class BackOfficeControllerUnitTests + { + public static object[] TestLegacyJsActionPaths = new object[] { + new string[] + { + "alert('hello');", + "function test() { window.location = 'http://www.google.com'; }", + "function openCourierSecurity(userid){ UmbClientMgr.contentFrame('page?userid=123); }", + @"function openRepository(repo, folder){ UmbClientMgr.contentFrame('page?repo=repo&folder=folder); } + function openTransfer(revision, repo, folder){ UmbClientMgr.contentFrame('page?revision=revision&repo=repo&folder=folder); }", + "umbraco/js/test.js", + "/umbraco/js/test.js", + "~/umbraco/js/test.js" + } + }; + + + [TestCaseSource("TestLegacyJsActionPaths")] + public void Separates_Legacy_JsActions_By_Block_Or_Url(object[] jsActions) + { + var jsBlocks = + BackOfficeController.GetLegacyActionJsForActions(BackOfficeController.LegacyJsActionType.JsBlock, + jsActions.Select(n => n.ToString())); + + var jsUrls = + BackOfficeController.GetLegacyActionJsForActions(BackOfficeController.LegacyJsActionType.JsUrl, + jsActions.Select(n => n.ToString())); + + Assert.That(jsBlocks.Count() == 4); + Assert.That(jsUrls.Count() == 3); + Assert.That(jsUrls.Last().StartsWith("~/") == false); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/EnumerableExtensionsTests.cs b/src/Umbraco.Tests/EnumerableExtensionsTests.cs index bbc0420ccc..8edba760a1 100644 --- a/src/Umbraco.Tests/EnumerableExtensionsTests.cs +++ b/src/Umbraco.Tests/EnumerableExtensionsTests.cs @@ -54,8 +54,12 @@ namespace Umbraco.Tests }; var flattened = hierarchy.Children.FlattenList(x => x.Children); + var selectRecursive = hierarchy.Children.SelectRecursive(x => x.Children); Assert.AreEqual(3, flattened.Count()); + Assert.AreEqual(3, selectRecursive.Count()); + + Assert.IsTrue(flattened.SequenceEqual(selectRecursive)); } [Test] @@ -115,8 +119,12 @@ namespace Umbraco.Tests }; var flattened = hierarchy.Children.FlattenList(x => x.Children); + var selectRecursive = hierarchy.Children.FlattenList(x => x.Children); Assert.AreEqual(10, flattened.Count()); + Assert.AreEqual(10, selectRecursive.Count()); + + Assert.IsTrue(flattened.SequenceEqual(selectRecursive)); } private class TestItem diff --git a/src/Umbraco.Tests/IO/IOHelperTest.cs b/src/Umbraco.Tests/IO/IOHelperTest.cs index 9f8822fbff..559ba1a2f3 100644 --- a/src/Umbraco.Tests/IO/IOHelperTest.cs +++ b/src/Umbraco.Tests/IO/IOHelperTest.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; using Umbraco.Core.IO; namespace Umbraco.Tests.IO @@ -13,11 +14,20 @@ namespace Umbraco.Tests.IO public class IOHelperTest { - [Test] - public void IOHelper_ResolveUrl() + [TestCase("~/Scripts", "/Scripts", null)] + [TestCase("/Scripts", "/Scripts", null)] + [TestCase("../Scripts", "/Scripts", typeof(ArgumentException))] + public void IOHelper_ResolveUrl(string input, string expected, Type expectedExceptionType) { - var result = IOHelper.ResolveUrl("~/Scripts"); - Assert.AreEqual("/Scripts", result); + if (expectedExceptionType != null) + { + Assert.Throws(expectedExceptionType, () => IOHelper.ResolveUrl(input)); + } + else + { + var result = IOHelper.ResolveUrl(input); + Assert.AreEqual(expected, result); + } } /// diff --git a/src/Umbraco.Tests/Integration/GetCultureTests.cs b/src/Umbraco.Tests/Integration/GetCultureTests.cs index 977e0f313e..0b630b9a41 100644 --- a/src/Umbraco.Tests/Integration/GetCultureTests.cs +++ b/src/Umbraco.Tests/Integration/GetCultureTests.cs @@ -51,9 +51,9 @@ namespace Umbraco.Tests.Integration foreach (var d in ServiceContext.DomainService.GetAll(true).ToArray()) ServiceContext.DomainService.Delete(d); - ServiceContext.DomainService.Save(new UmbracoDomain("domain1.com") {DomainName="domain1.com", RootContent = c1, Language = l0}); - ServiceContext.DomainService.Save(new UmbracoDomain("domain1.fr") { DomainName = "domain1.fr", RootContent = c1, Language = l1 }); - ServiceContext.DomainService.Save(new UmbracoDomain("*100112") { DomainName = "*100112", RootContent = c3, Language = l2 }); + ServiceContext.DomainService.Save(new UmbracoDomain("domain1.com") {DomainName="domain1.com", RootContentId = c1.Id, LanguageId = l0.Id}); + ServiceContext.DomainService.Save(new UmbracoDomain("domain1.fr") { DomainName = "domain1.fr", RootContentId = c1.Id, LanguageId = l1.Id }); + ServiceContext.DomainService.Save(new UmbracoDomain("*100112") { DomainName = "*100112", RootContentId = c3.Id, LanguageId = l2.Id }); var content = c2; var culture = Web.Models.ContentExtensions.GetCulture(null, diff --git a/src/Umbraco.Tests/Macros/MacroParserTests.cs b/src/Umbraco.Tests/Macros/MacroParserTests.cs index 6a6a6d6ffc..7fb5130d3c 100644 --- a/src/Umbraco.Tests/Macros/MacroParserTests.cs +++ b/src/Umbraco.Tests/Macros/MacroParserTests.cs @@ -200,6 +200,29 @@ namespace Umbraco.Tests.Macros

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); } + [Test] + public void Format_RTE_Data_For_Editor_With_Multiple_Macros() + { + var content = @"

asdfasdf

+ +

asdfsadf

+

 

+ +

 

"; + var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary()); + + Assert.AreEqual(@"

asdfasdf

+
+ +Macro alias: Breadcrumb
+

asdfsadf

+

 

+
+ +Macro alias: login
+

 

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); + } + [Test] public void Format_RTE_Data_For_Editor_With_Params_Closing_Tag() { diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index 99ec1f4e81..4f0b91d4ca 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -11,6 +11,8 @@ using Umbraco.Core.Logging; using Umbraco.Core.Profiling; using umbraco; using umbraco.cms.businesslogic.macro; +using Umbraco.Core.Configuration; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Macros { @@ -26,18 +28,14 @@ namespace Umbraco.Tests.Macros new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new NullCacheProvider()); - ApplicationContext.Current = new ApplicationContext(cacheHelper); - ProfilerResolver.Current = new ProfilerResolver(new LogProfiler(Mock.Of())) - { - CanResolveBeforeFrozen = true - }; + ApplicationContext.Current = new ApplicationContext(cacheHelper, new ProfilingLogger(Mock.Of(), Mock.Of())); + + UmbracoConfig.For.SetUmbracoSettings(SettingsForTests.GetDefault()); } [TearDown] public void TearDown() { - ProfilerResolver.Current.DisposeIfDisposable(); - ProfilerResolver.Reset(); ApplicationContext.Current.ApplicationCache.ClearAllCache(); ApplicationContext.Current.DisposeIfDisposable(); ApplicationContext.Current = null; diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs index dcb590889b..d9a8ef785e 100644 --- a/src/Umbraco.Tests/MockTests.cs +++ b/src/Umbraco.Tests/MockTests.cs @@ -30,7 +30,9 @@ namespace Umbraco.Tests [Test] public void Can_Create_Empty_App_Context() { - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); Assert.Pass(); } @@ -63,7 +65,9 @@ namespace Umbraco.Tests [Test] public void Can_Assign_App_Context_Singleton() { - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); var result = ApplicationContext.EnsureContext(appCtx, true); Assert.AreEqual(appCtx, result); } @@ -71,8 +75,14 @@ namespace Umbraco.Tests [Test] public void Does_Not_Overwrite_App_Context_Singleton() { - ApplicationContext.EnsureContext(new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()), true); - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + ApplicationContext.EnsureContext( + new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())), true); + + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); var result = ApplicationContext.EnsureContext(appCtx, false); Assert.AreNotEqual(appCtx, result); } @@ -80,7 +90,9 @@ namespace Umbraco.Tests [Test] public void Can_Get_Umbraco_Context() { - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); var umbCtx = UmbracoContext.EnsureContext( Mock.Of(), @@ -96,7 +108,9 @@ namespace Umbraco.Tests [Test] public void Can_Mock_Umbraco_Helper() { - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); var umbCtx = UmbracoContext.EnsureContext( Mock.Of(), @@ -122,7 +136,9 @@ namespace Umbraco.Tests [Test] public void Can_Mock_Umbraco_Helper_Get_Url() { - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); var umbCtx = UmbracoContext.EnsureContext( Mock.Of(), diff --git a/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs index 566b90b157..0286a5bd0f 100644 --- a/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs @@ -32,7 +32,9 @@ namespace Umbraco.Tests.Mvc [Test] public void Can_Construct_And_Get_Result() { - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); var umbCtx = UmbracoContext.EnsureContext( new Mock().Object, @@ -52,7 +54,10 @@ namespace Umbraco.Tests.Mvc [Test] public void Umbraco_Context_Not_Null() { - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); + ApplicationContext.EnsureContext(appCtx, true); var umbCtx = UmbracoContext.EnsureContext( @@ -93,7 +98,9 @@ namespace Umbraco.Tests.Mvc [Test] public void Can_Lookup_Content() { - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); var umbCtx = UmbracoContext.EnsureContext( new Mock().Object, @@ -127,7 +134,9 @@ namespace Umbraco.Tests.Mvc [Test] public void Mock_Current_Page() { - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); var webRoutingSettings = Mock.Of(section => section.UrlProviderMode == "AutoLegacy"); diff --git a/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs index 3cac22e448..fd2dc02292 100644 --- a/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Security; using umbraco.BusinessLogic; using Umbraco.Core; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.PublishedContent; @@ -400,6 +401,7 @@ namespace Umbraco.Tests.Mvc new Mock().Object, new RepositoryFactory(CacheHelper.CreateDisabledCacheHelper(), logger, Mock.Of(), umbracoSettings), logger, + new TransientMessagesFactory(), new Mock().Object), new Mock().Object, new Mock().Object, @@ -431,7 +433,12 @@ namespace Umbraco.Tests.Mvc urlProvider); umbracoContext.RoutingContext = routingContext; - var request = new PublishedContentRequest(new Uri("http://localhost/dang"), routingContext); + var request = new PublishedContentRequest( + new Uri("http://localhost/dang"), + routingContext, + settings.WebRouting, + s => Enumerable.Empty()); + request.Culture = CultureInfo.InvariantCulture; umbracoContext.PublishedContentRequest = request; diff --git a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs index 7bc4b2b76f..1da8237582 100644 --- a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs @@ -6,6 +6,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.ObjectResolution; @@ -55,11 +56,12 @@ namespace Umbraco.Tests.Persistence var repositoryFactory = new RepositoryFactory(cacheHelper, _logger, SqlSyntaxProvider, SettingsForTests.GenerateMockSettings()); + var evtMsgs = new TransientMessagesFactory(); ApplicationContext.Current = new ApplicationContext( //assign the db context dbContext, //assign the service context - new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(_logger), new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper, _logger), + new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(_logger), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, _logger), cacheHelper, _logger, evtMsgs), cacheHelper, new ProfilingLogger(_logger, Mock.Of())) { diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 3bb1135cee..5883562f3b 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -28,7 +28,9 @@ namespace Umbraco.Tests.Persistence Mock.Of(), new SqlCeSyntaxProvider(), "System.Data.SqlServerCe.4.0"); //unfortunately we have to set this up because the PetaPocoExtensions require singleton access - ApplicationContext.Current = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()) + ApplicationContext.Current = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())) { DatabaseContext = _dbContext, IsReady = true diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 81a1892f7b..7fad422817 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -337,13 +337,16 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { var contentType = repository.Get(NodeDto.NodeIdSeed + 1); + var childContentType = MockedContentTypes.CreateSimpleContentType("blah", "Blah", contentType, randomizeAliases:true); + repository.AddOrUpdate(childContentType); + unitOfWork.Commit(); // Act - contentType = repository.Get(contentType.Key); + var result = repository.Get(childContentType.Key); // Assert - Assert.That(contentType, Is.Not.Null); - Assert.That(contentType.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); + Assert.That(result, Is.Not.Null); + Assert.That(result.Id, Is.EqualTo(childContentType.Id)); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index 08fae6d3ef..782eeaf4e9 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Persistence.Repositories contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, templateRepository); contentRepository = new ContentRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository, Mock.Of()); languageRepository = new LanguageRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax); - var domainRepository = new DomainRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, contentRepository, languageRepository); + var domainRepository = new DomainRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax); return domainRepository; } @@ -72,7 +72,7 @@ namespace Umbraco.Tests.Persistence.Repositories var lang = langRepo.GetByIsoCode("en-AU"); var content = contentRepo.Get(contentId); - var domain = (IDomain)new UmbracoDomain("test.com") { RootContent = content, Language = lang }; + var domain = (IDomain)new UmbracoDomain("test.com") { RootContentId = content.Id, LanguageId = lang.Id }; repo.AddOrUpdate(domain); unitOfWork.Commit(); @@ -83,11 +83,43 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsTrue(domain.HasIdentity); Assert.Greater(domain.Id, 0); Assert.AreEqual("test.com", domain.DomainName); - Assert.AreEqual(content.Id, domain.RootContent.Id); - Assert.AreEqual(lang.Id, domain.Language.Id); + Assert.AreEqual(content.Id, domain.RootContentId); + Assert.AreEqual(lang.Id, domain.LanguageId); + Assert.AreEqual(lang.IsoCode, domain.LanguageIsoCode); } + } + [Test] + public void Can_Create_And_Get_By_Id_Empty_lang() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentType ct; + var contentId = CreateTestData("en-AU", out ct); + + ContentRepository contentRepo; + LanguageRepository langRepo; + ContentTypeRepository contentTypeRepo; + + using (var repo = CreateRepository(unitOfWork, out contentTypeRepo, out contentRepo, out langRepo)) + { + var content = contentRepo.Get(contentId); + + var domain = (IDomain)new UmbracoDomain("test.com") { RootContentId = content.Id }; + repo.AddOrUpdate(domain); + unitOfWork.Commit(); + + //re-get + domain = repo.Get(domain.Id); + + Assert.NotNull(domain); + Assert.IsTrue(domain.HasIdentity); + Assert.Greater(domain.Id, 0); + Assert.AreEqual("test.com", domain.DomainName); + Assert.AreEqual(content.Id, domain.RootContentId); + Assert.IsFalse(domain.LanguageId.HasValue); + } } [Test] @@ -108,11 +140,11 @@ namespace Umbraco.Tests.Persistence.Repositories var lang = langRepo.GetByIsoCode("en-AU"); var content = contentRepo.Get(contentId); - var domain1 = (IDomain)new UmbracoDomain("test.com") { RootContent = content, Language = lang }; + var domain1 = (IDomain)new UmbracoDomain("test.com") { RootContentId = content.Id, LanguageId = lang.Id }; repo.AddOrUpdate(domain1); unitOfWork.Commit(); - var domain2 = (IDomain)new UmbracoDomain("test.com") { RootContent = content, Language = lang }; + var domain2 = (IDomain)new UmbracoDomain("test.com") { RootContentId = content.Id, LanguageId = lang.Id }; repo.AddOrUpdate(domain2); Assert.Throws(unitOfWork.Commit); @@ -141,7 +173,7 @@ namespace Umbraco.Tests.Persistence.Repositories var lang = langRepo.GetByIsoCode("en-AU"); var content = contentRepo.Get(contentId); - var domain = (IDomain)new UmbracoDomain("test.com") { RootContent = content, Language = lang }; + var domain = (IDomain)new UmbracoDomain("test.com") { RootContentId = content.Id, LanguageId = lang.Id }; repo.AddOrUpdate(domain); unitOfWork.Commit(); @@ -184,7 +216,7 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); - var domain = (IDomain)new UmbracoDomain("test.com") { RootContent = content1, Language = lang1 }; + var domain = (IDomain)new UmbracoDomain("test.com") { RootContentId = content1.Id, LanguageId = lang1.Id }; repo.AddOrUpdate(domain); unitOfWork.Commit(); @@ -192,8 +224,8 @@ namespace Umbraco.Tests.Persistence.Repositories domain = repo.Get(domain.Id); domain.DomainName = "blah.com"; - domain.RootContent = content2; - domain.Language = lang2; + domain.RootContentId = content2.Id; + domain.LanguageId = lang2.Id; repo.AddOrUpdate(domain); unitOfWork.Commit(); @@ -201,13 +233,15 @@ namespace Umbraco.Tests.Persistence.Repositories domain = repo.Get(domain.Id); Assert.AreEqual("blah.com", domain.DomainName); - Assert.AreEqual(content2.Id, domain.RootContent.Id); - Assert.AreEqual(lang2.Id, domain.Language.Id); + Assert.AreEqual(content2.Id, domain.RootContentId); + Assert.AreEqual(lang2.Id, domain.LanguageId); + Assert.AreEqual(lang2.IsoCode, domain.LanguageIsoCode); } } + [Test] public void Exists() { @@ -228,7 +262,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (int i = 0; i < 10; i++) { - var domain = (IDomain)new UmbracoDomain("test" + i + ".com") { RootContent = content, Language = lang }; + var domain = (IDomain)new UmbracoDomain("test" + i + ".com") { RootContentId = content.Id, LanguageId = lang.Id }; repo.AddOrUpdate(domain); unitOfWork.Commit(); } @@ -259,7 +293,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (int i = 0; i < 10; i++) { - var domain = (IDomain)new UmbracoDomain("test" + i + ".com") { RootContent = content, Language = lang }; + var domain = (IDomain)new UmbracoDomain("test" + i + ".com") { RootContentId = content.Id, LanguageId = lang.Id }; repo.AddOrUpdate(domain); unitOfWork.Commit(); } @@ -290,7 +324,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (int i = 0; i < 10; i++) { - var domain = (IDomain)new UmbracoDomain("test " + i + ".com") { RootContent = content, Language = lang }; + var domain = (IDomain)new UmbracoDomain("test " + i + ".com") { RootContentId = content.Id, LanguageId = lang.Id }; repo.AddOrUpdate(domain); unitOfWork.Commit(); } @@ -322,7 +356,7 @@ namespace Umbraco.Tests.Persistence.Repositories var ids = new List(); for (int i = 0; i < 10; i++) { - var domain = (IDomain)new UmbracoDomain("test " + i + ".com") { RootContent = content, Language = lang }; + var domain = (IDomain)new UmbracoDomain("test " + i + ".com") { RootContentId = content.Id, LanguageId = lang.Id }; repo.AddOrUpdate(domain); unitOfWork.Commit(); ids.Add(domain.Id); @@ -356,8 +390,8 @@ namespace Umbraco.Tests.Persistence.Repositories { var domain = (IDomain)new UmbracoDomain((i % 2 == 0) ? "test " + i + ".com" : ("*" + i)) { - RootContent = content, - Language = lang + RootContentId = content.Id, + LanguageId = lang.Id }; repo.AddOrUpdate(domain); unitOfWork.Commit(); @@ -402,8 +436,8 @@ namespace Umbraco.Tests.Persistence.Repositories { var domain = (IDomain)new UmbracoDomain((i % 2 == 0) ? "test " + i + ".com" : ("*" + i)) { - RootContent = (i % 2 == 0) ? contentItems[0] : contentItems[1], - Language = lang + RootContentId = ((i % 2 == 0) ? contentItems[0] : contentItems[1]).Id, + LanguageId = lang.Id }; repo.AddOrUpdate(domain); unitOfWork.Commit(); @@ -453,8 +487,8 @@ namespace Umbraco.Tests.Persistence.Repositories { var domain = (IDomain)new UmbracoDomain((i % 2 == 0) ? "test " + i + ".com" : ("*" + i)) { - RootContent = (i % 2 == 0) ? contentItems[0] : contentItems[1], - Language = lang + RootContentId = ((i % 2 == 0) ? contentItems[0] : contentItems[1]).Id, + LanguageId = lang.Id }; repo.AddOrUpdate(domain); unitOfWork.Commit(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index 1c4e5e4c2b..dca6b4f9f1 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -122,6 +122,32 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Get_Member_Types_By_Guid_Id() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var memberType1 = MockedContentTypes.CreateSimpleMemberType(); + repository.AddOrUpdate(memberType1); + unitOfWork.Commit(); + + var memberType2 = MockedContentTypes.CreateSimpleMemberType(); + memberType2.Name = "AnotherType"; + memberType2.Alias = "anotherType"; + repository.AddOrUpdate(memberType2); + unitOfWork.Commit(); + + var result = repository.Get(memberType1.Key); + + //there are 3 because of the Member type created for init data + Assert.IsNotNull(result); + Assert.AreEqual(memberType1.Key, result.Key); + } + } + + //NOTE: This tests for left join logic (rev 7b14e8eacc65f82d4f184ef46c23340c09569052) [Test] public void Can_Get_All_Members_When_No_Properties_Assigned() diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index dcbab3dd88..87182e4ad0 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Data; +using System.IO; using System.Linq; using System.Text; using Moq; @@ -118,6 +119,26 @@ p{font-size:2em;}")); Assert.AreEqual(1, stylesheet.Properties.Count()); } + [Test] + public void Throws_When_Adding_Duplicate_Properties() + { + // Arrange + var provider = new FileUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + + var repository = new StylesheetRepository(unitOfWork, _fileSystem); + + // Act + var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; + repository.AddOrUpdate(stylesheet); + unitOfWork.Commit(); + + stylesheet.AddProperty(new StylesheetProperty("Test", "p", "font-size:2em;")); + + Assert.Throws(() => stylesheet.AddProperty(new StylesheetProperty("test", "p", "font-size:2em;"))); + + } + [Test] public void Can_Perform_Delete() { diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index d8232d9aaf..d79a45c2e9 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -340,7 +340,7 @@ namespace Umbraco.Tests.Persistence.Repositories } [Test] - public void Can_Get_Tags_For_Content() + public void Can_Get_Tags_For_Content_By_Id() { var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); @@ -381,6 +381,54 @@ namespace Umbraco.Tests.Persistence.Repositories var result = repository.GetTagsForEntity(content2.Id); Assert.AreEqual(2, result.Count()); + + } + } + } + + [Test] + public void Can_Get_Tags_For_Content_By_Key() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) + { + //create data to relate to + var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); + contentTypeRepository.AddOrUpdate(contentType); + unitOfWork.Commit(); + var content1 = MockedContent.CreateSimpleContent(contentType); + contentRepository.AddOrUpdate(content1); + var content2 = MockedContent.CreateSimpleContent(contentType); + contentRepository.AddOrUpdate(content2); + unitOfWork.Commit(); + + using (var repository = CreateRepository(unitOfWork)) + { + repository.AssignTagsToProperty( + content1.Id, + contentType.PropertyTypes.First().Id, + new[] + { + new Tag {Text = "tag1", Group = "test"}, + new Tag {Text = "tag2", Group = "test"}, + new Tag {Text = "tag3", Group = "test"}, + new Tag {Text = "tag4", Group = "test"} + }, false); + + repository.AssignTagsToProperty( + content2.Id, + contentType.PropertyTypes.First().Id, + new[] + { + new Tag {Text = "tag1", Group = "test"}, + new Tag {Text = "tag2", Group = "test"} + }, false); + + //get by key + var result = repository.GetTagsForEntity(content2.Key); + Assert.AreEqual(2, result.Count()); } } } @@ -511,7 +559,7 @@ namespace Umbraco.Tests.Persistence.Repositories } [Test] - public void Can_Get_Tags_For_Property() + public void Can_Get_Tags_For_Property_By_Id() { var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); @@ -557,6 +605,53 @@ namespace Umbraco.Tests.Persistence.Repositories } + [Test] + public void Can_Get_Tags_For_Property_By_Key() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) + { + //create data to relate to + var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); + contentTypeRepository.AddOrUpdate(contentType); + unitOfWork.Commit(); + var content1 = MockedContent.CreateSimpleContent(contentType); + contentRepository.AddOrUpdate(content1); + unitOfWork.Commit(); + + using (var repository = CreateRepository(unitOfWork)) + { + repository.AssignTagsToProperty( + content1.Id, + contentType.PropertyTypes.First().Id, + new[] + { + new Tag {Text = "tag1", Group = "test"}, + new Tag {Text = "tag2", Group = "test"}, + new Tag {Text = "tag3", Group = "test"}, + new Tag {Text = "tag4", Group = "test"} + }, false); + + repository.AssignTagsToProperty( + content1.Id, + contentType.PropertyTypes.Last().Id, + new[] + { + new Tag {Text = "tag1", Group = "test"}, + new Tag {Text = "tag2", Group = "test"} + }, false); + + var result1 = repository.GetTagsForProperty(content1.Key, contentType.PropertyTypes.First().Alias).ToArray(); + var result2 = repository.GetTagsForProperty(content1.Key, contentType.PropertyTypes.Last().Alias).ToArray(); + Assert.AreEqual(4, result1.Count()); + Assert.AreEqual(2, result2.Count()); + } + } + + } + [Test] public void Can_Get_Tags_For_Property_For_Group() { diff --git a/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs index e9066e121b..fb7e294e66 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; @@ -63,7 +64,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(0, found.First().AssigneeUserId); Assert.AreEqual(false, found.First().Closed); Assert.AreEqual("hello world", found.First().Comment); - Assert.GreaterOrEqual(found.First().CreateDate, created); + Assert.GreaterOrEqual(found.First().CreateDate.TruncateTo(DateTimeExtensions.DateTruncate.Second), created.TruncateTo(DateTimeExtensions.DateTruncate.Second)); Assert.AreEqual(-1, found.First().EntityId); Assert.AreEqual(0, found.First().OwnerUserId); Assert.AreEqual(true, found.First().HasIdentity); diff --git a/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs b/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs index a2ebdf450d..63ba1afb81 100644 --- a/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs +++ b/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs @@ -64,7 +64,7 @@ namespace Umbraco.Tests.Publishing ServiceContext.ContentTypeService.GetContentType("umbTextpage"), "Sub Sub Sub", mandatorContent.Id); ServiceContext.ContentService.Save(subContent, 0); - var strategy = new PublishingStrategy(); + var strategy = new PublishingStrategy(new TransientMessagesFactory(), Logger); //publish root and nodes at it's children level var listToPublish = ServiceContext.ContentService.GetDescendants(_homePage.Id).Concat(new[] { _homePage }); @@ -91,8 +91,8 @@ namespace Umbraco.Tests.Publishing { CreateTestData(); - var strategy = new PublishingStrategy(); - + var strategy = new PublishingStrategy(new TransientMessagesFactory(), Logger); + PublishingStrategy.Publishing +=PublishingStrategyPublishing; @@ -118,8 +118,8 @@ namespace Umbraco.Tests.Publishing { CreateTestData(); - var strategy = new PublishingStrategy(); - + var strategy = new PublishingStrategy(new TransientMessagesFactory(), Logger); + //publish root and nodes at it's children level var result1 = strategy.Publish(_homePage, 0); Assert.IsTrue(result1); @@ -145,7 +145,7 @@ namespace Umbraco.Tests.Publishing { CreateTestData(); - var strategy = new PublishingStrategy(); + var strategy = new PublishingStrategy(new TransientMessagesFactory(), Logger); //publish root and nodes at it's children level var result1 = strategy.Publish(_homePage, 0); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByNiceUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByNiceUrlWithDomainsTests.cs index 9b23b1c5e8..ea6e096fbf 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByNiceUrlWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByNiceUrlWithDomainsTests.cs @@ -13,7 +13,7 @@ namespace Umbraco.Tests.Routing { SetupDomainServiceMock(new[] { - new UmbracoDomain("domain1.com/") {Id = 1, Language = new Language("de-DE"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001}} + new UmbracoDomain("domain1.com/") {Id = 1, LanguageId = LangDeId, RootContentId = 1001} }); } @@ -22,12 +22,12 @@ namespace Umbraco.Tests.Routing { SetupDomainServiceMock(new[] { - new UmbracoDomain("domain1.com/") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001}}, - new UmbracoDomain("domain1.com/en") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test2", -1, new ContentType(-1)) {Id = 10011}}, - new UmbracoDomain("domain1.com/fr") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test3", -1, new ContentType(-1)) {Id = 10012}}, - new UmbracoDomain("http://domain3.com/") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1003}}, - new UmbracoDomain("http://domain3.com/en") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test2", -1, new ContentType(-1)) {Id = 10031}}, - new UmbracoDomain("http://domain3.com/fr") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test3", -1, new ContentType(-1)) {Id = 10032}} + new UmbracoDomain("domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001}, + new UmbracoDomain("domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011}, + new UmbracoDomain("domain1.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012}, + new UmbracoDomain("http://domain3.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1003}, + new UmbracoDomain("http://domain3.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10031}, + new UmbracoDomain("http://domain3.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10032} }); } diff --git a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs index a11eef335f..6af00d6127 100644 --- a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs +++ b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Routing { protected override void FreezeResolution() { - SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper()); + SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper()); base.FreezeResolution(); } @@ -29,20 +29,20 @@ namespace Umbraco.Tests.Routing new UmbracoDomain("domain1.com/") { Id = 1, - Language = new Language("de-DE"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001} + LanguageId = LangDeId, + RootContentId = 1001 }, new UmbracoDomain("domain1.com/en") { Id = 1, - Language = new Language("en-US"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10011} + LanguageId = LangEngId, + RootContentId = 10011 }, new UmbracoDomain("domain1.com/fr") { Id = 1, - Language = new Language("fr-FR"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10012} + LanguageId = LangFrId, + RootContentId = 10012 } }); } @@ -54,56 +54,56 @@ namespace Umbraco.Tests.Routing new UmbracoDomain("domain1.com/") { Id = 1, - Language = new Language("de-DE"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001} + LanguageId = LangDeId, + RootContentId = 1001 }, new UmbracoDomain("domain1.com/en") { Id = 1, - Language = new Language("en-US"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10011} + LanguageId = LangEngId, + RootContentId = 10011 }, new UmbracoDomain("domain1.com/fr") { Id = 1, - Language = new Language("fr-FR"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10012} + LanguageId = LangFrId, + RootContentId = 10012 }, new UmbracoDomain("*1001") { Id = 1, - Language = new Language("de-DE"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001} + LanguageId = LangDeId, + RootContentId = 1001 }, new UmbracoDomain("*10011") { Id = 1, - Language = new Language("cs-CZ"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10011} + LanguageId = LangCzId, + RootContentId = 10011 }, new UmbracoDomain("*100112") { Id = 1, - Language = new Language("nl-NL"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 100112} + LanguageId = LangNlId, + RootContentId = 100112 }, new UmbracoDomain("*1001122") { Id = 1, - Language = new Language("da-DK"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001122} + LanguageId = LangDkId, + RootContentId = 1001122 }, new UmbracoDomain("*10012") { Id = 1, - Language = new Language("nl-NL"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10012} + LanguageId = LangNlId, + RootContentId = 10012 }, new UmbracoDomain("*10031") { Id = 1, - Language = new Language("nl-NL"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10031} + LanguageId = LangNlId, + RootContentId =10031 } }); } @@ -259,7 +259,7 @@ namespace Umbraco.Tests.Routing var routingContext = GetRoutingContext(inputUrl); var url = routingContext.UmbracoContext.CleanedUmbracoUrl; - //very important to use the cleaned up umbraco url + //very important to use the cleaned up umbraco url var pcr = new PublishedContentRequest(url, routingContext); // lookup domain @@ -307,7 +307,7 @@ namespace Umbraco.Tests.Routing var routingContext = GetRoutingContext(inputUrl); var url = routingContext.UmbracoContext.CleanedUmbracoUrl; - //very important to use the cleaned up umbraco url + //very important to use the cleaned up umbraco url var pcr = new PublishedContentRequest(url, routingContext); // lookup domain @@ -326,7 +326,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(pcr.PublishedContent.Id, expectedNode); } - + #region Cases [TestCase(10011, "http://domain1.com/", "en-US")] @@ -342,20 +342,20 @@ namespace Umbraco.Tests.Routing new UmbracoDomain("domain1.com/") { Id = 1, - Language = new Language("en-US"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001} + LanguageId = LangEngId, + RootContentId = 1001 }, new UmbracoDomain("domain1.fr/") { Id = 1, - Language = new Language("fr-FR"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001} + LanguageId = LangFrId, + RootContentId = 1001 }, new UmbracoDomain("*100112") { Id = 1, - Language = new Language("de-DE"), - RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 100112} + LanguageId = LangDeId, + RootContentId = 100112 } }); @@ -365,7 +365,7 @@ namespace Umbraco.Tests.Routing var content = umbracoContext.ContentCache.GetById(nodeId); Assert.IsNotNull(content); - var culture = Web.Models.ContentExtensions.GetCulture(umbracoContext, domainService, null, null, content.Id, content.Path, new Uri(currentUrl)); + var culture = Web.Models.ContentExtensions.GetCulture(umbracoContext, domainService, ServiceContext.LocalizationService, null, content.Id, content.Path, new Uri(currentUrl)); Assert.AreEqual(expectedCulture, culture.Name); } } diff --git a/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs index 3a64389104..ffbed3e2f4 100644 --- a/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Routing SetupDomainServiceMock(new[] { - new UmbracoDomain("domain1.com") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001}} + new UmbracoDomain("domain1.com") {Id = 1, LanguageId = LangFrId, RootContentId = 1001} }); } @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Routing { SetupDomainServiceMock(new[] { - new UmbracoDomain("http://domain1.com/foo") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001}} + new UmbracoDomain("http://domain1.com/foo") {Id = 1, LanguageId = LangFrId, RootContentId = 1001} }); } @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Routing { SetupDomainServiceMock(new[] { - new UmbracoDomain("http://domain1.com/") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10011}} + new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangFrId, RootContentId = 10011} }); } @@ -52,12 +52,12 @@ namespace Umbraco.Tests.Routing { SetupDomainServiceMock(new[] { - new UmbracoDomain("http://domain1.com/") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001}}, - new UmbracoDomain("http://domain1.com/en") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10011}}, - new UmbracoDomain("http://domain1.com/fr") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10012}}, - new UmbracoDomain("http://domain3.com/") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1003}}, - new UmbracoDomain("http://domain3.com/en") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10031}}, - new UmbracoDomain("http://domain3.com/fr") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10032}} + new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001}, + new UmbracoDomain("http://domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011}, + new UmbracoDomain("http://domain1.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012}, + new UmbracoDomain("http://domain3.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1003}, + new UmbracoDomain("http://domain3.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10031}, + new UmbracoDomain("http://domain3.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10032} }); } @@ -65,14 +65,14 @@ namespace Umbraco.Tests.Routing { SetupDomainServiceMock(new[] { - new UmbracoDomain("http://domain1.com/en") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10011}}, - new UmbracoDomain("http://domain1a.com/en") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10011}}, - new UmbracoDomain("http://domain1b.com/en") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10011}}, - new UmbracoDomain("http://domain1.com/fr") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10012}}, - new UmbracoDomain("http://domain1a.com/fr") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10012}}, - new UmbracoDomain("http://domain1b.com/fr") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10012}}, - new UmbracoDomain("http://domain3.com/en") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10031}}, - new UmbracoDomain("http://domain3.com/fr") {Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10032}} + new UmbracoDomain("http://domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011}, + new UmbracoDomain("http://domain1a.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011}, + new UmbracoDomain("http://domain1b.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011}, + new UmbracoDomain("http://domain1.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012}, + new UmbracoDomain("http://domain1a.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012}, + new UmbracoDomain("http://domain1b.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012}, + new UmbracoDomain("http://domain3.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10031}, + new UmbracoDomain("http://domain3.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10032} }); } diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 9744f189aa..f7dc8c160c 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -13,6 +13,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.Routing; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; +using Umbraco.Core.Profiling; using Umbraco.Core.Strings; namespace Umbraco.Tests.Routing @@ -28,7 +29,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UmbracoPath = "~/umbraco"; - var webBoot = new WebBootManager(new UmbracoApplication(), true); + var webBoot = new WebBootManager(new UmbracoApplication(), new ProfilingLogger(Mock.Of(), Mock.Of()), true); //webBoot.Initialize(); //webBoot.Startup(null); -> don't call startup, we don't want any other application event handlers to bind for this test. //webBoot.Complete(null); diff --git a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs index 5802e023f0..643d007889 100644 --- a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs +++ b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Routing domainService.Setup(service => service.GetAll(It.IsAny())) .Returns((bool incWildcards) => incWildcards ? allDomains : allDomains.Where(d => d.IsWildcard == false)); domainService.Setup(service => service.GetAssignedDomains(It.IsAny(), It.IsAny())) - .Returns((int id, bool incWildcards) => allDomains.Where(d => d.RootContent.Id == id && (incWildcards || d.IsWildcard == false))); + .Returns((int id, bool incWildcards) => allDomains.Where(d => d.RootContentId == id && (incWildcards || d.IsWildcard == false))); return domainService.Object; } @@ -36,18 +36,36 @@ namespace Umbraco.Tests.Routing { //get the mocked service context to get the mocked domain service var svcCtx = MockHelper.GetMockedServiceContext(); + var domainService = Mock.Get(svcCtx.DomainService); //setup mock domain service domainService.Setup(service => service.GetAll(It.IsAny())) .Returns((bool incWildcards) => new[] { - new UmbracoDomain("domain1.com/"){Id = 1, Language = new Language("de-DE"), RootContent = new Content("test1", -1, new ContentType(-1)){ Id = 1001}}, - new UmbracoDomain("domain1.com/en"){Id = 1, Language = new Language("en-US"), RootContent = new Content("test2", -1, new ContentType(-1)){ Id = 10011}}, - new UmbracoDomain("domain1.com/fr"){Id = 1, Language = new Language("fr-FR"), RootContent = new Content("test3", -1, new ContentType(-1)){ Id = 10012}} + new UmbracoDomain("domain1.com/"){Id = 1, LanguageId = 333, RootContentId = 1001}, + new UmbracoDomain("domain1.com/en"){Id = 1, LanguageId = 334, RootContentId = 10011}, + new UmbracoDomain("domain1.com/fr"){Id = 1, LanguageId = 335, RootContentId = 10012} }); + + var langService = Mock.Get(svcCtx.LocalizationService); + //setup mock domain service + langService.Setup(service => service.GetLanguageById(LangDeId)).Returns(new Language("de-DE") {Id = LangDeId }); + langService.Setup(service => service.GetLanguageById(LangEngId)).Returns(new Language("en-US") { Id = LangEngId }); + langService.Setup(service => service.GetLanguageById(LangFrId)).Returns(new Language("fr-FR") { Id = LangFrId }); + langService.Setup(service => service.GetLanguageById(LangCzId)).Returns(new Language("cs-CZ") { Id = LangCzId }); + langService.Setup(service => service.GetLanguageById(LangNlId)).Returns(new Language("nl-NL") { Id = LangNlId }); + langService.Setup(service => service.GetLanguageById(LangDkId)).Returns(new Language("da-DK") { Id = LangDkId }); + return svcCtx; } + public const int LangDeId = 333; + public const int LangEngId = 334; + public const int LangFrId = 335; + public const int LangCzId = 336; + public const int LangNlId = 337; + public const int LangDkId = 338; + protected override void SetupApplicationContext() { var settings = SettingsForTests.GetDefault(); diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index 1378cd3547..79feac0b28 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -80,8 +80,8 @@ namespace Umbraco.Tests.Routing { SetupDomainServiceMock(new[] { - new UmbracoDomain("http://domain1.com/") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 1001}}, - new UmbracoDomain("http://domain2.com/") {Id = 1, Language = new Language("en-US"), RootContent = new Content("test1", -1, new ContentType(-1)) {Id = 10011}} + new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001}, + new UmbracoDomain("http://domain2.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 10011} }); } diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs index e6fc492350..be681c9e72 100644 --- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs +++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs @@ -547,7 +547,7 @@ namespace Umbraco.Tests.Scheduling runner.Shutdown(false, true); // check that task has been disposed (timer has been killed, etc) - Assert.IsTrue(task.Disposed); + Assert.IsTrue(task.IsDisposed); } } @@ -828,14 +828,6 @@ namespace Umbraco.Tests.Scheduling } public override bool RunsOnShutdown { get { return false; } } - - protected override void Dispose(bool disposing) - { - Disposed = true; - base.Dispose(disposing); - } - - public bool Disposed { get; private set; } } private class MyTask : BaseTask diff --git a/src/Umbraco.Tests/ServerEnvironmentHelperTests.cs b/src/Umbraco.Tests/ServerEnvironmentHelperTests.cs index a1f71f4f0d..7b0bdce8b4 100644 --- a/src/Umbraco.Tests/ServerEnvironmentHelperTests.cs +++ b/src/Umbraco.Tests/ServerEnvironmentHelperTests.cs @@ -29,14 +29,18 @@ namespace Umbraco.Tests [Test] public void SetApplicationUrlWhenNoSettings() { - var appContext = new ApplicationContext(null) + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())) { UmbracoApplicationUrl = null // NOT set }; + + ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true"); // does not make a diff here - ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appContext, _logger, + ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appCtx, _logger, Mock.Of( section => section.DistributedCall == Mock.Of(callSection => callSection.Servers == Enumerable.Empty()) @@ -45,17 +49,19 @@ namespace Umbraco.Tests // still NOT set - Assert.IsNull(appContext._umbracoApplicationUrl); + Assert.IsNull(appCtx._umbracoApplicationUrl); } [Test] public void SetApplicationUrlFromDcSettingsNoSsl() { - var appContext = new ApplicationContext(null); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); ConfigurationManager.AppSettings.Set("umbracoUseSSL", "false"); - ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appContext, _logger, + ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appCtx, _logger, Mock.Of( section => section.DistributedCall == Mock.Of(callSection => callSection.Servers == Enumerable.Empty()) @@ -63,17 +69,19 @@ namespace Umbraco.Tests && section.ScheduledTasks == Mock.Of(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world/"))); - Assert.AreEqual("http://mycoolhost.com/hello/world", appContext._umbracoApplicationUrl); + Assert.AreEqual("http://mycoolhost.com/hello/world", appCtx._umbracoApplicationUrl); } [Test] public void SetApplicationUrlFromDcSettingsSsl() { - var appContext = new ApplicationContext(null); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true"); - ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appContext, _logger, + ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appCtx, _logger, Mock.Of( section => section.DistributedCall == Mock.Of(callSection => callSection.Servers == Enumerable.Empty()) @@ -81,17 +89,19 @@ namespace Umbraco.Tests && section.ScheduledTasks == Mock.Of(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world"))); - Assert.AreEqual("https://mycoolhost.com/hello/world", appContext._umbracoApplicationUrl); + Assert.AreEqual("https://mycoolhost.com/hello/world", appCtx._umbracoApplicationUrl); } [Test] public void SetApplicationUrlFromWrSettingsSsl() { - var appContext = new ApplicationContext(null); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true"); // does not make a diff here - ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appContext, _logger, + ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appCtx, _logger, Mock.Of( section => section.DistributedCall == Mock.Of(callSection => callSection.Servers == Enumerable.Empty()) @@ -99,7 +109,7 @@ namespace Umbraco.Tests && section.ScheduledTasks == Mock.Of(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world"))); - Assert.AreEqual("httpx://whatever.com/hello/world", appContext._umbracoApplicationUrl); + Assert.AreEqual("httpx://whatever.com/hello/world", appCtx._umbracoApplicationUrl); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs index fd0fd1437d..4e2e744660 100644 --- a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs +++ b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core; @@ -105,6 +106,60 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Can_Get_Dictionary_Item_Descendants() + { + try + { + var en = ServiceContext.LocalizationService.GetLanguageById(_englishLangId); + var dk = ServiceContext.LocalizationService.GetLanguageById(_danishLangId); + + var currParentId = _childItemGuidId; + for (int i = 0; i < 25; i++) + { + //Create 2 per level + var desc1 = new DictionaryItem(currParentId, "D1" + i) + { + Translations = new List + { + new DictionaryTranslation(en, "ChildValue1 " + i), + new DictionaryTranslation(dk, "BørnVærdi1 " + i) + } + }; + var desc2 = new DictionaryItem(currParentId, "D2" + i) + { + Translations = new List + { + new DictionaryTranslation(en, "ChildValue2 " + i), + new DictionaryTranslation(dk, "BørnVærdi2 " + i) + } + }; + ServiceContext.LocalizationService.Save(desc1); + ServiceContext.LocalizationService.Save(desc2); + + currParentId = desc1.Key; + } + + DatabaseContext.Database.EnableSqlTrace = true; + DatabaseContext.Database.EnableSqlCount(); + + var items = ServiceContext.LocalizationService.GetDictionaryItemDescendants(_parentItemGuidId) + .ToArray(); + + Debug.WriteLine("SQL CALLS: " + DatabaseContext.Database.SqlCount); + + Assert.AreEqual(51, items.Length); + //there's a call or two to get languages, so apart from that there should only be one call per level + Assert.Less(DatabaseContext.Database.SqlCount, 30); + } + finally + { + DatabaseContext.Database.EnableSqlTrace = false; + DatabaseContext.Database.DisableSqlCount(); + } + + } + [Test] public void Can_Get_Language_By_Culture_Code() { diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index 7077cf19ce..ed87493c01 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -18,6 +18,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using umbraco.editorControls.tinyMCE3; using umbraco.interfaces; +using Umbraco.Core.Events; namespace Umbraco.Tests.Services { @@ -49,13 +50,15 @@ namespace Umbraco.Tests.Services //we need a new Database object for each thread. var repositoryFactory = new RepositoryFactory(cacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings()); _uowProvider = new PerThreadPetaPocoUnitOfWorkProvider(_dbFactory); - ApplicationContext.Services = new ServiceContext( + var evtMsgs = new TransientMessagesFactory(); + ApplicationContext.Services = new ServiceContext( repositoryFactory, _uowProvider, new FileUnitOfWorkProvider(), - new PublishingStrategy(), + new PublishingStrategy(evtMsgs, Logger), cacheHelper, - Logger); + Logger, + evtMsgs); CreateTestData(); } diff --git a/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs b/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs index 798cc57526..0f7b4fbd41 100644 --- a/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs +++ b/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs @@ -51,7 +51,7 @@ namespace Umbraco.Tests.Strings [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6 In The Village")] // issue is fixed public void CompatibleDefaultReplacement(string input, string expected) { - var helper = new DefaultShortStringHelper(); + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()); var output = input.Length < 2 ? input : helper.SplitPascalCasing(input, ' ').ToFirstUpperInvariant(); Assert.AreEqual(expected, output); } diff --git a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs index 04b632ffeb..ec7bbe3c72 100644 --- a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs +++ b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs @@ -15,14 +15,13 @@ using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Strings { [TestFixture] - public class DefaultShortStringHelperTests : BaseUmbracoConfigurationTest + public class DefaultShortStringHelperTests { private DefaultShortStringHelper _helper; [SetUp] - public override void Initialize() + public void Initialize() { - base.Initialize(); // NOTE: it is not possible to configure the helper once it has been assigned // to the resolver and resolution has frozen. but, obviously, it is possible @@ -32,7 +31,7 @@ namespace Umbraco.Tests.Strings // NOTE pre-filters runs _before_ Recode takes place // so there still may be utf8 chars even though you want ascii - _helper = new DefaultShortStringHelper() + _helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.FileName, new DefaultShortStringHelper.Config { //PreFilter = ClearFileChars, // done in IsTerm @@ -80,9 +79,8 @@ namespace Umbraco.Tests.Strings } [TearDown] - public override void TearDown() + public void TearDown() { - base.TearDown(); ShortStringHelperResolver.Reset(); } @@ -131,11 +129,11 @@ namespace Umbraco.Tests.Strings const string input = "ÆØÅ and æøå and 中文测试 and אודות האתר and größer БбДдЖж page"; - var helper = new DefaultShortStringHelper().WithDefaultConfig(); // unicode + var helper = new DefaultShortStringHelper(settings).WithDefaultConfig(); // unicode var output = helper.CleanStringForUrlSegment(input); Assert.AreEqual("æøå-and-æøå-and-中文测试-and-אודות-האתר-and-größer-ббдджж-page", output); - helper = new DefaultShortStringHelper() + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config { IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', @@ -149,7 +147,7 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringUnderscoreInTerm() { - var helper = new DefaultShortStringHelper() + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { // underscore is accepted within terms @@ -159,7 +157,7 @@ namespace Umbraco.Tests.Strings }); Assert.AreEqual("foo_bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias)); - helper = new DefaultShortStringHelper() + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { // underscore is not accepted within terms @@ -173,7 +171,7 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringLeadingChars() { - var helper = new DefaultShortStringHelper() + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { // letters and digits are valid leading chars @@ -183,7 +181,7 @@ namespace Umbraco.Tests.Strings }); Assert.AreEqual("0123foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias)); - helper = new DefaultShortStringHelper() + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { // only letters are valid leading chars @@ -194,14 +192,14 @@ namespace Umbraco.Tests.Strings Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias)); Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123 foo_bar 543 nil 321", CleanStringType.Alias)); - helper = new DefaultShortStringHelper().WithDefaultConfig(); + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()).WithDefaultConfig(); Assert.AreEqual("child2", helper.CleanStringForSafeAlias("1child2")); } [Test] public void CleanStringTermOnUpper() { - var helper = new DefaultShortStringHelper() + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -211,7 +209,7 @@ namespace Umbraco.Tests.Strings }); Assert.AreEqual("foo*Bar", helper.CleanString("fooBar", CleanStringType.Alias)); - helper = new DefaultShortStringHelper() + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -225,7 +223,7 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringAcronymOnNonUpper() { - var helper = new DefaultShortStringHelper() + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -238,7 +236,7 @@ namespace Umbraco.Tests.Strings Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); - helper = new DefaultShortStringHelper() + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -255,7 +253,7 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringGreedyAcronyms() { - var helper = new DefaultShortStringHelper() + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -268,7 +266,7 @@ namespace Umbraco.Tests.Strings Assert.AreEqual("foo*BA*nil", helper.CleanString("foo BAnil", CleanStringType.Alias)); Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); - helper = new DefaultShortStringHelper() + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -285,7 +283,7 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringWhiteSpace() { - var helper = new DefaultShortStringHelper() + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -298,7 +296,7 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringSeparator() { - var helper = new DefaultShortStringHelper() + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -306,7 +304,7 @@ namespace Umbraco.Tests.Strings }); Assert.AreEqual("foo*bar", helper.CleanString("foo bar", CleanStringType.Alias)); - helper = new DefaultShortStringHelper() + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -314,14 +312,14 @@ namespace Umbraco.Tests.Strings }); Assert.AreEqual("foo bar", helper.CleanString("foo bar", CleanStringType.Alias)); - helper = new DefaultShortStringHelper() + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged }); Assert.AreEqual("foobar", helper.CleanString("foo bar", CleanStringType.Alias)); - helper = new DefaultShortStringHelper() + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -333,7 +331,7 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringSymbols() { - var helper = new DefaultShortStringHelper() + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -387,7 +385,7 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringEncoding() { - var helper = new DefaultShortStringHelper() + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -396,7 +394,7 @@ namespace Umbraco.Tests.Strings Assert.AreEqual("中文测试", helper.CleanString("中文测试", CleanStringType.Alias)); Assert.AreEqual("léger*中文测试*ZÔRG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias)); - helper = new DefaultShortStringHelper() + helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Ascii | CleanStringType.Unchanged, @@ -415,7 +413,7 @@ namespace Umbraco.Tests.Strings contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false); SettingsForTests.ConfigureSettings(settings); - var helper = new DefaultShortStringHelper().WithDefaultConfig(); + var helper = new DefaultShortStringHelper(settings).WithDefaultConfig(); const string input = "0123 中文测试 中文测试 léger ZÔRG (2) a?? *x"; @@ -436,7 +434,7 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringCasing() { - var helper = new DefaultShortStringHelper() + var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, @@ -539,7 +537,7 @@ namespace Umbraco.Tests.Strings //#endregion //public void CleanStringWithUnderscore(string input, string expected, bool allowUnderscoreInTerm) //{ - // var helper = new DefaultShortStringHelper() + // var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) // .WithConfig(allowUnderscoreInTerm: allowUnderscoreInTerm); // var output = helper.CleanString(input, CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.CamelCase); // Assert.AreEqual(expected, output); diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 1bc4661de7..d47e7a21b2 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -24,6 +24,19 @@ namespace Umbraco.Tests.Strings ShortStringHelperResolver.Reset(); } + [TestCase("alert('hello');", false)] + [TestCase("~/Test.js", true)] + [TestCase("../Test.js", true)] + [TestCase("/Test.js", true)] + [TestCase("Test.js", true)] + [TestCase("Test.js==", false)] + [TestCase("/Test.js function(){return true;}", false)] + public void Detect_Is_JavaScript_Path(string input, bool result) + { + var output = input.DetectIsJavaScriptPath(); + Assert.AreEqual(result, output.Success); + } + [TestCase("hello.txt", "hello")] [TestCase("this.is.a.Txt", "this.is.a")] [TestCase("this.is.not.a. Txt", "this.is.not.a. Txt")] diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 70eac1b07d..967998e517 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -30,6 +30,7 @@ using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; using umbraco.BusinessLogic; +using Umbraco.Core.Events; namespace Umbraco.Tests.TestHelpers { @@ -75,11 +76,12 @@ namespace Umbraco.Tests.TestHelpers var repositoryFactory = new RepositoryFactory(cacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings()); + var evtMsgs = new TransientMessagesFactory(); _appContext = new ApplicationContext( //assign the db context new DatabaseContext(dbFactory, Logger, SqlSyntax, "System.Data.SqlServerCe.4.0"), //assign the service context - new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper, Logger), + new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), cacheHelper, Logger, evtMsgs), cacheHelper, ProfilingLogger) { diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 12914c8ecd..abf1edd3a5 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -19,6 +19,7 @@ using Umbraco.Core.Services; using Umbraco.Web; using Umbraco.Web.Models.Mapping; using umbraco.BusinessLogic; +using Umbraco.Core.Events; using ObjectExtensions = Umbraco.Core.ObjectExtensions; namespace Umbraco.Tests.TestHelpers @@ -63,15 +64,14 @@ namespace Umbraco.Tests.TestHelpers public override void TearDown() { base.TearDown(); - - LoggerResolver.Reset(); + //reset settings SettingsForTests.Reset(); UmbracoContext.Current = null; TestHelper.CleanContentDirectories(); TestHelper.CleanUmbracoSettingsConfig(); //reset the app context, this should reset most things that require resetting like ALL resolvers - ObjectExtensions.DisposeIfDisposable(ApplicationContext.Current); + ApplicationContext.Current.DisposeIfDisposable(); ApplicationContext.Current = null; ResetPluginManager(); @@ -153,12 +153,13 @@ namespace Umbraco.Tests.TestHelpers var sqlSyntax = new SqlCeSyntaxProvider(); var repoFactory = new RepositoryFactory(CacheHelper.CreateDisabledCacheHelper(), Logger, sqlSyntax, SettingsForTests.GenerateMockSettings()); + var evtMsgs = new TransientMessagesFactory(); ApplicationContext.Current = new ApplicationContext( //assign the db context new DatabaseContext(new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Logger), Logger, sqlSyntax, "System.Data.SqlServerCe.4.0"), //assign the service context - new ServiceContext(repoFactory, new PetaPocoUnitOfWorkProvider(Logger), new FileUnitOfWorkProvider(), new PublishingStrategy(), CacheHelper, Logger), + new ServiceContext(repoFactory, new PetaPocoUnitOfWorkProvider(Logger), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), CacheHelper, Logger, evtMsgs), CacheHelper, ProfilingLogger) { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 517bfacc25..30433ed00b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -59,9 +59,9 @@ False ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - - False - ..\packages\Examine.0.1.63.0\lib\Examine.dll + + ..\packages\Examine.0.1.66.0\lib\Examine.dll + True False @@ -79,9 +79,6 @@ True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\packages\Microsoft.AspNet.Mvc.FixedDisplayModes.1.0.1\lib\net40\Microsoft.Web.Mvc.FixedDisplayModes.dll - False ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll @@ -90,8 +87,8 @@ ..\packages\Moq.4.1.1309.0919\lib\net40\Moq.dll - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True False @@ -124,9 +121,9 @@ - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll False @@ -139,25 +136,26 @@ 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.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll - + + ..\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.2.0.30506.0\lib\net40\System.Web.WebPages.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll @@ -178,6 +176,7 @@ + diff --git a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs index 18343a96f5..d01b298bb4 100644 --- a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs @@ -14,7 +14,7 @@ namespace Umbraco.Tests.UmbracoExamine public void Events_Ignoring_Node() { //change the parent id so that they are all ignored - var existingCriteria = ((IndexCriteria)_indexer.IndexerData); + var existingCriteria = _indexer.IndexerData; _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, 999); //change to 999 diff --git a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs index d36e49bc25..5fed998194 100644 --- a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs @@ -15,7 +15,7 @@ namespace Umbraco.Tests.UmbracoExamine { UmbracoExamineSearcher.DisableInitializationCheck = true; BaseUmbracoIndexer.DisableInitializationCheck = true; - ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper()); + ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault())); Resolution.Freeze(); } diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 7196d59c0c..6c56c76ef7 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -49,7 +49,7 @@ namespace Umbraco.Tests.UmbracoExamine } if (mediaService == null) { - int totalRecs; + long totalRecs; var allRecs = dataService.MediaService.GetLatestMediaByXpath("//node") .Root @@ -74,7 +74,7 @@ namespace Umbraco.Tests.UmbracoExamine mediaService = Mock.Of( x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny()) == allRecs); } diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 77a9fd2dba..a5418649fe 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -53,7 +53,7 @@ namespace Umbraco.Tests.UmbracoExamine public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID() { //change parent id to 1116 - var existingCriteria = ((IndexCriteria)_indexer.IndexerData); + var existingCriteria = _indexer.IndexerData; _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, 1116); @@ -110,7 +110,7 @@ namespace Umbraco.Tests.UmbracoExamine _indexer.ReIndexNode(node, IndexTypes.Media); //change the parent node id to be the one it used to exist under - var existingCriteria = ((IndexCriteria)_indexer.IndexerData); + var existingCriteria = _indexer.IndexerData; _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, 2222); diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index 15de2204f7..4ed8172598 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -2,25 +2,24 @@ - - + + - - - + + - + - - - + + + - + \ No newline at end of file diff --git a/src/Umbraco.Tests/unit-test-log4net.config b/src/Umbraco.Tests/unit-test-log4net.config index 930a5ed1d8..703471f5a8 100644 --- a/src/Umbraco.Tests/unit-test-log4net.config +++ b/src/Umbraco.Tests/unit-test-log4net.config @@ -18,7 +18,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/Umbraco.Web.UI.Client.proj.DotSettings b/src/Umbraco.Web.UI.Client/Umbraco.Web.UI.Client.proj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/Umbraco.Web.UI.Client.proj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index d568eed5dd..40e1428510 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -26,7 +26,6 @@ "angular-dynamic-locale": "~0.1.27", "ng-file-upload": "~3.0.2", "tinymce": "~4.1.10", - "bootstrap-tabdrop": "~1.0.0", "codemirror": "~5.3.0" } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index e17651caee..5533052a71 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -433,7 +433,8 @@ module.exports = function (grunt) { ignorePackages: ['bootstrap'], packageSpecific: { 'typeahead.js': { - keepExpandedHierarchy: false + keepExpandedHierarchy: false, + files: ['dist/typeahead.bundle.min.js'] }, 'underscore': { files: ['underscore-min.js', 'underscore-min.map'] @@ -462,10 +463,7 @@ module.exports = function (grunt) { }, 'angular-dynamic-locale': { files: ['tmhDynamicLocale.min.js', 'tmhDynamicLocale.min.js.map'] - }, - 'bootstrap-tabdrop': { - keepExpandedHierarchy: false - }, + }, 'ng-file-upload': { keepExpandedHierarchy: false, files: ['angular-file-upload.min.js'] diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap-tabdrop/README.md b/src/Umbraco.Web.UI.Client/lib/bootstrap-tabdrop/README.md new file mode 100644 index 0000000000..e8d266fb24 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap-tabdrop/README.md @@ -0,0 +1,62 @@ +bootstrap-tabdrop +================= + +***************************************************************** +NOTE: THIS IS A CUSTOM FIXED VERSION!!!!!!!!!!!!!!!!!!!!!! +- THE ORIGINAL HAS A MEMORY LEAK, SO WE'VE HAD TO EMBED THIS + INTO THE CORE WITH THE FIX + +--- UMBRACO CORE TEAM +***************************************************************** + +A dropdown tab tool for @twitter bootstrap forked from Stefan Petre's (of eyecon.ro), + +The dropdown tab appears when your tabs do not all fit in the same row. + +Original site and examples: http://www.eyecon.ro/bootstrap-tabdrop/ + +Added functionality: Displays the text of an active tab selected from the dropdown list instead of the text option on the dropdown tab. + + +## Requirements + +* [Bootstrap](http://twitter.github.com/bootstrap/) 2.0.4+ +* [jQuery](http://jquery.com/) 1.7.1+ + +## Example + +No additional HTML needed - the script adds it when the dropdown tab is needed. + +Using bootstrap-tabdrop.js +Call the tab drop via javascript on .nav-tabs and .nav-pills: +```js +$('.nav-pills, .nav-tabs').tabdrop() +``` + +### Options + +#### text +Type: string +Default: icon +```html + +``` +To change the default value, call +```javascript +.tabdrop({text: "your text here"}); +``` +when initalizing the tabdrop. The displayed value will change when a tab is selected from the dropdown list. + +### Methods + +```js +.tabdrop(options) +``` + +Initializes an tab drop. + +```js +.tabdrop('layout') +``` + +Checks if the tabs fit in one single row. diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap-tabdrop/bootstrap-tabdrop.js b/src/Umbraco.Web.UI.Client/lib/bootstrap-tabdrop/bootstrap-tabdrop.js new file mode 100644 index 0000000000..ede04bfc6b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap-tabdrop/bootstrap-tabdrop.js @@ -0,0 +1,132 @@ +/* ========================================================= + * bootstrap-tabdrop.js + * http://www.eyecon.ro/bootstrap-tabdrop + * ========================================================= + * Copyright 2012 Stefan Petre + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + +/***************************************************************** + * NOTE: THIS IS A CUSTOM FIXED VERSION!!!!!!!!!!!!!!!!!!!!!! + * - THE ORIGINAL HAS A MEMORY LEAK, SO WE'VE HAD TO EMBED THIS + * INTO THE CORE WITH THE FIX + * + * --- UMBRACO CORE TEAM + *****************************************************************/ + +!function( $ ) { + + var WinReszier = (function(){ + var registered = []; + var inited = false; + var timer; + var resize = function(ev) { + clearTimeout(timer); + timer = setTimeout(notify, 100); + }; + var notify = function() { + for(var i=0, cnt=registered.length; i -1) { + registered.splice(index, 1); + } + } + }; + }()); + + var TabDrop = function(element, options) { + this.element = $(element); + this.dropdown = $('').prependTo(this.element); + if (this.element.parent().is('.tabs-below')) { + this.dropdown.addClass('dropup'); + } + this.resizeCallback = $.proxy(this.layout, this); + WinReszier.register(this.resizeCallback); + this.layout(); + }; + + TabDrop.prototype = { + constructor: TabDrop, + + layout: function() { + var collection = []; + this.dropdown.removeClass('hide'); + this.element + .append(this.dropdown.find('li')) + .find('>li') + .not('.tabdrop') + .each(function(){ + if(this.offsetTop > 0) { + collection.push(this); + } + }); + if (collection.length > 0) { + collection = $(collection); + this.dropdown + .find('ul') + .empty() + .append(collection); + if (this.dropdown.find('.active').length == 1) { + this.dropdown.addClass('active'); + } else { + this.dropdown.removeClass('active'); + } + } else { + this.dropdown.addClass('hide'); + } + }, + + destroy: function() { + this.dropdown.html(); + WinReszier.unregister(this.resizeCallback); + } + }; + + $.fn.tabdrop = function ( option ) { + return this.each(function () { + var $this = $(this), + data = $this.data('tabdrop'), + options = typeof option === 'object' && option; + if (!data) { + $this.data('tabdrop', (data = new TabDrop(this, $.extend({}, + $.fn.tabdrop.defaults,options)))); + } + if (typeof option == 'string') { + data[option](); + } + }); + }; + + $.fn.tabdrop.defaults = { + text: '' + }; + + $.fn.tabdrop.Constructor = TabDrop; + +}(window.jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg index d460bf4cd3..2e00445286 100644 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg and b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg differ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoResize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoResize.directive.js index fa8c85509d..35007909eb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoResize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoResize.directive.js @@ -1,38 +1,47 @@ angular.module("umbraco.directives") .directive('umbAutoResize', function($timeout) { - - return function(scope, element, attr){ - var domEl = element[0]; - var update = function(force) { - - if(force === true){ - element.height(0); + return { + require: "^?umbTabs", + link: function(scope, element, attr, tabsCtrl) { + var domEl = element[0]; + var update = function(force) { + if (force === true) { + element.height(0); element.width(0); - } + } - if(domEl.scrollHeight !== domEl.clientHeight){ - element.height(domEl.scrollHeight); - } + if (domEl.scrollHeight !== domEl.clientHeight) { + element.height(domEl.scrollHeight); + } if(domEl.scrollWidth !== domEl.clientWidth) { element.width(domEl.scrollWidth); } - }; + }; + var blur = function() { + update(true); + }; - element.bind('keyup keydown keypress change', update); - element.bind('blur', function(){ update(true); }); + element.bind('keyup keydown keypress change', update); + element.bind('blur', blur); - $timeout(function() { - update(true); - }, 500); + $timeout(function() { + update(true); + }, 200); - //I hate bootstrap tabs - $('a[data-toggle="tab"]').on('shown', update); + //listen for tab changes + if (tabsCtrl != null) { + tabsCtrl.onTabShown(function(args) { + update(); + }); + } - scope.$on('$destroy', function() { - $('a[data-toggle="tab"]').unbind("shown", update); - }); - }; -}); + scope.$on('$destroy', function () { + element.unbind('keyup keydown keypress change', update); + element.unbind('blur', blur); + }); + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js index 15a1a09c05..8a13a3c714 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js @@ -1,5 +1,13 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbHeader +* @restrict E +* @function +* @description +* The header on an editor that contains tabs using bootstrap tabs - THIS IS OBSOLETE, use umbTabHeader instead +**/ angular.module("umbraco.directives") -.directive('umbHeader', function($parse, $timeout){ +.directive('umbHeader', function ($parse, $timeout) { return { restrict: 'E', replace: true, @@ -11,50 +19,13 @@ angular.module("umbraco.directives") tabs: "=" }, link: function (scope, iElement, iAttrs) { - - var maxTabs = 4; - - function collectFromDom(activeTab){ - var $panes = $('div.tab-content'); - - angular.forEach($panes.find('.tab-pane'), function (pane, index) { - var $this = angular.element(pane); - - var id = $this.attr("rel"); - var label = $this.attr("label"); - var tab = {id: id, label: label, active: false}; - if(!activeTab){ - tab.active = true; - activeTab = tab; - } - - if ($this.attr("rel") === String(activeTab.id)) { - $this.addClass('active'); - } - else { - $this.removeClass('active'); - } - - if(label){ - scope.visibleTabs.push(tab); - } - - }); - - $('.nav-pills, .nav-tabs').tabdrop(); - } - + scope.showTabs = iAttrs.tabs ? true : false; scope.visibleTabs = []; - scope.overflownTabs = []; - $timeout(function () { - collectFromDom(undefined); - }, 500); - - //when the tabs change, we need to hack the planet a bit and force the first tab content to be active, - //unfortunately twitter bootstrap tabs is not playing perfectly with angular. - scope.$watch("tabs", function (newValue, oldValue) { + //since tabs are loaded async, we need to put a watch on them to determine + // when they are loaded, then we can close the watch + var tabWatch = scope.$watch("tabs", function (newValue, oldValue) { angular.forEach(newValue, function(val, index){ var tab = {id: val.id, label: val.label}; @@ -64,16 +35,25 @@ angular.module("umbraco.directives") //don't process if we cannot or have already done so if (!newValue) {return;} if (!newValue.length || newValue.length === 0){return;} - - var activeTab = _.find(newValue, function (item) { - return item.active; - }); - + //we need to do a timeout here so that the current sync operation can complete // and update the UI, then this will fire and the UI elements will be available. $timeout(function () { - collectFromDom(activeTab); - }, 500); + + //use bootstrap tabs API to show the first one + iElement.find(".nav-tabs a:first").tab('show'); + + //enable the tab drop + iElement.find('.nav-pills, .nav-tabs').tabdrop(); + + //ensure to destroy tabdrop (unbinds window resize listeners) + scope.$on('$destroy', function () { + iElement.find('.nav-pills, .nav-tabs').tabdrop("destroy"); + }); + + //stop watching now + tabWatch(); + }); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtab.directive.js index 3758c64179..f12544646b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtab.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtab.directive.js @@ -4,11 +4,11 @@ * @restrict E **/ angular.module("umbraco.directives") -.directive('umbTab', function(){ - return { +.directive('umbTab', function ($parse, $timeout) { + return { restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/directives/umb-tab.html' - }; + replace: true, + transclude: 'true', + templateUrl: 'views/directives/umb-tab.html' + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtabs.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtabs.directive.js new file mode 100644 index 0000000000..882372aba9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtabs.directive.js @@ -0,0 +1,45 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbTabs +* @restrict A +* @description Used to bind to bootstrap tab events so that sub directives can use this API to listen to tab changes +**/ +angular.module("umbraco.directives") +.directive('umbTabs', function () { + return { + restrict: 'A', + controller: function ($scope, $element, $attrs) { + + var callbacks = []; + this.onTabShown = function(cb) { + callbacks.push(cb); + }; + + function tabShown(event) { + + var curr = $(event.target); // active tab + var prev = $(event.relatedTarget); // previous tab + + for (var c in callbacks) { + callbacks[c].apply(this, [{current: curr, previous: prev}]); + } + } + + //NOTE: it MUST be done this way - binding to an ancestor element that exists + // in the DOM to bind to the dynamic elements that will be created. + // It would be nicer to create this event handler as a directive for which child + // directives can attach to. + $element.on('shown', '.nav-tabs a', tabShown); + + //ensure to unregister + $scope.$on('$destroy', function () { + $element.off('shown', '.nav-tabs a', tabShown); + + for (var c in callbacks) { + delete callbacks[c]; + } + callbacks = null; + }); + } + }; +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/navresize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/navresize.directive.js new file mode 100644 index 0000000000..b4ee63b8dc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/navresize.directive.js @@ -0,0 +1,107 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:navResize +* @restrict A + * + * @description + * Handles how the navigation responds to window resizing and controls how the draggable resize panel works +**/ +angular.module("umbraco.directives") + .directive('navResize', function (appState, eventsService, windowResizeListener) { + return { + restrict: 'A', + link: function (scope, element, attrs, ctrl) { + + var minScreenSize = 1100; + var resizeEnabled = false; + + function setTreeMode() { + appState.setGlobalState("showNavigation", appState.getGlobalState("isTablet") === false); + } + + function enableResize() { + //only enable when the size is correct and it's not already enabled + if (!resizeEnabled && appState.getGlobalState("isTablet") === false) { + element.resizable( + { + containment: $("#mainwrapper"), + autoHide: true, + handles: "e", + alsoResize: ".navigation-inner-container", + resize: function(e, ui) { + var wrapper = $("#mainwrapper"); + var contentPanel = $("#contentwrapper"); + var apps = $("#applications"); + var bottomBar = contentPanel.find(".umb-bottom-bar"); + var navOffeset = $("#navOffset"); + + var leftPanelWidth = ui.element.width() + apps.width(); + + contentPanel.css({ left: leftPanelWidth }); + bottomBar.css({ left: leftPanelWidth }); + + navOffeset.css({ "margin-left": ui.element.outerWidth() }); + }, + stop: function (e, ui) { + + } + }); + + resizeEnabled = true; + } + } + + function resetResize() { + if (resizeEnabled) { + //kill the resize + element.resizable("destroy"); + + element.css("width", ""); + var navInnerContainer = element.find(".navigation-inner-container"); + navInnerContainer.css("width", ""); + $("#contentwrapper").css("left", ""); + $("#navOffset").css("margin-left", ""); + + resizeEnabled = false; + } + } + + var evts = []; + + //Listen for global state changes + evts.push(eventsService.on("appState.globalState.changed", function (e, args) { + if (args.key === "showNavigation") { + if (args.value === false) { + resetResize(); + } + else { + enableResize(); + } + } + })); + + var resizeCallback = function(size) { + //set the global app state + appState.setGlobalState("isTablet", (size.width <= minScreenSize)); + setTreeMode(); + }; + + windowResizeListener.register(resizeCallback); + + //ensure to unregister from all events and kill jquery plugins + scope.$on('$destroy', function () { + windowResizeListener.unregister(resizeCallback); + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + var navInnerContainer = element.find(".navigation-inner-container"); + navInnerContainer.resizable("destroy"); + }); + + //init + //set the global app state + appState.setGlobalState("isTablet", ($(window).width() <= minScreenSize)); + setTreeMode(); + } + }; + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js index 4f4971f312..c56e133759 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js @@ -37,8 +37,7 @@ angular.module("umbraco.directives") template: '
  • ' + '
    ' + //NOTE: This ins element is used to display the search icon if the node is a container/listview and the tree is currently in dialog - //'' + - '' + + //'' + '' + '' + '' + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/detectfold.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/detectfold.directive.js index a0d1a8293b..f890ee4989 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/detectfold.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/detectfold.directive.js @@ -1,49 +1,80 @@ /** * @ngdoc directive * @name umbraco.directives.directive:umbPanel -* @restrict E +* @description This is used for the editor buttons to ensure they are displayed correctly if the horizontal overflow of the editor + * exceeds the height of the window **/ angular.module("umbraco.directives.html") - .directive('detectFold', function($timeout, $log){ - return { + .directive('detectFold', function ($timeout, $log, windowResizeListener) { + return { + require: "^?umbTabs", restrict: 'A', - link: function (scope, el, attrs) { - - var state = false, - parent = $(".umb-panel-body"), - winHeight = $(window).height(), - calculate = _.throttle(function(){ - if(el && el.is(":visible") && !el.hasClass("umb-bottom-bar")){ - //var parent = el.parent(); - var hasOverflow = parent.innerHeight() < parent[0].scrollHeight; - //var belowFold = (el.offset().top + el.height()) > winHeight; - if(hasOverflow){ - el.addClass("umb-bottom-bar"); - } - } - return state; - }, 1000); + link: function (scope, el, attrs, tabsCtrl) { - scope.$watch(calculate, function(newVal, oldVal) { - if(newVal !== oldVal){ - if(newVal){ - el.addClass("umb-bottom-bar"); - }else{ - el.removeClass("umb-bottom-bar"); - } - } - }); + var firstRun = false; + var parent = $(".umb-panel-body"); + var winHeight = $(window).height(); + var calculate = function () { + if (el && el.is(":visible") && !el.hasClass("umb-bottom-bar")) { - $(window).bind("resize", function () { - winHeight = $(window).height(); - el.removeClass("umb-bottom-bar"); - state = false; - calculate(); - }); + //now that the element is visible, set the flag in a couple of seconds, + // this will ensure that loading time of a current tab get's completed and that + // we eventually stop watching to save on CPU time + $timeout(function() { + firstRun = true; + }, 4000); - $('a[data-toggle="tab"]').on('shown', function (e) { - calculate(); - }); + //var parent = el.parent(); + var hasOverflow = parent.innerHeight() < parent[0].scrollHeight; + //var belowFold = (el.offset().top + el.height()) > winHeight; + if (hasOverflow) { + el.addClass("umb-bottom-bar"); + + //I wish we didn't have to put this logic here but unfortunately we + // do. This needs to calculate the left offest to place the bottom bar + // depending on if the left column splitter has been moved by the user + // (based on the nav-resize directive) + var wrapper = $("#mainwrapper"); + var contentPanel = $("#leftcolumn").next(); + var contentPanelLeftPx = contentPanel.css("left"); + + el.css({ left: contentPanelLeftPx }); + } + } + return firstRun; + }; + + var resizeCallback = function(size) { + winHeight = size.height; + el.removeClass("umb-bottom-bar"); + calculate(); + }; + + windowResizeListener.register(resizeCallback); + + //Only execute the watcher if this tab is the active (first) tab on load, otherwise there's no reason to execute + // the watcher since it will be recalculated when the tab changes! + if (el.closest(".umb-tab-pane").index() === 0) { + //run a watcher to ensure that the calculation occurs until it's firstRun but ensure + // the calculations are throttled to save a bit of CPU + var listener = scope.$watch(_.throttle(calculate, 1000), function (newVal, oldVal) { + if (newVal !== oldVal) { + listener(); + } + }); + } + + //listen for tab changes + if (tabsCtrl != null) { + tabsCtrl.onTabShown(function (args) { + calculate(); + }); + } + + //ensure to unregister + scope.$on('$destroy', function() { + windowResizeListener.unregister(resizeCallback); + }); } }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js index 1177da323d..e51310f584 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js @@ -43,7 +43,8 @@ function appState(eventsService) { showTray: null, stickyNavigation: null, navMode: null, - isReady: null + isReady: null, + isTablet: null }; var sectionState = { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js b/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js index f36b932d5f..f0f1649bbe 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js @@ -15,7 +15,7 @@ function macroService() { //This regex will match an alias of anything except characters that are quotes or new lines (for legacy reasons, when new macros are created // their aliases are cleaned an invalid chars are stripped) - var expression = /(<\?UMBRACO_MACRO(?:.+?)?macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i; + var expression = /(<\?UMBRACO_MACRO (?:.+?)?macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i; var match = expression.exec(syntax); if (!match || match.length < 3) { return null; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index ba4a4b1488..f404419340 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -17,21 +17,15 @@ */ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeout, $injector, dialogService, umbModelMapper, treeService, notificationsService, historyService, appState, angularHelper) { - var minScreenSize = 1100; + //used to track the current dialog object var currentDialog = null; - //tracks the screen size as a tablet - var isTablet = false; + //the main tree event handler, which gets assigned via the setupTreeEvents method var mainTreeEventHandler = null; //tracks the user profile dialog var userDialog = null; - function setTreeMode() { - isTablet = ($(window).width() <= minScreenSize); - appState.setGlobalState("showNavigation", !isTablet); - } - function setMode(mode) { switch (mode) { case 'tree': @@ -80,7 +74,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setGlobalState("stickyNavigation", false); appState.setGlobalState("showTray", false); - if (isTablet) { + if (appState.getGlobalState("isTablet") === true) { appState.setGlobalState("showNavigation", false); } @@ -93,8 +87,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo /** initializes the navigation service */ init: function() { - setTreeMode(); - //keep track of the current section - initially this will always be undefined so // no point in setting it now until it changes. $rootScope.$watch(function () { @@ -103,10 +95,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setSectionState("currentSection", newVal); }); - //TODO: This does not belong here - would be much better off in a directive - $(window).bind("resize", function() { - setTreeMode(); - }); + }, /** @@ -353,7 +342,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo */ hideTree: function() { - if (isTablet && !appState.getGlobalState("stickyNavigation")) { + if (appState.getGlobalState("isTablet") === true && !appState.getGlobalState("stickyNavigation")) { //reset it to whatever is in the url appState.setSectionState("currentSection", $routeParams.section); setMode("default-hidesectiontree"); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 8d99086ba5..a73e51e54d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -102,7 +102,14 @@ angular.module('umbraco.services') if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) angularHelper.safeApply($rootScope, function () { - userAuthExpired(); + try { + //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we + // don't actually care about this result. + authResource.getRemainingTimeoutSeconds(); + } + finally { + userAuthExpired(); + } }); } else { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js b/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js new file mode 100644 index 0000000000..3b86510173 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js @@ -0,0 +1,67 @@ +/** + * @ngdoc service + * @name umbraco.services.windowResizeListener + * @function + * + * @description + * A single window resize listener... we don't want to have more than one in theory to ensure that + * there aren't too many events raised. This will debounce the event with 100 ms intervals and force + * a $rootScope.$apply when changed and notify all listeners + * + */ +function windowResizeListener($rootScope) { + + var WinReszier = (function () { + var registered = []; + var inited = false; + var resize = _.debounce(function(ev) { + notify(); + }, 100); + var notify = function () { + var h = $(window).height(); + var w = $(window).width(); + //execute all registrations inside of a digest + $rootScope.$apply(function() { + for (var i = 0, cnt = registered.length; i < cnt; i++) { + registered[i].apply($(window), [{ width: w, height: h }]); + } + }); + }; + return { + register: function (fn) { + registered.push(fn); + if (inited === false) { + $(window).bind('resize', resize); + inited = true; + } + }, + unregister: function (fn) { + var index = registered.indexOf(fn); + if (index > -1) { + registered.splice(index, 1); + } + } + }; + }()); + + return { + + /** + * Register a callback for resizing + * @param {Function} cb + */ + register: function (cb) { + WinReszier.register(cb); + }, + + /** + * Removes a registered callback + * @param {Function} cb + */ + unregister: function(cb) { + WinReszier.unregister(cb); + } + + }; +} +angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index 1ab84900f4..970cf8da31 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -28,7 +28,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop 'Umbraco is the best of both worlds: 100% free and open source, and backed by a professional and profitable company', "There's a pretty big chance, you've visited a website powered by Umbraco today", "'Umbraco-spotting' is the game of spotting big brands running Umbraco", - "At least 2 people have the Umbraco logo tattooed on them", + "At least 4 people have the Umbraco logo tattooed on them", "'Umbraco' is the danish name for an allen key", "Umbraco has been around since 2005, that's a looong time in IT", "More than 400 people from all over the world meet each year in Denmark in June for our annual conference CodeGarden", @@ -335,4 +335,4 @@ angular.module("umbraco.install").factory('installerService', function($rootScop }; return service; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index 53ac40f66a..f716d390ea 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -120,6 +120,7 @@ body { right: 0px; padding-top: 100px; border-right: 1px solid @grayLight; + z-index: 100; } #dialog { @@ -167,6 +168,19 @@ body { border-radius: 0; } +.ui-resizable-e { + cursor: e-resize; + width: 4px; + right: -5px; + top: 0; + bottom: 0; + background-color: @grayLighter; + border: solid 1px @grayLight; + position:absolute; + z-index:9999 !important; + +} + @media (min-width: 1101px) { #contentwrapper {left: 440px;} #speechbubble {left: 360px;} diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less index 29a0ac9454..db3dbee640 100644 --- a/src/Umbraco.Web.UI.Client/src/less/gridview.less +++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less @@ -5,7 +5,7 @@ overflow-y:hidden!important; } -IFRAME {overflow:hidden;} +.usky-grid IFRAME {overflow:hidden;} // Sortabel @@ -279,12 +279,12 @@ IFRAME {overflow:hidden;} .usky-grid .usky-control-inner.selectedControl , .usky-grid .usky-row-inner.selectedRow{ border: 1px dashed @grayLight; - + > ins.item-label { display: block; z-index:100000; } -} +} @@ -322,12 +322,12 @@ IFRAME {overflow:hidden;} padding-bottom: 30px; position: relative; background-color: white; - border: 4px dashed @grayLight; + border: 4px dashed @grayLighter; text-align: center; text-align: -moz-center; } .usky-grid .usky-editor-placeholder i{ - color: @grayLight; + color: @grayLighter; font-size: 85px; line-height: 85px; display: block; @@ -380,7 +380,7 @@ IFRAME {overflow:hidden;} border-radius: 200px; background: rgba(255,255,255, 1); border:1px solid rgb(182, 182, 182); - margin: 2px; + margin: 2px; } .usky-grid .iconBox span.prompt { @@ -464,7 +464,7 @@ IFRAME {overflow:hidden;} // TINYMCE EDITOR // ------------------------- .usky-grid .mce-panel { - border:none !important; + border: none !important; clear:both; } @@ -499,6 +499,9 @@ IFRAME {overflow:hidden;} background-color: #F7F7F7 !important; } +.usky-cell-rte{ + border: 1px solid @grayLighter; +} // MEDIA EDITOR // ------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index b26778ef26..4e2e3e77e8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -3,14 +3,13 @@ .umb-item-list { - margin: 0px; + margin: 0; width: auto; display: block } .umb-item-list li { display: block; width: auto; - display: block } @@ -20,7 +19,7 @@ // ------------------------- .umb-tree { - margin: 0px; + margin: 0; min-width: 100%; width: auto; } @@ -29,17 +28,17 @@ display: block; min-width: 100%; width: auto; - display: block } .umb-tree li.current > div, .umb-tree div.selected { background: @blue; } -.umb-tree li.current > div a.umb-options i, .umb-tree div.selected i{ +.umb-tree li.current > div a.umb-options i, .umb-tree div.selected i { background: #fff; border-color: @blue; } .umb-tree li.current > div a, -.umb-tree li.current > div i.icon{ +.umb-tree li.current > div i.icon, +.umb-tree li.current > div ins { color: white !important; background: @blue; border-color: @blue; @@ -57,8 +56,8 @@ white-space: nowrap } .umb-tree ul { - padding: 0px; - margin: 0px; + padding: 0; + margin: 0; min-width: 100%; width: 100%; //display: table @@ -111,21 +110,19 @@ display: inline-block; visibility: hidden; text-decoration: none; + font-size: 12px; } + .umb-tree li:hover ins { visibility: visible; cursor: pointer } -.umb-tree ins { - font-size: 12px; -} - .umb-tree .icon { vertical-align: middle; margin: 1px 13px 1px 0px; color: #21201C; - font-size: 15px; + font-size: 20px; } .umb-tree i.noSpr { display: inline-block; @@ -434,7 +431,7 @@ overflow:hidden; } -body.touch .umb-tree .icon{font-size: 17px;} +/*body.touch .umb-tree .icon{font-size: 19px;}*/ body.touch .umb-tree ins{font-size: 14px; visibility: visible; padding: 7px;} body.touch .umb-tree li div { padding-top: 8px; diff --git a/src/Umbraco.Web.UI.Client/src/loader.js b/src/Umbraco.Web.UI.Client/src/loader.js index c86345a707..2e164f0eef 100644 --- a/src/Umbraco.Web.UI.Client/src/loader.js +++ b/src/Umbraco.Web.UI.Client/src/loader.js @@ -15,8 +15,8 @@ LazyLoad.js( 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', 'lib/bootstrap/js/bootstrap.2.3.2.min.js', - 'lib/bootstrap-tabdrop/bootstrap-tabdrop.js', - 'lib/umbraco/Extensions.js', + 'lib/bootstrap-tabdrop/bootstrap-tabdrop.min.js', + 'lib/umbraco/Extensions.js', 'lib/umbraco/NamespaceManager.js', 'lib/umbraco/LegacyUmbClientMgr.js', diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js index ad14051696..ae3dc44525 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Dialogs.MediaPickerController", - function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, eventsService, treeService, $cookies, $element, $timeout) { + function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, eventsService, treeService, $cookies, $element, $timeout, notificationsService) { var dialogOptions = $scope.dialogOptions; @@ -79,6 +79,12 @@ angular.module("umbraco") $scope.currentFolder = folder; }; + } + //Show notifications!!!! + if (data.result && data.result.notifications && angular.isArray(data.result.notifications)) { + for (var n = 0; n < data.result.notifications.length; n++) { + notificationsService.showNotification(data.result.notifications[n]); + } $scope.clickHandler = function(image, ev, select) { ev.preventDefault(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js index d7ff1843c7..1d24fee190 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js @@ -6,7 +6,7 @@ * @description * The controller for deleting content */ -function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location) { +function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) { $scope.performDelete = function() { @@ -41,6 +41,20 @@ function ContentDeleteController($scope, contentResource, treeService, navigatio } navigationService.hideMenu(); + }, function(err) { + + $scope.currentNode.loading = false; + + //check if response is ysod + if (err.status && err.status >= 500) { + dialogService.ysodDialog(err); + } + + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.recyclebin.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.recyclebin.controller.js index a644e52a2c..f72075149c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.recyclebin.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.recyclebin.controller.js @@ -8,7 +8,7 @@ * */ -function ContentRecycleBinController($scope, $routeParams, dataTypeResource) { +function ContentRecycleBinController($scope, $routeParams, dataTypeResource, navigationService) { //ensures the list view doesn't actually load until we query for the list view config // for the section @@ -24,6 +24,8 @@ function ContentRecycleBinController($scope, $routeParams, dataTypeResource) { $scope.model = { config: { entityType: $routeParams.section } }; + // sync tree node + navigationService.syncTree({ tree: "content", path: ["-1", $routeParams.id], forceReload: false }); } angular.module('umbraco').controller("Umbraco.Editors.Content.RecycleBinController", ContentRecycleBinController); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/content/edit.html index e29ee99925..d6050dbcb1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/edit.html @@ -4,7 +4,7 @@ ng-submit="save()" val-form-manager> - +
    @@ -20,7 +20,7 @@ + current-section="{{currentSection}}">
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemanagement.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemanagement.html index da07683034..5dafd5f330 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemanagement.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemanagement.html @@ -29,9 +29,11 @@
    -
    -
    + +
    +
    +
    The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation
    @@ -183,10 +185,11 @@
    - -
    -
    + +
    +
    + diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-header.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-header.html index f7798bba09..016055b84a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/umb-header.html @@ -5,7 +5,7 @@ - + diff --git a/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js b/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js index a7bb1d9f29..eb62afd805 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js +++ b/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js @@ -59,7 +59,7 @@ $.ajax({ type: "POST", url: self._opts.serviceUrl, - data: '{ "ParentId": ' + parseInt(self._opts.currentId) + ', "SortOrder": "' + sortOrder + '"}', + data: '{ "ParentId": "' + self._opts.currentId + '", "SortOrder": "' + sortOrder + '"}', contentType: "application/json; charset=utf-8", dataType: "json", success: function(msg) { diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index 6b519747fa..dfbf72173e 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -126,14 +126,14 @@ xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='System.Web.Helpers']])" /> - + - + - + - + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 1cb16b3f67..b3d69ff778 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -117,6 +117,8 @@ + + @@ -190,6 +192,9 @@ + + + @@ -231,15 +236,15 @@ - + - + - + @@ -260,7 +265,7 @@ - + diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index 38de7ef3cc..c20f330e13 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -1,10 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Web; +using Newtonsoft.Json; +using umbraco.interfaces; using Umbraco.Core; -using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Sync; using Umbraco.Web.Routing; +using Umbraco.Core.Logging; namespace Umbraco.Web { @@ -14,27 +18,29 @@ namespace Umbraco.Web /// /// This binds to appropriate umbraco events in order to trigger the Boot(), Sync() & FlushBatch() calls /// - public class BatchedDatabaseServerMessenger : Core.Sync.BatchedDatabaseServerMessenger + public class BatchedDatabaseServerMessenger : DatabaseServerMessenger { public BatchedDatabaseServerMessenger(ApplicationContext appContext, bool enableDistCalls, DatabaseServerMessengerOptions options) : base(appContext, enableDistCalls, options) + { } + + // invoked by BatchedDatabaseServerMessengerStartup which is an ApplicationEventHandler + // with default "ShouldExecute", so that method will run if app IsConfigured and database + // context IsDatabaseConfigured - we still want to check CanConnect though to be safe + internal void Startup() { - UmbracoApplicationBase.ApplicationStarted += Application_Started; UmbracoModule.EndRequest += UmbracoModule_EndRequest; UmbracoModule.RouteAttempt += UmbracoModule_RouteAttempt; - } - private void Application_Started(object sender, EventArgs eventArgs) - { - if (ApplicationContext.IsConfigured == false - || ApplicationContext.DatabaseContext.IsDatabaseConfigured == false - || ApplicationContext.DatabaseContext.CanConnect == false) - - LogHelper.Warn("The app is not configured or cannot connect to the database, this server cannot be initialized with " - + typeof(BatchedDatabaseServerMessenger) + ", distributed calls will not be enabled for this server"); - - // because .ApplicationStarted triggers only once, this is thread-safe - Boot(); + if (ApplicationContext.DatabaseContext.CanConnect == false) + { + ApplicationContext.ProfilingLogger.Logger.Warn( + "Cannot connect to the database, distributed calls will not be enabled for this server."); + } + else + { + Boot(); + } } private void UmbracoModule_RouteAttempt(object sender, RoutableAttemptEventArgs e) @@ -66,7 +72,37 @@ namespace Umbraco.Web FlushBatch(); } - protected override ICollection GetBatch(bool ensureHttpContext) + protected override void DeliverRemote(IEnumerable servers, ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) + { + var idsA = ids == null ? null : ids.ToArray(); + + Type arrayType; + if (GetArrayType(idsA, out arrayType) == false) + throw new ArgumentException("All items must be of the same type, either int or Guid.", "ids"); + + BatchMessage(servers, refresher, messageType, idsA, arrayType, json); + } + + public void FlushBatch() + { + var batch = GetBatch(false); + if (batch == null) return; + + var instructions = batch.SelectMany(x => x.Instructions).ToArray(); + batch.Clear(); + if (instructions.Length == 0) return; + + var dto = new CacheInstructionDto + { + UtcStamp = DateTime.UtcNow, + Instructions = JsonConvert.SerializeObject(instructions, Formatting.None), + OriginIdentity = LocalIdentity + }; + + ApplicationContext.DatabaseContext.Database.Insert(dto); + } + + protected ICollection GetBatch(bool ensureHttpContext) { var httpContext = UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext; if (httpContext == null) @@ -84,5 +120,21 @@ namespace Umbraco.Web httpContext.Items[key] = batch = new List(); return batch; } + + protected void BatchMessage( + IEnumerable servers, + ICacheRefresher refresher, + MessageType messageType, + IEnumerable ids = null, + Type idType = null, + string json = null) + { + var batch = GetBatch(true); + if (batch == null) + throw new Exception("Failed to get a batch."); + + batch.Add(new RefreshInstructionEnvelope(servers, refresher, + RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json))); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessengerStartup.cs b/src/Umbraco.Web/BatchedDatabaseServerMessengerStartup.cs new file mode 100644 index 0000000000..a5b88a08ec --- /dev/null +++ b/src/Umbraco.Web/BatchedDatabaseServerMessengerStartup.cs @@ -0,0 +1,22 @@ +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Sync; + +namespace Umbraco.Web +{ + /// + /// Used to boot up the server messenger once the application succesfully starts + /// + internal class BatchedDatabaseServerMessengerStartup : ApplicationEventHandler + { + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + var messenger = ServerMessengerResolver.HasCurrent + ? ServerMessengerResolver.Current.Messenger as BatchedDatabaseServerMessenger + : null; + + if (messenger != null) + messenger.Startup(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 48c7692dae..c552fb6a3b 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -10,6 +10,7 @@ using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using System.Linq; using umbraco.cms.businesslogic.web; +using Umbraco.Core.Logging; using Umbraco.Core.Publishing; using Content = Umbraco.Core.Models.Content; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; @@ -23,7 +24,9 @@ namespace Umbraco.Web.Cache public class CacheRefresherEventHandler : ApplicationEventHandler { protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { + { + LogHelper.Info("Initializing Umbraco internal event handlers for cache refreshing"); + //bind to application tree events ApplicationTreeService.Deleted += ApplicationTreeDeleted; ApplicationTreeService.Updated += ApplicationTreeUpdated; diff --git a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs index aabe95548f..51a2c79b2d 100644 --- a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; @@ -41,7 +42,7 @@ namespace Umbraco.Web.Cache private void ClearCache() { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); // SD: we need to clear the routes cache here! // diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 95f985148c..7f7b59199a 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Security.Claims; +using System.ServiceModel.Channels; using System.Text; using System.Threading.Tasks; using System.Web; @@ -244,7 +245,11 @@ namespace Umbraco.Web.Editors [ClearAngularAntiForgeryToken] [ValidateAngularAntiForgeryToken] public HttpResponseMessage PostLogout() - { + { + Logger.Info("User {0} from IP address {1} has logged out", + () => User.Identity == null ? "UNKNOWN" : User.Identity.Name, + () => TryGetOwinContext().Result.Request.RemoteIpAddress); + return Request.CreateResponse(HttpStatusCode.OK); } diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index ea4a21d840..96ccf10e71 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -3,44 +3,36 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Security.Claims; -using System.ServiceModel.Security; using System.Text; -using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Web; using System.Web.Mvc; using System.Web.UI; -using dotless.Core.Parser.Tree; +using ClientDependency.Core.Config; using Microsoft.AspNet.Identity; -using Microsoft.Owin; +using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Manifest; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.PropertyEditors; +using Umbraco.Web.Security.Identity; using Umbraco.Web.Trees; using Umbraco.Web.UI.JavaScript; -using Umbraco.Web.PropertyEditors; -using Umbraco.Web.Models; -using Umbraco.Web.WebServices; using Umbraco.Web.WebApi.Filters; -using System.Web; -using AutoMapper; -using Microsoft.AspNet.Identity.Owin; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Security; -using Task = System.Threading.Tasks.Task; -using Umbraco.Web.Security.Identity; +using Umbraco.Web.WebServices; +using Action = umbraco.BusinessLogic.Actions.Action; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { @@ -417,7 +409,7 @@ namespace Umbraco.Web.Editors public async Task ExternalLinkLoginCallback() { var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync( - Core.Constants.Security.BackOfficeExternalAuthenticationType, + Constants.Security.BackOfficeExternalAuthenticationType, XsrfKey, User.Identity.GetUserId()); if (loginInfo == null) @@ -459,7 +451,7 @@ namespace Umbraco.Web.Editors //First check if there's external login info, if there's not proceed as normal var loginInfo = await OwinContext.Authentication.GetExternalLoginInfoAsync( - Core.Constants.Security.BackOfficeExternalAuthenticationType); + Constants.Security.BackOfficeExternalAuthenticationType); if (loginInfo == null || loginInfo.ExternalIdentity.IsAuthenticated == false) { @@ -496,9 +488,9 @@ namespace Umbraco.Web.Editors } //Remove the cookie otherwise this message will keep appearing - if (Response.Cookies[Core.Constants.Security.BackOfficeExternalCookieName] != null) + if (Response.Cookies[Constants.Security.BackOfficeExternalCookieName] != null) { - Response.Cookies[Core.Constants.Security.BackOfficeExternalCookieName].Expires = DateTime.MinValue; + Response.Cookies[Constants.Security.BackOfficeExternalCookieName].Expires = DateTime.MinValue; } } @@ -620,7 +612,7 @@ namespace Umbraco.Web.Editors var version = UmbracoVersion.GetSemanticVersion().ToSemanticString(); app.Add("version", version); - app.Add("cdf", ClientDependency.Core.Config.ClientDependencySettings.Instance.Version); + app.Add("cdf", ClientDependencySettings.Instance.Version); //useful for dealing with virtual paths on the client side when hosted in virtual directories especially app.Add("applicationPath", HttpContext.Request.ApplicationPath.EnsureEndsWith('/')); return app; @@ -685,48 +677,21 @@ namespace Umbraco.Web.Editors return JavaScript(result); } - /// - /// Renders out all JavaScript references that have bee declared in IActions - /// - private static IEnumerable GetLegacyActionJs(LegacyJsActionType type) + internal static IEnumerable GetLegacyActionJsForActions(LegacyJsActionType type, IEnumerable values) { var blockList = new List(); var urlList = new List(); - foreach (var jsFile in global::umbraco.BusinessLogic.Actions.Action.GetJavaScriptFileReferences()) + foreach (var jsFile in values) { - //validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text - //block instead. - var isValid = true; - - if (Uri.IsWellFormedUriString(jsFile, UriKind.RelativeOrAbsolute)) + var isJsPath = jsFile.DetectIsJavaScriptPath(); + if (isJsPath.Success) + { - //ok it validates, but so does alert('hello'); ! so we need to do more checks - - //here are the valid chars in a url without escaping - if (Regex.IsMatch(jsFile, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]")) - isValid = false; - - //we'll have to be smarter and just check for certain js patterns now too! - var jsPatterns = new string[] {@"\+\s*\=", @"\);", @"function\s*\(", @"!=", @"=="}; - if (jsPatterns.Any(p => Regex.IsMatch(jsFile, p))) - { - isValid = false; - } - if (isValid) - { - //it is a valid URL add to Url list - urlList.Add(jsFile); - } + urlList.Add(isJsPath.Result); } else { - isValid = false; - } - - if (isValid == false) - { - //it isn't a valid URL, must be a js block - blockList.Add(jsFile); + blockList.Add(isJsPath.Result); } } @@ -740,8 +705,16 @@ namespace Umbraco.Web.Editors return blockList; } - - private enum LegacyJsActionType + + /// + /// Renders out all JavaScript references that have bee declared in IActions + /// + private static IEnumerable GetLegacyActionJs(LegacyJsActionType type) + { + return GetLegacyActionJsForActions(type, Action.GetJavaScriptFileReferences()); + } + + internal enum LegacyJsActionType { JsBlock, JsUrl diff --git a/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs new file mode 100644 index 0000000000..f4350bc596 --- /dev/null +++ b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs @@ -0,0 +1,21 @@ +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Editors +{ + /// + /// An abstract controller that automatically checks if any request is a non-GET and if the + /// resulting message is INotificationModel in which case it will append any Event Messages + /// currently in the request. + /// + [AppendCurrentEventMessages] + public abstract class BackOfficeNotificationsController : UmbracoAuthorizedJsonController + { + protected BackOfficeNotificationsController() + { + } + + protected BackOfficeNotificationsController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 046a433d3a..573439f4e4 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -30,7 +30,9 @@ using Umbraco.Core.Dynamics; using umbraco.BusinessLogic.Actions; using umbraco.cms.businesslogic.web; using umbraco.presentation.preview; +using Umbraco.Web.UI; using Constants = Umbraco.Core.Constants; +using Notification = Umbraco.Web.Models.ContentEditing.Notification; namespace Umbraco.Web.Editors { @@ -250,22 +252,25 @@ namespace Umbraco.Web.Editors //initialize this to successful var publishStatus = Attempt.Succeed(); + var wasCancelled = false; if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) { //save the item - Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id); + var saveResult = Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); + + wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; } else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) { - Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); + var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = sendResult == false; } else { //publish the item and check if it worked, if not we will show a diff msg below publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); } - //return the updated model var display = Mapper.Map(contentItem.PersistedContent); @@ -278,11 +283,29 @@ namespace Umbraco.Web.Editors { case ContentSaveAction.Save: case ContentSaveAction.SaveNew: - display.AddSuccessNotification(ui.Text("speechBubbles", "editContentSavedHeader"), ui.Text("speechBubbles", "editContentSavedText")); + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + Services.TextService.Localize("speechBubbles/editContentSavedText")); + } + else + { + AddCancelMessage(display); + } break; case ContentSaveAction.SendPublish: case ContentSaveAction.SendPublishNew: - display.AddSuccessNotification(ui.Text("speechBubbles", "editContentSendToPublish"), ui.Text("speechBubbles", "editContentSendToPublishText")); + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + } + else + { + AddCancelMessage(display); + } break; case ContentSaveAction.Publish: case ContentSaveAction.PublishNew: @@ -295,57 +318,7 @@ namespace Umbraco.Web.Editors return display; } - /// - /// Checks if the user is currently in preview mode and if so will update the preview content for this item - /// - /// - private void UpdatePreviewContext(int contentId) - { - var previewId = Request.GetPreviewCookieValue(); - if (previewId.IsNullOrWhiteSpace()) return; - Guid id; - if (Guid.TryParse(previewId, out id)) - { - var d = new Document(contentId); - var pc = new PreviewContent(UmbracoUser, id, false); - pc.PrepareDocument(UmbracoUser, d, true); - pc.SavePreviewSet(); - } - } - - /// - /// Maps the dto property values to the persisted model - /// - /// - private void MapPropertyValues(ContentItemSave contentItem) - { - UpdateName(contentItem); - - //TODO: We need to support 'send to publish' - - contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; - contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; - //only set the template if it didn't change - var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) - || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); - if (templateChanged) - { - var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); - if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - { - //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); - LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); - } - else - { - //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template - contentItem.PersistedContent.Template = template; - } - } - - base.MapPropertyValues(contentItem); - } + /// /// Publishes a document with a given ID @@ -367,31 +340,12 @@ namespace Umbraco.Web.Editors return HandleContentNotFound(id, false); } - var publishResult = Services.ContentService.PublishWithStatus(foundContent, UmbracoUser.Id); + var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.GetUserId()); if (publishResult.Success == false) { - switch (publishResult.Result.StatusType) - { - case PublishStatusType.FailedPathNotPublished: - return Request.CreateValidationErrorResponse( - ui.Text("publish", "contentPublishedFailedByParent", - string.Format("{0} ({1})", publishResult.Result.ContentItem.Name, publishResult.Result.ContentItem.Id), - Security.CurrentUser).Trim()); - case PublishStatusType.FailedCancelledByEvent: - return Request.CreateValidationErrorResponse( - ui.Text("speechBubbles", "contentPublishedFailedByEvent")); - case PublishStatusType.FailedHasExpired: - case PublishStatusType.FailedAwaitingRelease: - case PublishStatusType.FailedIsTrashed: - case PublishStatusType.FailedContentInvalid: - return Request.CreateValidationErrorResponse( - ui.Text("publish", "contentPublishedFailedInvalid", - new[] - { - string.Format("{0} ({1})", publishResult.Result.ContentItem.Name, publishResult.Result.ContentItem.Id), - string.Join(",", publishResult.Result.InvalidProperties.Select(x => x.Alias)) - }, Security.CurrentUser)); - } + var notificationModel = new SimpleNotificationModel(); + ShowMessageForPublishStatus(publishResult.Result, notificationModel); + return Request.CreateValidationErrorResponse(notificationModel); } //return ok @@ -423,11 +377,23 @@ namespace Umbraco.Web.Editors //if the current item is in the recycle bin if (foundContent.IsInRecycleBin() == false) { - Services.ContentService.MoveToRecycleBin(foundContent, UmbracoUser.Id); + var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.GetUserId()); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } } else { - Services.ContentService.Delete(foundContent, UmbracoUser.Id); + var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.GetUserId()); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } } return Request.CreateResponse(HttpStatusCode.OK); @@ -535,13 +501,73 @@ namespace Umbraco.Web.Editors if (foundContent == null) HandleContentNotFound(id); + + var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); - Services.ContentService.UnPublish(foundContent); var content = Mapper.Map(foundContent); - content.AddSuccessNotification(ui.Text("content", "unPublish"), ui.Text("speechBubbles", "contentUnpublished")); + if (unpublishResult == false) + { + AddCancelMessage(content); + throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); + } + else + { + content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); + return content; + } + } - return content; + /// + /// Checks if the user is currently in preview mode and if so will update the preview content for this item + /// + /// + private void UpdatePreviewContext(int contentId) + { + var previewId = Request.GetPreviewCookieValue(); + if (previewId.IsNullOrWhiteSpace()) return; + Guid id; + if (Guid.TryParse(previewId, out id)) + { + var d = new Document(contentId); + var pc = new PreviewContent(UmbracoUser, id, false); + pc.PrepareDocument(UmbracoUser, d, true); + pc.SavePreviewSet(); + } + } + + /// + /// Maps the dto property values to the persisted model + /// + /// + private void MapPropertyValues(ContentItemSave contentItem) + { + UpdateName(contentItem); + + //TODO: We need to support 'send to publish' + + contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; + contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; + //only set the template if it didn't change + var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) + || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); + if (templateChanged) + { + var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); + if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + { + //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); + LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); + } + else + { + //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template + contentItem.PersistedContent.Template = template; + } + } + + base.MapPropertyValues(contentItem); } /// @@ -568,7 +594,7 @@ namespace Umbraco.Web.Editors if (toMove.ContentType.AllowedAsRoot == false) { throw new HttpResponseException( - Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedAtRoot", Security.CurrentUser))); + Request.CreateValidationErrorResponse(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); } } else @@ -584,51 +610,44 @@ namespace Umbraco.Web.Editors .Any(x => x.Value == toMove.ContentType.Id) == false) { throw new HttpResponseException( - Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedByContentType", Security.CurrentUser))); + Request.CreateValidationErrorResponse(Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); } // Check on paths if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) { throw new HttpResponseException( - Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedByPath", Security.CurrentUser))); + Request.CreateValidationErrorResponse(Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); } } return toMove; } - private void ShowMessageForPublishStatus(PublishStatus status, ContentItemDisplay display) + private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display) { switch (status.StatusType) { case PublishStatusType.Success: case PublishStatusType.SuccessAlreadyPublished: display.AddSuccessNotification( - ui.Text("speechBubbles", "editContentPublishedHeader", UmbracoUser), - ui.Text("speechBubbles", "editContentPublishedText", UmbracoUser)); + Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), + Services.TextService.Localize("speechBubbles/editContentPublishedText")); break; case PublishStatusType.FailedPathNotPublished: display.AddWarningNotification( - ui.Text("publish"), - ui.Text("publish", "contentPublishedFailedByParent", - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - UmbracoUser).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedByParent", + new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim()); break; case PublishStatusType.FailedCancelledByEvent: - display.AddWarningNotification( - ui.Text("publish"), - ui.Text("speechBubbles", "contentPublishedFailedByEvent")); + AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); break; case PublishStatusType.FailedAwaitingRelease: display.AddWarningNotification( - ui.Text("publish"), - ui.Text("publish", "contentPublishedFailedAwaitingRelease", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) - }, - UmbracoUser).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", + new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim()); break; case PublishStatusType.FailedHasExpired: //TODO: We should add proper error messaging for this! @@ -636,14 +655,13 @@ namespace Umbraco.Web.Editors //TODO: We should add proper error messaging for this! case PublishStatusType.FailedContentInvalid: display.AddWarningNotification( - ui.Text("publish"), - ui.Text("publish", "contentPublishedFailedInvalid", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - string.Join(",", status.InvalidProperties.Select(x => x.Alias)) - }, - UmbracoUser).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedInvalid", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Join(",", status.InvalidProperties.Select(x => x.Alias)) + }).Trim()); break; default: throw new IndexOutOfRangeException(); diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 5f02819f4d..bf9f2056b3 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -13,7 +13,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Models; + namespace Umbraco.Web.Editors { @@ -21,7 +21,7 @@ namespace Umbraco.Web.Editors /// An abstract base controller used for media/content (and probably members) to try to reduce code replication. /// [OutgoingDateTimeFormat] - public abstract class ContentControllerBase : UmbracoAuthorizedJsonController + public abstract class ContentControllerBase : BackOfficeNotificationsController { /// /// Constructor @@ -172,5 +172,19 @@ namespace Umbraco.Web.Editors return (action.ToString().EndsWith("New")); } + protected void AddCancelMessage(INotificationModel display, + string header = "speechBubbles/operationCancelledHeader", + string message = "speechBubbles/operationCancelledText", + bool localizeHeader = true, + bool localizeMessage = true) + { + //if there's already a default event message, don't add our default one + var msgs = UmbracoContext.GetCurrentEventMessages(); + if (msgs != null && msgs.GetAll().Any(x => x.IsDefaultEventMessage)) return; + + display.AddWarningNotification( + localizeHeader ? Services.TextService.Localize(header) : header, + localizeMessage ? Services.TextService.Localize(message): message); + } } } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 7f9d703763..12a35e3b20 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -26,6 +26,7 @@ using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using System.Linq; +using System.Runtime.Serialization; using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; using umbraco; @@ -33,6 +34,8 @@ using umbraco.BusinessLogic.Actions; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.FaultHandling; +using Umbraco.Web.UI; +using Notification = Umbraco.Web.Models.ContentEditing.Notification; namespace Umbraco.Web.Editors { @@ -182,11 +185,23 @@ namespace Umbraco.Web.Editors //if the current item is in the recycle bin if (foundMedia.IsInRecycleBin() == false) { - Services.MediaService.MoveToRecycleBin(foundMedia, (int)Security.CurrentUser.Id); + var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, (int)Security.CurrentUser.Id); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } } else { - Services.MediaService.Delete(foundMedia, (int)Security.CurrentUser.Id); + var deleteResult = Services.MediaService.WithResult().Delete(foundMedia, (int)Security.CurrentUser.Id); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } } return Request.CreateResponse(HttpStatusCode.OK); @@ -234,7 +249,7 @@ namespace Umbraco.Web.Editors // then we cannot continue saving, we can only display errors // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display // a message indicating this - if (!ModelState.IsValid) + if (ModelState.IsValid == false) { if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && (contentItem.Action == ContentSaveAction.SaveNew)) @@ -248,7 +263,7 @@ namespace Umbraco.Web.Editors } //save the item - Services.MediaService.Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); + var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); //return the updated model var display = Mapper.Map(contentItem.PersistedContent); @@ -261,7 +276,17 @@ namespace Umbraco.Web.Editors { case ContentSaveAction.Save: case ContentSaveAction.SaveNew: - display.AddSuccessNotification(ui.Text("speechBubbles", "editMediaSaved"), ui.Text("speechBubbles", "editMediaSavedText")); + if (saveStatus.Success) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editMediaSaved"), + Services.TextService.Localize("speechBubbles/editMediaSavedText")); + } + else + { + AddCancelMessage(display); + } + break; } @@ -337,7 +362,7 @@ namespace Umbraco.Web.Editors { var mediaService = ApplicationContext.Services.MediaService; var f = mediaService.CreateMedia(folder.Name, folder.ParentId, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(f); + mediaService.Save(f, Security.CurrentUser.Id); return Mapper.Map(f); } @@ -374,9 +399,7 @@ namespace Umbraco.Web.Editors int parentId; if (int.TryParse(result.FormData["currentFolder"], out parentId) == false) { - var response = Request.CreateResponse(HttpStatusCode.BadRequest); - response.ReasonPhrase = "The request was not formatted correctly, the currentFolder is not an integer"; - throw new HttpResponseException(response); + return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer"); } //ensure the user has access to this folder by parent id! @@ -385,7 +408,12 @@ namespace Umbraco.Web.Editors Security.CurrentUser, Services.MediaService, parentId) == false) { - return Request.CreateResponse(HttpStatusCode.Unauthorized); + return Request.CreateResponse( + HttpStatusCode.Unauthorized, + new SimpleNotificationModel(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), + SpeechBubbleIcon.Warning))); } var tempFiles = new PostedFiles(); @@ -396,7 +424,7 @@ namespace Umbraco.Web.Editors var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); var ext = fileName.Substring(fileName.LastIndexOf('.')+1).ToLower(); - if (!UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext)) + if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) { var mediaType = Constants.Conventions.MediaTypes.File; @@ -404,7 +432,7 @@ namespace Umbraco.Web.Editors mediaType = Constants.Conventions.MediaTypes.Image; var mediaService = ApplicationContext.Services.MediaService; - var f = mediaService.CreateMedia(fileName, parentId, mediaType); + var f = mediaService.CreateMedia(fileName, parentId, mediaType, Security.CurrentUser.Id); var fileInfo = new FileInfo(file.LocalFileName); var fs = fileInfo.OpenReadWithRetry(); @@ -414,19 +442,30 @@ namespace Umbraco.Web.Editors f.SetValue(Constants.Conventions.Media.File, fileName, fs); } - mediaService.Save(f); + var saveResult = mediaService.WithResult().Save(f, Security.CurrentUser.Id); + if (saveResult == false) + { + AddCancelMessage(tempFiles, + message: Services.TextService.Localize("speechBubbles/operationCancelledText") + " -- " + fileName, + localizeMessage: false); + } + else + { + tempFiles.UploadedFiles.Add(new ContentItemFile + { + FileName = fileName, + PropertyAlias = Constants.Conventions.Media.File, + TempFilePath = file.LocalFileName + }); + } } else { - LogHelper.Warn("Cannot upload file " + file + ", it is not an approved file type"); + tempFiles.Notifications.Add(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + "Cannot upload file " + file + ", it is not an approved file type", + SpeechBubbleIcon.Warning)); } - - tempFiles.UploadedFiles.Add(new ContentItemFile - { - FileName = fileName, - PropertyAlias = Constants.Conventions.Media.File, - TempFilePath = file.LocalFileName - }); } //Different response if this is a 'blueimp' request @@ -436,13 +475,7 @@ namespace Umbraco.Web.Editors if (origin.Value == "blueimp") { return Request.CreateResponse(HttpStatusCode.OK, - tempFiles.UploadedFiles.Select(x => new - { - name = x.FileName, - size = "", - url = "", - thumbnailUrl = "" - }), + tempFiles, //Don't output the angular xsrf stuff, blue imp doesn't like that new JsonMediaTypeFormatter()); } @@ -455,13 +488,18 @@ namespace Umbraco.Web.Editors /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the /// temporary files that were created. /// - private class PostedFiles : IHaveUploadedFiles + [DataContract] + private class PostedFiles : IHaveUploadedFiles, INotificationModel { public PostedFiles() { UploadedFiles = new List(); + Notifications = new List(); } public List UploadedFiles { get; private set; } + + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } /// diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 30320b278e..7d231c46f2 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -80,7 +80,7 @@ namespace Umbraco.Web.Editors string filter = "", string memberTypeAlias = null) { - int totalRecords; + if (pageNumber <= 0 || pageSize <= 0) { throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); @@ -88,6 +88,7 @@ namespace Umbraco.Web.Editors if (MembershipScenario == MembershipScenario.NativeUmbraco) { + long totalRecords; var members = Services.MemberService.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, memberTypeAlias, filter).ToArray(); if (totalRecords == 0) { @@ -100,6 +101,7 @@ namespace Umbraco.Web.Editors } else { + int totalRecords; var members = _provider.GetAllUsers((pageNumber - 1), pageSize, out totalRecords); if (totalRecords == 0) { @@ -385,6 +387,7 @@ namespace Umbraco.Web.Editors } var shouldReFetchMember = false; + var providedUserName = contentItem.PersistedContent.Username; //Update the membership user if it has changed try @@ -444,7 +447,9 @@ namespace Umbraco.Web.Editors if (shouldReFetchMember) { RefetchMemberData(contentItem, LookupType.ByKey); + RestoreProvidedUserName(contentItem, providedUserName); } + return null; } @@ -456,6 +461,7 @@ namespace Umbraco.Web.Editors if (shouldReFetchMember) { RefetchMemberData(contentItem, LookupType.ByKey); + RestoreProvidedUserName(contentItem, providedUserName); } //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword @@ -467,7 +473,6 @@ namespace Umbraco.Web.Editors passwordChangeResult.Result.ChangeError, string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - return null; } @@ -528,6 +533,17 @@ namespace Umbraco.Web.Editors } } + /// + /// Following a refresh of member data called during an update if the membership provider has changed some underlying data, + /// we don't want to lose the provided, and potentiallly changed, username + /// + /// + /// + private static void RestoreProvidedUserName(MemberSave contentItem, string providedUserName) + { + contentItem.PersistedContent.Username = providedUserName; + } + /// /// This is going to create the user with the membership provider and check for validation /// diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 2c38287d9a..b72992a9b0 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -83,6 +83,8 @@ namespace Umbraco.Web.Editors { var targetNode = umbraco.TypedContent(model.Source.Id); + //TODO: Null check!!!!!!!!!!!! + var aliases = this.GetChildContentTypeAliases(targetNode, currentPage).Reverse(); foreach (var contentTypeAlias in aliases) diff --git a/src/Umbraco.Web/Install/FilePermissionHelper.cs b/src/Umbraco.Web/Install/FilePermissionHelper.cs index b5bc908fde..e9835e0f32 100644 --- a/src/Umbraco.Web/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Web/Install/FilePermissionHelper.cs @@ -42,8 +42,10 @@ namespace Umbraco.Web.Install { errorReport = new List(); bool succes = true; - foreach (string dir in PermissionDirs) + foreach (string dir in directories) { + if (Directory.Exists(dir) == false) continue; + bool result = SaveAndDeleteFile(IOHelper.MapPath(dir + "/configWizardPermissionTest.txt")); if (result == false) diff --git a/src/Umbraco.Web/Install/UmbracoInstallArea.cs b/src/Umbraco.Web/Install/UmbracoInstallArea.cs index c1e47f4a66..789fe854f4 100644 --- a/src/Umbraco.Web/Install/UmbracoInstallArea.cs +++ b/src/Umbraco.Web/Install/UmbracoInstallArea.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; -using Microsoft.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Web.Editors; diff --git a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs index bf3586a174..b4171add35 100644 --- a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs +++ b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs @@ -140,12 +140,11 @@ namespace Umbraco.Web.Macros string output; using (var controller = new PartialViewMacroController(macro, content)) { - //bubble up the model state from the main view context to our custom controller. - //when merging we'll create a new dictionary, otherwise you might run into an enumeration error - // caused from ModelStateDictionary - controller.ModelState.Merge(new ModelStateDictionary(viewContext.ViewData.ModelState)); + controller.ViewData = viewContext.ViewData; + controller.ControllerContext = new ControllerContext(request, controller); - //call the action to render + + //call the action to render var result = controller.Index(); output = controller.RenderViewResultAsString(result); } diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs index a27bbe7f8e..2513e8c1f4 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs @@ -12,7 +12,7 @@ using umbraco.BusinessLogic.Utils; namespace Umbraco.Web.Media.ThumbnailProviders { - internal sealed class ThumbnailProvidersResolver : ManyObjectsResolverBase + public sealed class ThumbnailProvidersResolver : ManyObjectsResolverBase { /// /// Constructor diff --git a/src/Umbraco.Web/Models/ContentEditing/Notification.cs b/src/Umbraco.Web/Models/ContentEditing/Notification.cs index cc9ffb5010..0f042b6cbc 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Notification.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Notification.cs @@ -6,6 +6,18 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "notification", Namespace = "")] public class Notification { + public Notification() + { + + } + + public Notification(string header, string message, SpeechBubbleIcon notificationType) + { + Header = header; + Message = message; + NotificationType = notificationType; + } + [DataMember(Name = "header")] public string Header { get; set; } [DataMember(Name = "message")] diff --git a/src/Umbraco.Web/Models/ContentEditing/SimpleNotificationModel.cs b/src/Umbraco.Web/Models/ContentEditing/SimpleNotificationModel.cs new file mode 100644 index 0000000000..9c4ce6e9a4 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/SimpleNotificationModel.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "notificationModel", Namespace = "")] + public class SimpleNotificationModel : INotificationModel + { + public SimpleNotificationModel() + { + Notifications = new List(); + } + + public SimpleNotificationModel(params Notification[] notifications) + { + Notifications = new List(notifications); + } + + /// + /// 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; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentExtensions.cs b/src/Umbraco.Web/Models/ContentExtensions.cs index a832235bb0..ef5161ec9d 100644 --- a/src/Umbraco.Web/Models/ContentExtensions.cs +++ b/src/Umbraco.Web/Models/ContentExtensions.cs @@ -83,10 +83,22 @@ namespace Umbraco.Web.Models if (domain == null) return GetDefaultCulture(localizationService); - var wcDomain = DomainHelper.FindWildcardDomainInPath(domainService.GetAll(true), contentPath, domain.RootContent.Id); - return wcDomain == null - ? new CultureInfo(domain.Language.IsoCode) - : new CultureInfo(wcDomain.Language.IsoCode); + //NOTE: The domain service IDomain model has changed and no longer holds a reference to IContent or ILanguage to + // improve performance, this means that we need to lookup the language below, but this was already the case previously + // in the repository and since the language lookup is cached it should be ok. If we want however we could create + // another method on the DomainService to return domains with their culture info on them + var wcDomain = DomainHelper.FindWildcardDomainInPath(domainService.GetAll(true), contentPath, domain.RootContentId); + if (wcDomain != null && wcDomain.LanguageIsoCode.IsNullOrWhiteSpace() == false) + { + return new CultureInfo(wcDomain.LanguageIsoCode); + } + + if (domain.LanguageIsoCode.IsNullOrWhiteSpace() == false) + { + return new CultureInfo(domain.LanguageIsoCode); + } + + return GetDefaultCulture(localizationService); } private static CultureInfo GetDefaultCulture(ILocalizationService localizationService) diff --git a/src/Umbraco.Web/Mvc/ControllerExtensions.cs b/src/Umbraco.Web/Mvc/ControllerExtensions.cs index 03e31f34c4..c15520b57c 100644 --- a/src/Umbraco.Web/Mvc/ControllerExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerExtensions.cs @@ -130,25 +130,13 @@ namespace Umbraco.Web.Mvc /// /// Normally in MVC the way that the View object gets assigned to the result is to Execute the ViewResult, this however /// will write to the Response output stream which isn't what we want. Instead, this method will use the same logic inside - /// of MVC to assign the View object to the result but without executing it. This also ensures that the ViewData and the TempData - /// is assigned from the controller. + /// of MVC to assign the View object to the result but without executing it. /// This is only relavent for view results of PartialViewResult or ViewResult. /// /// /// - internal static void EnsureViewObjectDataOnResult(this ControllerBase controller, ViewResultBase result) - { - //when merging we'll create a new dictionary, otherwise you might run into an enumeration error - // caused from ModelStateDictionary - result.ViewData.ModelState.Merge(new ModelStateDictionary(controller.ViewData.ModelState)); - - // Temporarily copy the dictionary to avoid enumerator-modification errors - var newViewDataDict = new ViewDataDictionary(controller.ViewData); - foreach (var d in newViewDataDict) - result.ViewData[d.Key] = d.Value; - - result.TempData = controller.TempData; - + private static void EnsureViewObjectDataOnResult(this ControllerBase controller, ViewResultBase result) + { if (result.View != null) return; if (string.IsNullOrEmpty(result.ViewName)) diff --git a/src/Umbraco.Web/Mvc/PluginViewEngine.cs b/src/Umbraco.Web/Mvc/PluginViewEngine.cs index cc4174b76a..b4b72e36d5 100644 --- a/src/Umbraco.Web/Mvc/PluginViewEngine.cs +++ b/src/Umbraco.Web/Mvc/PluginViewEngine.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using System.Web.Mvc; using Lucene.Net.Util; -using Microsoft.Web.Mvc; using Umbraco.Core.IO; namespace Umbraco.Web.Mvc @@ -10,7 +9,7 @@ namespace Umbraco.Web.Mvc /// /// A view engine to look into the App_Plugins folder for views for packaged controllers /// - public class PluginViewEngine : ReflectedFixedRazorViewEngine + public class PluginViewEngine : RazorViewEngine { /// diff --git a/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs b/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs index 5cb082c032..320e8c6d8a 100644 --- a/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs +++ b/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs @@ -7,6 +7,7 @@ namespace Umbraco.Web.Mvc /// /// This is here to support compatibility with both MVC4 and MVC5 /// + [Obsolete("MVC5 does not have a 'fixed' viewengine so there's no reason to use this any more", false)] public abstract class ReflectedFixedRazorViewEngine : IViewEngine { protected ReflectedFixedRazorViewEngine() diff --git a/src/Umbraco.Web/Mvc/RenderViewEngine.cs b/src/Umbraco.Web/Mvc/RenderViewEngine.cs index 794eecf0ca..ed7b941580 100644 --- a/src/Umbraco.Web/Mvc/RenderViewEngine.cs +++ b/src/Umbraco.Web/Mvc/RenderViewEngine.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Web.Mvc; -using Microsoft.Web.Mvc; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Web.Models; diff --git a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs index 1c12e7e498..f23515c1d9 100644 --- a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs @@ -110,7 +110,7 @@ namespace Umbraco.Web.Mvc tempDataDictionary.Save(context, new SessionStateTempDataProvider()); var viewCtx = new ViewContext(context, new DummyView(), new ViewDataDictionary(), tempDataDictionary, new StringWriter()); - viewCtx.ViewData.ModelState.Merge(context.Controller.ViewData.ModelState); + viewCtx.ViewData.ModelState.Merge(new ModelStateDictionary(context.Controller.ViewData.ModelState)); foreach (var d in context.Controller.ViewData) viewCtx.ViewData[d.Key] = d.Value; diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs index 944b85c576..b5c3d850d2 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs @@ -24,15 +24,6 @@ namespace Umbraco.Web.PublishedCache UmbracoContext = umbracoContext; } - /// - /// Informs the contextual cache that content has changed. - /// - /// The contextual cache may, although that is not mandatory, provide an immutable snapshot of - /// the content over the duration of the context. If you make changes to the content and do want to have - /// the cache update its snapshot, you have to explicitely ask it to do so by calling ContentHasChanged. - public virtual void ContentHasChanged() - { } - /// /// Gets a content identified by its unique identifier. /// diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs index ef84f31473..3fa11a0dc2 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs @@ -38,7 +38,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private const int MaxWaitMilliseconds = 30000; // save the cache after some time (ie no more than 30s of changes) // save the cache when the app goes down - public override bool RunsOnShutdown { get { return true; } } + public override bool RunsOnShutdown { get { return _timer != null; } } // initialize the first instance, which is inactive (not touched yet) public XmlCacheFilePersister(IBackgroundTaskRunner runner, content content, ProfilingLogger logger) @@ -142,13 +142,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache lock (_locko) { _logger.Logger.Debug("Timer: release."); - if (_timer != null) - _timer.Dispose(); - _timer = null; _released = true; - // if running (because of shutdown) this will have no effect - // else it tells the runner it is time to run the task Release(); } } @@ -197,5 +192,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _content.SaveXmlToFile(); } } + + protected override void DisposeResources() + { + base.DisposeResources(); + + // stop the timer + if (_timer == null) return; + _timer.Change(Timeout.Infinite, Timeout.Infinite); + _timer.Dispose(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs new file mode 100644 index 0000000000..ec3a8b2442 --- /dev/null +++ b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs @@ -0,0 +1,28 @@ +using System; +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 readonly IUmbracoContextAccessor _ctxAccessor; + + public RequestLifespanMessagesFactory(IUmbracoContextAccessor ctxAccessor) + { + if (ctxAccessor == null) throw new ArgumentNullException("ctxAccessor"); + _ctxAccessor = ctxAccessor; + } + + public EventMessages Get() + { + if (_ctxAccessor.Value.HttpContext.Items[typeof (RequestLifespanMessagesFactory).Name] == null) + { + _ctxAccessor.Value.HttpContext.Items[typeof(RequestLifespanMessagesFactory).Name] = new EventMessages(); + } + return (EventMessages)_ctxAccessor.Value.HttpContext.Items[typeof (RequestLifespanMessagesFactory).Name]; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs b/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs index 22c4364891..aae16b5fa9 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs @@ -28,7 +28,8 @@ namespace Umbraco.Web.Routing pcr.RoutingContext.UmbracoContext.HttpContext.Request.ServerVariables["SERVER_NAME"], pcr.RoutingContext.UmbracoContext.Application.Services.EntityService, new PublishedContentQuery(pcr.RoutingContext.UmbracoContext.ContentCache, pcr.RoutingContext.UmbracoContext.MediaCache), - pcr.RoutingContext.UmbracoContext.Application.Services.DomainService); + pcr.RoutingContext.UmbracoContext.Application.Services.DomainService, + pcr.RoutingContext.UmbracoContext.Application.Services.LocalizationService); IPublishedContent content = null; diff --git a/src/Umbraco.Web/Routing/DomainAndUri.cs b/src/Umbraco.Web/Routing/DomainAndUri.cs index cef8231375..6395e937df 100644 --- a/src/Umbraco.Web/Routing/DomainAndUri.cs +++ b/src/Umbraco.Web/Routing/DomainAndUri.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Routing { var name = domain.DomainName.ToCSharpString(); throw new ArgumentException(string.Format("Failed to parse invalid domain: node id={0}, hostname=\"{1}\"." - + " Hostname should be a valid uri.", domain.RootContent.Id, name), "domain"); + + " Hostname should be a valid uri.", domain.RootContentId, name), "domain"); } } diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index 72360d9f22..6934b08cf2 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -235,7 +235,7 @@ namespace Umbraco.Web.Routing .Reverse() .Select(int.Parse) .TakeWhile(id => id != stopNodeId) - .Select(id => domains.FirstOrDefault(d => d.RootContent.Id == id && d.IsWildcard == false)) + .Select(id => domains.FirstOrDefault(d => d.RootContentId == id && d.IsWildcard == false)) .SkipWhile(domain => domain == null) .FirstOrDefault(); } @@ -256,7 +256,7 @@ namespace Umbraco.Web.Routing .Reverse() .Select(int.Parse) .TakeWhile(id => id != stopNodeId) - .Select(id => domains.FirstOrDefault(d => d.RootContent.Id == id && d.IsWildcard)) + .Select(id => domains.FirstOrDefault(d => d.RootContentId == id && d.IsWildcard)) .FirstOrDefault(domain => domain != null); } diff --git a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs index 20441b6c11..798efc674d 100644 --- a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs +++ b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs @@ -230,28 +230,28 @@ namespace Umbraco.Web.Routing /// /// /// + /// /// internal static int? GetCurrentNotFoundPageId( IContentErrorPage[] error404Collection, string requestServerName, IEntityService entityService, ITypedPublishedContentQuery publishedContentQuery, - IDomainService domainService) + IDomainService domainService, + ILocalizationService localizationService) { if (error404Collection.Count() > 1) { // try to get the 404 based on current culture (via domain) IContentErrorPage cultureErr; - //TODO: Remove the dependency on this legacy Domain service, - // in 7.3 the real domain service should be passed in as a parameter. - if (domainService.Exists(requestServerName)) - { - var d = domainService.GetByName(requestServerName); + var d = domainService.GetByName(requestServerName); + if (d != null && d.LanguageId.HasValue) + { // test if a 404 page exists with current culture cultureErr = error404Collection - .FirstOrDefault(x => x.Culture == d.Language.IsoCode); + .FirstOrDefault(x => x.Culture == d.LanguageIsoCode); if (cultureErr != null) { diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index b8b454e197..65371ecba4 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -242,27 +242,27 @@ namespace Umbraco.Web.Routing var domainAndUri = DomainHelper.DomainForUri(Services.DomainService.GetAll(false), _pcr.Uri); // handle domain - if (domainAndUri != null) + if (domainAndUri != null && domainAndUri.UmbracoDomain.LanguageIsoCode.IsNullOrWhiteSpace() == false) { - // matching an existing domain - ProfilingLogger.Logger.Debug("{0}Matches domain=\"{1}\", rootId={2}, culture=\"{3}\"", - () => tracePrefix, - () => domainAndUri.UmbracoDomain.DomainName, - () => domainAndUri.UmbracoDomain.RootContent.Id, - () => domainAndUri.UmbracoDomain.Language.IsoCode); + // matching an existing domain + ProfilingLogger.Logger.Debug("{0}Matches domain=\"{1}\", rootId={2}, culture=\"{3}\"", + () => tracePrefix, + () => domainAndUri.UmbracoDomain.DomainName, + () => domainAndUri.UmbracoDomain.RootContentId, + () => domainAndUri.UmbracoDomain.LanguageIsoCode); _pcr.UmbracoDomain = domainAndUri.UmbracoDomain; - _pcr.DomainUri = domainAndUri.Uri; - _pcr.Culture = new CultureInfo(domainAndUri.UmbracoDomain.Language.IsoCode); + _pcr.DomainUri = domainAndUri.Uri; + _pcr.Culture = new CultureInfo(domainAndUri.UmbracoDomain.LanguageIsoCode); - // canonical? not implemented at the moment - // if (...) - // { - // _pcr.RedirectUrl = "..."; - // return true; - // } - } - else + // canonical? not implemented at the moment + // if (...) + // { + // _pcr.RedirectUrl = "..."; + // return true; + // } + } + else { // not matching any existing domain ProfilingLogger.Logger.Debug("{0}Matches no domain", () => tracePrefix); @@ -288,15 +288,15 @@ namespace Umbraco.Web.Routing var nodePath = _pcr.PublishedContent.Path; ProfilingLogger.Logger.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => nodePath); - var rootNodeId = _pcr.HasDomain ? _pcr.UmbracoDomain.RootContent.Id : (int?)null; + var rootNodeId = _pcr.HasDomain ? _pcr.UmbracoDomain.RootContentId : (int?)null; var domain = DomainHelper.FindWildcardDomainInPath(Services.DomainService.GetAll(true), nodePath, rootNodeId); - if (domain != null) + if (domain != null && domain.LanguageIsoCode.IsNullOrWhiteSpace() == false) { - _pcr.Culture = new CultureInfo(domain.Language.IsoCode); - ProfilingLogger.Logger.Debug("{0}Got domain on node {1}, set culture to \"{2}\".", () => tracePrefix, - () => domain.RootContent.Id, () => _pcr.Culture.Name); - } + _pcr.Culture = new CultureInfo(domain.LanguageIsoCode); + ProfilingLogger.Logger.Debug("{0}Got domain on node {1}, set culture to \"{2}\".", () => tracePrefix, + () => domain.RootContentId, () => _pcr.Culture.Name); + } else { ProfilingLogger.Logger.Debug("{0}No match.", () => tracePrefix); diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index 388012930b..afa5eb8132 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -96,7 +96,8 @@ namespace Umbraco.Web.Scheduling _logPrefix = "[" + name + "] "; _logger = logger; - HostingEnvironment.RegisterObject(this); + if (options.Hosted) + HostingEnvironment.RegisterObject(this); if (options.AutoStart) StartUp(); diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs index 4688ff37d6..55df42d3b7 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs @@ -15,6 +15,8 @@ namespace Umbraco.Web.Scheduling LongRunning = false; KeepAlive = false; AutoStart = false; + PreserveRunningTask = false; + Hosted = true; } /// @@ -36,9 +38,16 @@ namespace Umbraco.Web.Scheduling public bool AutoStart { get; set; } /// - /// Gets or setes a value indicating whether the running task should be preserved + /// Gets or sets a value indicating whether the running task should be preserved /// once completed, or reset to null. For unit tests. /// public bool PreserveRunningTask { get; set; } + + /// + /// Gets or sets a value indicating whether the runner should register with (and be + /// stopped by) the hosting. Otherwise, something else should take care of stopping + /// the runner. True by default. + /// + public bool Hosted { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index d5ffbc811d..d11bf2af35 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -29,11 +29,18 @@ namespace Umbraco.Web.Scheduling { if (_appContext == null) return true; // repeat... - string umbracoAppUrl = null; - - try + // ensure we do not run if not main domain, but do NOT lock it + if (_appContext.MainDom.IsMainDom == false) { - using (DisposableTimer.DebugDuration(() => "Keep alive executing", () => "Keep alive complete")) + LogHelper.Debug("Does not run if not MainDom."); + return false; // do NOT repeat, going down + } + + using (DisposableTimer.DebugDuration(() => "Keep alive executing", () => "Keep alive complete")) + { + string umbracoAppUrl = null; + + try { umbracoAppUrl = _appContext.UmbracoApplicationUrl; if (umbracoAppUrl.IsNullOrWhiteSpace()) @@ -54,10 +61,10 @@ namespace Umbraco.Web.Scheduling var result = await wc.SendAsync(request, token); } } - } - catch (Exception e) - { - LogHelper.Error(string.Format("Failed (at \"{0}\").", umbracoAppUrl), e); + catch (Exception e) + { + LogHelper.Error(string.Format("Failed (at \"{0}\").", umbracoAppUrl), e); + } } return true; // repeat diff --git a/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs b/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs index c024382ee8..3315fa7c34 100644 --- a/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs +++ b/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs @@ -1,13 +1,13 @@ using System; using System.Threading; using System.Threading.Tasks; +using Umbraco.Core; namespace Umbraco.Web.Scheduling { - internal abstract class LatchedBackgroundTaskBase : ILatchedBackgroundTask + internal abstract class LatchedBackgroundTaskBase : DisposableObject, ILatchedBackgroundTask { private readonly ManualResetEventSlim _latch; - private bool _disposed; protected LatchedBackgroundTaskBase() { @@ -51,27 +51,13 @@ namespace Umbraco.Web.Scheduling public abstract bool RunsOnShutdown { get; } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - // the task is going to be disposed again after execution, + // the task is going to be disposed after execution, // unless it is latched again, thus indicating it wants to // remain active - protected virtual void Dispose(bool disposing) + protected override void DisposeResources() { - // lock on _latch instead of creating a new object as _timer is - // private, non-null, readonly - so safe here - lock (_latch) - { - if (_disposed) return; - _disposed = true; - - _latch.Dispose(); - } + _latch.Dispose(); } } } diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index f920b5bb9d..66ba966823 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -66,6 +66,13 @@ namespace Umbraco.Web.Scheduling return false; // do NOT repeat, server status comes from config and will NOT change } + // ensure we do not run if not main domain, but do NOT lock it + if (_appContext.MainDom.IsMainDom == false) + { + LogHelper.Debug("Does not run if not MainDom."); + return false; // do NOT repeat, going down + } + using (DisposableTimer.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { Log.CleanLogs(GetLogScrubbingMaximumAge(_settings)); diff --git a/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs b/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs index 3fea70a2b8..567f85f1f5 100644 --- a/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs +++ b/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs @@ -84,19 +84,13 @@ namespace Umbraco.Web.Scheduling /// and returning a value indicating whether to repeat the task. public abstract Task PerformRunAsync(CancellationToken token); - protected override void Dispose(bool disposing) + protected override void DisposeResources() { - // lock on _timer instead of creating a new object as _timer is - // private, non-null, readonly - so safe here - lock (_timer) - { - if (_disposed) return; - _disposed = true; + base.DisposeResources(); - // stop the timer - _timer.Change(Timeout.Infinite, Timeout.Infinite); - _timer.Dispose(); - } + // stop the timer + _timer.Change(Timeout.Infinite, Timeout.Infinite); + _timer.Dispose(); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 78a91f6341..11cffb3648 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -38,6 +38,13 @@ namespace Umbraco.Web.Scheduling return false; // do NOT repeat, server status comes from config and will NOT change } + // ensure we do not run if not main domain, but do NOT lock it + if (_appContext.MainDom.IsMainDom == false) + { + LogHelper.Debug("Does not run if not MainDom."); + return false; // do NOT repeat, going down + } + using (DisposableTimer.DebugDuration(() => "Scheduled publishing executing", () => "Scheduled publishing complete")) { string umbracoAppUrl = null; diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs index 92214e5199..def7b606b5 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs @@ -101,6 +101,13 @@ namespace Umbraco.Web.Scheduling return false; // do NOT repeat, server status comes from config and will NOT change } + // ensure we do not run if not main domain, but do NOT lock it + if (_appContext.MainDom.IsMainDom == false) + { + LogHelper.Debug("Does not run if not MainDom."); + return false; // do NOT repeat, going down + } + using (DisposableTimer.DebugDuration(() => "Scheduled tasks executing", () => "Scheduled tasks complete")) { try diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index 58a77d91cf..11bdf48897 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web.Security.Identity userMembershipProvider)); //Create a sign in manager per request - app.CreatePerOwinContext(BackOfficeSignInManager.Create); + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger())); } /// @@ -87,7 +87,7 @@ namespace Umbraco.Web.Security.Identity userMembershipProvider)); //Create a sign in manager per request - app.CreatePerOwinContext(BackOfficeSignInManager.Create); + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } /// @@ -112,7 +112,7 @@ namespace Umbraco.Web.Security.Identity app.CreatePerOwinContext(userManager); //Create a sign in manager per request - app.CreatePerOwinContext(BackOfficeSignInManager.Create); + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } /// @@ -148,7 +148,10 @@ namespace Umbraco.Web.Security.Identity }; //This is a custom middleware, we need to return the user's remaining logged in seconds - app.Use(authOptions, UmbracoConfig.For.UmbracoSettings().Security); + app.Use( + authOptions, + UmbracoConfig.For.UmbracoSettings().Security, + app.CreateLogger()); app.UseCookieAuthentication(authOptions); diff --git a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs index eeebe13f3e..d909ff76e2 100644 --- a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs +++ b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs @@ -1,7 +1,9 @@ using System; +using System.Diagnostics; using System.Globalization; using System.Threading.Tasks; using Microsoft.Owin; +using Microsoft.Owin.Logging; using Microsoft.Owin.Security.Cookies; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -21,15 +23,20 @@ namespace Umbraco.Web.Security.Identity { private readonly UmbracoBackOfficeCookieAuthOptions _authOptions; private readonly ISecuritySection _security; + private readonly ILogger _logger; public GetUserSecondsMiddleWare( OwinMiddleware next, UmbracoBackOfficeCookieAuthOptions authOptions, - ISecuritySection security) + ISecuritySection security, + ILogger logger) : base(next) { + if (authOptions == null) throw new ArgumentNullException("authOptions"); + if (logger == null) throw new ArgumentNullException("logger"); _authOptions = authOptions; _security = security; + _logger = logger; } public override async Task Invoke(IOwinContext context) @@ -88,6 +95,17 @@ namespace Umbraco.Web.Security.Identity _authOptions.CookieName, cookieValue, cookieOptions); + + remainingSeconds = (ticket.Properties.ExpiresUtc.Value - DateTime.Now.ToUniversalTime()).TotalSeconds; + } + else if (remainingSeconds <=30) + { + //NOTE: We are using 30 seconds because that is what is coded into angular to force logout to give some headway in + // the timeout process. + + _logger.WriteCore(TraceEventType.Information, 0, + string.Format("User logged will be logged out due to timeout: {0}, IP Address: {1}", ticket.Identity.Name, request.RemoteIpAddress), + null, null); } await response.WriteAsync(remainingSeconds.ToString(CultureInfo.InvariantCulture)); diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 3284674338..3bb916ed13 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -125,9 +125,7 @@ namespace Umbraco.Web.Security .Where(p => member.ContentType.PropertyTypeExists(p.Alias)) .Where(property => member.Properties.Contains(property.Alias)) //needs to be editable - .Where(p => member.ContentType.MemberCanEditProperty(p.Alias)) - //needs to have a value - .Where(p => p.Value != null)) + .Where(p => member.ContentType.MemberCanEditProperty(p.Alias))) { member.Properties[property.Alias].Value = property.Value; } diff --git a/src/Umbraco.Web/Standalone/PowershellAssemblyResolver.cs b/src/Umbraco.Web/Standalone/PowershellAssemblyResolver.cs deleted file mode 100644 index 63dd2ff252..0000000000 --- a/src/Umbraco.Web/Standalone/PowershellAssemblyResolver.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; - -namespace Umbraco.Web.Standalone -{ - internal static class PowershellAssemblyResolver - { - private static readonly Dictionary Assemblies; - private static readonly object Locko = new object(); - - static PowershellAssemblyResolver() - { - var comparer = StringComparer.CurrentCultureIgnoreCase; - Assemblies = new Dictionary(comparer); - AppDomain.CurrentDomain.AssemblyResolve += ResolveHandler; - } - - public static void AddAssemblyLocation(string path) - { - if (string.IsNullOrWhiteSpace(path)) - throw new ArgumentException("Arg is null or empty.", "path"); - - lock (Locko) - { - var name = Path.GetFileNameWithoutExtension(path); - Assemblies.Add(name, path); - } - } - - private static Assembly ResolveHandler(object sender, ResolveEventArgs args) - { - var assemblyName = new AssemblyName(args.Name); - string assemblyFile; - return Assemblies.TryGetValue(assemblyName.Name, out assemblyFile) - ? Assembly.LoadFrom(assemblyFile) - : null; - } - } -} diff --git a/src/Umbraco.Web/Standalone/ServiceContextManager.cs b/src/Umbraco.Web/Standalone/ServiceContextManager.cs deleted file mode 100644 index 32c0867429..0000000000 --- a/src/Umbraco.Web/Standalone/ServiceContextManager.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Diagnostics; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; - -namespace Umbraco.Web.Standalone -{ - //TODO: Why does this exist? there's the same thing in the Core proj - - internal class ServiceContextManager : IDisposable - { - private readonly string _connectionString; - private readonly string _providerName; - private readonly ILogger _logger; - private readonly ISqlSyntaxProvider _syntaxProvider; - private ServiceContext _serviceContext; - private readonly StandaloneApplication _application; - - public ServiceContextManager(string connectionString, string providerName, string baseDirectory, ILogger logger, ISqlSyntaxProvider syntaxProvider) - { - _connectionString = connectionString; - _providerName = providerName; - _logger = logger; - _syntaxProvider = syntaxProvider; - - Trace.WriteLine("Current AppDomain: " + AppDomain.CurrentDomain.FriendlyName); - Trace.WriteLine("Current AppDomain: " + AppDomain.CurrentDomain.BaseDirectory); - - _application = StandaloneApplication.GetApplication(baseDirectory); - _application.Start(); - } - - public ServiceContext Services - { - get - { - if (_serviceContext == null) - { - var cacheHelper = new CacheHelper( - //SD: Not sure if this is correct? Should we be using HttpRuntime.Cache here since this is for 'Web' ? - // just not quite sure what this standalone stuff is. - new ObjectCacheRuntimeCacheProvider(), - new StaticCacheProvider(), - //SD: Not sure if this is correct? Should we be using request cache here since this is for 'Web' ? - // just not quite sure what this standalone stuff is. - new NullCacheProvider()); - - var dbFactory = new DefaultDatabaseFactory(_connectionString, _providerName, _logger); - var dbContext = new DatabaseContext(dbFactory, _logger, _syntaxProvider, _providerName); - Database.Mapper = new PetaPocoMapper(); - _serviceContext = new ServiceContext( - new RepositoryFactory(cacheHelper, _logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), - new PetaPocoUnitOfWorkProvider(dbFactory), - new FileUnitOfWorkProvider(), - new PublishingStrategy(), - cacheHelper, - new DebugDiagnosticsLogger()); - - //initialize the DatabaseContext - dbContext.Initialize(_providerName); - } - - return _serviceContext; - } - } - - public void Dispose() - { - ((IDisposable)ApplicationContext.Current).Dispose(); - _application.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Standalone/StandaloneApplication.cs b/src/Umbraco.Web/Standalone/StandaloneApplication.cs deleted file mode 100644 index e490684dc7..0000000000 --- a/src/Umbraco.Web/Standalone/StandaloneApplication.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.Standalone -{ - /// - /// An application standalone applications. - /// - internal class StandaloneApplication : UmbracoApplicationBase - { - /// - /// Initializes a new instance of the class. - /// - protected StandaloneApplication(string baseDirectory) - { - _baseDirectory = baseDirectory; - } - - /// - /// Provides the application boot manager. - /// - /// An application boot manager. - protected override IBootManager GetBootManager() - { - return new StandaloneBootManager(this, _handlersToAdd, _handlersToRemove, _baseDirectory); - } - - #region Application - - private static StandaloneApplication _application; - private readonly string _baseDirectory; - private static bool _started; - private static readonly object AppLock = new object(); - - /// - /// Gets the instance of the standalone Umbraco application. - /// - public static StandaloneApplication GetApplication(string baseDirectory) - { - lock (AppLock) - { - return _application ?? (_application = new StandaloneApplication(baseDirectory)); - } - } - - /// - /// Starts the application. - /// - public void Start(bool noerr = false) - { - lock (AppLock) - { - if (_started) - { - if (noerr) return; - throw new InvalidOperationException("Application has already started."); - } - try - { - Application_Start(this, EventArgs.Empty); - } - catch - { - TerminateInternal(); - throw; - } - _started = true; - } - } - - public void Terminate(bool noerr = false) - { - lock (AppLock) - { - if (_started == false) - { - if (noerr) return; - throw new InvalidOperationException("Application has already been terminated."); - } - - TerminateInternal(); - } - } - - private void TerminateInternal() - { - if (ApplicationContext.Current != null) - { - ApplicationContext.Current.DisposeIfDisposable(); // should reset resolution, clear caches & resolvers... - ApplicationContext.Current = null; - } - if (UmbracoContext.Current != null) - { - UmbracoContext.Current.DisposeIfDisposable(); // dunno - UmbracoContext.Current = null; - } - _started = false; - _application = null; - } - - #endregion - - #region IApplicationEventHandler management - - private readonly List _handlersToAdd = new List(); - private readonly List _handlersToRemove = new List(); - - /// - /// Associates an type with the application. - /// - /// The type to associate. - /// The application. - /// Types implementing from within - /// an executable are not automatically discovered by Umbraco and have to be - /// explicitely associated with the application using this method. - public StandaloneApplication WithApplicationEventHandler() - where T : IApplicationEventHandler - { - _handlersToAdd.Add(typeof(T)); - return this; - } - - /// - /// Dissociates an type from the application. - /// - /// The type to dissociate. - /// The application. - public StandaloneApplication WithoutApplicationEventHandler() - where T : IApplicationEventHandler - { - _handlersToRemove.Add(typeof(T)); - return this; - } - - /// - /// Associates an type with the application. - /// - /// The type to associate. - /// The application. - /// Types implementing from within - /// an executable are not automatically discovered by Umbraco and have to be - /// explicitely associated with the application using this method. - public StandaloneApplication WithApplicationEventHandler(Type type) - { - if (type.Implements() == false) - throw new ArgumentException("Type does not implement IApplicationEventHandler.", "type"); - _handlersToAdd.Add(type); - return this; - } - - /// - /// Dissociates an type from the application. - /// - /// The type to dissociate. - /// The application. - public StandaloneApplication WithoutApplicationEventHandler(Type type) - { - if (type.Implements() == false) - throw new ArgumentException("Type does not implement IApplicationEventHandler.", "type"); - _handlersToRemove.Add(type); - return this; - } - - #endregion - - #region Shortcuts to contexts - - /// - /// Gets the current . - /// - /// This is a shortcut for scripts to be able to do $app.ApplicationContext. - public ApplicationContext ApplicationContext { get { return ApplicationContext.Current; } } - - /// - /// Gets the current . - /// - /// This is a shortcut for scripts to be able to do $app.UmbracoContext. - public UmbracoContext UmbracoContext { get { return UmbracoContext.Current; } } - - #endregion - } -} diff --git a/src/Umbraco.Web/Standalone/StandaloneBootManager.cs b/src/Umbraco.Web/Standalone/StandaloneBootManager.cs deleted file mode 100644 index b06135d522..0000000000 --- a/src/Umbraco.Web/Standalone/StandaloneBootManager.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; -using umbraco.interfaces; - -namespace Umbraco.Web.Standalone -{ - /// - /// A boot manager for use in standalone applications. - /// - internal class StandaloneBootManager : CoreBootManager - { - // TODO - // this is highly experimental and probably not complete - not for production usage! - // also, could we inherit from WebBootManager? - - private readonly IEnumerable _handlersToAdd; - private readonly IEnumerable _handlersToRemove; - private readonly string _baseDirectory; - - public StandaloneBootManager(UmbracoApplicationBase umbracoApplication, IEnumerable handlersToAdd, IEnumerable handlersToRemove, string baseDirectory) - : base(umbracoApplication) - { - _handlersToAdd = handlersToAdd; - _handlersToRemove = handlersToRemove; - _baseDirectory = baseDirectory; - - base.InitializeApplicationRootPath(_baseDirectory); - - // this is only here to ensure references to the assemblies needed for - // the DataTypesResolver otherwise they won't be loaded into the AppDomain. - var interfacesAssemblyName = typeof(IDataType).Assembly.FullName; - - // TODO there's also that one... but we don't use it in standalone? - //using umbraco.editorControls; - //var editorControlsAssemblyName = typeof(uploadField).Assembly.FullName; - } - - protected override void InitializeApplicationEventsResolver() - { - base.InitializeApplicationEventsResolver(); - foreach (var type in _handlersToAdd) - ApplicationEventsResolver.Current.AddType(type); - foreach (var type in _handlersToRemove) - ApplicationEventsResolver.Current.RemoveType(type); - } - - protected override void InitializeResolvers() - { - base.InitializeResolvers(); - - var caches = new PublishedCaches( - new PublishedCache.XmlPublishedCache.PublishedContentCache(), - new PublishedCache.XmlPublishedCache.PublishedMediaCache(ApplicationContext.Current)); - - PublishedCachesResolver.Current = new PublishedCachesResolver(caches); - - UrlProviderResolver.Current = new UrlProviderResolver(ServiceProvider, LoggerResolver.Current.Logger, typeof(DefaultUrlProvider)); - SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper()); - - ContentLastChanceFinderResolver.Current = new ContentLastChanceFinderResolver(); - ContentFinderResolver.Current = new ContentFinderResolver( - ServiceProvider, LoggerResolver.Current.Logger, - typeof (ContentFinderByPageIdQuery), - typeof (ContentFinderByNiceUrl), - typeof (ContentFinderByIdPath), - typeof (ContentFinderByNotFoundHandlers) - ); - - // TODO what else? - } - - // can't create context before resolution is frozen! - protected override void FreezeResolution() - { - base.FreezeResolution(); - - var httpContext = new StandaloneHttpContext(); - UmbracoContext.EnsureContext(httpContext, ApplicationContext.Current, new WebSecurity(httpContext, ApplicationContext.Current), false, false); - } - } -} diff --git a/src/Umbraco.Web/Standalone/StandaloneHttpContext.cs b/src/Umbraco.Web/Standalone/StandaloneHttpContext.cs deleted file mode 100644 index dec95dd372..0000000000 --- a/src/Umbraco.Web/Standalone/StandaloneHttpContext.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Web; - -namespace Umbraco.Web.Standalone -{ - /// - /// An Http context for use in standalone applications. - /// - internal class StandaloneHttpContext : HttpContextBase - { - private readonly string _url; - private readonly HttpSessionStateBase _session = new StandaloneHttpSessionState(); - private readonly HttpResponseBase _response; - private readonly HttpRequestBase _request = new StandaloneHttpRequest(); - private readonly TextWriter _writer = new StringWriter(); - private readonly IDictionary _items = new Dictionary(); - - public StandaloneHttpContext() - { - _response = new HttpResponseWrapper(new HttpResponse(_writer)); - } - - public StandaloneHttpContext(string url) - : this() - { - if (url == null) throw new ArgumentNullException("url"); - _url = url; - _request = new HttpRequestWrapper(new HttpRequest("", _url, "")); - } - - - // what else should we implement here? - - public override IDictionary Items - { - get { return _items; } - } - - public override HttpSessionStateBase Session - { - get { return _session; } - } - - public override HttpRequestBase Request - { - get { return _request; } - } - - public override HttpResponseBase Response - { - get { return _response; } - } - - } - - internal class StandaloneHttpSessionState : HttpSessionStateBase - { - - } - - internal class StandaloneHttpRequest : HttpRequestBase - { - public override Uri Url - { - get { return new Uri("http://localhost"); } - } - } -} diff --git a/src/Umbraco.Web/Standalone/WriteableConfigSystem.cs b/src/Umbraco.Web/Standalone/WriteableConfigSystem.cs deleted file mode 100644 index 2438256cce..0000000000 --- a/src/Umbraco.Web/Standalone/WriteableConfigSystem.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.Configuration; -using System.Configuration.Internal; -using System.Reflection; -using System.Threading; - -namespace Umbraco.Web.Standalone -{ - // see http://stackoverflow.com/questions/15653621/how-to-update-add-modify-delete-keys-in-appsettings-section-of-web-config-at-r - // see http://www.codeproject.com/Articles/69364/Override-Configuration-Manager - - internal sealed class WriteableConfigSystem : IInternalConfigSystem - { - private static readonly ReaderWriterLockSlim RwLock = new ReaderWriterLockSlim(); - private static WriteableConfigSystem _installed; - private static IInternalConfigSystem _clientConfigSystem; - private object _appsettings; - private object _connectionStrings; - private static object _sInitStateOrig; - private static object _sConfigSystemOrig; - - public static bool Installed - { - get - { - try - { - RwLock.EnterReadLock(); - return _installed != null; - } - finally - { - RwLock.ExitReadLock(); - } - } - } - - /// - /// Re-initializes the ConfigurationManager, allowing us to merge in the settings from Core.Config - /// - public static void Install() - { - try - { - RwLock.EnterWriteLock(); - - if (_installed != null) - throw new InvalidOperationException("ConfigSystem is already installed."); - - FieldInfo[] fiStateValues = null; - var tInitState = typeof(ConfigurationManager).GetNestedType("InitState", BindingFlags.NonPublic); - - if (tInitState != null) - fiStateValues = tInitState.GetFields(); - // 0: NotStarted - // 1: Started - // 2: Usable - // 3: Completed - - var fiInit = typeof(ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static); - var fiSystem = typeof(ConfigurationManager).GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static); - - if (fiInit != null && fiSystem != null && fiStateValues != null) - { - _sInitStateOrig = fiInit.GetValue(null); - _sConfigSystemOrig = fiSystem.GetValue(null); - fiInit.SetValue(null, fiStateValues[1].GetValue(null)); // set to Started - fiSystem.SetValue(null, null); // clear current config system - } - - _installed = new WriteableConfigSystem(); - - var configFactoryType = Type.GetType("System.Configuration.Internal.InternalConfigSettingsFactory, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true); - var configSettingsFactory = (IInternalConfigSettingsFactory)Activator.CreateInstance(configFactoryType, true); - // just does ConfigurationManager.SetConfigurationSystem(_installed, false); - // 'false' turns initState to 2 ie usable (vs 3 ie completed) - configSettingsFactory.SetConfigurationSystem(_installed, false); - - // note: prob. don't need the factory... see how we uninstall... - - var clientConfigSystemType = Type.GetType("System.Configuration.ClientConfigurationSystem, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true); - _clientConfigSystem = (IInternalConfigSystem)Activator.CreateInstance(clientConfigSystemType, true); - } - finally - { - RwLock.ExitWriteLock(); - } - } - - public static void Uninstall() - { - try - { - RwLock.EnterWriteLock(); - - if (_installed == null) - throw new InvalidOperationException("ConfigSystem is not installed."); - - FieldInfo[] fiStateValues = null; - var tInitState = typeof(ConfigurationManager).GetNestedType("InitState", BindingFlags.NonPublic); - - if (tInitState != null) - fiStateValues = tInitState.GetFields(); - - var fiInit = typeof(ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static); - var fiSystem = typeof(ConfigurationManager).GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static); - - if (fiInit != null && fiSystem != null && fiStateValues != null) - { - // reset - the hard way - fiInit.SetValue(null, _sInitStateOrig); - fiSystem.SetValue(null, _sConfigSystemOrig); - } - - _installed = null; - _clientConfigSystem = null; - } - finally - { - RwLock.ExitWriteLock(); - } - } - - public static void Reset() - { - try - { - RwLock.EnterWriteLock(); - - if (_installed == null) - throw new InvalidOperationException("ConfigSystem is not installed."); - - _installed._appsettings = null; - _installed._connectionStrings = null; - } - finally - { - RwLock.ExitWriteLock(); - } - } - - #region IInternalConfigSystem Members - - public object GetSection(string configKey) - { - // get the section from the default location (web.config or app.config) - var section = _clientConfigSystem.GetSection(configKey); - - try - { - RwLock.EnterReadLock(); - - switch (configKey) - { - case "appSettings": - // Return cached version if exists - if (_appsettings != null) - return _appsettings; - - // create a new collection because the underlying collection is read-only - var cfg = new NameValueCollection(); - - // If an AppSettings section exists in Web.config, read and add values from it - var nvSection = section as NameValueCollection; - if (nvSection != null) - { - var localSettings = nvSection; - foreach (string key in localSettings) - cfg.Add(key, localSettings[key]); - } - - //// -------------------------------------------------------------------- - //// Here I read and decrypt keys and add them to secureConfig dictionary - //// To test assume the following line is a key stored in secure sotrage. - ////secureConfig = SecureConfig.LoadConfig(); - //secureConfig.Add("ACriticalKey", "VeryCriticalValue"); - //// -------------------------------------------------------------------- - //foreach (KeyValuePair item in secureConfig) - //{ - // if (cfg.AllKeys.Contains(item.Key)) - // { - // cfg[item.Key] = item.Value; - // } - // else - // { - // cfg.Add(item.Key, item.Value); - // } - //} - //// -------------------------------------------------------------------- - - - // Cach the settings for future use - - _appsettings = cfg; - // return the merged version of the items from secure storage and appsettings - section = _appsettings; - break; - - case "connectionStrings": - // Return cached version if exists - if (_connectionStrings != null) - return _connectionStrings; - - // create a new collection because the underlying collection is read-only - var connectionStringsSection = new ConnectionStringsSection(); - - // copy the existing connection strings into the new collection - foreach ( - ConnectionStringSettings connectionStringSetting in - ((ConnectionStringsSection)section).ConnectionStrings) - connectionStringsSection.ConnectionStrings.Add(connectionStringSetting); - - // -------------------------------------------------------------------- - // Again Load connection strings from secure storage and merge like below - // connectionStringsSection.ConnectionStrings.Add(connectionStringSetting); - // -------------------------------------------------------------------- - - // Cach the settings for future use - _connectionStrings = connectionStringsSection; - // return the merged version of the items from secure storage and appsettings - section = _connectionStrings; - break; - } - } - finally - { - RwLock.ExitReadLock(); - } - - return section; - } - - public void RefreshConfig(string sectionName) - { - try - { - RwLock.EnterWriteLock(); - - if (sectionName == "appSettings") - { - _appsettings = null; - } - - if (sectionName == "connectionStrings") - { - _connectionStrings = null; - } - } - finally - { - RwLock.ExitWriteLock(); - } - - _clientConfigSystem.RefreshConfig(sectionName); - } - - public bool SupportsUserConfig { get { return _clientConfigSystem.SupportsUserConfig; } } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs index 610c57b592..ffdd64dd57 100644 --- a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs +++ b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs @@ -31,9 +31,6 @@ namespace Umbraco.Web.Strategies.Migrations private void EnsureListViewDataTypeCreated(MigrationEventArgs e) { - var exists = e.MigrationContext.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE id=1037"); - if (exists > 0) return; - using (var transaction = e.MigrationContext.Database.GetTransaction()) { try diff --git a/src/Umbraco.Web/TagQuery.cs b/src/Umbraco.Web/TagQuery.cs index ded4dea66e..807a0699cb 100644 --- a/src/Umbraco.Web/TagQuery.cs +++ b/src/Umbraco.Web/TagQuery.cs @@ -13,6 +13,11 @@ namespace Umbraco.Web /// public class TagQuery : ITagQuery { + + //TODO: This class also acts as a wrapper for ITagQuery due to breaking changes, need to fix in + // version 8: http://issues.umbraco.org/issue/U4-6899 + private readonly ITagQuery _wrappedQuery; + private readonly ITagService _tagService; private readonly ITypedPublishedContentQuery _typedContentQuery; @@ -22,7 +27,7 @@ namespace Umbraco.Web { } - [Obsolete("Use the alternate constructor specifying the ITypedPublishedContentQuery instead")] + [Obsolete("Use the alternate constructor specifying the ITypedPublishedContentQuery instead")] public TagQuery(ITagService tagService, PublishedContentQuery contentQuery) { if (tagService == null) throw new ArgumentNullException("tagService"); @@ -31,6 +36,16 @@ namespace Umbraco.Web _typedContentQuery = contentQuery; } + /// + /// Constructor for wrapping ITagQuery, see http://issues.umbraco.org/issue/U4-6899 + /// + /// + internal TagQuery(ITagQuery wrappedQuery) + { + if (wrappedQuery == null) throw new ArgumentNullException("wrappedQuery"); + _wrappedQuery = wrappedQuery; + } + /// /// Constructor /// @@ -52,6 +67,9 @@ namespace Umbraco.Web /// public IEnumerable GetContentByTag(string tag, string tagGroup = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetContentByTag(tag, tagGroup); + var ids = _tagService.GetTaggedContentByTag(tag, tagGroup) .Select(x => x.EntityId); return _typedContentQuery.TypedContent(ids) @@ -65,6 +83,9 @@ namespace Umbraco.Web /// public IEnumerable GetContentByTagGroup(string tagGroup) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetContentByTagGroup(tagGroup); + var ids = _tagService.GetTaggedContentByTagGroup(tagGroup) .Select(x => x.EntityId); return _typedContentQuery.TypedContent(ids) @@ -79,6 +100,9 @@ namespace Umbraco.Web /// public IEnumerable GetMediaByTag(string tag, string tagGroup = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetMediaByTag(tag, tagGroup); + var ids = _tagService.GetTaggedMediaByTag(tag, tagGroup) .Select(x => x.EntityId); return _typedContentQuery.TypedMedia(ids) @@ -92,6 +116,9 @@ namespace Umbraco.Web /// public IEnumerable GetMediaByTagGroup(string tagGroup) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetMediaByTagGroup(tagGroup); + var ids = _tagService.GetTaggedMediaByTagGroup(tagGroup) .Select(x => x.EntityId); return _typedContentQuery.TypedMedia(ids) @@ -113,6 +140,9 @@ namespace Umbraco.Web /// public IEnumerable GetAllTags(string group = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetAllTags(group); + return Mapper.Map>(_tagService.GetAllTags(group)); } @@ -123,6 +153,9 @@ namespace Umbraco.Web /// public IEnumerable GetAllContentTags(string group = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetAllContentTags(group); + return Mapper.Map>(_tagService.GetAllContentTags(group)); } @@ -133,6 +166,9 @@ namespace Umbraco.Web /// public IEnumerable GetAllMediaTags(string group = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetAllMediaTags(group); + return Mapper.Map>(_tagService.GetAllMediaTags(group)); } @@ -143,6 +179,9 @@ namespace Umbraco.Web /// public IEnumerable GetAllMemberTags(string group = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetAllMemberTags(group); + return Mapper.Map>(_tagService.GetAllMemberTags(group)); } @@ -155,6 +194,9 @@ namespace Umbraco.Web /// public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup); + return Mapper.Map>(_tagService.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup)); } @@ -166,6 +208,9 @@ namespace Umbraco.Web /// public IEnumerable GetTagsForEntity(int contentId, string tagGroup = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetTagsForEntity(contentId, tagGroup); + return Mapper.Map>(_tagService.GetTagsForEntity(contentId, tagGroup)); } } diff --git a/src/Umbraco.Web/Templates/TemplateRenderer.cs b/src/Umbraco.Web/Templates/TemplateRenderer.cs index 22d6b5b54b..d7d331d887 100644 --- a/src/Umbraco.Web/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web/Templates/TemplateRenderer.cs @@ -81,9 +81,9 @@ namespace Umbraco.Web.Templates //set the doc that was found by id contentRequest.PublishedContent = doc; //set the template, either based on the AltTemplate found or the standard template of the doc - contentRequest.TemplateModel = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates || AltTemplate.HasValue == false - ? ApplicationContext.Current.Services.FileService.GetTemplate(doc.TemplateId) - : ApplicationContext.Current.Services.FileService.GetTemplate(AltTemplate.Value); + contentRequest.TemplateModel = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates || AltTemplate.HasValue == false + ? _umbracoContext.Application.Services.FileService.GetTemplate(doc.TemplateId) + : _umbracoContext.Application.Services.FileService.GetTemplate(AltTemplate.Value); //if there is not template then exit if (!contentRequest.HasTemplate) @@ -103,14 +103,20 @@ namespace Umbraco.Web.Templates //after this page has rendered. SaveExistingItems(); - //set the new items on context objects for this templates execution - SetNewItemsOnContextObjects(contentRequest); + try + { + //set the new items on context objects for this templates execution + SetNewItemsOnContextObjects(contentRequest); - //Render the template - ExecuteTemplateRendering(writer, contentRequest); - - //restore items on context objects to continuing rendering the parent template - RestoreItems(); + //Render the template + ExecuteTemplateRendering(writer, contentRequest); + } + finally + { + //restore items on context objects to continuing rendering the parent template + RestoreItems(); + } + } private void ExecuteTemplateRendering(TextWriter sw, PublishedContentRequest contentRequest) diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js index f81046e52b..4ef8d03214 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js @@ -16,7 +16,7 @@ 'lib/bootstrap/js/bootstrap.2.3.2.min.js', 'lib/bootstrap-tabdrop/bootstrap-tabdrop.js', - 'lib/umbraco/Extensions.js', + 'lib/umbraco/Extensions.js', 'lib/umbraco/NamespaceManager.js', 'lib/umbraco/LegacyUmbClientMgr.js', diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 656580be21..31ebe06918 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -106,8 +106,8 @@ ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - False ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + True False @@ -116,9 +116,9 @@ ..\packages\dotless.1.4.1.0\lib\dotless.Core.dll - - False - ..\packages\Examine.0.1.63.0\lib\Examine.dll + + ..\packages\Examine.0.1.66.0\lib\Examine.dll + True False @@ -165,17 +165,13 @@ True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - False - ..\packages\Microsoft.AspNet.Mvc.FixedDisplayModes.1.0.1\lib\net40\Microsoft.Web.Mvc.FixedDisplayModes.dll - False ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True False @@ -216,9 +212,9 @@ - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll @@ -228,28 +224,28 @@ 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.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll - + + ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll True - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll System.Web.Services - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll @@ -301,11 +297,15 @@ + + + + @@ -640,6 +640,7 @@ + @@ -748,7 +749,6 @@ - @@ -760,10 +760,6 @@ - - - - @@ -818,7 +814,6 @@ - True True diff --git a/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings b/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoContextExtensions.cs b/src/Umbraco.Web/UmbracoContextExtensions.cs index f75487ac66..ec85b4dad6 100644 --- a/src/Umbraco.Web/UmbracoContextExtensions.cs +++ b/src/Umbraco.Web/UmbracoContextExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Umbraco.Core.Events; namespace Umbraco.Web { @@ -10,20 +11,18 @@ namespace Umbraco.Web /// public static class UmbracoContextExtensions { + /// - /// Informs the context that content has changed. + /// If there are event messages in the current request this will return them , otherwise it will return null /// - /// The context. - /// - /// The contextual caches may, although that is not mandatory, provide an immutable snapshot of - /// the content over the duration of the context. If you make changes to the content and do want to have - /// the caches update their snapshot, you have to explicitely ask them to do so by calling ContentHasChanged. - /// The context informs the contextual caches that content has changed. - /// - public static void ContentHasChanged(this UmbracoContext context) + /// + /// + public static EventMessages GetCurrentEventMessages(this UmbracoContext umbracoContext) { - context.ContentCache.ContentHasChanged(); - context.MediaCache.ContentHasChanged(); + var msgs = umbracoContext.HttpContext.Items[typeof (RequestLifespanMessagesFactory).Name]; + if (msgs == null) return null; + return (EventMessages) msgs; } + } } diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index d60630a58d..e407edda4f 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web private IUmbracoComponentRenderer _componentRenderer; private PublishedContentQuery _query; private MembershipHelper _membershipHelper; - private ITagQuery _tag; + private TagQuery _tag; private IDataTypeService _dataTypeService; private UrlProvider _urlProvider; private ICultureDictionary _cultureDictionary; @@ -40,9 +40,18 @@ namespace Umbraco.Web /// /// Lazy instantiates the tag context /// - public ITagQuery TagQuery + public TagQuery TagQuery { - get { return _tag ?? (_tag = new TagQuery(UmbracoContext.Application.Services.TagService, _typedQuery)); } + //TODO: Unfortunately we cannot change this return value to be ITagQuery + // since it's a breaking change, need to fix it for v8 + // http://issues.umbraco.org/issue/U4-6899 + + get + { + return _tag ?? + (_tag = new TagQuery(UmbracoContext.Application.Services.TagService, + _typedQuery ?? ContentQuery)); + } } /// @@ -154,7 +163,7 @@ namespace Umbraco.Web if (membershipHelper == null) throw new ArgumentNullException("membershipHelper"); _umbracoContext = umbracoContext; - _tag = tagQuery; + _tag = new TagQuery(tagQuery); _dataTypeService = dataTypeService; _urlProvider = urlProvider; _cultureDictionary = cultureDictionary; @@ -312,7 +321,7 @@ namespace Umbraco.Web bool formatAsDateWithTime = false, string formatAsDateWithTimeSeparator = "") { - return _componentRenderer.Field(AssignedContentItem, fieldAlias, altFieldAlias, + return UmbracoComponentRenderer.Field(AssignedContentItem, fieldAlias, altFieldAlias, altText, insertBefore, insertAfter, recursive, convertLineBreaks, removeParagraphTags, casing, encoding, formatAsDate, formatAsDateWithTime, formatAsDateWithTimeSeparator); } @@ -345,7 +354,7 @@ namespace Umbraco.Web bool formatAsDateWithTime = false, string formatAsDateWithTimeSeparator = "") { - return _componentRenderer.Field(currentPage, fieldAlias, altFieldAlias, + return UmbracoComponentRenderer.Field(currentPage, fieldAlias, altFieldAlias, altText, insertBefore, insertAfter, recursive, convertLineBreaks, removeParagraphTags, casing, encoding, formatAsDate, formatAsDateWithTime, formatAsDateWithTimeSeparator); } diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 838dea94b8..54d106574a 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -51,9 +51,8 @@ namespace Umbraco.Web // use ports from request // otherwise, // if non-standard ports used, - // user may need to set baseUrl manually per + // user may need to set umbracoApplicationUrl manually per // http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks - // TODO update the doc, prefer web.routing/@appUrl to scheduledTasks/@baseUrl var port = (request.IsSecureConnection == false && GlobalSettings.UseSSL == false) || (request.IsSecureConnection && GlobalSettings.UseSSL) ? ":" + request.ServerVariables["SERVER_PORT"] diff --git a/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs b/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs new file mode 100644 index 0000000000..8cf70826fe --- /dev/null +++ b/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs @@ -0,0 +1,65 @@ +using System; +using System.Net.Http; +using System.Web.Http.Filters; +using Umbraco.Core.Events; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.UI; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Automatically checks if any request is a non-GET and if the + /// resulting message is INotificationModel in which case it will append any Event Messages + /// currently in the request. + /// + internal sealed class AppendCurrentEventMessagesAttribute : ActionFilterAttribute + { + public override void OnActionExecuted(HttpActionExecutedContext context) + { + if (context.Response == null) return; + if (context.Request.Method == HttpMethod.Get) return; + if (UmbracoContext.Current == null) return; + + var obj = context.Response.Content as ObjectContent; + if (obj == null) return; + + var notifications = obj.Value as INotificationModel; + if (notifications == null) return; + + var msgs = UmbracoContext.Current.GetCurrentEventMessages(); + if (msgs == null) return; + + foreach (var eventMessage in msgs.GetAll()) + { + SpeechBubbleIcon msgType; + switch (eventMessage.MessageType) + { + case EventMessageType.Default: + msgType = SpeechBubbleIcon.Save; + break; + case EventMessageType.Info: + msgType = SpeechBubbleIcon.Info; + break; + case EventMessageType.Error: + msgType = SpeechBubbleIcon.Error; + break; + case EventMessageType.Success: + msgType = SpeechBubbleIcon.Success; + break; + case EventMessageType.Warning: + msgType = SpeechBubbleIcon.Warning; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + notifications.Notifications.Add(new Notification + { + Message = eventMessage.Message, + Header = eventMessage.Category, + NotificationType = msgType + }); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs index 239b9bd576..88eda3c64e 100644 --- a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs @@ -152,10 +152,26 @@ namespace Umbraco.Web.WebApi.Filters if (p.ValidationRegExp.IsNullOrWhiteSpace() == false) { - foreach (var result in p.PropertyEditor.ValueEditor.RegexValidator.Validate(postedValue, p.ValidationRegExp, preValues, editor)) + + //We only want to execute the regex statement if: + // * the value is null or empty AND it is required OR + // * the value is not null or empty + //See: http://issues.umbraco.org/issue/U4-4669 + + var asString = postedValue as string; + + if ( + //Value is not null or empty + (postedValue != null && asString.IsNullOrWhiteSpace() == false) + //It's required + || (p.IsRequired)) { - actionContext.ModelState.AddPropertyError(result, p.Alias); + foreach (var result in p.PropertyEditor.ValueEditor.RegexValidator.Validate(postedValue, p.ValidationRegExp, preValues, editor)) + { + actionContext.ModelState.AddPropertyError(result, p.Alias); + } } + } } diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs index d8e185963a..4c4d831241 100644 --- a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Net.Http.Formatting; using System.Web.Http.Controllers; -using System.Web.Http.Filters; using Umbraco.Core; namespace Umbraco.Web.WebApi.Filters @@ -10,7 +9,7 @@ namespace Umbraco.Web.WebApi.Filters /// /// Sets the json outgoing/serialized datetime format /// - internal sealed class OutgoingDateTimeFormatAttribute : Attribute, IControllerConfiguration + internal sealed class OutgoingDateTimeFormatAttribute : Attribute, IControllerConfiguration { private readonly string _format = "yyyy-MM-dd HH:mm:ss"; @@ -27,8 +26,9 @@ namespace Umbraco.Web.WebApi.Filters /// /// Will use the standard ISO format /// - public OutgoingDateTimeFormatAttribute(){ - + public OutgoingDateTimeFormatAttribute() + { + } public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index 97448b9976..ad29726d4e 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -35,7 +35,7 @@ namespace Umbraco.Web.WebApi /// /// Returns the currently logged in Umbraco User /// - [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object")] + [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object, or Security.GetUserId() if you want to just get the user id")] protected User UmbracoUser { get diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 99bdaada4c..baac1109e4 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -11,6 +11,7 @@ using System.Web.Mvc; using System.Web.Routing; using ClientDependency.Core.Config; using Examine; +using Examine.Config; using umbraco; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -38,6 +39,10 @@ using Umbraco.Web.Scheduling; using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; @@ -54,22 +59,43 @@ namespace Umbraco.Web private readonly List _indexesToRebuild = new List(); public WebBootManager(UmbracoApplicationBase umbracoApplication) - : this(umbracoApplication, false) + : base(umbracoApplication) { - + _isForTesting = false; } /// /// Constructor for unit tests, ensures some resolvers are not initialized /// /// + /// /// - internal WebBootManager(UmbracoApplicationBase umbracoApplication, bool isForTesting) - : base(umbracoApplication) + internal WebBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger, bool isForTesting) + : base(umbracoApplication, logger) { _isForTesting = isForTesting; } + /// + /// Creates and returns the service context for the app + /// + /// + /// + /// + protected override ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory) + { + //use a request based messaging factory + var evtMsgs = new RequestLifespanMessagesFactory(new SingletonUmbracoContextAccessor()); + return new ServiceContext( + new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), + new PetaPocoUnitOfWorkProvider(dbFactory), + new FileUnitOfWorkProvider(), + new PublishingStrategy(evtMsgs, ProfilingLogger.Logger), + ApplicationCache, + ProfilingLogger.Logger, + evtMsgs); + } + /// /// Initialize objects before anything during the boot cycle happens /// @@ -140,7 +166,10 @@ namespace Umbraco.Web UmbracoContext.EnsureContext( httpContext, ApplicationContext, - new WebSecurity(httpContext, ApplicationContext)); + new WebSecurity(httpContext, ApplicationContext), + UmbracoConfig.For.UmbracoSettings(), + UrlProviderResolver.Current.Providers, + false); } /// @@ -153,15 +182,7 @@ namespace Umbraco.Web //Set the profiler to be the web profiler ProfilerResolver.Current.SetProfiler(new WebProfiler()); } - - /// - /// Adds custom types to the ApplicationEventsResolver - /// - protected override void InitializeApplicationEventsResolver() - { - base.InitializeApplicationEventsResolver(); - } - + /// /// Ensure that the OnApplicationStarted methods of the IApplicationEvents are called /// @@ -214,10 +235,10 @@ namespace Umbraco.Web /// /// Creates the application cache based on the HttpRuntime cache /// - protected override void CreateApplicationCache() + protected override CacheHelper CreateApplicationCache() { //create a web-based cache helper - ApplicationCache = new CacheHelper(); + return new CacheHelper(); } /// @@ -281,7 +302,6 @@ namespace Umbraco.Web } } - private void RouteLocalApiController(Type controller, string umbracoPath) { var meta = PluginController.GetMetadata(controller); @@ -303,6 +323,7 @@ namespace Umbraco.Web } route.DataTokens.Add("umbraco", "api"); //ensure the umbraco token is set } + private void RouteLocalSurfaceController(Type controller, string umbracoPath) { var meta = PluginController.GetMetadata(controller); @@ -329,32 +350,86 @@ namespace Umbraco.Web //set the default RenderMvcController DefaultRenderMvcControllerResolver.Current = new DefaultRenderMvcControllerResolver(typeof(RenderMvcController)); - ServerMessengerResolver.Current.SetServerMessenger(new BatchedWebServiceServerMessenger(() => + //Override the default server messenger, we need to check if the legacy dist calls is enabled, if that is the + // case, then we'll set the default messenger to be the old one, otherwise we'll set it to the db messenger + // which will always be on. + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) { - //we should not proceed to change this if the app/database is not configured since there will - // be no user, plus we don't need to have server messages sent if this is the case. - if (ApplicationContext.IsConfigured && ApplicationContext.DatabaseContext.IsDatabaseConfigured) + //set the legacy one by default - this maintains backwards compat + ServerMessengerResolver.Current.SetServerMessenger(new BatchedWebServiceServerMessenger(() => { - //disable if they are not enabled - if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled == false) + //we should not proceed to change this if the app/database is not configured since there will + // be no user, plus we don't need to have server messages sent if this is the case. + if (ApplicationContext.IsConfigured && ApplicationContext.DatabaseContext.IsDatabaseConfigured) { - return null; - } + //disable if they are not enabled + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled == false) + { + return null; + } - try - { - var user = User.GetUser(UmbracoConfig.For.UmbracoSettings().DistributedCall.UserId); - return new System.Tuple(user.LoginName, user.GetPassword()); + try + { + var user = ApplicationContext.Services.UserService.GetUserById(UmbracoConfig.For.UmbracoSettings().DistributedCall.UserId); + return new Tuple(user.Username, user.RawPasswordValue); + } + catch (Exception e) + { + LoggerResolver.Current.Logger.Error("An error occurred trying to set the IServerMessenger during application startup", e); + return null; + } } - catch (Exception e) + LoggerResolver.Current.Logger.Warn("Could not initialize the DefaultServerMessenger, the application is not configured or the database is not configured"); + return null; + })); + } + else + { + + // NOTE: This is IMPORTANT! ... we don't want to rebuild any index that is already flagged to be re-indexed + // on startup based on our _indexesToRebuild variable and how Examine auto-rebuilds when indexes are empty + // this callback is used below for the DatabaseServerMessenger startup options + Action rebuildIndexes = () => + { + //If the developer has explicitly opted out of rebuilding indexes on startup then we + // should adhere to that and not do it, this means that if they are load balancing things will be + // out of sync if they are auto-scaling but there's not much we can do about that. + if (ExamineSettings.Instance.RebuildOnAppStart == false) return; + + if (_indexesToRebuild.Any()) { - LoggerResolver.Current.Logger.Error("An error occurred trying to set the IServerMessenger during application startup", e); - return null; + var otherIndexes = ExamineManager.Instance.IndexProviderCollection.Except(_indexesToRebuild); + foreach (var otherIndex in otherIndexes) + { + otherIndex.RebuildIndex(); + } } - } - LoggerResolver.Current.Logger.Warn("Could not initialize the DefaultServerMessenger, the application is not configured or the database is not configured"); - return null; - })); + else + { + //rebuild them all + ExamineManager.Instance.RebuildIndex(); + } + }; + + ServerMessengerResolver.Current.SetServerMessenger(new BatchedDatabaseServerMessenger( + ApplicationContext, + true, + //Default options for web including the required callbacks to build caches + new DatabaseServerMessengerOptions + { + //These callbacks will be executed if the server has not been synced + // (i.e. it is a new server or the lastsynced.txt file has been removed) + InitializingCallbacks = new Action[] + { + //rebuild the xml cache file if the server is not synced + () => global::umbraco.content.Instance.RefreshContentFromDatabase(), + //rebuild indexes if the server is not synced + // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific + // indexes then they can adjust this logic themselves. + rebuildIndexes + } + })); + } SurfaceControllerResolver.Current = new SurfaceControllerResolver( ServiceProvider, LoggerResolver.Current.Logger, @@ -441,7 +516,6 @@ namespace Umbraco.Web new DefaultCultureDictionaryFactory()); } - private void OnInstanceOnBuildingEmptyIndexOnStartup(object sender, BuildingEmptyIndexOnStartupEventArgs args) { //store the indexer that needs rebuilding because it's empty for when the boot process diff --git a/src/Umbraco.Web/WebServices/DomainsApiController.cs b/src/Umbraco.Web/WebServices/DomainsApiController.cs index a72f7537b0..f3344a7fb7 100644 --- a/src/Umbraco.Web/WebServices/DomainsApiController.cs +++ b/src/Umbraco.Web/WebServices/DomainsApiController.cs @@ -54,14 +54,14 @@ namespace Umbraco.Web.WebServices { var wildcard = domains.FirstOrDefault(d => d.IsWildcard); if (wildcard != null) - wildcard.Language = language; + wildcard.LanguageId = language.Id; else { // yet there is a race condition here... var newDomain = new UmbracoDomain("*" + model.NodeId) { - Language = Services.LocalizationService.GetLanguageById(model.Language), - RootContent = Services.ContentService.GetById(model.NodeId) + LanguageId = model.Language, + RootContentId = model.NodeId }; Services.DomainService.Save(newDomain); } @@ -103,7 +103,7 @@ namespace Umbraco.Web.WebServices names.Add(name); var domain = domains.FirstOrDefault(d => d.DomainName.InvariantEquals(domainModel.Name)); if (domain != null) - domain.Language = language; + domain.LanguageId = language.Id; else if (Services.DomainService.Exists(domainModel.Name)) domainModel.Duplicate = true; else @@ -111,8 +111,8 @@ namespace Umbraco.Web.WebServices // yet there is a race condition here... var newDomain = new UmbracoDomain(name) { - Language = Services.LocalizationService.GetLanguageById(domainModel.Lang), - RootContent = Services.ContentService.GetById(model.NodeId) + LanguageId = domainModel.Lang, + RootContentId = model.NodeId }; Services.DomainService.Save(newDomain); } diff --git a/src/Umbraco.Web/app.config b/src/Umbraco.Web/app.config index 548dde0987..2b15194775 100644 --- a/src/Umbraco.Web/app.config +++ b/src/Umbraco.Web/app.config @@ -29,11 +29,11 @@ - + - + diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 1ad7d7c403..6cdd67fe04 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -3,19 +3,18 @@ - + - - - + + - + @@ -23,10 +22,10 @@ - + - - + + \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs b/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs index dc354be71d..00ec43229a 100644 --- a/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs +++ b/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs @@ -16,25 +16,11 @@ namespace umbraco.presentation { base.ApplicationInitialized(umbracoApplication, applicationContext); - EnsurePathExists("~/App_Code"); - EnsurePathExists("~/App_Data"); - EnsurePathExists(SystemDirectories.AppPlugins); - EnsurePathExists(SystemDirectories.Css); - EnsurePathExists(SystemDirectories.Masterpages); - EnsurePathExists(SystemDirectories.Media); - EnsurePathExists(SystemDirectories.Scripts); - EnsurePathExists(SystemDirectories.UserControls); - EnsurePathExists(SystemDirectories.Xslt); - EnsurePathExists(SystemDirectories.MvcViews); - EnsurePathExists(SystemDirectories.MvcViews + "/Partials"); - EnsurePathExists(SystemDirectories.MvcViews + "/MacroPartials"); - } - - public void EnsurePathExists(string path) - { - var absolutePath = IOHelper.MapPath(path); - if (!Directory.Exists(absolutePath)) - Directory.CreateDirectory(absolutePath); + IOHelper.EnsurePathExists("~/App_Data"); + IOHelper.EnsurePathExists(SystemDirectories.Media); + IOHelper.EnsurePathExists(SystemDirectories.MvcViews); + IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/Partials"); + IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/MacroPartials"); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/NotFoundHandlers.cs b/src/Umbraco.Web/umbraco.presentation/NotFoundHandlers.cs index 27e62888d4..6854bb9a4d 100644 --- a/src/Umbraco.Web/umbraco.presentation/NotFoundHandlers.cs +++ b/src/Umbraco.Web/umbraco.presentation/NotFoundHandlers.cs @@ -326,7 +326,8 @@ namespace umbraco { HttpContext.Current.Request.ServerVariables["SERVER_NAME"], ApplicationContext.Current.Services.EntityService, new PublishedContentQuery(UmbracoContext.Current.ContentCache, UmbracoContext.Current.MediaCache), - ApplicationContext.Current.Services.DomainService); + ApplicationContext.Current.Services.DomainService, + ApplicationContext.Current.Services.LocalizationService); if (error404.HasValue) { diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index ecb21ba5b7..23e44896f4 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -1,31 +1,30 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; -using System.Web.Hosting; using System.Xml; +using umbraco.BusinessLogic; +using umbraco.cms.businesslogic; +using umbraco.cms.businesslogic.web; +using umbraco.DataLayer; +using umbraco.presentation.nodeFactory; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.web; using Umbraco.Core.Models; using Umbraco.Core.Profiling; -using umbraco.DataLayer; -using umbraco.presentation.nodeFactory; using Umbraco.Web; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Scheduling; -using Node = umbraco.NodeFactory.Node; using File = System.IO.File; +using Node = umbraco.NodeFactory.Node; +using Task = System.Threading.Tasks.Task; namespace umbraco { @@ -36,63 +35,49 @@ namespace umbraco { private XmlCacheFilePersister _persisterTask; + private volatile bool _released; + #region Constructors private content() { if (SyncToXmlFile) { - // if we write to file, prepare the lock - // (if we don't use the file, or just read from it, no need to lock) - InitializeFileLock(); - - // and prepare the persister task - var logger = LoggerResolver.HasCurrent ? LoggerResolver.Current.Logger : new DebugDiagnosticsLogger(); - var profingLogger = new ProfilingLogger( - logger, - ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : new LogProfiler(logger)); - + var logger = LoggerResolver.HasCurrent ? LoggerResolver.Current.Logger : new DebugDiagnosticsLogger(); + var profingLogger = new ProfilingLogger( + logger, + ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : new LogProfiler(logger)); + + // prepare the persister task // there's always be one task keeping a ref to the runner // so it's safe to just create it as a local var here var runner = new BackgroundTaskRunner("XmlCacheFilePersister", new BackgroundTaskRunnerOptions { LongRunning = true, - KeepAlive = true - }, logger); - - // when the runner is terminating we need to ensure that no modifications - // to content are possible anymore, as they would not be written out to - // the xml file - unfortunately that is not possible in 7.x because we - // cannot lock the content service... and so we do nothing... - //runner.Terminating += (sender, args) => - //{ - //}; - - // when the runner has terminated we know we will not be writing to the file - // anymore, so we can release the lock now - no need to wait for the AppDomain - // unload - which means any "last minute" saves will be lost - but waiting for - // the AppDomain to unload has issues... - runner.Terminated += (sender, args) => - { - if (_fileLock == null) return; // not locking (testing?) - if (_fileLocked == null) return; // not locked - - // thread-safety - // lock something that's readonly and not null.. - lock (_xmlFileName) - { - // double-check - if (_fileLocked == null) return; - - LogHelper.Debug("Release file lock."); - _fileLocked.Dispose(); - _fileLocked = null; - _fileLock = null; // ensure we don't lock again - } - }; + KeepAlive = true, + Hosted = false // main domain will take care of stopping the runner (see below) + }, logger); // create (and add to runner) - _persisterTask = new XmlCacheFilePersister(runner, this, profingLogger); + _persisterTask = new XmlCacheFilePersister(runner, this, profingLogger); + + var registered = ApplicationContext.Current.MainDom.Register( + null, + () => + { + // once released, the cache still works but does not write to file anymore, + // which is OK with database server messenger but will cause data loss with + // another messenger... + + runner.Shutdown(false, true); // wait until flushed + _released = true; + }); + + // failed to become the main domain, we will never use the file + if (registered == false) + runner.Shutdown(false, true); + + _released = (registered == false); } // initialize content - populate the cache @@ -212,7 +197,7 @@ namespace umbraco var node = GetPreviewOrPublishedNode(d, xmlContentCopy, false); var attr = ((XmlElement)node).GetAttributeNode("sortOrder"); attr.Value = d.sortOrder.ToString(); - AddOrUpdateXmlNode(xmlContentCopy, d.Id, d.Level, parentId, node); + xmlContentCopy = GetAddOrUpdateXmlNode(xmlContentCopy, d.Id, d.Level, parentId, node); // update sitemapprovider if (updateSitemapProvider && SiteMap.Provider is UmbracoSiteMapProvider) @@ -376,7 +361,7 @@ namespace umbraco { foreach (Document d in Documents) { - PublishNodeDo(d, safeXml.Xml, true); + safeXml.Xml = PublishNodeDo(d, safeXml.Xml, true); } } @@ -398,7 +383,18 @@ namespace umbraco public virtual void ClearDocumentCache(int documentId) { // Get the document - var d = new Document(documentId); + Document d; + try + { + d = new Document(documentId); + } + catch + { + // if we need the document to remove it... this cannot be LB?! + // shortcut everything here + ClearDocumentXmlCache(documentId); + return; + } ClearDocumentCache(d); } @@ -419,26 +415,8 @@ namespace umbraco // remove from xml db cache doc.XmlRemoveFromDB(); - // We need to lock content cache here, because we cannot allow other threads - // making changes at the same time, they need to be queued - using (var safeXml = GetSafeXmlReader()) - { - // Check if node present, before cloning - x = safeXml.Xml.GetElementById(doc.Id.ToString()); - if (x == null) - return; - - safeXml.UpgradeToWriter(false); - - // Find the document in the xml cache - x = safeXml.Xml.GetElementById(doc.Id.ToString()); - if (x != null) - { - // The document already exists in cache, so repopulate it - x.ParentNode.RemoveChild(x); - safeXml.Commit(); - } - } + // clear xml cache + ClearDocumentXmlCache(doc.Id); ClearContextCache(); @@ -450,6 +428,30 @@ namespace umbraco { var prov = (UmbracoSiteMapProvider)SiteMap.Provider; prov.RemoveNode(doc.Id); + } + } + } + + internal void ClearDocumentXmlCache(int id) + { + // We need to lock content cache here, because we cannot allow other threads + // making changes at the same time, they need to be queued + using (var safeXml = GetSafeXmlReader()) + { + // Check if node present, before cloning + var x = safeXml.Xml.GetElementById(id.ToString()); + if (x == null) + return; + + safeXml.UpgradeToWriter(false); + + // Find the document in the xml cache + x = safeXml.Xml.GetElementById(id.ToString()); + if (x != null) + { + // The document already exists in cache, so repopulate it + x.ParentNode.RemoveChild(x); + safeXml.Commit(); } } } @@ -809,7 +811,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; return xmlDoc == null ? null : (XmlDocument)xmlDoc.CloneNode(true); } - private static void EnsureSchema(string contentTypeAlias, XmlDocument xml) + private static XmlDocument EnsureSchema(string contentTypeAlias, XmlDocument xml) { string subset = null; @@ -822,15 +824,22 @@ order by umbracoNode.level, umbracoNode.sortOrder"; // ensure it contains the content type if (subset != null && subset.Contains(string.Format("", contentTypeAlias))) - return; + return xml; - // remove current doctype - xml.RemoveChild(n); + // alas, that does not work, replacing a doctype is ignored and GetElementById fails + // + //// remove current doctype, set new doctype + //xml.RemoveChild(n); + //subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset); + //var doctype = xml.CreateDocumentType("root", null, null, subset); + //xml.InsertAfter(doctype, xml.FirstChild); - // set new doctype + var xml2 = new XmlDocument(); subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset); - var doctype = xml.CreateDocumentType("root", null, null, subset); - xml.InsertAfter(doctype, xml.FirstChild); + var doctype = xml2.CreateDocumentType("root", null, null, subset); + xml2.AppendChild(doctype); + xml2.AppendChild(xml2.ImportNode(xml.DocumentElement, true)); + return xml2; } private static void InitializeXml(XmlDocument xml, string dtd) @@ -985,100 +994,6 @@ order by umbracoNode.level, umbracoNode.sortOrder"; private readonly string _xmlFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml); private DateTime _lastFileRead; // last time the file was read private DateTime _nextFileCheck; // last time we checked whether the file was changed - private AsyncLock _fileLock; // protects the file - private IDisposable _fileLocked; // protects the file - - private const int FileLockTimeoutMilliseconds = 4 * 60 * 1000; // 4' - - private void InitializeFileLock() - { - // initialize file lock - // ApplicationId will look like "/LM/W3SVC/1/Root/AppName" - // name is system-wide and must be less than 260 chars - // - // From MSDN C++ CreateSemaphore doc: - // "The name can have a "Global\" or "Local\" prefix to explicitly create the object in - // the global or session namespace. The remainder of the name can contain any character - // except the backslash character (\). For more information, see Kernel Object Namespaces." - // - // From MSDN "Kernel object namespaces" doc: - // "The separate client session namespaces enable multiple clients to run the same - // applications without interfering with each other. For processes started under - // a client session, the system uses the session namespace by default. However, these - // processes can use the global namespace by prepending the "Global\" prefix to the object name." - // - // just use "default" (whatever it is) for now - ie, no prefix - // - var name = HostingEnvironment.ApplicationID + "/XmlStore/XmlFile"; - _fileLock = new AsyncLock(name); - - // the file lock works with a shared, system-wide, semaphore - and we don't want - // to leak a count on that semaphore else the whole process will hang - so we have - // to ensure we dispose of the locker when the domain goes down - in theory the - // async lock should do it via its finalizer, but then there are some weird cases - // where the semaphore has been disposed of before it's been released, and then - // we'd need to GC-pin the semaphore... better dispose the locker explicitely - // when the app domain unloads. - - if (AppDomain.CurrentDomain.IsDefaultAppDomain()) - { - LogHelper.Debug("Registering Unload handler for default app domain."); - AppDomain.CurrentDomain.ProcessExit += OnDomainUnloadReleaseFileLock; - } - else - { - LogHelper.Debug("Registering Unload handler for non-default app domain."); - AppDomain.CurrentDomain.DomainUnload += OnDomainUnloadReleaseFileLock; - } - } - - private void EnsureFileLock() - { - if (_fileLock == null) return; // not locking (testing?) - if (_fileLocked != null) return; // locked already - - // thread-safety, acquire lock only once! - // lock something that's readonly and not null.. - lock (_xmlFileName) - { - // double-check - if (_fileLock == null) return; - if (_fileLocked != null) return; - - // don't hang forever, throws if it cannot lock within the timeout - LogHelper.Debug("Acquiring exclusive access to file for this AppDomain..."); - _fileLocked = _fileLock.Lock(FileLockTimeoutMilliseconds); - LogHelper.Debug("Acquired exclusive access to file for this AppDomain."); - } - } - - private void OnDomainUnloadReleaseFileLock(object sender, EventArgs args) - { - // the unload event triggers AFTER all hosted objects (eg the file persister - // background task runner) have been stopped, so we should have released the - // lock already - this is for safety - might be possible to get rid of it - - // NOTE - // trying to write to the log via LogHelper at that point is a BAD idea - // it can lead to ugly deadlocks with the named semaphore - DONT do it - - if (_fileLock == null) return; // not locking (testing?) - if (_fileLocked == null) return; // not locked - - // thread-safety - // lock something that's readonly and not null.. - lock (_xmlFileName) - { - // double-check - if (_fileLocked == null) return; - - // in case you really need to debug... that should be safe... - //System.IO.File.AppendAllText(HostingEnvironment.MapPath("~/App_Data/log.txt"), string.Format("{0} {1} unlock", DateTime.Now, AppDomain.CurrentDomain.Id)); - _fileLocked.Dispose(); - - _fileLock = null; // ensure we don't lock again - } - } // not used - just try to read the file //private bool XmlFileExists @@ -1100,7 +1015,9 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } } - // assumes file lock + // invoked by XmlCacheFilePersister ONLY and that one manages the MainDom, ie it + // will NOT try to save once the current app domain is not the main domain anymore + // (no need to test _released) internal void SaveXmlToFile() { LogHelper.Info("Save Xml to file..."); @@ -1110,8 +1027,6 @@ order by umbracoNode.level, umbracoNode.sortOrder"; var xml = _xmlContent; // capture (atomic + volatile), immutable anyway if (xml == null) return; - EnsureFileLock(); - // delete existing file, if any DeleteXmlFile(); @@ -1119,7 +1034,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; var directoryName = Path.GetDirectoryName(_xmlFileName); if (directoryName == null) throw new Exception(string.Format("Invalid XmlFileName \"{0}\".", _xmlFileName)); - if (System.IO.File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) + if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) Directory.CreateDirectory(directoryName); // save @@ -1140,8 +1055,10 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } } - // assumes file lock - internal async System.Threading.Tasks.Task SaveXmlToFileAsync() + // invoked by XmlCacheFilePersister ONLY and that one manages the MainDom, ie it + // will NOT try to save once the current app domain is not the main domain anymore + // (no need to test _released) + internal async Task SaveXmlToFileAsync() { LogHelper.Info("Save Xml to file..."); @@ -1150,8 +1067,6 @@ order by umbracoNode.level, umbracoNode.sortOrder"; var xml = _xmlContent; // capture (atomic + volatile), immutable anyway if (xml == null) return; - EnsureFileLock(); - // delete existing file, if any DeleteXmlFile(); @@ -1159,7 +1074,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; var directoryName = Path.GetDirectoryName(_xmlFileName); if (directoryName == null) throw new Exception(string.Format("Invalid XmlFileName \"{0}\".", _xmlFileName)); - if (System.IO.File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) + if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) Directory.CreateDirectory(directoryName); // save @@ -1208,17 +1123,15 @@ order by umbracoNode.level, umbracoNode.sortOrder"; return sb.ToString(); } - // assumes file lock private XmlDocument LoadXmlFromFile() { + // do NOT try to load if we are not the main domain anymore + if (_released) return null; + LogHelper.Info("Load Xml from file..."); try { - // if we're not writing back to the file, no need to lock - if (SyncToXmlFile) - EnsureFileLock(); - var xml = new XmlDocument(); using (var fs = new FileStream(_xmlFileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { @@ -1241,12 +1154,11 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } } - // (files is always locked) private void DeleteXmlFile() { - if (System.IO.File.Exists(_xmlFileName) == false) return; - System.IO.File.SetAttributes(_xmlFileName, FileAttributes.Normal); - System.IO.File.Delete(_xmlFileName); + if (File.Exists(_xmlFileName) == false) return; + File.SetAttributes(_xmlFileName, FileAttributes.Normal); + File.Delete(_xmlFileName); } private void ReloadXmlFromFileIfChanged() @@ -1276,8 +1188,15 @@ order by umbracoNode.level, umbracoNode.sortOrder"; #region Manage change - // adds or updates a node (docNode) into a cache (xml) + //TODO remove as soon as we can break backward compatibility + [Obsolete("Use GetAddOrUpdateXmlNode which returns an updated Xml document.", false)] public static void AddOrUpdateXmlNode(XmlDocument xml, int id, int level, int parentId, XmlNode docNode) + { + GetAddOrUpdateXmlNode(xml, id, level, parentId, docNode); + } + + // adds or updates a node (docNode) into a cache (xml) + public static XmlDocument GetAddOrUpdateXmlNode(XmlDocument xml, int id, int level, int parentId, XmlNode docNode) { // sanity checks if (id != docNode.AttributeValue("id")) @@ -1291,7 +1210,12 @@ order by umbracoNode.level, umbracoNode.sortOrder"; // if the document is not there already then it's a new document // we must make sure that its document type exists in the schema if (currentNode == null && UseLegacySchema == false) - EnsureSchema(docNode.Name, xml); + { + var xml2 = EnsureSchema(docNode.Name, xml); + if (ReferenceEquals(xml, xml2) == false) + docNode = xml2.ImportNode(docNode, true); + xml = xml2; + } // find the parent XmlNode parentNode = level == 1 @@ -1300,7 +1224,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; // no parent = cannot do anything if (parentNode == null) - return; + return xml; // insert/move the node under the parent if (currentNode == null) @@ -1368,6 +1292,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; // then we just need to ensure that currentNode is at the right position. // should be faster that moving all the nodes around. XmlHelper.SortNode(parentNode, ChildNodesXPath, currentNode, x => x.AttributeValue("sortOrder")); + return xml; } private static void TransferValuesFromDocumentXmlToPublishedXml(XmlNode documentNode, XmlNode publishedNode) diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index 136c30c799..53bdaaa9de 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -1578,7 +1578,7 @@ namespace umbraco //Trace out to profiling... doesn't actually profile, just for informational output. if (excludeProfiling == false) { - using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}", message))) + using (ApplicationContext.Current.ProfilingLogger.TraceDuration(string.Format("{0}", message))) { } } @@ -1592,7 +1592,7 @@ namespace umbraco //Trace out to profiling... doesn't actually profile, just for informational output. if (excludeProfiling == false) { - using (ProfilerResolver.Current.Profiler.Step(string.Format("Warning: {0}", message))) + using (ApplicationContext.Current.ProfilingLogger.TraceDuration(string.Format("Warning: {0}", message))) { } } @@ -1606,7 +1606,7 @@ namespace umbraco //Trace out to profiling... doesn't actually profile, just for informational output. if (excludeProfiling == false) { - using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}, Error: {1}", message, ex))) + using (ApplicationContext.Current.ProfilingLogger.TraceDuration(string.Format("{0}, Error: {1}", message, ex))) { } } diff --git a/src/Umbraco.Web/umbraco.presentation/template.cs b/src/Umbraco.Web/umbraco.presentation/template.cs index cf1186867c..6c1e23a13e 100644 --- a/src/Umbraco.Web/umbraco.presentation/template.cs +++ b/src/Umbraco.Web/umbraco.presentation/template.cs @@ -497,8 +497,12 @@ namespace umbraco var t = ApplicationContext.Current.ApplicationCache.GetCacheItem( string.Format("{0}{1}", CacheKeys.TemplateFrontEndCacheKey, tId), () => { - using (var templateData = SqlHelper.ExecuteReader("select nodeId, alias, master, text, design from cmsTemplate inner join umbracoNode node on node.id = cmsTemplate.nodeId where nodeId = @templateID", SqlHelper.CreateParameter("@templateID", templateID))) - { + using (var templateData = SqlHelper.ExecuteReader(@"select nodeId, alias, node.parentID as master, text, design +from cmsTemplate +inner join umbracoNode node on (node.id = cmsTemplate.nodeId) +where nodeId = @templateID", + SqlHelper.CreateParameter("@templateID", templateID))) + { if (templateData.Read()) { // Get template master and replace content where the template diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs index 2349d8943f..aed6009d86 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs @@ -16,18 +16,18 @@ namespace umbraco.cms.presentation.Trees { public FileSystemTree(string application) : base(application) { } - + public override abstract void RenderJS(ref System.Text.StringBuilder Javascript); protected override abstract void CreateRootNode(ref XmlTreeNode rootNode); - protected abstract string FilePath { get;} - protected abstract string FileSearchPattern { get;} + protected abstract string FilePath { get; } + protected abstract string FileSearchPattern { get; } /// /// Inheritors can override this method to modify the file node that is created. /// /// - protected virtual void OnRenderFileNode(ref XmlTreeNode xNode) { } + protected virtual void OnRenderFileNode(ref XmlTreeNode xNode) { } /// /// Inheritors can override this method to modify the folder node that is created. @@ -51,7 +51,9 @@ namespace umbraco.cms.presentation.Trees } DirectoryInfo dirInfo = new DirectoryInfo(path); - DirectoryInfo[] dirInfos = dirInfo.GetDirectories(); + + + DirectoryInfo[] dirInfos = dirInfo.Exists ? dirInfo.GetDirectories() : new DirectoryInfo[] { }; var args = new TreeEventArgs(tree); OnBeforeTreeRender(dirInfo, args); @@ -61,15 +63,15 @@ namespace umbraco.cms.presentation.Trees if ((dir.Attributes & FileAttributes.Hidden) == 0) { XmlTreeNode xDirNode = XmlTreeNode.Create(this); - xDirNode.NodeID = orgPath + dir.Name; + xDirNode.NodeID = orgPath + dir.Name; xDirNode.Menu.Clear(); xDirNode.Text = dir.Name; xDirNode.Action = string.Empty; xDirNode.Source = GetTreeServiceUrl(orgPath + dir.Name); xDirNode.Icon = FolderIcon; xDirNode.OpenIcon = FolderIconOpen; - xDirNode.HasChildren = dir.GetFiles().Length > 0 || dir.GetDirectories().Length > 0; - + xDirNode.HasChildren = dir.GetFiles().Length > 0 || dir.GetDirectories().Length > 0; + OnRenderFolderNode(ref xDirNode); OnBeforeNodeRender(ref tree, ref xDirNode, EventArgs.Empty); if (xDirNode != null) @@ -77,8 +79,8 @@ namespace umbraco.cms.presentation.Trees tree.Add(xDirNode); OnAfterNodeRender(ref tree, ref xDirNode, EventArgs.Empty); } - - + + } } @@ -88,24 +90,28 @@ namespace umbraco.cms.presentation.Trees bool filterByMultipleExtensions = FileSearchPattern.Contains(","); FileInfo[] fileInfo; - if (filterByMultipleExtensions){ - fileInfo = dirInfo.GetFiles(); + if (filterByMultipleExtensions) + { + fileInfo = dirInfo.Exists ? dirInfo.GetFiles() : new FileInfo[] {}; allowedExtensions = FileSearchPattern.ToLower().Split(','); - }else - fileInfo = dirInfo.GetFiles(FileSearchPattern); - + } + else + { + fileInfo = dirInfo.Exists ? dirInfo.GetFiles(FileSearchPattern) : new FileInfo[] { }; + } + foreach (FileInfo file in fileInfo) { if ((file.Attributes & FileAttributes.Hidden) == 0) { if (filterByMultipleExtensions && Array.IndexOf(allowedExtensions, file.Extension.ToLower().Trim('.')) < 0) continue; - + XmlTreeNode xFileNode = XmlTreeNode.Create(this); - xFileNode.NodeID = orgPath + file.Name; + xFileNode.NodeID = orgPath + file.Name; xFileNode.Text = file.Name; if (!((orgPath == ""))) - xFileNode.Action = "javascript:openFile('" + orgPath + file.Name + "');"; + xFileNode.Action = "javascript:openFile('" + orgPath + file.Name + "');"; else xFileNode.Action = "javascript:openFile('" + file.Name + "');"; xFileNode.Icon = "doc.gif"; @@ -118,11 +124,11 @@ namespace umbraco.cms.presentation.Trees tree.Add(xFileNode); OnAfterNodeRender(ref tree, ref xFileNode, EventArgs.Empty); } - + } } OnAfterTreeRender(dirInfo, args); - } + } } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs index 0b8644aa43..bf7c04bb27 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs @@ -87,7 +87,7 @@ namespace umbraco private void RenderTemplateFolderItems(string folder, string folderPath, ref XmlTree tree) { string relPath = SystemDirectories.Masterpages + "/" + folder; - if (!string.IsNullOrEmpty(folderPath)) + if (string.IsNullOrEmpty(folderPath) == false) relPath += folderPath; string fullPath = IOHelper.MapPath(relPath); @@ -160,26 +160,31 @@ namespace umbraco { if (base.m_id == -1) { - foreach (string s in Directory.GetDirectories(IOHelper.MapPath(SystemDirectories.Masterpages))) + var dir = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Masterpages)); + if (dir.Exists) { - var _s = Path.GetFileNameWithoutExtension(s); - - XmlTreeNode xNode = XmlTreeNode.Create(this); - xNode.NodeID = _s; - xNode.Text = _s; - xNode.Icon = "icon-folder"; - xNode.OpenIcon = "icon-folder"; - xNode.Source = GetTreeServiceUrl(_s) + "&folder=" + _s; - xNode.HasChildren = true; - xNode.Menu.Clear(); - xNode.Menu.Add(ActionRefresh.Instance); - - OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); - if (xNode != null) + foreach (var s in dir.GetDirectories()) { - tree.Add(xNode); - OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); + var _s = Path.GetFileNameWithoutExtension(s.FullName); + + XmlTreeNode xNode = XmlTreeNode.Create(this); + xNode.NodeID = _s; + xNode.Text = _s; + xNode.Icon = "icon-folder"; + xNode.OpenIcon = "icon-folder"; + xNode.Source = GetTreeServiceUrl(_s) + "&folder=" + _s; + xNode.HasChildren = true; + xNode.Menu.Clear(); + xNode.Menu.Add(ActionRefresh.Instance); + + OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); + if (xNode != null) + { + tree.Add(xNode); + OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); + } } + } } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs index 7a9b777c83..6c6174c0bb 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs @@ -1,5 +1,6 @@ using System; using System.Data; +using System.IO; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.IO; @@ -9,6 +10,7 @@ using umbraco.BusinessLogic; using umbraco.DataLayer; using umbraco.BasePages; using umbraco.cms.businesslogic.member; +using Umbraco.Core.FileResources; namespace umbraco { @@ -22,14 +24,17 @@ namespace umbraco public override bool PerformSave() { + IOHelper.EnsurePathExists(SystemDirectories.Xslt); + IOHelper.EnsureFileExists(Path.Combine(IOHelper.MapPath(SystemDirectories.Xslt), "web.config"), Files.BlockingWebConfig); + var template = Alias.Substring(0, Alias.IndexOf("|||")); var fileName = Alias.Substring(Alias.IndexOf("|||") + 3, Alias.Length - Alias.IndexOf("|||") - 3).Replace(" ", ""); - if (!fileName.ToLowerInvariant().EndsWith(".xslt")) + if (fileName.ToLowerInvariant().EndsWith(".xslt") == false) fileName += ".xslt"; var xsltTemplateSource = IOHelper.MapPath(SystemDirectories.Umbraco + "/xslt/templates/" + template); var xsltNewFilename = IOHelper.MapPath(SystemDirectories.Xslt + "/" + fileName); - if (!System.IO.File.Exists(xsltNewFilename)) + if (File.Exists(xsltNewFilename) == false) { if (fileName.Contains("/")) //if there's a / create the folder structure for it { 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 9dde8ec59f..f8d15004da 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 @@ -130,6 +130,8 @@ namespace umbraco.cms.presentation.developer { var dirInfo = new DirectoryInfo(path); + if (dirInfo.Exists == false) return; + // Populate subdirectories var dirInfos = dirInfo.GetDirectories(); foreach (var dir in dirInfos) @@ -269,6 +271,7 @@ namespace umbraco.cms.presentation.developer private void PopulateUserControls(string path) { var directoryInfo = new DirectoryInfo(path); + if (directoryInfo.Exists == false) return; var rootDir = IOHelper.MapPath(SystemDirectories.UserControls); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs index 136cc1a27d..4f71e20d08 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs @@ -54,12 +54,12 @@ namespace umbraco.dialogs sb.AppendFormat("{0}{{ \"Id\": {1}, \"Code\": \"{2}\" }}", (i++ == 0 ? "" : ","), language.Id, language.IsoCode); sb.Append("]\r\n"); - sb.AppendFormat(",language: {0}", wildcard == null ? "undefined" : wildcard.Language.Id.ToString()); + sb.AppendFormat(",language: {0}", wildcard == null ? "undefined" : wildcard.LanguageId.ToString()); sb.Append(",domains: ["); i = 0; foreach (var domain in nodeDomains.Where(d => d.IsWildcard == false)) - sb.AppendFormat("{0}{{ \"Name\": \"{1}\", \"Lang\": \"{2}\" }}", (i++ == 0 ? "" :","), domain.DomainName, domain.Language.Id); + sb.AppendFormat("{0}{{ \"Name\": \"{1}\", \"Lang\": \"{2}\" }}", (i++ == 0 ? "" :","), domain.DomainName, domain.LanguageId); sb.Append("]\r\n"); data.Text = sb.ToString(); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs index 2b104404b4..547d2c2ac7 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs @@ -23,6 +23,12 @@ namespace umbraco.cms.presentation { private readonly List _nodes = new List(); + protected bool HideDateColumn + { + set { ViewState["HideDateColumn"] = value; } + get { return ViewState["HideDateColumn"] == null ? false : (bool) ViewState["HideDateColumn"]; } + } + protected override void OnInit(EventArgs e) { CurrentApp = helper.Request("app"); @@ -56,13 +62,13 @@ namespace umbraco.cms.presentation if (parentId == -1) { foreach (var child in mediaService.GetRootMedia().ToList().OrderBy(x => x.SortOrder)) - _nodes.Add(CreateNode(child.Id, child.SortOrder, child.Name, child.CreateDate, icon)); + _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } else { var children = mediaService.GetChildren(parentId); foreach (var child in children.OrderBy(x => x.SortOrder)) - _nodes.Add(CreateNode(child.Id, child.SortOrder, child.Name, child.CreateDate, icon)); + _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } } @@ -73,29 +79,41 @@ namespace umbraco.cms.presentation if (parentId == -1) { foreach (var child in contentService.GetRootContent().ToList().OrderBy(x => x.SortOrder)) - _nodes.Add(CreateNode(child.Id, child.SortOrder, child.Name, child.CreateDate, icon)); + _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } else { var children = contentService.GetChildren(parentId); foreach (var child in children) - _nodes.Add(CreateNode(child.Id, child.SortOrder, child.Name, child.CreateDate, icon)); + _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } } - + + bindNodesToList(string.Empty); + } + else + { // hack for stylesheet, used to sort stylesheet properties if (app == Constants.Applications.Settings) { icon = "../images/umbraco/settingCss.gif"; - var ss = new StyleSheet(parentId); - foreach (var child in ss.Properties) - { - var node = new CMSNode(child.Id); - _nodes.Add(CreateNode(child.Id, node.sortOrder, child.Text, child.CreateDateTime, icon)); - } - } - bindNodesToList(string.Empty); + HideDateColumn = true; + + var stylesheetName = Request.GetItemAsString("ID"); + if (stylesheetName.IsNullOrWhiteSpace())throw new NullReferenceException("No Id passed in to editor"); + var stylesheet = Services.FileService.GetStylesheetByName(stylesheetName.EnsureEndsWith(".css")); + if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + stylesheetName); + + var sort = 0; + foreach (var child in stylesheet.Properties) + { + _nodes.Add(CreateNode(child.Name, sort, child.Name, DateTime.Now, icon)); + sort++; + } + + bindNodesToList(string.Empty); + } } } @@ -115,10 +133,12 @@ namespace umbraco.cms.presentation } foreach (var n in _nodes) - lt_nodes.Text += string.Format("", n.id, n.Name, n.createDate.ToShortDateString(), n.createDate.ToShortTimeString(), n.sortOder); + lt_nodes.Text += string.Format( + "", + n.id, n.Name, n.createDate.ToShortDateString(), n.createDate.ToShortTimeString(), n.sortOder, HideDateColumn ? "none" : "block"); } - private static SortableNode CreateNode(int id, int sortOrder, string name, DateTime createDateTime, string icon) + private static SortableNode CreateNode(string id, int sortOrder, string name, DateTime createDateTime, string icon) { var node = new SortableNode { @@ -133,7 +153,7 @@ namespace umbraco.cms.presentation public struct SortableNode { - public int id; + public string id; public int sortOder; public string Name; public string icon; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs index 8e6e952abe..5a92470a82 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs @@ -102,7 +102,7 @@ namespace umbraco.presentation.preview if (document.ContentEntity.Published == false && ApplicationContext.Current.Services.ContentService.HasPublishedVersion(document.Id)) previewXml.Attributes.Append(XmlContent.CreateAttribute("isDraft")); - content.AddOrUpdateXmlNode(XmlContent, document.Id, document.Level, parentId, previewXml); + XmlContent = content.GetAddOrUpdateXmlNode(XmlContent, document.Id, document.Level, parentId, previewXml); } if (includeSubs) @@ -112,7 +112,7 @@ namespace umbraco.presentation.preview var previewXml = XmlContent.ReadNode(XmlReader.Create(new StringReader(prevNode.Xml))); if (prevNode.IsDraft) previewXml.Attributes.Append(XmlContent.CreateAttribute("isDraft")); - content.AddOrUpdateXmlNode(XmlContent, prevNode.NodeId, prevNode.Level, prevNode.ParentId, previewXml); + XmlContent = content.GetAddOrUpdateXmlNode(XmlContent, prevNode.NodeId, prevNode.Level, prevNode.ParentId, previewXml); } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs index 75471cd676..f058b31bb7 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs @@ -45,7 +45,7 @@ namespace umbraco.cms.presentation.settings.stylesheet var propName = IsPostBack ? OriginalName.Value : Request.QueryString["prop"]; - _stylesheetproperty = _sheet.Properties.FirstOrDefault(x => x.Name == propName); + _stylesheetproperty = _sheet.Properties.FirstOrDefault(x => x.Name.InvariantEquals(propName)); if (_stylesheetproperty == null) throw new InvalidOperationException("No stylesheet property found with name: " + Request.QueryString["prop"]); Panel1.Text = ui.Text("stylesheet", "editstylesheetproperty", UmbracoUser); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs index 08217ad106..65fbf2537b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs @@ -62,6 +62,7 @@ namespace umbraco.presentation.webservices { if (AuthorizeRequest(DefaultApps.developer.ToString())) { + IOHelper.EnsurePathExists(SystemDirectories.Xslt); // validate file IOHelper.ValidateEditPath(IOHelper.MapPath(SystemDirectories.Xslt + "/" + fileName), @@ -69,8 +70,7 @@ namespace umbraco.presentation.webservices // validate extension IOHelper.ValidateFileExtension(IOHelper.MapPath(SystemDirectories.Xslt + "/" + fileName), new List() { "xsl", "xslt" }); - - + StreamWriter SW; string tempFileName = IOHelper.MapPath(SystemDirectories.Xslt + "/" + DateTime.Now.Ticks + "_temp.xslt"); SW = File.CreateText(tempFileName); @@ -392,7 +392,10 @@ namespace umbraco.presentation.webservices if (File.Exists(saveOldPath)) File.Delete(saveOldPath); } - + + //ensure the folder exists before saving + Directory.CreateDirectory(Path.GetDirectoryName(savePath)); + using (var sw = File.CreateText(savePath)) { sw.Write(val); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs index 3ec59bd378..c3382138fa 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs @@ -276,7 +276,9 @@ namespace umbraco.presentation.webservices } private string SaveXslt(string fileName, string fileContents, bool ignoreDebugging) - { + { + IOHelper.EnsurePathExists(SystemDirectories.Xslt); + var tempFileName = IOHelper.MapPath(SystemDirectories.Xslt + "/" + System.DateTime.Now.Ticks + "_temp.xslt"); using (var sw = File.CreateText(tempFileName)) { @@ -412,6 +414,9 @@ namespace umbraco.presentation.webservices //Directory check.. only allow files in script dir and below to be edited if (savePath.StartsWith(IOHelper.MapPath(SystemDirectories.Scripts + "/"))) { + //ensure the folder exists before saving + Directory.CreateDirectory(Path.GetDirectoryName(savePath)); + using (var sw = File.CreateText(IOHelper.MapPath(SystemDirectories.Scripts + "/" + filename))) { sw.Write(val); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs index 2177fedeb1..1ab2346054 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs @@ -31,54 +31,75 @@ namespace umbraco.presentation.webservices public class nodeSorter : UmbracoAuthorizedWebService { [WebMethod] - public SortNode GetNodes(int ParentId, string App) + public SortNode GetNodes(string ParentId, string App) { if (BasePage.ValidateUserContextID(BasePage.umbracoUserContextID)) { - var parent = new SortNode { Id = ParentId }; var nodes = new List(); - var entityService = base.ApplicationContext.Services.EntityService; - // Root nodes? - if (ParentId == -1) + // "hack for stylesheet" + if (App == "settings") { - if (App == "media") + var stylesheet = Services.FileService.GetStylesheetByName(ParentId.EnsureEndsWith(".css")); + if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + ParentId); + + var sort = 0; + foreach (var child in stylesheet.Properties) { - var rootMedia = entityService.GetRootEntities(UmbracoObjectTypes.Media); - nodes.AddRange(rootMedia.Select(media => new SortNode(media.Id, media.SortOrder, media.Name, media.CreateDate))); + nodes.Add(new SortNode(child.Name.GetHashCode(), sort, child.Name, DateTime.Now)); + sort++; } - else + + return new SortNode() { - var rootContent = entityService.GetRootEntities(UmbracoObjectTypes.Document); - nodes.AddRange(rootContent.Select(content => new SortNode(content.Id, content.SortOrder, content.Name, content.CreateDate))); - } + SortNodes = nodes.ToArray() + }; } else { - // "hack for stylesheet" - if (App == "settings") + var asInt = int.Parse(ParentId); + + var parent = new SortNode { Id = asInt }; + + var entityService = base.ApplicationContext.Services.EntityService; + + // Root nodes? + if (asInt == -1) { - var cmsNode = new cms.businesslogic.CMSNode(ParentId); - var styleSheet = new StyleSheet(cmsNode.Id); - nodes.AddRange(styleSheet.Properties.Select(child => new SortNode(child.Id, child.sortOrder, child.Text, child.CreateDateTime))); + if (App == "media") + { + var rootMedia = entityService.GetRootEntities(UmbracoObjectTypes.Media); + nodes.AddRange(rootMedia.Select(media => new SortNode(media.Id, media.SortOrder, media.Name, media.CreateDate))); + } + else + { + var rootContent = entityService.GetRootEntities(UmbracoObjectTypes.Document); + nodes.AddRange(rootContent.Select(content => new SortNode(content.Id, content.SortOrder, content.Name, content.CreateDate))); + } } else { - var children = entityService.GetChildren(ParentId); + var children = entityService.GetChildren(asInt); nodes.AddRange(children.Select(child => new SortNode(child.Id, child.SortOrder, child.Name, child.CreateDate))); } + + + parent.SortNodes = nodes.ToArray(); + + return parent; } - - parent.SortNodes = nodes.ToArray(); - - return parent; } throw new ArgumentException("User not logged in"); } - [WebMethod] public void UpdateSortOrder(int ParentId, string SortOrder) + { + UpdateSortOrder(ParentId.ToString(), SortOrder); + } + + [WebMethod] + public void UpdateSortOrder(string ParentId, string SortOrder) { if (AuthorizeRequest() == false) return; if (SortOrder.Trim().Length <= 0) return; @@ -92,13 +113,17 @@ namespace umbraco.presentation.webservices var ids = SortOrder.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); if (isContent) - SortContent(ids, ParentId); - - if (isMedia) + { + SortContent(ids, int.Parse(ParentId)); + } + else if (isMedia) + { SortMedia(ids); - - if (isContent == false && isMedia == false) - SortStylesheetProperties(ids); + } + else + { + SortStylesheetProperties(ParentId, ids); + } } private void SortMedia(string[] ids) @@ -123,20 +148,27 @@ namespace umbraco.presentation.webservices } } - private void SortStylesheetProperties(string[] ids) + + private void SortStylesheetProperties(string stylesheetName, string[] names) { - try + var stylesheet = Services.FileService.GetStylesheetByName(stylesheetName.EnsureEndsWith(".css")); + if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + stylesheetName); + + var currProps = stylesheet.Properties.ToArray(); + //remove them all first + foreach (var prop in currProps) { - for (var i = 0; i < ids.Length; i++) - { - var id = int.Parse(ids[i]); - new cms.businesslogic.CMSNode(id).sortOrder = i; - } + stylesheet.RemoveProperty(prop.Name); } - catch (Exception ex) + + //re-add them in the right order + for (var i = 0; i < names.Length; i++) { - LogHelper.Error("Could not update stylesheet property sort order", ex); + var found = currProps.Single(x => x.Name == names[i]); + stylesheet.AddProperty(found); } + + Services.FileService.SaveStylesheet(stylesheet); } private void SortContent(string[] ids, int parentId) @@ -168,7 +200,7 @@ namespace umbraco.presentation.webservices LogHelper.Error("Could not update content sort order", ex); } } - + } [Serializable] diff --git a/src/UmbracoExamine/Config/IndexSetExtensions.cs b/src/UmbracoExamine/Config/IndexSetExtensions.cs index ac432b16ec..1255f50a3c 100644 --- a/src/UmbracoExamine/Config/IndexSetExtensions.cs +++ b/src/UmbracoExamine/Config/IndexSetExtensions.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; using Examine; @@ -17,60 +16,7 @@ namespace UmbracoExamine.Config internal static IIndexCriteria ToIndexCriteria(this IndexSet set, IDataService svc, IEnumerable indexFieldPolicies) { - - var attributeFields = set.IndexAttributeFields.Cast().ToArray(); - var userFields = set.IndexUserFields.Cast().ToArray(); - var includeNodeTypes = set.IncludeNodeTypes.ToList().Select(x => x.Name).ToArray(); - var excludeNodeTypes = set.ExcludeNodeTypes.ToList().Select(x => x.Name).ToArray(); - var parentId = set.IndexParentId; - - //if there are no user fields defined, we'll populate them from the data source (include them all) - if (set.IndexUserFields.Count == 0) - { - //we need to add all user fields to the collection if it is empty (this is the default if none are specified) - var userProps = svc.ContentService.GetAllUserPropertyNames(); - var fields = new List(); - foreach (var u in userProps) - { - var field = new IndexField() { Name = u }; - var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == u); - if (policy != null) - { - field.Type = policy.Type; - field.EnableSorting = policy.EnableSorting; - } - fields.Add(field); - } - userFields = fields.ToArray(); - } - - //if there are no attribute fields defined, we'll populate them from the data source (include them all) - if (set.IndexAttributeFields.Count == 0) - { - //we need to add all system fields to the collection if it is empty (this is the default if none are specified) - var sysProps = svc.ContentService.GetAllSystemPropertyNames(); - var fields = new List(); - foreach (var s in sysProps) - { - var field = new IndexField() { Name = s }; - var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == s); - if (policy != null) - { - field.Type = policy.Type; - field.EnableSorting = policy.EnableSorting; - } - fields.Add(field); - } - attributeFields = fields.ToArray(); - } - - - return new IndexCriteria( - attributeFields, - userFields, - includeNodeTypes, - excludeNodeTypes, - parentId); + return new LazyIndexCriteria(set, svc, indexFieldPolicies); } /// diff --git a/src/UmbracoExamine/Config/LazyIndexCriteria.cs b/src/UmbracoExamine/Config/LazyIndexCriteria.cs new file mode 100644 index 0000000000..72ab3f31ba --- /dev/null +++ b/src/UmbracoExamine/Config/LazyIndexCriteria.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Examine; +using Examine.LuceneEngine.Config; +using UmbracoExamine.DataServices; + +namespace UmbracoExamine.Config +{ + internal class LazyIndexCriteria : IIndexCriteria + { + public LazyIndexCriteria( + IndexSet set, + IDataService svc, + IEnumerable indexFieldPolicies) + { + if (set == null) throw new ArgumentNullException("set"); + if (indexFieldPolicies == null) throw new ArgumentNullException("indexFieldPolicies"); + if (svc == null) throw new ArgumentNullException("svc"); + + _lazyCriteria = new Lazy(() => + { + var attributeFields = set.IndexAttributeFields.Cast().ToArray(); + var userFields = set.IndexUserFields.Cast().ToArray(); + var includeNodeTypes = set.IncludeNodeTypes.Cast().Select(x => x.Name).ToArray(); + var excludeNodeTypes = set.ExcludeNodeTypes.Cast().Select(x => x.Name).ToArray(); + var parentId = set.IndexParentId; + + //if there are no user fields defined, we'll populate them from the data source (include them all) + if (set.IndexUserFields.Count == 0) + { + //we need to add all user fields to the collection if it is empty (this is the default if none are specified) + var userProps = svc.ContentService.GetAllUserPropertyNames(); + var fields = new List(); + foreach (var u in userProps) + { + var field = new IndexField() { Name = u }; + var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == u); + if (policy != null) + { + field.Type = policy.Type; + field.EnableSorting = policy.EnableSorting; + } + fields.Add(field); + } + userFields = fields.ToArray(); + } + + //if there are no attribute fields defined, we'll populate them from the data source (include them all) + if (set.IndexAttributeFields.Count == 0) + { + //we need to add all system fields to the collection if it is empty (this is the default if none are specified) + var sysProps = svc.ContentService.GetAllSystemPropertyNames(); + var fields = new List(); + foreach (var s in sysProps) + { + var field = new IndexField() { Name = s }; + var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == s); + if (policy != null) + { + field.Type = policy.Type; + field.EnableSorting = policy.EnableSorting; + } + fields.Add(field); + } + attributeFields = fields.ToArray(); + } + + + return new IndexCriteria( + attributeFields, + userFields, + includeNodeTypes, + excludeNodeTypes, + parentId); + }); + } + + private readonly Lazy _lazyCriteria; + + public IEnumerable ExcludeNodeTypes + { + get { return _lazyCriteria.Value.ExcludeNodeTypes; } + } + + public IEnumerable IncludeNodeTypes + { + get { return _lazyCriteria.Value.IncludeNodeTypes; } + } + + public int? ParentNodeId + { + get { return _lazyCriteria.Value.ParentNodeId; } + } + + public IEnumerable StandardFields + { + get { return _lazyCriteria.Value.StandardFields; } + } + + public IEnumerable UserFields + { + get { return _lazyCriteria.Value.UserFields; } + } + } +} \ No newline at end of file diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index 3813e650e8..de76ab8e79 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -348,7 +348,7 @@ namespace UmbracoExamine protected override void PerformIndexAll(string type) { - const int pageSize = 5000; + const int pageSize = 1000; var pageIndex = 0; switch (type) @@ -370,7 +370,7 @@ namespace UmbracoExamine do { - int total; + long total; var descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total); //if specific types are declared we need to post filter them @@ -386,6 +386,8 @@ namespace UmbracoExamine AddNodesToIndex(GetSerializedContent(content), type); pageIndex++; + + } while (content.Length == pageSize); } @@ -401,7 +403,7 @@ namespace UmbracoExamine do { - int total; + long total; var descendants = _mediaService.GetPagedDescendants(mediaParentId, pageIndex, pageSize, out total); //if specific types are declared we need to post filter them diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj index b7fc35542a..30d8039dcf 100644 --- a/src/UmbracoExamine/UmbracoExamine.csproj +++ b/src/UmbracoExamine/UmbracoExamine.csproj @@ -82,9 +82,9 @@ ..\Solution Items\TheFARM-Public.snk - - False - ..\packages\Examine.0.1.63.0\lib\Examine.dll + + ..\packages\Examine.0.1.66.0\lib\Examine.dll + True False @@ -111,6 +111,7 @@ + diff --git a/src/UmbracoExamine/UmbracoExamine.csproj.DotSettings b/src/UmbracoExamine/UmbracoExamine.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/UmbracoExamine/UmbracoExamine.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index abcb63eec7..c93172b9a1 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Linq; using System.Xml.Linq; using Examine.LuceneEngine.Config; @@ -15,8 +16,9 @@ using Lucene.Net.Analysis; namespace UmbracoExamine { + /// - /// + /// Custom indexer for members /// public class UmbracoMemberIndexer : UmbracoContentIndexer { @@ -126,30 +128,26 @@ namespace UmbracoExamine //This only supports members if (SupportedTypes.Contains(type) == false) return; - - //Re-index all members in batches of 5000 - int memberCount = 0; + const int pageSize = 1000; var pageIndex = 0; - var serializer = new EntityXmlSerializer(); - if (IndexerData.IncludeNodeTypes.Any()) + IMember[] members; + + if (IndexerData.IncludeNodeTypes.Any()) { //if there are specific node types then just index those foreach (var nodeType in IndexerData.IncludeNodeTypes) { do { - int total; - var members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, nodeType); - memberCount = 0; - foreach (var member in members) - { - AddNodesToIndex(new[] { serializer.Serialize(_dataTypeService, member) }, type); - memberCount++; - } + long total; + members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, nodeType).ToArray(); + + AddNodesToIndex(GetSerializedMembers(members), type); + pageIndex++; - } while (memberCount == pageSize); + } while (members.Length == pageSize); } } else @@ -158,18 +156,21 @@ namespace UmbracoExamine do { int total; - var members = _memberService.GetAll(pageIndex, pageSize, out total); - memberCount = 0; - foreach (var member in members) - { - AddNodesToIndex(new[] {serializer.Serialize(_dataTypeService, member)}, type); - memberCount++; - } + members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray(); + + AddNodesToIndex(GetSerializedMembers(members), type); + pageIndex++; - } while (memberCount == pageSize); + } while (members.Length == pageSize); } } + private IEnumerable GetSerializedMembers(IEnumerable members) + { + var serializer = new EntityXmlSerializer(); + return members.Select(member => serializer.Serialize(_dataTypeService, member)); + } + protected override XDocument GetXDocument(string xPath, string type) { throw new NotSupportedException(); diff --git a/src/UmbracoExamine/app.config b/src/UmbracoExamine/app.config index 0f2278a158..a0794caa99 100644 --- a/src/UmbracoExamine/app.config +++ b/src/UmbracoExamine/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/src/UmbracoExamine/packages.config b/src/UmbracoExamine/packages.config index efa216d153..5f4d96245a 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/RazorDynamicNode/DynamicNode.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs index bb7144de75..4c72325714 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs @@ -1008,8 +1008,7 @@ namespace umbraco.MacroEngines } public DynamicNodeList Descendants(Func func) { - //var flattenedNodes = this.n.ChildrenAsList.Map(func, (DynamicBackingItem n) => { return n.ChildrenAsList; }); - var flattenedNodes = this.n.ChildrenAsList.FlattenList(item => item.ChildrenAsList).Where(func); + var flattenedNodes = this.n.ChildrenAsList.SelectRecursive(item => item.ChildrenAsList).Where(func); return new DynamicNodeList(flattenedNodes.Select(dynamicBackingItem => new DynamicNode(dynamicBackingItem))); } public DynamicNodeList DescendantsOrSelf(int level) @@ -1033,8 +1032,7 @@ namespace umbraco.MacroEngines { thisNode.Add(this.n); } - //var flattenedNodes = this.n.ChildrenAsList.Map(func, (DynamicBackingItem n) => { return n.ChildrenAsList; }); - var flattenedNodes = this.n.ChildrenAsList.FlattenList(item => item.ChildrenAsList).Where(func); + var flattenedNodes = this.n.ChildrenAsList.SelectRecursive(item => item.ChildrenAsList).Where(func); return new DynamicNodeList(thisNode.Concat(flattenedNodes).Select(dynamicBackingItem => new DynamicNode(dynamicBackingItem))); } return new DynamicNodeList(new List()); diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs b/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs index 276b71354e..02882b686e 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs @@ -15,8 +15,7 @@ namespace umbraco.MacroEngines Func selectorFunction, Func> getChildrenFunction) { - //return Umbraco.Core.Dynamics.ExtensionMethods.Map(source, selectorFunction, getChildrenFunction); - return source.FlattenList(getChildrenFunction).Where(selectorFunction); + return source.SelectRecursive(getChildrenFunction).Where(selectorFunction); } public static DynamicNodeList Random(this DynamicNodeList all, int Min, int Max) diff --git a/src/umbraco.MacroEngines/app.config b/src/umbraco.MacroEngines/app.config index 6cd2ad76f2..cc98223bd1 100644 --- a/src/umbraco.MacroEngines/app.config +++ b/src/umbraco.MacroEngines/app.config @@ -8,7 +8,7 @@ - + @@ -16,7 +16,7 @@ - + diff --git a/src/umbraco.MacroEngines/packages.config b/src/umbraco.MacroEngines/packages.config index 2e2427feb4..0ff7390e4f 100644 --- a/src/umbraco.MacroEngines/packages.config +++ b/src/umbraco.MacroEngines/packages.config @@ -1,16 +1,16 @@  - + - - + + - + - + \ No newline at end of file diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj index 4d84010535..e186b184c6 100644 --- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj @@ -45,9 +45,9 @@ false - - False - ..\packages\Examine.0.1.63.0\lib\Examine.dll + + ..\packages\Examine.0.1.66.0\lib\Examine.dll + True False @@ -66,8 +66,8 @@ ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True @@ -80,9 +80,9 @@ - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll False @@ -92,25 +92,25 @@ 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.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll - + + ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll True - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj.DotSettings b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.businesslogic/app.config b/src/umbraco.businesslogic/app.config index 0f2278a158..a0794caa99 100644 --- a/src/umbraco.businesslogic/app.config +++ b/src/umbraco.businesslogic/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/src/umbraco.businesslogic/packages.config b/src/umbraco.businesslogic/packages.config index 0fbf2f8645..65ef682839 100644 --- a/src/umbraco.businesslogic/packages.config +++ b/src/umbraco.businesslogic/packages.config @@ -1,10 +1,10 @@  - - - + + + - + \ No newline at end of file diff --git a/src/umbraco.businesslogic/umbraco.businesslogic.csproj b/src/umbraco.businesslogic/umbraco.businesslogic.csproj index 2adb021e63..c34a55d08b 100644 --- a/src/umbraco.businesslogic/umbraco.businesslogic.csproj +++ b/src/umbraco.businesslogic/umbraco.businesslogic.csproj @@ -140,29 +140,29 @@ 3.5 - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll - + + ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll True - ..\packages\Microsoft.AspNet.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll - + + ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll True - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll System.XML diff --git a/src/umbraco.businesslogic/umbraco.businesslogic.csproj.DotSettings b/src/umbraco.businesslogic/umbraco.businesslogic.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.businesslogic/umbraco.businesslogic.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.cms/Actions/Action.cs b/src/umbraco.cms/Actions/Action.cs index 72f06b8445..df6c257396 100644 --- a/src/umbraco.cms/Actions/Action.cs +++ b/src/umbraco.cms/Actions/Action.cs @@ -90,7 +90,7 @@ namespace umbraco.BusinessLogic.Actions { return ActionsResolver.Current.Actions .Where(x => !string.IsNullOrWhiteSpace(x.JsSource)) - .Select(x => IOHelper.ResolveUrl(x.JsSource)).ToList(); + .Select(x => x.JsSource).ToList(); //return ActionJsReference; } diff --git a/src/umbraco.cms/app.config b/src/umbraco.cms/app.config index 0f2278a158..a0794caa99 100644 --- a/src/umbraco.cms/app.config +++ b/src/umbraco.cms/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index a4de392d4c..1df6dd7c40 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -17,6 +17,7 @@ using umbraco.BusinessLogic; using umbraco.BusinessLogic.Actions; using umbraco.cms.helpers; using umbraco.DataLayer; +using Umbraco.Core.Events; using Property = umbraco.cms.businesslogic.property.Property; using Umbraco.Core.Strings; @@ -894,7 +895,7 @@ namespace umbraco.cms.businesslogic.web [Obsolete("Don't use! Only used internally to support the legacy events", false)] internal Attempt SaveAndPublish(int userId) { - var result = Attempt.Fail(new PublishStatus(ContentEntity, PublishStatusType.FailedCancelledByEvent)); + var result = Attempt.Fail(new PublishStatus(ContentEntity, PublishStatusType.FailedCancelledByEvent, new EventMessages())); foreach (var property in GenericProperties) { ContentEntity.SetValue(property.PropertyType.Alias, property.Value); @@ -1019,10 +1020,10 @@ namespace umbraco.cms.businesslogic.web return result; } - return new Attempt(false, new PublishStatus(ContentEntity, PublishStatusType.FailedCancelledByEvent)); + return new Attempt(false, new PublishStatus(ContentEntity, PublishStatusType.FailedCancelledByEvent, new EventMessages())); } - return new Attempt(false, new PublishStatus(ContentEntity, PublishStatusType.FailedCancelledByEvent)); + return new Attempt(false, new PublishStatus(ContentEntity, PublishStatusType.FailedCancelledByEvent, new EventMessages())); } /// diff --git a/src/umbraco.cms/businesslogic/web/Domain.cs b/src/umbraco.cms/businesslogic/web/Domain.cs index 370d75b4db..fe695a25d6 100644 --- a/src/umbraco.cms/businesslogic/web/Domain.cs +++ b/src/umbraco.cms/businesslogic/web/Domain.cs @@ -64,22 +64,23 @@ namespace umbraco.cms.businesslogic.web public Language Language { - get { return new Language(DomainEntity.Language); } - set { DomainEntity.Language = value.LanguageEntity; } + get + { + if (DomainEntity.LanguageId.HasValue == false) return null; + var lang = ApplicationContext.Current.Services.LocalizationService.GetLanguageById(DomainEntity.LanguageId.Value); + if (lang == null) throw new InvalidOperationException("No language exists with id " + DomainEntity.LanguageId.Value); + return new Language(lang); + } + set + { + DomainEntity.LanguageId = value.LanguageEntity.Id; + } } public int RootNodeId { - get { return DomainEntity.RootContent.Id; } - set - { - var content = ApplicationContext.Current.Services.ContentService.GetById(value); - if (content == null) - { - throw new NullReferenceException("No content found with id " + value); - } - DomainEntity.RootContent = content; - } + get { return DomainEntity.RootContentId ?? -1; } + set { DomainEntity.RootContentId = value; } } public int Id @@ -135,7 +136,7 @@ namespace umbraco.cms.businesslogic.web public static int GetRootFromDomain(string DomainName) { var found = ApplicationContext.Current.Services.DomainService.GetByName(DomainName); - return found == null ? -1 : found.RootContent.Id; + return found == null ? -1 : found.RootContentId ?? -1; } public static Domain[] GetDomainsById(int nodeId) @@ -159,15 +160,10 @@ namespace umbraco.cms.businesslogic.web public static void MakeNew(string DomainName, int RootNodeId, int LanguageId) { - var content = ApplicationContext.Current.Services.ContentService.GetById(RootNodeId); - if (content == null) throw new NullReferenceException("No content exists with id " + RootNodeId); - var lang = ApplicationContext.Current.Services.LocalizationService.GetLanguageById(LanguageId); - if (lang == null) throw new NullReferenceException("No language exists with id " + LanguageId); - var domain = new UmbracoDomain(DomainName) { - RootContent = content, - Language = lang + RootContentId = RootNodeId, + LanguageId = LanguageId }; ApplicationContext.Current.Services.DomainService.Save(domain); diff --git a/src/umbraco.cms/packages.config b/src/umbraco.cms/packages.config index e8d51df7f8..98da680a36 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 9561c3afb9..ece97210f4 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -107,8 +107,8 @@ - False ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + True False @@ -120,8 +120,8 @@ - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True System diff --git a/src/umbraco.cms/umbraco.cms.csproj.DotSettings b/src/umbraco.cms/umbraco.cms.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.cms/umbraco.cms.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.controls/app.config b/src/umbraco.controls/app.config index 0f2278a158..a0794caa99 100644 --- a/src/umbraco.controls/app.config +++ b/src/umbraco.controls/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/src/umbraco.controls/umbraco.controls.csproj b/src/umbraco.controls/umbraco.controls.csproj index 2fb5d7174c..3980e5ed27 100644 --- a/src/umbraco.controls/umbraco.controls.csproj +++ b/src/umbraco.controls/umbraco.controls.csproj @@ -69,8 +69,8 @@ - False ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + True diff --git a/src/umbraco.controls/umbraco.controls.csproj.DotSettings b/src/umbraco.controls/umbraco.controls.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.controls/umbraco.controls.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.datalayer/packages.config b/src/umbraco.datalayer/packages.config index 6c3f4c3945..caf5b0d195 100644 --- a/src/umbraco.datalayer/packages.config +++ b/src/umbraco.datalayer/packages.config @@ -1,6 +1,6 @@  - - - + + + \ No newline at end of file diff --git a/src/umbraco.datalayer/umbraco.datalayer.csproj b/src/umbraco.datalayer/umbraco.datalayer.csproj index 1002075a03..0bd6950356 100644 --- a/src/umbraco.datalayer/umbraco.datalayer.csproj +++ b/src/umbraco.datalayer/umbraco.datalayer.csproj @@ -78,7 +78,7 @@ False - ..\packages\MySql.Data.6.9.6\lib\net45\MySql.Data.dll + ..\packages\MySql.Data.6.9.7\lib\net45\MySql.Data.dll diff --git a/src/umbraco.datalayer/umbraco.datalayer.csproj.DotSettings b/src/umbraco.datalayer/umbraco.datalayer.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.datalayer/umbraco.datalayer.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.editorControls/app.config b/src/umbraco.editorControls/app.config index 0b61939c20..1d7a37c980 100644 --- a/src/umbraco.editorControls/app.config +++ b/src/umbraco.editorControls/app.config @@ -4,11 +4,11 @@ - + - + @@ -47,6 +47,10 @@ + + + + \ No newline at end of file diff --git a/src/umbraco.editorControls/umbraco.editorControls.csproj b/src/umbraco.editorControls/umbraco.editorControls.csproj index 9b669e284e..828b541923 100644 --- a/src/umbraco.editorControls/umbraco.editorControls.csproj +++ b/src/umbraco.editorControls/umbraco.editorControls.csproj @@ -115,8 +115,8 @@ Umbraco.Web - False ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + True System diff --git a/src/umbraco.editorControls/umbraco.editorControls.csproj.DotSettings b/src/umbraco.editorControls/umbraco.editorControls.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.editorControls/umbraco.editorControls.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.editorControls/userControlWrapper/usercontrolPrevalueEditor.cs b/src/umbraco.editorControls/userControlWrapper/usercontrolPrevalueEditor.cs index 5760168776..555407b875 100644 --- a/src/umbraco.editorControls/userControlWrapper/usercontrolPrevalueEditor.cs +++ b/src/umbraco.editorControls/userControlWrapper/usercontrolPrevalueEditor.cs @@ -77,6 +77,7 @@ namespace umbraco.editorControls.userControlGrapper private void populateUserControls(string path) { DirectoryInfo di = new DirectoryInfo(path); + if (di.Exists == false) return; foreach (FileInfo uc in di.GetFiles("*.ascx")) { diff --git a/src/umbraco.interfaces/umbraco.interfaces.csproj.DotSettings b/src/umbraco.interfaces/umbraco.interfaces.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.interfaces/umbraco.interfaces.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.providers/app.config b/src/umbraco.providers/app.config index 0f2278a158..a0794caa99 100644 --- a/src/umbraco.providers/app.config +++ b/src/umbraco.providers/app.config @@ -4,11 +4,11 @@ - + - +
    NameCreation date">Creation date Sort order
    {1}{2} {3}{4}
    {1}{2} {3}{4}