diff --git a/.editorconfig b/.editorconfig index c63ef39430..5a35b71ce6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -34,4 +34,7 @@ dotnet_naming_style.prefix_underscore.required_prefix = _ csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion csharp_style_var_elsewhere = true:suggestion -csharp_prefer_braces = false : none +csharp_prefer_braces = false : none + +[*.{js,less}] +trim_trailing_whitespace = false diff --git a/.github/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md index 8c2bfffd87..422dd1f664 100644 --- a/.github/CONTRIBUTING_DETAILED.md +++ b/.github/CONTRIBUTING_DETAILED.md @@ -104,7 +104,6 @@ There's two big areas that you should know about: You may need to run the following commands to set up gulp properly: ``` npm cache clean - npm install -g bower npm install -g gulp npm install -g gulp-cli npm install diff --git a/.github/README.md b/.github/README.md index 5a1340006e..a60eed0a97 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,4 +1,5 @@ _You are browsing the Umbraco v8 branch. Umbraco 8 is currently under development._ +[![pullreminders](https://pullreminders.com/badge.svg)](https://pullreminders.com?ref=badge) _Looking for Umbraco version 7? [Click here](https://github.com/umbraco/Umbraco-CMS) to go to the v7 branch._ @@ -6,7 +7,7 @@ _Ready to try out Version 8? [See the quick start guide](V8_GETTING_STARTED.md). When is Umbraco 8 coming? ========================= -When it's ready. We're done with the major parts of the architecture work and are focusing on three seperate tracks to prepare Umbraco 8 for release: +When it's ready. We're done with the major parts of the architecture work and are focusing on three separate tracks to prepare Umbraco 8 for release: 1) Editor Track (_currently in progress_). Without editors, there's no market for Umbraco. So we want to make sure that Umbraco 8 is full of love for editors. 2) Partner Track. Without anyone implementing Umbraco, there's nothing for editors to update. So we want to make sure that Umbraco 8 is a joy to implement 3) Contributor Track. Without our fabulous ecosystem of both individual Umbracians and 3rd party ISVs, Umbraco wouldn't be as rich a platform as it is today. We want to make sure that it's easy, straight forward and as backwards-compatible as possible to create packages for Umbraco diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md index 1cc33bb126..eaf4aa334e 100644 --- a/.github/V8_GETTING_STARTED.md +++ b/.github/V8_GETTING_STARTED.md @@ -11,7 +11,7 @@ * Open the `/src/umbraco.sln` Visual Studio solution * Start the solution (easiest way is to use `ctrl + F5`) -* When the solution is first built this may take some time since it will restore all nuget, npm and bower packages, build the .net solution and also build the angular solution +* When the solution is first built this may take some time since it will restore all nuget and npm packages, build the .net solution and also build the angular solution * When the website starts you'll see the Umbraco installer and just follow the prompts * You're all set! diff --git a/.gitignore b/.gitignore index 279bdb39dd..57b4e18aaa 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ *.suo *.vs10x *.ndproj +*.ignorer.* # common directories .DS_Store @@ -156,4 +157,4 @@ build/temp/ -# eof \ No newline at end of file +# eof diff --git a/build/Azure/azuregalleryrelease.ps1 b/build/Azure/azuregalleryrelease.ps1 index 502ca3010e..61505171a2 100644 --- a/build/Azure/azuregalleryrelease.ps1 +++ b/build/Azure/azuregalleryrelease.ps1 @@ -3,57 +3,57 @@ [string]$Directory ) $workingDirectory = $Directory -CD $workingDirectory +CD "$($workingDirectory)" # Clone repo -$fullGitUrl = "https://$env:GIT_URL/$env:GIT_REPOSITORYNAME.git" -git clone $fullGitUrl 2>&1 | % { $_.ToString() } +$fullGitUrl = "https://$($env:GIT_URL)/$($env:GIT_REPOSITORYNAME).git" +git clone $($fullGitUrl) $($env:GIT_REPOSITORYNAME) 2>&1 | % { $_.ToString() } # Remove everything so that unzipping the release later will update everything # Don't remove the readme file nor the git directory Write-Host "Cleaning up git directory before adding new version" -Remove-Item -Recurse $workingDirectory\$env:GIT_REPOSITORYNAME\* -Exclude README.md,.git +Remove-Item -Recurse "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)\*" -Exclude README.md,.git # Find release zip -$zipsDir = "$workingDirectory\$env:BUILD_DEFINITIONNAME\zips" +$zipsDir = "$($workingDirectory)\$($env:BUILD_DEFINITIONNAME)\zips" $pattern = "UmbracoCms.([0-9]{1,2}.[0-9]{1,3}.[0-9]{1,3}).zip" -Write-Host "Searching for Umbraco release files in $workingDirectory\$zipsDir for a file with pattern $pattern" -$file = (Get-ChildItem $zipsDir | Where-Object { $_.Name -match "$pattern" }) +Write-Host "Searching for Umbraco release files in $($zipsDir) for a file with pattern $($pattern)" +$file = (Get-ChildItem "$($zipsDir)" | Where-Object { $_.Name -match "$($pattern)" }) if($file) { # Get release name - $version = [regex]::Match($file.Name, $pattern).captures.groups[1].value - $releaseName = "Umbraco $version" - Write-Host "Found $releaseName" + $version = [regex]::Match($($file.Name), $($pattern)).captures.groups[1].value + $releaseName = "Umbraco $($version)" + Write-Host "Found $($releaseName)" # Unzip into repository to update release Add-Type -AssemblyName System.IO.Compression.FileSystem - Write-Host "Unzipping $($file.FullName) to $workingDirectory\$env:GIT_REPOSITORYNAME" - [System.IO.Compression.ZipFile]::ExtractToDirectory("$($file.FullName)", "$workingDirectory\$env:GIT_REPOSITORYNAME") + Write-Host "Unzipping $($file.FullName) to $($workingDirectory)\$($env:GIT_REPOSITORYNAME)" + [System.IO.Compression.ZipFile]::ExtractToDirectory("$($file.FullName)", "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)") # Telling git who we are git config --global user.email "coffee@umbraco.com" 2>&1 | % { $_.ToString() } git config --global user.name "Umbraco HQ" 2>&1 | % { $_.ToString() } # Commit - CD $env:GIT_REPOSITORYNAME - Write-Host "Committing Umbraco $version Release from Build Output" + CD "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)" + Write-Host "Committing Umbraco $($version) Release from Build Output" git add . 2>&1 | % { $_.ToString() } - git commit -m " Release $releaseName from Build Output" 2>&1 | % { $_.ToString() } + git commit -m " Release $($releaseName) from Build Output" 2>&1 | % { $_.ToString() } # Tag the release - git tag -a "v$version" -m "v$version" + git tag -a "v$($version)" -m "v$($version)" # Push release to master - $fullGitAuthUrl = "https://$($env:GIT_USERNAME):$GitHubPersonalAccessToken@$env:GIT_URL/$env:GIT_REPOSITORYNAME.git" - git push $fullGitAuthUrl 2>&1 | % { $_.ToString() } + $fullGitAuthUrl = "https://$($env:GIT_USERNAME):$($GitHubPersonalAccessToken)@$($env:GIT_URL)/$($env:GIT_REPOSITORYNAME).git" + git push $($fullGitAuthUrl) 2>&1 | % { $_.ToString() } #Push tag to master - git push $fullGitAuthUrl --tags 2>&1 | % { $_.ToString() } + git push $($fullGitAuthUrl) --tags 2>&1 | % { $_.ToString() } } else { - Write-Error "Umbraco release file not found, searched in $workingDirectory\$zipsDir for a file with pattern $pattern - cancelling" + Write-Error "Umbraco release file not found, searched in $($workingDirectory)\$($zipsDir) for a file with pattern $($pattern) - canceling" } diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 51d7e3b8d0..30fa303b30 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -25,7 +25,7 @@ - + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 434fe812ef..a188377c19 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,11 +22,11 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - + - - + + @@ -52,10 +52,8 @@ - - diff --git a/build/build.ps1 b/build/build.ps1 index ec53199251..65b4041e30 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -330,9 +330,6 @@ $ubuild.DefineMethod("PrepareBuild", { - Write-Host "Clear folders and files" - $this.RemoveDirectory("$($this.SolutionRoot)\src\Umbraco.Web.UI.Client\bower_components") - $this.TempStoreFile("$($this.SolutionRoot)\src\Umbraco.Web.UI\web.config") Write-Host "Create clean web.config" $this.CopyFile("$($this.SolutionRoot)\src\Umbraco.Web.UI\web.Template.config", "$($this.SolutionRoot)\src\Umbraco.Web.UI\web.config") @@ -452,9 +449,22 @@ if ($this.OnError()) { return } $this.PrepareAzureGallery() if ($this.OnError()) { return } + $this.PostPackageHook() + if ($this.OnError()) { return } Write-Host "Done" }) + $ubuild.DefineMethod("PostPackageHook", + { + # run hook + if ($this.HasMethod("PostPackage")) + { + Write-Host "Run PostPackage hook" + $this.PostPackage(); + if (-not $?) { throw "Failed to run hook." } + } + }) + # ################################################################ # RUN # ################################################################ diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index b5af335791..ce40bd9baa 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.0.0")] -[assembly: AssemblyInformationalVersion("8.0.0-alpha.52")] +[assembly: AssemblyInformationalVersion("8.0.0-alpha.58")] diff --git a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs b/src/Umbraco.Core/Cache/AppCacheExtensions.cs similarity index 59% rename from src/Umbraco.Core/Cache/CacheProviderExtensions.cs rename to src/Umbraco.Core/Cache/AppCacheExtensions.cs index e42cb4d9ad..ddba8be1b2 100644 --- a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs +++ b/src/Umbraco.Core/Cache/AppCacheExtensions.cs @@ -8,9 +8,9 @@ namespace Umbraco.Core.Cache /// /// Extensions for strongly typed access /// - public static class CacheProviderExtensions + public static class AppCacheExtensions { - public static T GetCacheItem(this IRuntimeCacheProvider provider, + public static T GetCacheItem(this IAppPolicyCache provider, string cacheKey, Func getCacheItem, TimeSpan? timeout, @@ -19,11 +19,11 @@ namespace Umbraco.Core.Cache CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - var result = provider.GetCacheItem(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles); + var result = provider.Get(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles); return result == null ? default(T) : result.TryConvertTo().Result; } - public static void InsertCacheItem(this IRuntimeCacheProvider provider, + public static void InsertCacheItem(this IAppPolicyCache provider, string cacheKey, Func getCacheItem, TimeSpan? timeout = null, @@ -32,24 +32,24 @@ namespace Umbraco.Core.Cache CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - provider.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles); + provider.Insert(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles); } - public static IEnumerable GetCacheItemsByKeySearch(this ICacheProvider provider, string keyStartsWith) + public static IEnumerable GetCacheItemsByKeySearch(this IAppCache provider, string keyStartsWith) { - var result = provider.GetCacheItemsByKeySearch(keyStartsWith); + var result = provider.SearchByKey(keyStartsWith); return result.Select(x => x.TryConvertTo().Result); } - public static IEnumerable GetCacheItemsByKeyExpression(this ICacheProvider provider, string regexString) + public static IEnumerable GetCacheItemsByKeyExpression(this IAppCache provider, string regexString) { - var result = provider.GetCacheItemsByKeyExpression(regexString); + var result = provider.SearchByRegex(regexString); return result.Select(x => x.TryConvertTo().Result); } - public static T GetCacheItem(this ICacheProvider provider, string cacheKey) + public static T GetCacheItem(this IAppCache provider, string cacheKey) { - var result = provider.GetCacheItem(cacheKey); + var result = provider.Get(cacheKey); if (result == null) { return default(T); @@ -57,9 +57,9 @@ namespace Umbraco.Core.Cache return result.TryConvertTo().Result; } - public static T GetCacheItem(this ICacheProvider provider, string cacheKey, Func getCacheItem) + public static T GetCacheItem(this IAppCache provider, string cacheKey, Func getCacheItem) { - var result = provider.GetCacheItem(cacheKey, () => getCacheItem()); + var result = provider.Get(cacheKey, () => getCacheItem()); if (result == null) { return default(T); diff --git a/src/Umbraco.Core/Cache/AppCaches.cs b/src/Umbraco.Core/Cache/AppCaches.cs new file mode 100644 index 0000000000..e8bc49605a --- /dev/null +++ b/src/Umbraco.Core/Cache/AppCaches.cs @@ -0,0 +1,86 @@ +using System; +using System.Web; + +namespace Umbraco.Core.Cache +{ + /// + /// Represents the application caches. + /// + public class AppCaches + { + /// + /// Initializes a new instance of the for use in a web application. + /// + public AppCaches() + : this(HttpRuntime.Cache) + { } + + /// + /// Initializes a new instance of the for use in a web application. + /// + public AppCaches(System.Web.Caching.Cache cache) + : this( + new WebCachingAppCache(cache), + new HttpRequestAppCache(), + new IsolatedCaches(t => new ObjectCacheAppCache())) + { } + + /// + /// Initializes a new instance of the with cache providers. + /// + public AppCaches( + IAppPolicyCache runtimeCache, + IAppCache requestCache, + IsolatedCaches isolatedCaches) + { + RuntimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); + RequestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); + IsolatedCaches = isolatedCaches ?? throw new ArgumentNullException(nameof(isolatedCaches)); + } + + /// + /// Gets the special disabled instance. + /// + /// + /// When used by repositories, all cache policies apply, but the underlying caches do not cache anything. + /// Used by tests. + /// + public static AppCaches Disabled { get; } = new AppCaches(NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(_ => NoAppCache.Instance)); + + /// + /// Gets the special no-cache instance. + /// + /// + /// When used by repositories, all cache policies are bypassed. + /// Used by repositories that do no cache. + /// + public static AppCaches NoCache { get; } = new AppCaches(NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(_ => NoAppCache.Instance)); + + /// + /// Gets the per-request cache. + /// + /// + /// The per-request caches works on top of the current HttpContext items. + /// fixme - what about non-web applications? + /// + public IAppCache RequestCache { get; } + + /// + /// Gets the runtime cache. + /// + /// + /// The runtime cache is the main application cache. + /// + public IAppPolicyCache RuntimeCache { get; } + + /// + /// Gets the isolated caches. + /// + /// + /// Isolated caches are used by e.g. repositories, to ensure that each cached entity + /// type has its own cache, so that lookups are fast and the repository does not need to + /// search through all keys on a global scale. + /// + public IsolatedCaches IsolatedCaches { get; } + } +} diff --git a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs new file mode 100644 index 0000000000..5c60dededa --- /dev/null +++ b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Concurrent; + +namespace Umbraco.Core.Cache +{ + /// + /// Provides a base class for implementing a dictionary of . + /// + /// The type of the dictionary key. + public abstract class AppPolicedCacheDictionary + { + private readonly ConcurrentDictionary _caches = new ConcurrentDictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// + protected AppPolicedCacheDictionary(Func cacheFactory) + { + CacheFactory = cacheFactory; + } + + /// + /// Gets the internal cache factory, for tests only! + /// + internal readonly Func CacheFactory; + + /// + /// Gets or creates a cache. + /// + public IAppPolicyCache GetOrCreate(TKey key) + => _caches.GetOrAdd(key, k => CacheFactory(k)); + + /// + /// Tries to get a cache. + /// + public Attempt Get(TKey key) + => _caches.TryGetValue(key, out var cache) ? Attempt.Succeed(cache) : Attempt.Fail(); + + /// + /// Removes a cache. + /// + public void Remove(TKey key) + { + _caches.TryRemove(key, out _); + } + + /// + /// Removes all caches. + /// + public void RemoveAll() + { + _caches.Clear(); + } + + /// + /// Clears a cache. + /// + public void ClearCache(TKey key) + { + if (_caches.TryGetValue(key, out var cache)) + cache.Clear(); + } + + /// + /// Clears all caches. + /// + public void ClearAllCaches() + { + foreach (var cache in _caches.Values) + cache.Clear(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/CacheHelper.cs b/src/Umbraco.Core/Cache/CacheHelper.cs deleted file mode 100644 index f99b1e847b..0000000000 --- a/src/Umbraco.Core/Cache/CacheHelper.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Web; - -namespace Umbraco.Core.Cache -{ - /// - /// Class that is exposed by the ApplicationContext for application wide caching purposes - /// - public class CacheHelper - { - public static CacheHelper NoCache { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance)); - - /// - /// Creates a cache helper with disabled caches - /// - /// - /// - /// Good for unit testing - /// - public static CacheHelper CreateDisabledCacheHelper() - { - // do *not* return NoCache - // NoCache is a special instance that is detected by RepositoryBase and disables all cache policies - // CreateDisabledCacheHelper is used in tests to use no cache, *but* keep all cache policies - return new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance)); - } - - /// - /// Initializes a new instance for use in the web - /// - public CacheHelper() - : this( - new HttpRuntimeCacheProvider(HttpRuntime.Cache), - new StaticCacheProvider(), - new HttpRequestCacheProvider(), - new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) - { - } - - /// - /// Initializes a new instance for use in the web - /// - /// - public CacheHelper(System.Web.Caching.Cache cache) - : this( - new HttpRuntimeCacheProvider(cache), - new StaticCacheProvider(), - new HttpRequestCacheProvider(), - new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) - { - } - - - /// - /// Initializes a new instance based on the provided providers - /// - /// - /// - /// - /// - public CacheHelper( - IRuntimeCacheProvider httpCacheProvider, - ICacheProvider staticCacheProvider, - ICacheProvider requestCacheProvider, - IsolatedRuntimeCache isolatedCacheManager) - { - if (httpCacheProvider == null) throw new ArgumentNullException("httpCacheProvider"); - if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider"); - if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); - if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager"); - RuntimeCache = httpCacheProvider; - StaticCache = staticCacheProvider; - RequestCache = requestCacheProvider; - IsolatedRuntimeCache = isolatedCacheManager; - } - - /// - /// Returns the current Request cache - /// - public ICacheProvider RequestCache { get; internal set; } - - /// - /// Returns the current Runtime cache - /// - public ICacheProvider StaticCache { get; internal set; } - - /// - /// Returns the current Runtime cache - /// - public IRuntimeCacheProvider RuntimeCache { get; internal set; } - - /// - /// Returns the current Isolated Runtime cache manager - /// - public IsolatedRuntimeCache IsolatedRuntimeCache { get; internal set; } - - } - -} diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs index 7242ab225e..bfa16ff3fa 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs @@ -16,10 +16,10 @@ namespace Umbraco.Core.Cache /// /// Initializes a new instance of the . /// - /// A cache helper. - protected CacheRefresherBase(CacheHelper cacheHelper) + /// A cache helper. + protected CacheRefresherBase(AppCaches appCaches) { - CacheHelper = cacheHelper; + AppCaches = appCaches; } /// @@ -93,7 +93,7 @@ namespace Umbraco.Core.Cache /// /// Gets the cache helper. /// - protected CacheHelper CacheHelper { get; } + protected AppCaches AppCaches { get; } /// /// Clears the cache for all repository entities of a specified type. @@ -102,7 +102,7 @@ namespace Umbraco.Core.Cache protected void ClearAllIsolatedCacheByEntityType() where TEntity : class, IEntity { - CacheHelper.IsolatedRuntimeCache.ClearCache(); + AppCaches.IsolatedCaches.ClearCache(); } /// diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs index 11ac05844b..8bae755149 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs @@ -1,15 +1,9 @@ -using System.Collections.Generic; -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.Cache { public class CacheRefresherCollectionBuilder : LazyCollectionBuilderBase { - public CacheRefresherCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override CacheRefresherCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs new file mode 100644 index 0000000000..eff06e2aad --- /dev/null +++ b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Caching; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Cache +{ + /// + /// Implements by wrapping an inner other + /// instance, and ensuring that all inserts and returns are deep cloned copies of the cache item, + /// when the item is deep-cloneable. + /// + internal class DeepCloneAppCache : IAppPolicyCache + { + /// + /// Initializes a new instance of the class. + /// + public DeepCloneAppCache(IAppPolicyCache innerCache) + { + var type = typeof (DeepCloneAppCache); + + if (innerCache.GetType() == type) + throw new InvalidOperationException($"A {type} cannot wrap another instance of itself."); + + InnerCache = innerCache; + } + + /// + /// Gets the inner cache. + /// + public IAppPolicyCache InnerCache { get; } + + /// + public object Get(string key) + { + var item = InnerCache.Get(key); + return CheckCloneableAndTracksChanges(item); + } + + /// + public object Get(string key, Func factory) + { + var cached = InnerCache.Get(key, () => + { + var result = FastDictionaryAppCacheBase.GetSafeLazy(factory); + var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + // do not store null values (backward compat), clone / reset to go into the cache + return value == null ? null : CheckCloneableAndTracksChanges(value); + }); + return CheckCloneableAndTracksChanges(cached); + } + + /// + public IEnumerable SearchByKey(string keyStartsWith) + { + return InnerCache.SearchByKey(keyStartsWith) + .Select(CheckCloneableAndTracksChanges); + } + + /// + public IEnumerable SearchByRegex(string regex) + { + return InnerCache.SearchByRegex(regex) + .Select(CheckCloneableAndTracksChanges); + } + + /// + public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + var cached = InnerCache.Get(key, () => + { + var result = FastDictionaryAppCacheBase.GetSafeLazy(factory); + var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + // do not store null values (backward compat), clone / reset to go into the cache + return value == null ? null : CheckCloneableAndTracksChanges(value); + + // clone / reset to go into the cache + }, timeout, isSliding, priority, removedCallback, dependentFiles); + + // clone / reset to go into the cache + return CheckCloneableAndTracksChanges(cached); + } + + /// + public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + InnerCache.Insert(key, () => + { + var result = FastDictionaryAppCacheBase.GetSafeLazy(factory); + var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + // do not store null values (backward compat), clone / reset to go into the cache + return value == null ? null : CheckCloneableAndTracksChanges(value); + }, timeout, isSliding, priority, removedCallback, dependentFiles); + } + + /// + public void Clear() + { + InnerCache.Clear(); + } + + /// + public void Clear(string key) + { + InnerCache.Clear(key); + } + + /// + public void ClearOfType(string typeName) + { + InnerCache.ClearOfType(typeName); + } + + /// + public void ClearOfType() + { + InnerCache.ClearOfType(); + } + + /// + public void ClearOfType(Func predicate) + { + InnerCache.ClearOfType(predicate); + } + + /// + public void ClearByKey(string keyStartsWith) + { + InnerCache.ClearByKey(keyStartsWith); + } + + /// + public void ClearByRegex(string regex) + { + InnerCache.ClearByRegex(regex); + } + + private static object CheckCloneableAndTracksChanges(object input) + { + if (input is IDeepCloneable cloneable) + { + input = cloneable.DeepClone(); + } + + // reset dirty initial properties + if (input is IRememberBeingDirty tracksChanges) + { + tracksChanges.ResetDirtyProperties(false); + input = tracksChanges; + } + + return input; + } + } +} diff --git a/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs deleted file mode 100644 index 255d7b526d..0000000000 --- a/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Caching; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Cache -{ - /// - /// Interface describing this cache provider as a wrapper for another - /// - internal interface IRuntimeCacheProviderWrapper - { - IRuntimeCacheProvider InnerProvider { get; } - } - - /// - /// A wrapper for any IRuntimeCacheProvider that ensures that all inserts and returns - /// are a deep cloned copy of the item when the item is IDeepCloneable and that tracks changes are - /// reset if the object is TracksChangesEntityBase - /// - internal class DeepCloneRuntimeCacheProvider : IRuntimeCacheProvider, IRuntimeCacheProviderWrapper - { - public IRuntimeCacheProvider InnerProvider { get; } - - public DeepCloneRuntimeCacheProvider(IRuntimeCacheProvider innerProvider) - { - var type = typeof (DeepCloneRuntimeCacheProvider); - - if (innerProvider.GetType() == type) - throw new InvalidOperationException($"A {type} cannot wrap another instance of {type}."); - - InnerProvider = innerProvider; - } - - #region Clear - doesn't require any changes - - public void ClearAllCache() - { - InnerProvider.ClearAllCache(); - } - - public void ClearCacheItem(string key) - { - InnerProvider.ClearCacheItem(key); - } - - public void ClearCacheObjectTypes(string typeName) - { - InnerProvider.ClearCacheObjectTypes(typeName); - } - - public void ClearCacheObjectTypes() - { - InnerProvider.ClearCacheObjectTypes(); - } - - public void ClearCacheObjectTypes(Func predicate) - { - InnerProvider.ClearCacheObjectTypes(predicate); - } - - public void ClearCacheByKeySearch(string keyStartsWith) - { - InnerProvider.ClearCacheByKeySearch(keyStartsWith); - } - - public void ClearCacheByKeyExpression(string regexString) - { - InnerProvider.ClearCacheByKeyExpression(regexString); - } - - #endregion - - public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - return InnerProvider.GetCacheItemsByKeySearch(keyStartsWith) - .Select(CheckCloneableAndTracksChanges); - } - - public IEnumerable GetCacheItemsByKeyExpression(string regexString) - { - return InnerProvider.GetCacheItemsByKeyExpression(regexString) - .Select(CheckCloneableAndTracksChanges); - } - - public object GetCacheItem(string cacheKey) - { - var item = InnerProvider.GetCacheItem(cacheKey); - return CheckCloneableAndTracksChanges(item); - } - - public object GetCacheItem(string cacheKey, Func getCacheItem) - { - var cached = InnerProvider.GetCacheItem(cacheKey, () => - { - var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); - var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache - if (value == null) return null; // do not store null values (backward compat) - - return CheckCloneableAndTracksChanges(value); - }); - return CheckCloneableAndTracksChanges(cached); - } - - public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - var cached = InnerProvider.GetCacheItem(cacheKey, () => - { - var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); - var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache - if (value == null) return null; // do not store null values (backward compat) - - // clone / reset to go into the cache - return CheckCloneableAndTracksChanges(value); - }, timeout, isSliding, priority, removedCallback, dependentFiles); - - // clone / reset to go into the cache - return CheckCloneableAndTracksChanges(cached); - } - - public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - InnerProvider.InsertCacheItem(cacheKey, () => - { - var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); - var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache - if (value == null) return null; // do not store null values (backward compat) - - // clone / reset to go into the cache - return CheckCloneableAndTracksChanges(value); - }, timeout, isSliding, priority, removedCallback, dependentFiles); - } - - private static object CheckCloneableAndTracksChanges(object input) - { - var cloneable = input as IDeepCloneable; - if (cloneable != null) - { - input = cloneable.DeepClone(); - } - - // reset dirty initial properties (U4-1946) - var tracksChanges = input as IRememberBeingDirty; - if (tracksChanges != null) - { - tracksChanges.ResetDirtyProperties(false); - input = tracksChanges; - } - - return input; - } - } -} diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 8387f547d3..c11309c827 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Cache private static readonly TEntity[] EmptyEntities = new TEntity[0]; // const private readonly RepositoryCachePolicyOptions _options; - public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) + public DefaultRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) : base(cache, scopeAccessor) { _options = options ?? throw new ArgumentNullException(nameof(options)); @@ -42,7 +42,7 @@ namespace Umbraco.Core.Cache protected virtual void InsertEntity(string cacheKey, TEntity entity) { - Cache.InsertCacheItem(cacheKey, () => entity, TimeSpan.FromMinutes(5), true); + Cache.Insert(cacheKey, () => entity, TimeSpan.FromMinutes(5), true); } protected virtual void InsertEntities(TId[] ids, TEntity[] entities) @@ -52,7 +52,7 @@ namespace Umbraco.Core.Cache // getting all of them, and finding nothing. // if we can cache a zero count, cache an empty array, // for as long as the cache is not cleared (no expiration) - Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => EmptyEntities); + Cache.Insert(GetEntityTypeCacheKey(), () => EmptyEntities); } else { @@ -60,7 +60,7 @@ namespace Umbraco.Core.Cache foreach (var entity in entities) { var capture = entity; - Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => capture, TimeSpan.FromMinutes(5), true); + Cache.Insert(GetEntityCacheKey(entity.Id), () => capture, TimeSpan.FromMinutes(5), true); } } } @@ -77,21 +77,21 @@ namespace Umbraco.Core.Cache // just to be safe, we cannot cache an item without an identity if (entity.HasIdentity) { - Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true); + Cache.Insert(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true); } // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared - Cache.ClearCacheItem(GetEntityTypeCacheKey()); + Cache.Clear(GetEntityTypeCacheKey()); } catch { // if an exception is thrown we need to remove the entry from cache, // this is ONLY a work around because of the way // that we cache entities: http://issues.umbraco.org/issue/U4-4259 - Cache.ClearCacheItem(GetEntityCacheKey(entity.Id)); + Cache.Clear(GetEntityCacheKey(entity.Id)); // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared - Cache.ClearCacheItem(GetEntityTypeCacheKey()); + Cache.Clear(GetEntityTypeCacheKey()); throw; } @@ -109,21 +109,21 @@ namespace Umbraco.Core.Cache // just to be safe, we cannot cache an item without an identity if (entity.HasIdentity) { - Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true); + Cache.Insert(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true); } // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared - Cache.ClearCacheItem(GetEntityTypeCacheKey()); + Cache.Clear(GetEntityTypeCacheKey()); } catch { // if an exception is thrown we need to remove the entry from cache, // this is ONLY a work around because of the way // that we cache entities: http://issues.umbraco.org/issue/U4-4259 - Cache.ClearCacheItem(GetEntityCacheKey(entity.Id)); + Cache.Clear(GetEntityCacheKey(entity.Id)); // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared - Cache.ClearCacheItem(GetEntityTypeCacheKey()); + Cache.Clear(GetEntityTypeCacheKey()); throw; } @@ -142,9 +142,9 @@ namespace Umbraco.Core.Cache { // whatever happens, clear the cache var cacheKey = GetEntityCacheKey(entity.Id); - Cache.ClearCacheItem(cacheKey); + Cache.Clear(cacheKey); // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared - Cache.ClearCacheItem(GetEntityTypeCacheKey()); + Cache.Clear(GetEntityTypeCacheKey()); } } @@ -238,7 +238,7 @@ namespace Umbraco.Core.Cache /// public override void ClearAll() { - Cache.ClearCacheByKeySearch(GetEntityTypeCacheKey()); + Cache.ClearByKey(GetEntityTypeCacheKey()); } } } diff --git a/src/Umbraco.Core/Cache/DictionaryAppCache.cs b/src/Umbraco.Core/Cache/DictionaryAppCache.cs new file mode 100644 index 0000000000..4c08bd0524 --- /dev/null +++ b/src/Umbraco.Core/Cache/DictionaryAppCache.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Umbraco.Core.Cache +{ + /// + /// Implements on top of a concurrent dictionary. + /// + public class DictionaryAppCache : IAppCache + { + /// + /// Gets the internal items dictionary, for tests only! + /// + internal readonly ConcurrentDictionary Items = new ConcurrentDictionary(); + + /// + public virtual object Get(string key) + { + // fixme throws if non-existing, shouldn't it return null? + return Items[key]; + } + + /// + public virtual object Get(string key, Func factory) + { + return Items.GetOrAdd(key, _ => factory()); + } + + /// + public virtual IEnumerable SearchByKey(string keyStartsWith) + { + var items = new List(); + foreach (var (key, value) in Items) + if (key.InvariantStartsWith(keyStartsWith)) + items.Add(value); + return items; + } + + /// + public IEnumerable SearchByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); + var items = new List(); + foreach (var (key, value) in Items) + if (compiled.IsMatch(key)) + items.Add(value); + return items; + } + + /// + public virtual void Clear() + { + Items.Clear(); + } + + /// + public virtual void Clear(string key) + { + Items.TryRemove(key, out _); + } + + /// + public virtual void ClearOfType(string typeName) + { + Items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType().ToString().InvariantEquals(typeName)); + } + + /// + public virtual void ClearOfType() + { + var typeOfT = typeof(T); + Items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT); + } + + /// + public virtual void ClearOfType(Func predicate) + { + var typeOfT = typeof(T); + Items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value)); + } + + /// + public virtual void ClearByKey(string keyStartsWith) + { + Items.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)); + } + + /// + public virtual void ClearByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); + Items.RemoveAll(kvp => compiled.IsMatch(kvp.Key)); + } + } +} diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs similarity index 53% rename from src/Umbraco.Core/Cache/DictionaryCacheProvider.cs rename to src/Umbraco.Core/Cache/FastDictionaryAppCache.cs index 98dceb80b0..bd545694f7 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs @@ -7,79 +7,130 @@ using Umbraco.Core.Composing; namespace Umbraco.Core.Cache { - internal class DictionaryCacheProvider : ICacheProvider + /// + /// Implements a fast on top of a concurrent dictionary. + /// + internal class FastDictionaryAppCache : IAppCache { - private readonly ConcurrentDictionary> _items - = new ConcurrentDictionary>(); + /// + /// Gets the internal items dictionary, for tests only! + /// + internal readonly ConcurrentDictionary> Items = new ConcurrentDictionary>(); - // for tests - internal ConcurrentDictionary> Items => _items; - - public void ClearAllCache() + /// + public object Get(string cacheKey) { - _items.Clear(); + Items.TryGetValue(cacheKey, out var result); // else null + return result == null ? null : FastDictionaryAppCacheBase.GetSafeLazyValue(result); // return exceptions as null } - public void ClearCacheItem(string key) + /// + public object Get(string cacheKey, Func getCacheItem) { - _items.TryRemove(key, out _); + var result = Items.GetOrAdd(cacheKey, k => FastDictionaryAppCacheBase.GetSafeLazy(getCacheItem)); + + var value = result.Value; // will not throw (safe lazy) + if (!(value is FastDictionaryAppCacheBase.ExceptionHolder eh)) + return value; + + // and... it's in the cache anyway - so contrary to other cache providers, + // which would trick with GetSafeLazyValue, we need to remove by ourselves, + // in order NOT to cache exceptions + + Items.TryRemove(cacheKey, out result); + eh.Exception.Throw(); // throw once! + return null; // never reached } - public void ClearCacheObjectTypes(string typeName) + /// + public IEnumerable SearchByKey(string keyStartsWith) + { + return Items + .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)) + .Select(kvp => FastDictionaryAppCacheBase.GetSafeLazyValue(kvp.Value)) + .Where(x => x != null); + } + + /// + public IEnumerable SearchByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); + return Items + .Where(kvp => compiled.IsMatch(kvp.Key)) + .Select(kvp => FastDictionaryAppCacheBase.GetSafeLazyValue(kvp.Value)) + .Where(x => x != null); + } + + /// + public void Clear() + { + Items.Clear(); + } + + /// + public void Clear(string key) + { + Items.TryRemove(key, out _); + } + + /// + public void ClearOfType(string typeName) { var type = TypeFinder.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; - foreach (var kvp in _items + foreach (var kvp in Items .Where(x => { // entry.Value is Lazy and not null, its value may be null // remove null values as well, does not hurt // get non-created as NonCreatedValue & exceptions as null - var value = DictionaryCacheProviderBase.GetSafeLazyValue(x.Value, true); + var value = FastDictionaryAppCacheBase.GetSafeLazyValue(x.Value, true); // if T is an interface remove anything that implements that interface // otherwise remove exact types (not inherited types) return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); })) - _items.TryRemove(kvp.Key, out _); + Items.TryRemove(kvp.Key, out _); } - public void ClearCacheObjectTypes() + /// + public void ClearOfType() { var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; - foreach (var kvp in _items + foreach (var kvp in Items .Where(x => { // entry.Value is Lazy and not null, its value may be null // remove null values as well, does not hurt // compare on exact type, don't use "is" // get non-created as NonCreatedValue & exceptions as null - var value = DictionaryCacheProviderBase.GetSafeLazyValue(x.Value, true); + var value = FastDictionaryAppCacheBase.GetSafeLazyValue(x.Value, true); // if T is an interface remove anything that implements that interface // otherwise remove exact types (not inherited types) return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); })) - _items.TryRemove(kvp.Key, out _); + Items.TryRemove(kvp.Key, out _); } - public void ClearCacheObjectTypes(Func predicate) + /// + public void ClearOfType(Func predicate) { var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; - foreach (var kvp in _items + foreach (var kvp in Items .Where(x => { // entry.Value is Lazy and not null, its value may be null // remove null values as well, does not hurt // compare on exact type, don't use "is" // get non-created as NonCreatedValue & exceptions as null - var value = DictionaryCacheProviderBase.GetSafeLazyValue(x.Value, true); + var value = FastDictionaryAppCacheBase.GetSafeLazyValue(x.Value, true); if (value == null) return true; // if T is an interface remove anything that implements that interface @@ -88,60 +139,24 @@ namespace Umbraco.Core.Cache // run predicate on the 'public key' part only, ie without prefix && predicate(x.Key, (T)value); })) - _items.TryRemove(kvp.Key, out _); + Items.TryRemove(kvp.Key, out _); } - public void ClearCacheByKeySearch(string keyStartsWith) + /// + public void ClearByKey(string keyStartsWith) { - foreach (var ikvp in _items + foreach (var ikvp in Items .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith))) - _items.TryRemove(ikvp.Key, out _); + Items.TryRemove(ikvp.Key, out _); } - public void ClearCacheByKeyExpression(string regexString) + /// + public void ClearByRegex(string regex) { - foreach (var ikvp in _items - .Where(kvp => Regex.IsMatch(kvp.Key, regexString))) - _items.TryRemove(ikvp.Key, out _); - } - - public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - return _items - .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)) - .Select(kvp => DictionaryCacheProviderBase.GetSafeLazyValue(kvp.Value)) - .Where(x => x != null); - } - - public IEnumerable GetCacheItemsByKeyExpression(string regexString) - { - return _items - .Where(kvp => Regex.IsMatch(kvp.Key, regexString)) - .Select(kvp => DictionaryCacheProviderBase.GetSafeLazyValue(kvp.Value)) - .Where(x => x != null); - } - - public object GetCacheItem(string cacheKey) - { - _items.TryGetValue(cacheKey, out var result); // else null - return result == null ? null : DictionaryCacheProviderBase.GetSafeLazyValue(result); // return exceptions as null - } - - public object GetCacheItem(string cacheKey, Func getCacheItem) - { - var result = _items.GetOrAdd(cacheKey, k => DictionaryCacheProviderBase.GetSafeLazy(getCacheItem)); - - var value = result.Value; // will not throw (safe lazy) - if (!(value is DictionaryCacheProviderBase.ExceptionHolder eh)) - return value; - - // and... it's in the cache anyway - so contrary to other cache providers, - // which would trick with GetSafeLazyValue, we need to remove by ourselves, - // in order NOT to cache exceptions - - _items.TryRemove(cacheKey, out result); - eh.Exception.Throw(); // throw once! - return null; // never reached + var compiled = new Regex(regex, RegexOptions.Compiled); + foreach (var ikvp in Items + .Where(kvp => compiled.IsMatch(kvp.Key))) + Items.TryRemove(ikvp.Key, out _); } } } diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs similarity index 81% rename from src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs rename to src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs index f556d47a8e..371ab90a57 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs @@ -8,7 +8,10 @@ using Umbraco.Core.Composing; namespace Umbraco.Core.Cache { - internal abstract class DictionaryCacheProviderBase : ICacheProvider + /// + /// Provides a base class to fast, dictionary-based implementations. + /// + internal abstract class FastDictionaryAppCacheBase : IAppCache { // prefix cache keys so we know which one are ours protected const string CacheItemPrefix = "umbrtmche"; @@ -16,82 +19,75 @@ namespace Umbraco.Core.Cache // an object that represent a value that has not been created yet protected internal static readonly object ValueNotCreated = new object(); - // manupulate the underlying cache entries - // these *must* be called from within the appropriate locks - // and use the full prefixed cache keys - protected abstract IEnumerable GetDictionaryEntries(); - protected abstract void RemoveEntry(string key); - protected abstract object GetEntry(string key); + #region IAppCache - // read-write lock the underlying cache - //protected abstract IDisposable ReadLock { get; } - //protected abstract IDisposable WriteLock { get; } - - protected abstract void EnterReadLock(); - protected abstract void ExitReadLock(); - protected abstract void EnterWriteLock(); - protected abstract void ExitWriteLock(); - - protected string GetCacheKey(string key) + /// + public virtual object Get(string key) { - return string.Format("{0}-{1}", CacheItemPrefix, key); - } - - protected internal static Lazy GetSafeLazy(Func getCacheItem) - { - // try to generate the value and if it fails, - // wrap in an ExceptionHolder - would be much simpler - // to just use lazy.IsValueFaulted alas that field is - // internal - return new Lazy(() => - { - try - { - return getCacheItem(); - } - catch (Exception e) - { - return new ExceptionHolder(ExceptionDispatchInfo.Capture(e)); - } - }); - } - - protected internal static object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false) - { - // if onlyIfValueIsCreated, do not trigger value creation - // must return something, though, to differenciate from null values - if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated; - - // if execution has thrown then lazy.IsValueCreated is false - // and lazy.IsValueFaulted is true (but internal) so we use our - // own exception holder (see Lazy source code) to return null - if (lazy.Value is ExceptionHolder) return null; - - // we have a value and execution has not thrown so returning - // here does not throw - unless we're re-entering, take care of it + key = GetCacheKey(key); + Lazy result; try { - return lazy.Value; + EnterReadLock(); + result = GetEntry(key) as Lazy; // null if key not found } - catch (InvalidOperationException e) + finally { - throw new InvalidOperationException("The method that computes a value for the cache has tried to read that value from the cache.", e); + ExitReadLock(); } + return result == null ? null : GetSafeLazyValue(result); // return exceptions as null } - internal class ExceptionHolder + /// + public abstract object Get(string key, Func factory); + + /// + public virtual IEnumerable SearchByKey(string keyStartsWith) { - public ExceptionHolder(ExceptionDispatchInfo e) + var plen = CacheItemPrefix.Length + 1; + IEnumerable entries; + try { - Exception = e; + EnterReadLock(); + entries = GetDictionaryEntries() + .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) + .ToArray(); // evaluate while locked + } + finally + { + ExitReadLock(); } - public ExceptionDispatchInfo Exception { get; } + return entries + .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null); // backward compat, don't store null values in the cache } - #region Clear + /// + public virtual IEnumerable SearchByRegex(string regex) + { + const string prefix = CacheItemPrefix + "-"; + var compiled = new Regex(regex, RegexOptions.Compiled); + var plen = prefix.Length; + IEnumerable entries; + try + { + EnterReadLock(); + entries = GetDictionaryEntries() + .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen))) + .ToArray(); // evaluate while locked + } + finally + { + ExitReadLock(); + } + return entries + .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null); // backward compat, don't store null values in the cache + } - public virtual void ClearAllCache() + /// + public virtual void Clear() { try { @@ -106,7 +102,8 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheItem(string key) + /// + public virtual void Clear(string key) { var cacheKey = GetCacheKey(key); try @@ -120,7 +117,8 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheObjectTypes(string typeName) + /// + public virtual void ClearOfType(string typeName) { var type = TypeFinder.GetTypeByName(typeName); if (type == null) return; @@ -149,7 +147,8 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheObjectTypes() + /// + public virtual void ClearOfType() { var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; @@ -178,7 +177,8 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheObjectTypes(Func predicate) + /// + public virtual void ClearOfType(Func predicate) { var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; @@ -210,7 +210,8 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheByKeySearch(string keyStartsWith) + /// + public virtual void ClearByKey(string keyStartsWith) { var plen = CacheItemPrefix.Length + 1; try @@ -227,14 +228,16 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheByKeyExpression(string regexString) + /// + public virtual void ClearByRegex(string regex) { + var compiled = new Regex(regex, RegexOptions.Compiled); var plen = CacheItemPrefix.Length + 1; try { EnterWriteLock(); foreach (var entry in GetDictionaryEntries() - .Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString)) + .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen))) .ToArray()) RemoveEntry((string) entry.Key); } @@ -246,67 +249,80 @@ namespace Umbraco.Core.Cache #endregion - #region Get + #region Dictionary - public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + // manipulate the underlying cache entries + // these *must* be called from within the appropriate locks + // and use the full prefixed cache keys + protected abstract IEnumerable GetDictionaryEntries(); + protected abstract void RemoveEntry(string key); + protected abstract object GetEntry(string key); + + // read-write lock the underlying cache + //protected abstract IDisposable ReadLock { get; } + //protected abstract IDisposable WriteLock { get; } + + protected abstract void EnterReadLock(); + protected abstract void ExitReadLock(); + protected abstract void EnterWriteLock(); + protected abstract void ExitWriteLock(); + + protected string GetCacheKey(string key) { - var plen = CacheItemPrefix.Length + 1; - IEnumerable entries; - try - { - EnterReadLock(); - entries = GetDictionaryEntries() - .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) - .ToArray(); // evaluate while locked - } - finally - { - ExitReadLock(); - } - - return entries - .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null); // backward compat, don't store null values in the cache + return $"{CacheItemPrefix}-{key}"; } - public virtual IEnumerable GetCacheItemsByKeyExpression(string regexString) + protected internal static Lazy GetSafeLazy(Func getCacheItem) { - const string prefix = CacheItemPrefix + "-"; - var plen = prefix.Length; - IEnumerable entries; - try + // try to generate the value and if it fails, + // wrap in an ExceptionHolder - would be much simpler + // to just use lazy.IsValueFaulted alas that field is + // internal + return new Lazy(() => { - EnterReadLock(); - entries = GetDictionaryEntries() - .Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString)) - .ToArray(); // evaluate while locked - } - finally - { - ExitReadLock(); - } - return entries - .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null); // backward compat, don't store null values in the cache + try + { + return getCacheItem(); + } + catch (Exception e) + { + return new ExceptionHolder(ExceptionDispatchInfo.Capture(e)); + } + }); } - public virtual object GetCacheItem(string cacheKey) + protected internal static object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false) { - cacheKey = GetCacheKey(cacheKey); - Lazy result; + // if onlyIfValueIsCreated, do not trigger value creation + // must return something, though, to differentiate from null values + if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated; + + // if execution has thrown then lazy.IsValueCreated is false + // and lazy.IsValueFaulted is true (but internal) so we use our + // own exception holder (see Lazy source code) to return null + if (lazy.Value is ExceptionHolder) return null; + + // we have a value and execution has not thrown so returning + // here does not throw - unless we're re-entering, take care of it try { - EnterReadLock(); - result = GetEntry(cacheKey) as Lazy; // null if key not found + return lazy.Value; } - finally + catch (InvalidOperationException e) { - ExitReadLock(); + throw new InvalidOperationException("The method that computes a value for the cache has tried to read that value from the cache.", e); } - return result == null ? null : GetSafeLazyValue(result); // return exceptions as null } - public abstract object GetCacheItem(string cacheKey, Func getCacheItem); + internal class ExceptionHolder + { + public ExceptionHolder(ExceptionDispatchInfo e) + { + Exception = e; + } + + public ExceptionDispatchInfo Exception { get; } + } #endregion } diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index 319d84d41f..c3b69d9a6d 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Cache private readonly Func _entityGetId; private readonly bool _expires; - public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, IScopeAccessor scopeAccessor, Func entityGetId, bool expires) + public FullDataSetRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, Func entityGetId, bool expires) : base(cache, scopeAccessor) { _entityGetId = entityGetId; @@ -55,11 +55,11 @@ namespace Umbraco.Core.Cache if (_expires) { - Cache.InsertCacheItem(key, () => new DeepCloneableList(entities), TimeSpan.FromMinutes(5), true); + Cache.Insert(key, () => new DeepCloneableList(entities), TimeSpan.FromMinutes(5), true); } else { - Cache.InsertCacheItem(key, () => new DeepCloneableList(entities)); + Cache.Insert(key, () => new DeepCloneableList(entities)); } } @@ -171,7 +171,7 @@ namespace Umbraco.Core.Cache /// public override void ClearAll() { - Cache.ClearCacheItem(GetEntityTypeCacheKey()); + Cache.Clear(GetEntityTypeCacheKey()); } } } diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestAppCache.cs similarity index 53% rename from src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs rename to src/Umbraco.Core/Cache/HttpRequestAppCache.cs index 52c230ff71..dcb2621d75 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestAppCache.cs @@ -7,54 +7,83 @@ using System.Web; namespace Umbraco.Core.Cache { /// - /// A cache provider that caches items in the HttpContext.Items + /// Implements a fast on top of HttpContext.Items. /// /// - /// If the Items collection is null, then this provider has no effect + /// If no current HttpContext items can be found (no current HttpContext, + /// or no Items...) then this cache acts as a pass-through and does not cache + /// anything. /// - internal class HttpRequestCacheProvider : DictionaryCacheProviderBase + internal class HttpRequestAppCache : FastDictionaryAppCacheBase { - // context provider - // the idea is that there is only one, application-wide HttpRequestCacheProvider instance, - // that is initialized with a method that returns the "current" context. - // NOTE - // but then it is initialized with () => new HttpContextWrapper(HttpContent.Current) - // which is higly inefficient because it creates a new wrapper each time we refer to _context() - // so replace it with _context1 and _context2 below + a way to get context.Items. - //private readonly Func _context; - - // NOTE - // and then in almost 100% cases _context2 will be () => HttpContext.Current - // so why not bring that logic in here and fallback on to HttpContext.Current when - // _context1 is null? - //private readonly HttpContextBase _context1; - //private readonly Func _context2; private readonly HttpContextBase _context; - private IDictionary ContextItems - { - //get { return _context1 != null ? _context1.Items : _context2().Items; } - get { return _context != null ? _context.Items : HttpContext.Current.Items; } - } - - private bool HasContextItems - { - get { return (_context != null && _context.Items != null) || HttpContext.Current != null; } - } - - // for unit tests - public HttpRequestCacheProvider(HttpContextBase context) + /// + /// Initializes a new instance of the class with a context, for unit tests! + /// + public HttpRequestAppCache(HttpContextBase context) { _context = context; } - // main constructor - // will use HttpContext.Current - public HttpRequestCacheProvider(/*Func context*/) + /// + /// Initializes a new instance of the class. + /// + /// + /// Will use HttpContext.Current. + /// fixme - should use IHttpContextAccessor + /// + public HttpRequestAppCache() + { } + + private IDictionary ContextItems => _context?.Items ?? HttpContext.Current?.Items; + + private bool HasContextItems => _context?.Items != null || HttpContext.Current != null; + + /// + public override object Get(string key, Func factory) { - //_context2 = context; + //no place to cache so just return the callback result + if (HasContextItems == false) return factory(); + + key = GetCacheKey(key); + + Lazy result; + + try + { + EnterWriteLock(); + result = ContextItems[key] as Lazy; // null if key not found + + // 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(factory); + ContextItems[key] = result; + } + } + finally + { + ExitWriteLock(); + } + + // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache + // exceptions (but try again and again) and silently eat them - however at + // some point we have to report them - so need to re-throw here + + // this does not throw anymore + //return result.Value; + + var value = result.Value; // will not throw (safe lazy) + if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once! + return value; } + #region Entries + protected override IEnumerable GetDictionaryEntries() { const string prefix = CacheItemPrefix + "-"; @@ -62,7 +91,7 @@ namespace Umbraco.Core.Cache if (HasContextItems == false) return Enumerable.Empty(); return ContextItems.Cast() - .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); + .Where(x => x.Key is string s && s.StartsWith(prefix)); } protected override void RemoveEntry(string key) @@ -77,6 +106,8 @@ namespace Umbraco.Core.Cache return HasContextItems ? ContextItems[key] : null; } + #endregion + #region Lock private bool _entered; @@ -103,59 +134,5 @@ namespace Umbraco.Core.Cache } #endregion - - #region Get - - public override object GetCacheItem(string cacheKey, Func getCacheItem) - { - //no place to cache so just return the callback result - if (HasContextItems == false) return getCacheItem(); - - cacheKey = GetCacheKey(cacheKey); - - Lazy result; - - try - { - EnterWriteLock(); - result = ContextItems[cacheKey] as Lazy; // null if key not found - - // 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); - ContextItems[cacheKey] = result; - } - } - finally - { - ExitWriteLock(); - } - - // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache - // exceptions (but try again and again) and silently eat them - however at - // some point we have to report them - so need to re-throw here - - // this does not throw anymore - //return result.Value; - - var value = result.Value; // will not throw (safe lazy) - if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once! - return value; - } - - #endregion - - #region Insert - #endregion - - private class NoopLocker : DisposableObjectSlim - { - protected override void DisposeResources() - { } - } } } diff --git a/src/Umbraco.Core/Cache/IAppCache.cs b/src/Umbraco.Core/Cache/IAppCache.cs new file mode 100644 index 0000000000..674781f6d6 --- /dev/null +++ b/src/Umbraco.Core/Cache/IAppCache.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Cache +{ + /// + /// Defines an application cache. + /// + public interface IAppCache + { + /// + /// Gets an item identified by its key. + /// + /// The key of the item. + /// The item, or null if the item was not found. + object Get(string key); + + /// + /// Gets or creates an item identified by its key. + /// + /// The key of the item. + /// A factory function that can create the item. + /// The item. + object Get(string key, Func factory); + + /// + /// Gets items with a key starting with the specified value. + /// + /// The StartsWith value to use in the search. + /// Items matching the search. + IEnumerable SearchByKey(string keyStartsWith); + + /// + /// Gets items with a key matching a regular expression. + /// + /// The regular expression. + /// Items matching the search. + IEnumerable SearchByRegex(string regex); + + /// + /// Removes all items from the cache. + /// + void Clear(); + + /// + /// Removes an item identified by its key from the cache. + /// + /// The key of the item. + void Clear(string key); + + /// + /// Removes items of a specified type from the cache. + /// + /// The name of the type to remove. + /// + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). + /// Performs a case-sensitive search. + /// + void ClearOfType(string typeName); + + /// + /// Removes items of a specified type from the cache. + /// + /// The type of the items to remove. + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). + void ClearOfType(); + + /// + /// Removes items of a specified type from the cache. + /// + /// The type of the items to remove. + /// The predicate to satisfy. + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). + void ClearOfType(Func predicate); + + /// + /// Clears items with a key starting with the specified value. + /// + /// The StartsWith value to use in the search. + void ClearByKey(string keyStartsWith); + + /// + /// Clears items with a key matching a regular expression. + /// + /// The regular expression. + void ClearByRegex(string regex); + } +} diff --git a/src/Umbraco.Core/Cache/IAppPolicyCache.cs b/src/Umbraco.Core/Cache/IAppPolicyCache.cs new file mode 100644 index 0000000000..90b0ccb9fd --- /dev/null +++ b/src/Umbraco.Core/Cache/IAppPolicyCache.cs @@ -0,0 +1,54 @@ +using System; +using System.Web.Caching; + +// fixme should this be/support non-web? + +namespace Umbraco.Core.Cache +{ + /// + /// Defines an application cache that support cache policies. + /// + /// A cache policy can be used to cache with timeouts, + /// or depending on files, and with a remove callback, etc. + public interface IAppPolicyCache : IAppCache + { + /// + /// Gets an item identified by its key. + /// + /// The key of the item. + /// A factory function that can create the item. + /// An optional cache timeout. + /// An optional value indicating whether the cache timeout is sliding (default is false). + /// An optional cache priority (default is Normal). + /// An optional callback to handle removals. + /// Files the cache entry depends on. + /// The item. + object Get( + string key, + Func factory, + TimeSpan? timeout, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null); + + /// + /// Inserts an item. + /// + /// The key of the item. + /// A factory function that can create the item. + /// An optional cache timeout. + /// An optional value indicating whether the cache timeout is sliding (default is false). + /// An optional cache priority (default is Normal). + /// An optional callback to handle removals. + /// Files the cache entry depends on. + void Insert( + string key, + Func factory, + TimeSpan? timeout = null, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null); + } +} diff --git a/src/Umbraco.Core/Cache/ICacheProvider.cs b/src/Umbraco.Core/Cache/ICacheProvider.cs deleted file mode 100644 index 177f7570c2..0000000000 --- a/src/Umbraco.Core/Cache/ICacheProvider.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Cache -{ - /// - /// An interface for implementing a basic cache provider - /// - public interface ICacheProvider - { - /// - /// Removes all items from the cache. - /// - void ClearAllCache(); - - /// - /// Removes an item from the cache, identified by its key. - /// - /// The key of the item. - void ClearCacheItem(string key); - - /// - /// Removes items from the cache, of a specified type. - /// - /// The name of the type to remove. - /// - /// If the type is an interface, then all items of a type implementing that interface are - /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from - /// the specified type are not removed). - /// Performs a case-sensitive search. - /// - void ClearCacheObjectTypes(string typeName); - - /// - /// Removes items from the cache, of a specified type. - /// - /// The type of the items to remove. - /// If the type is an interface, then all items of a type implementing that interface are - /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from - /// the specified type are not removed). - void ClearCacheObjectTypes(); - - /// - /// Removes items from the cache, of a specified type, satisfying a predicate. - /// - /// The type of the items to remove. - /// The predicate to satisfy. - /// If the type is an interface, then all items of a type implementing that interface are - /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from - /// the specified type are not removed). - void ClearCacheObjectTypes(Func predicate); - - void ClearCacheByKeySearch(string keyStartsWith); - void ClearCacheByKeyExpression(string regexString); - - IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); - IEnumerable GetCacheItemsByKeyExpression(string regexString); - - /// - /// Returns an item with a given key - /// - /// - /// - object GetCacheItem(string cacheKey); - - object GetCacheItem(string cacheKey, Func getCacheItem); - } -} diff --git a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs deleted file mode 100644 index 9f3d687e1d..0000000000 --- a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Runtime.Caching; -using System.Text; -using System.Web.Caching; -using CacheItemPriority = System.Web.Caching.CacheItemPriority; - -namespace Umbraco.Core.Cache -{ - /// - /// An abstract class for implementing a runtime cache provider - /// - /// - /// - public interface IRuntimeCacheProvider : ICacheProvider - { - object GetCacheItem( - string cacheKey, - Func getCacheItem, - TimeSpan? timeout, - bool isSliding = false, - CacheItemPriority priority = CacheItemPriority.Normal, - CacheItemRemovedCallback removedCallback = null, - string[] dependentFiles = null); - - void InsertCacheItem( - string cacheKey, - Func getCacheItem, - TimeSpan? timeout = null, - bool isSliding = false, - CacheItemPriority priority = CacheItemPriority.Normal, - CacheItemRemovedCallback removedCallback = null, - string[] dependentFiles = null); - - } -} diff --git a/src/Umbraco.Core/Cache/IsolatedCaches.cs b/src/Umbraco.Core/Cache/IsolatedCaches.cs new file mode 100644 index 0000000000..f070fe8b55 --- /dev/null +++ b/src/Umbraco.Core/Cache/IsolatedCaches.cs @@ -0,0 +1,41 @@ +using System; + +namespace Umbraco.Core.Cache +{ + /// + /// Represents a dictionary of for types. + /// + /// + /// Isolated caches are used by e.g. repositories, to ensure that each cached entity + /// type has its own cache, so that lookups are fast and the repository does not need to + /// search through all keys on a global scale. + /// + public class IsolatedCaches : AppPolicedCacheDictionary + { + /// + /// Initializes a new instance of the class. + /// + /// + public IsolatedCaches(Func cacheFactory) + : base(cacheFactory) + { } + + /// + /// Gets a cache. + /// + public IAppPolicyCache GetOrCreate() + => GetOrCreate(typeof(T)); + + /// + /// Tries to get a cache. + /// + public Attempt Get() + => Get(typeof(T)); + + /// + /// Clears a cache. + /// + public void ClearCache() + => ClearCache(typeof(T)); + } +} diff --git a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs deleted file mode 100644 index ee73a17532..0000000000 --- a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Concurrent; - -namespace Umbraco.Core.Cache -{ - /// - /// Used to get/create/manipulate isolated runtime cache - /// - /// - /// This is useful for repository level caches to ensure that cache lookups by key are fast so - /// that the repository doesn't need to search through all keys on a global scale. - /// - public class IsolatedRuntimeCache - { - internal Func CacheFactory { get; set; } - - /// - /// Constructor that allows specifying a factory for the type of runtime isolated cache to create - /// - /// - public IsolatedRuntimeCache(Func cacheFactory) - { - CacheFactory = cacheFactory; - } - - private readonly ConcurrentDictionary _isolatedCache = new ConcurrentDictionary(); - - /// - /// Returns an isolated runtime cache for a given type - /// - /// - /// - public IRuntimeCacheProvider GetOrCreateCache() - { - return _isolatedCache.GetOrAdd(typeof(T), type => CacheFactory(type)); - } - - /// - /// Returns an isolated runtime cache for a given type - /// - /// - public IRuntimeCacheProvider GetOrCreateCache(Type type) - { - return _isolatedCache.GetOrAdd(type, t => CacheFactory(t)); - } - - /// - /// Tries to get a cache by the type specified - /// - /// - /// - public Attempt GetCache() - { - IRuntimeCacheProvider cache; - if (_isolatedCache.TryGetValue(typeof(T), out cache)) - { - return Attempt.Succeed(cache); - } - return Attempt.Fail(); - } - - /// - /// Clears all values inside this isolated runtime cache - /// - /// - /// - public void ClearCache() - { - IRuntimeCacheProvider cache; - if (_isolatedCache.TryGetValue(typeof(T), out cache)) - { - cache.ClearAllCache(); - } - } - - /// - /// Clears all of the isolated caches - /// - public void ClearAllCaches() - { - foreach (var key in _isolatedCache.Keys) - { - IRuntimeCacheProvider cache; - if (_isolatedCache.TryRemove(key, out cache)) - { - cache.ClearAllCache(); - } - } - } - } -} diff --git a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs index 27302f0786..d9d9644a36 100644 --- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs @@ -13,8 +13,8 @@ namespace Umbraco.Core.Cache /// /// Initializes a new instance of the . /// - /// A cache helper. - protected JsonCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper) + /// A cache helper. + protected JsonCacheRefresherBase(AppCaches appCaches) : base(appCaches) { } /// diff --git a/src/Umbraco.Core/Cache/NoAppCache.cs b/src/Umbraco.Core/Cache/NoAppCache.cs new file mode 100644 index 0000000000..d3359a30ba --- /dev/null +++ b/src/Umbraco.Core/Cache/NoAppCache.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Caching; + +namespace Umbraco.Core.Cache +{ + /// + /// Implements and do not cache. + /// + public class NoAppCache : IAppPolicyCache + { + private NoAppCache() { } + + /// + /// Gets the singleton instance. + /// + public static NoAppCache Instance { get; } = new NoAppCache(); + + /// + public virtual object Get(string cacheKey) + { + return null; + } + + /// + public virtual object Get(string cacheKey, Func factory) + { + return factory(); + } + + /// + public virtual IEnumerable SearchByKey(string keyStartsWith) + { + return Enumerable.Empty(); + } + + /// + public IEnumerable SearchByRegex(string regex) + { + return Enumerable.Empty(); + } + + /// + public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + return factory(); + } + + /// + public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { } + + /// + public virtual void Clear() + { } + + /// + public virtual void Clear(string key) + { } + + /// + public virtual void ClearOfType(string typeName) + { } + + /// + public virtual void ClearOfType() + { } + + /// + public virtual void ClearOfType(Func predicate) + { } + + /// + public virtual void ClearByKey(string keyStartsWith) + { } + + /// + public virtual void ClearByRegex(string regex) + { } + } +} diff --git a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs index a3e7335d7f..b1a12ec411 100644 --- a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Cache public static NoCacheRepositoryCachePolicy Instance { get; } = new NoCacheRepositoryCachePolicy(); - public IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) + public IRepositoryCachePolicy Scoped(IAppPolicyCache runtimeCache, IScope scope) { throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs deleted file mode 100644 index 78286f75e2..0000000000 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Caching; - -namespace Umbraco.Core.Cache -{ - /// - /// Represents a cache provider that does not cache anything. - /// - public class NullCacheProvider : IRuntimeCacheProvider - { - private NullCacheProvider() { } - - public static NullCacheProvider Instance { get; } = new NullCacheProvider(); - - public virtual void ClearAllCache() - { } - - public virtual void ClearCacheItem(string key) - { } - - public virtual void ClearCacheObjectTypes(string typeName) - { } - - public virtual void ClearCacheObjectTypes() - { } - - public virtual void ClearCacheObjectTypes(Func predicate) - { } - - public virtual void ClearCacheByKeySearch(string keyStartsWith) - { } - - public virtual void ClearCacheByKeyExpression(string regexString) - { } - - public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - return Enumerable.Empty(); - } - - public IEnumerable GetCacheItemsByKeyExpression(string regexString) - { - return Enumerable.Empty(); - } - - public virtual object GetCacheItem(string cacheKey) - { - return default(object); - } - - public virtual object GetCacheItem(string cacheKey, Func getCacheItem) - { - return getCacheItem(); - } - - public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - return getCacheItem(); - } - - public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { } - } -} diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs similarity index 73% rename from src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs rename to src/Umbraco.Core/Cache/ObjectCacheAppCache.cs index 8a844bbc9b..5c4f76f51d 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs @@ -11,31 +11,142 @@ using CacheItemPriority = System.Web.Caching.CacheItemPriority; namespace Umbraco.Core.Cache { /// - /// Represents a cache provider that caches item in a . - /// A cache provider that wraps the logic of a System.Runtime.Caching.ObjectCache + /// Implements on top of a . /// - /// The is created with name "in-memory". That name is - /// used to retrieve configuration options. It does not identify the memory cache, i.e. - /// each instance of this class has its own, independent, memory cache. - public class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider + public class ObjectCacheAppCache : IAppPolicyCache { private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - internal ObjectCache MemoryCache; /// - /// Used for debugging + /// Initializes a new instance of the . /// - internal Guid InstanceId { get; private set; } - - public ObjectCacheRuntimeCacheProvider() + public ObjectCacheAppCache() { + // the MemoryCache is created with name "in-memory". That name is + // used to retrieve configuration options. It does not identify the memory cache, i.e. + // each instance of this class has its own, independent, memory cache. MemoryCache = new MemoryCache("in-memory"); - InstanceId = Guid.NewGuid(); } - #region Clear + /// + /// Gets the internal memory cache, for tests only! + /// + internal ObjectCache MemoryCache { get; private set; } - public virtual void ClearAllCache() + /// + public object Get(string key) + { + Lazy result; + try + { + _locker.EnterReadLock(); + result = MemoryCache.Get(key) as Lazy; // null if key not found + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + return result == null ? null : FastDictionaryAppCacheBase.GetSafeLazyValue(result); // return exceptions as null + } + + /// + public object Get(string key, Func factory) + { + return Get(key, factory, null); + } + + /// + public IEnumerable SearchByKey(string keyStartsWith) + { + KeyValuePair[] entries; + try + { + _locker.EnterReadLock(); + entries = MemoryCache + .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) + .ToArray(); // evaluate while locked + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + return entries + .Select(x => FastDictionaryAppCacheBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null) // backward compat, don't store null values in the cache + .ToList(); + } + + /// + public IEnumerable SearchByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); + + KeyValuePair[] entries; + try + { + _locker.EnterReadLock(); + entries = MemoryCache + .Where(x => compiled.IsMatch(x.Key)) + .ToArray(); // evaluate while locked + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + return entries + .Select(x => FastDictionaryAppCacheBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null) // backward compat, don't store null values in the cache + .ToList(); + } + + /// + public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + // see notes in HttpRuntimeAppCache + + Lazy result; + + using (var lck = new UpgradeableReadLock(_locker)) + { + result = MemoryCache.Get(key) as Lazy; + if (result == null || FastDictionaryAppCacheBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null + { + result = FastDictionaryAppCacheBase.GetSafeLazy(factory); + var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); + + lck.UpgradeToWriteLock(); + //NOTE: This does an add or update + MemoryCache.Set(key, result, policy); + } + } + + //return result.Value; + + var value = result.Value; // will not throw (safe lazy) + if (value is FastDictionaryAppCacheBase.ExceptionHolder eh) eh.Exception.Throw(); // throw once! + return value; + } + + /// + public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + // NOTE - here also we must insert a Lazy but we can evaluate it right now + // and make sure we don't store a null value. + + var result = FastDictionaryAppCacheBase.GetSafeLazy(factory); + var value = result.Value; // force evaluation now + if (value == null) return; // do not store null values (backward compat) + + var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); + //NOTE: This does an add or update + MemoryCache.Set(key, result, policy); + } + + /// + public virtual void Clear() { try { @@ -50,7 +161,8 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheItem(string key) + /// + public virtual void Clear(string key) { try { @@ -65,7 +177,8 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheObjectTypes(string typeName) + /// + public virtual void ClearOfType(string typeName) { var type = TypeFinder.GetTypeByName(typeName); if (type == null) return; @@ -79,7 +192,7 @@ namespace Umbraco.Core.Cache // x.Value is Lazy and not null, its value may be null // remove null values as well, does not hurt // get non-created as NonCreatedValue & exceptions as null - var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); + var value = FastDictionaryAppCacheBase.GetSafeLazyValue((Lazy)x.Value, true); // if T is an interface remove anything that implements that interface // otherwise remove exact types (not inherited types) @@ -96,12 +209,13 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheObjectTypes() + /// + public virtual void ClearOfType() { try { _locker.EnterWriteLock(); - var typeOfT = typeof (T); + var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; foreach (var key in MemoryCache .Where(x => @@ -109,7 +223,7 @@ namespace Umbraco.Core.Cache // x.Value is Lazy and not null, its value may be null // remove null values as well, does not hurt // get non-created as NonCreatedValue & exceptions as null - var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); + var value = FastDictionaryAppCacheBase.GetSafeLazyValue((Lazy)x.Value, true); // if T is an interface remove anything that implements that interface // otherwise remove exact types (not inherited types) @@ -127,7 +241,8 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheObjectTypes(Func predicate) + /// + public virtual void ClearOfType(Func predicate) { try { @@ -140,7 +255,7 @@ namespace Umbraco.Core.Cache // x.Value is Lazy and not null, its value may be null // remove null values as well, does not hurt // get non-created as NonCreatedValue & exceptions as null - var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); + var value = FastDictionaryAppCacheBase.GetSafeLazyValue((Lazy)x.Value, true); if (value == null) return true; // if T is an interface remove anything that implements that interface @@ -159,7 +274,8 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheByKeySearch(string keyStartsWith) + /// + public virtual void ClearByKey(string keyStartsWith) { try { @@ -177,13 +293,16 @@ namespace Umbraco.Core.Cache } } - public virtual void ClearCacheByKeyExpression(string regexString) + /// + public virtual void ClearByRegex(string regex) { + var compiled = new Regex(regex, RegexOptions.Compiled); + try { _locker.EnterWriteLock(); foreach (var key in MemoryCache - .Where(x => Regex.IsMatch(x.Key, regexString)) + .Where(x => compiled.IsMatch(x.Key)) .Select(x => x.Key) .ToArray()) // ToArray required to remove MemoryCache.Remove(key); @@ -195,120 +314,6 @@ namespace Umbraco.Core.Cache } } - #endregion - - #region Get - - public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - KeyValuePair[] entries; - try - { - _locker.EnterReadLock(); - entries = MemoryCache - .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) - .ToArray(); // evaluate while locked - } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - return entries - .Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null) // backward compat, don't store null values in the cache - .ToList(); - } - - public IEnumerable GetCacheItemsByKeyExpression(string regexString) - { - KeyValuePair[] entries; - try - { - _locker.EnterReadLock(); - entries = MemoryCache - .Where(x => Regex.IsMatch(x.Key, regexString)) - .ToArray(); // evaluate while locked - } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - return entries - .Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null) // backward compat, don't store null values in the cache - .ToList(); - } - - public object GetCacheItem(string cacheKey) - { - Lazy result; - try - { - _locker.EnterReadLock(); - result = MemoryCache.Get(cacheKey) as Lazy; // null if key not found - } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - return result == null ? null : DictionaryCacheProviderBase.GetSafeLazyValue(result); // return exceptions as null - } - - public object GetCacheItem(string cacheKey, Func getCacheItem) - { - 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) - { - // see notes in HttpRuntimeCacheProvider - - Lazy result; - - using (var lck = new UpgradeableReadLock(_locker)) - { - result = MemoryCache.Get(cacheKey) as Lazy; - if (result == null || DictionaryCacheProviderBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null - { - result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); - var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); - - lck.UpgradeToWriteLock(); - //NOTE: This does an add or update - MemoryCache.Set(cacheKey, result, policy); - } - } - - //return result.Value; - - var value = result.Value; // will not throw (safe lazy) - if (value is DictionaryCacheProviderBase.ExceptionHolder eh) eh.Exception.Throw(); // throw once! - return value; - } - - #endregion - - #region Insert - - public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - // NOTE - here also we must insert a Lazy but we can evaluate it right now - // and make sure we don't store a null value. - - var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); - var value = result.Value; // force evaluation now - if (value == null) return; // do not store null values (backward compat) - - var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); - //NOTE: This does an add or update - MemoryCache.Set(cacheKey, result, policy); - } - - #endregion - private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { var absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : (timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); diff --git a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs index 22bc46e020..4a3a03a0d6 100644 --- a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs @@ -15,8 +15,8 @@ namespace Umbraco.Core.Cache /// /// Initializes a new instance of the . /// - /// A cache helper. - protected PayloadCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper) + /// A cache helper. + protected PayloadCacheRefresherBase(AppCaches appCaches) : base(appCaches) { } #region Json diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs index 2d21d410a7..27fe4e3035 100644 --- a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs @@ -13,16 +13,16 @@ namespace Umbraco.Core.Cache internal abstract class RepositoryCachePolicyBase : IRepositoryCachePolicy where TEntity : class, IEntity { - private readonly IRuntimeCacheProvider _globalCache; + private readonly IAppPolicyCache _globalCache; private readonly IScopeAccessor _scopeAccessor; - protected RepositoryCachePolicyBase(IRuntimeCacheProvider globalCache, IScopeAccessor scopeAccessor) + protected RepositoryCachePolicyBase(IAppPolicyCache globalCache, IScopeAccessor scopeAccessor) { _globalCache = globalCache ?? throw new ArgumentNullException(nameof(globalCache)); _scopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor)); } - protected IRuntimeCacheProvider Cache + protected IAppPolicyCache Cache { get { @@ -32,9 +32,9 @@ namespace Umbraco.Core.Cache case RepositoryCacheMode.Default: return _globalCache; case RepositoryCacheMode.Scoped: - return ambientScope.IsolatedRuntimeCache.GetOrCreateCache(); + return ambientScope.IsolatedCaches.GetOrCreate(); case RepositoryCacheMode.None: - return NullCacheProvider.Instance; + return NoAppCache.Instance; default: throw new NotSupportedException($"Repository cache mode {ambientScope.RepositoryCacheMode} is not supported."); } diff --git a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs index d89524d4f9..9de7519edb 100644 --- a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Cache internal class SingleItemsOnlyRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IEntity { - public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) + public SingleItemsOnlyRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) : base(cache, scopeAccessor, options) { } diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs deleted file mode 100644 index 1604add4d7..0000000000 --- a/src/Umbraco.Core/Cache/StaticCacheProvider.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace Umbraco.Core.Cache -{ - /// - /// Represents a cache provider that statically caches item in a concurrent dictionary. - /// - public class StaticCacheProvider : ICacheProvider - { - internal readonly ConcurrentDictionary StaticCache = new ConcurrentDictionary(); - - public virtual void ClearAllCache() - { - StaticCache.Clear(); - } - - public virtual void ClearCacheItem(string key) - { - object val; - StaticCache.TryRemove(key, out val); - } - - public virtual void ClearCacheObjectTypes(string typeName) - { - StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType().ToString().InvariantEquals(typeName)); - } - - public virtual void ClearCacheObjectTypes() - { - var typeOfT = typeof(T); - StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT); - } - - public virtual void ClearCacheObjectTypes(Func predicate) - { - var typeOfT = typeof(T); - StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value)); - } - - public virtual void ClearCacheByKeySearch(string keyStartsWith) - { - StaticCache.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)); - } - - public virtual void ClearCacheByKeyExpression(string regexString) - { - StaticCache.RemoveAll(kvp => Regex.IsMatch(kvp.Key, regexString)); - } - - public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - return (from KeyValuePair c in StaticCache - where c.Key.InvariantStartsWith(keyStartsWith) - select c.Value).ToList(); - } - - public IEnumerable GetCacheItemsByKeyExpression(string regexString) - { - return (from KeyValuePair c in StaticCache - where Regex.IsMatch(c.Key, regexString) - select c.Value).ToList(); - } - - public virtual object GetCacheItem(string cacheKey) - { - var result = StaticCache[cacheKey]; - return result; - } - - public virtual object GetCacheItem(string cacheKey, Func getCacheItem) - { - return StaticCache.GetOrAdd(cacheKey, key => getCacheItem()); - } - } -} diff --git a/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs b/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs index 4defc15482..0b5a04b571 100644 --- a/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs @@ -14,9 +14,9 @@ namespace Umbraco.Core.Cache /// /// Initializes a new instance of the . /// - /// A cache helper. - protected TypedCacheRefresherBase(CacheHelper cacheHelper) - : base(cacheHelper) + /// A cache helper. + protected TypedCacheRefresherBase(AppCaches appCaches) + : base(appCaches) { } #region Refresher diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs similarity index 67% rename from src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs rename to src/Umbraco.Core/Cache/WebCachingAppCache.cs index 835c5d1ee6..c6e104221a 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -4,14 +4,14 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Web.Caching; -using CacheItemPriority = System.Web.Caching.CacheItemPriority; namespace Umbraco.Core.Cache { /// - /// A CacheProvider that wraps the logic of the HttpRuntime.Cache + /// Implements on top of a . /// - internal class HttpRuntimeCacheProvider : DictionaryCacheProviderBase, IRuntimeCacheProvider + /// The underlying cache is expected to be HttpRuntime.Cache. + internal class WebCachingAppCache : FastDictionaryAppCacheBase, IAppPolicyCache { // locker object that supports upgradeable read locking // does not need to support recursion if we implement the cache correctly and ensure @@ -21,16 +21,43 @@ namespace Umbraco.Core.Cache private readonly System.Web.Caching.Cache _cache; /// - /// Used for debugging + /// Initializes a new instance of the class. /// - internal Guid InstanceId { get; private set; } - - public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache) + public WebCachingAppCache(System.Web.Caching.Cache cache) { _cache = cache; - InstanceId = Guid.NewGuid(); } + /// + public override object Get(string key, Func factory) + { + return Get(key, factory, null, dependentFiles: null); + } + + /// + public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + CacheDependency dependency = null; + if (dependentFiles != null && dependentFiles.Any()) + { + dependency = new CacheDependency(dependentFiles); + } + return Get(key, factory, timeout, isSliding, priority, removedCallback, dependency); + } + + /// + public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + CacheDependency dependency = null; + if (dependentFiles != null && dependentFiles.Any()) + { + dependency = new CacheDependency(dependentFiles); + } + Insert(key, factory, timeout, isSliding, priority, removedCallback, dependency); + } + + #region Dictionary + protected override IEnumerable GetDictionaryEntries() { const string prefix = CacheItemPrefix + "-"; @@ -48,6 +75,8 @@ namespace Umbraco.Core.Cache return _cache.Get(key); } + #endregion + #region Lock protected override void EnterReadLock() @@ -74,33 +103,9 @@ namespace Umbraco.Core.Cache #endregion - #region Get - - /// - /// Gets (and adds if necessary) an item from the cache with all of the default parameters - /// - /// - /// - /// - public override object GetCacheItem(string cacheKey, Func getCacheItem) + private object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) { - return GetCacheItem(cacheKey, getCacheItem, null, dependentFiles: null); - } - - /// - /// This overload is here for legacy purposes - /// - /// - /// - /// - /// - /// - /// - /// - /// - internal object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) - { - cacheKey = GetCacheKey(cacheKey); + key = GetCacheKey(key); // NOTE - because we don't know what getCacheItem does, how long it will take and whether it will hang, // getCacheItem should run OUTSIDE of the global application lock else we run into lock contention and @@ -133,7 +138,7 @@ namespace Umbraco.Core.Cache try { _locker.EnterReadLock(); - result = _cache.Get(cacheKey) as Lazy; // null if key not found + result = _cache.Get(key) as Lazy; // null if key not found } finally { @@ -145,7 +150,7 @@ namespace Umbraco.Core.Cache using (var lck = new UpgradeableReadLock(_locker)) { - result = _cache.Get(cacheKey) as Lazy; // null if key not found + result = _cache.Get(key) as Lazy; // null if key not found // 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 @@ -153,13 +158,13 @@ namespace Umbraco.Core.Cache if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null { - result = GetSafeLazy(getCacheItem); + result = GetSafeLazy(factory); 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); lck.UpgradeToWriteLock(); //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); + _cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback); } } @@ -175,31 +180,7 @@ namespace Umbraco.Core.Cache return value; } - public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - CacheDependency dependency = null; - if (dependentFiles != null && dependentFiles.Any()) - { - dependency = new CacheDependency(dependentFiles); - } - return GetCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency); - } - - #endregion - - #region Insert - - /// - /// This overload is here for legacy purposes - /// - /// - /// - /// - /// - /// - /// - /// - internal void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) + private void Insert(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) { // NOTE - here also we must insert a Lazy but we can evaluate it right now // and make sure we don't store a null value. @@ -225,17 +206,5 @@ namespace Umbraco.Core.Cache _locker.ExitWriteLock(); } } - - public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - CacheDependency dependency = null; - if (dependentFiles != null && dependentFiles.Any()) - { - dependency = new CacheDependency(dependentFiles); - } - InsertCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency); - } - - #endregion } } diff --git a/src/Umbraco.Core/Components/AuditEventsComponent.cs b/src/Umbraco.Core/Components/AuditEventsComponent.cs index 134aa18414..08d4702afa 100644 --- a/src/Umbraco.Core/Components/AuditEventsComponent.cs +++ b/src/Umbraco.Core/Components/AuditEventsComponent.cs @@ -12,11 +12,36 @@ using Umbraco.Core.Services.Implement; namespace Umbraco.Core.Components { - public sealed class AuditEventsComponent : UmbracoComponentBase, IUmbracoCoreComponent + public sealed class AuditEventsComponent : IComponent { - private IAuditService _auditService; - private IUserService _userService; - private IEntityService _entityService; + private readonly IAuditService _auditService; + private readonly IUserService _userService; + private readonly IEntityService _entityService; + + public AuditEventsComponent(IAuditService auditService, IUserService userService, IEntityService entityService) + { + _auditService = auditService; + _userService = userService; + _entityService = entityService; + } + + public void Initialize() + { + UserService.SavedUserGroup += OnSavedUserGroupWithUsers; + + UserService.SavedUser += OnSavedUser; + UserService.DeletedUser += OnDeletedUser; + UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned; + + MemberService.Saved += OnSavedMember; + MemberService.Deleted += OnDeletedMember; + MemberService.AssignedRoles += OnAssignedRoles; + MemberService.RemovedRoles += OnRemovedRoles; + MemberService.Exported += OnMemberExported; + } + + public void Terminate() + { } private IUser CurrentPerformingUser { @@ -46,25 +71,6 @@ namespace Umbraco.Core.Components } } - public void Initialize(IAuditService auditService, IUserService userService, IEntityService entityService) - { - _auditService = auditService; - _userService = userService; - _entityService = entityService; - - UserService.SavedUserGroup += OnSavedUserGroupWithUsers; - - UserService.SavedUser += OnSavedUser; - UserService.DeletedUser += OnDeletedUser; - UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned; - - MemberService.Saved += OnSavedMember; - MemberService.Deleted += OnDeletedMember; - MemberService.AssignedRoles += OnAssignedRoles; - MemberService.RemovedRoles += OnRemovedRoles; - MemberService.Exported += OnMemberExported; - } - private string FormatEmail(IMember member) { return member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>"; diff --git a/src/Umbraco.Core/Components/AuditEventsComposer.cs b/src/Umbraco.Core/Components/AuditEventsComposer.cs new file mode 100644 index 0000000000..692cb6c6dd --- /dev/null +++ b/src/Umbraco.Core/Components/AuditEventsComposer.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Components +{ + public sealed class AuditEventsComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Core/Components/BootLoader.cs b/src/Umbraco.Core/Components/BootLoader.cs deleted file mode 100644 index fd292990c8..0000000000 --- a/src/Umbraco.Core/Components/BootLoader.cs +++ /dev/null @@ -1,368 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using LightInject; -using Umbraco.Core.Collections; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Logging; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Components -{ - // note: this class is NOT thread-safe in any ways - - internal class BootLoader - { - private readonly IServiceContainer _container; - private readonly ProfilingLogger _proflog; - private readonly ILogger _logger; - private IUmbracoComponent[] _components; - private bool _booted; - - private const int LogThresholdMilliseconds = 100; - - /// - /// Initializes a new instance of the class. - /// - /// The application container. - public BootLoader(IServiceContainer container) - { - _container = container ?? throw new ArgumentNullException(nameof(container)); - _proflog = container.GetInstance(); - _logger = container.GetInstance(); - } - - private class EnableInfo - { - public bool Enabled; - public int Weight = -1; - } - - public void Boot(IEnumerable componentTypes, RuntimeLevel level) - { - if (_booted) throw new InvalidOperationException("Can not boot, has already booted."); - - var orderedComponentTypes = PrepareComponentTypes(componentTypes, level); - - InstanciateComponents(orderedComponentTypes); - ComposeComponents(level); - - using (var scope = _container.GetInstance().CreateScope()) - { - InitializeComponents(); - scope.Complete(); - } - - // rejoice! - _booted = true; - } - - private IEnumerable PrepareComponentTypes(IEnumerable componentTypes, RuntimeLevel level) - { - using (_proflog.DebugDuration("Preparing component types.", "Prepared component types.")) - { - return PrepareComponentTypes2(componentTypes, level); - } - } - - private IEnumerable PrepareComponentTypes2(IEnumerable componentTypes, RuntimeLevel level) - { - // create a list, remove those that cannot be enabled due to runtime level - var componentTypeList = componentTypes - .Where(x => - { - // use the min level specified by the attribute if any - // otherwise, user components have Run min level, anything else is Unknown (always run) - var attr = x.GetCustomAttribute(); - var minLevel = attr?.MinLevel ?? (x.Implements() ? RuntimeLevel.Run : RuntimeLevel.Unknown); - return level >= minLevel; - }) - .ToList(); - - // cannot remove that one - ever - if (componentTypeList.Contains(typeof(UmbracoCoreComponent)) == false) - componentTypeList.Add(typeof(UmbracoCoreComponent)); - - // enable or disable components - EnableDisableComponents(componentTypeList); - - // sort the components according to their dependencies - var requirements = new Dictionary>(); - foreach (var type in componentTypeList) requirements[type] = null; - foreach (var type in componentTypeList) - { - GatherRequirementsFromRequireAttribute(type, componentTypeList, requirements); - GatherRequirementsFromRequiredAttribute(type, componentTypeList, requirements); - } - - // only for debugging, this is verbose - //_logger.Debug(GetComponentsReport(requirements)); - - // sort components - var graph = new TopoGraph>>(kvp => kvp.Key, kvp => kvp.Value); - graph.AddItems(requirements); - List sortedComponentTypes; - try - { - sortedComponentTypes = graph.GetSortedItems().Select(x => x.Key).ToList(); - } - catch (Exception e) - { - // in case of an error, force-dump everything to log - _logger.Info("Component Report:\r\n{ComponentReport}", GetComponentsReport(requirements)); - _logger.Error(e, "Failed to sort compontents."); - throw; - } - - // bit verbose but should help for troubleshooting - var text = "Ordered Components: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComponentTypes) + Environment.NewLine; - Console.WriteLine(text); - _logger.Debug("Ordered Components: {SortedComponentTypes}", sortedComponentTypes); - - return sortedComponentTypes; - } - - private static string GetComponentsReport(Dictionary> requirements) - { - var text = new StringBuilder(); - text.AppendLine("Components & Dependencies:"); - text.AppendLine(); - - foreach (var kvp in requirements) - { - var type = kvp.Key; - - text.AppendLine(type.FullName); - foreach (var attribute in type.GetCustomAttributes()) - text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue - ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")")) - : "")); - foreach (var attribute in type.GetCustomAttributes()) - text.AppendLine(" -< " + attribute.RequiringType); - foreach (var i in type.GetInterfaces()) - { - text.AppendLine(" : " + i.FullName); - foreach (var attribute in i.GetCustomAttributes()) - text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue - ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")")) - : "")); - foreach (var attribute in i.GetCustomAttributes()) - text.AppendLine(" -< " + attribute.RequiringType); - } - if (kvp.Value != null) - foreach (var t in kvp.Value) - text.AppendLine(" = " + t); - text.AppendLine(); - } - text.AppendLine("/"); - text.AppendLine(); - return text.ToString(); - } - - private static void EnableDisableComponents(ICollection types) - { - var enabled = new Dictionary(); - - // process the enable/disable attributes - // these two attributes are *not* inherited and apply to *classes* only (not interfaces). - // remote declarations (when a component enables/disables *another* component) - // have priority over local declarations (when a component disables itself) so that - // ppl can enable components that, by default, are disabled. - // what happens in case of conflicting remote declarations is unspecified. more - // precisely, the last declaration to be processed wins, but the order of the - // declarations depends on the type finder and is unspecified. - foreach (var componentType in types) - { - foreach (var attr in componentType.GetCustomAttributes()) - { - var type = attr.EnabledType ?? componentType; - if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo(); - var weight = type == componentType ? 1 : 2; - if (enableInfo.Weight > weight) continue; - - enableInfo.Enabled = true; - enableInfo.Weight = weight; - } - foreach (var attr in componentType.GetCustomAttributes()) - { - var type = attr.DisabledType ?? componentType; - if (type == typeof(UmbracoCoreComponent)) throw new InvalidOperationException("Cannot disable UmbracoCoreComponent."); - if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo(); - var weight = type == componentType ? 1 : 2; - if (enableInfo.Weight > weight) continue; - - enableInfo.Enabled = false; - enableInfo.Weight = weight; - } - } - - // remove components that end up being disabled - foreach (var kvp in enabled.Where(x => x.Value.Enabled == false)) - types.Remove(kvp.Key); - } - - private static void GatherRequirementsFromRequireAttribute(Type type, ICollection types, IDictionary> requirements) - { - // get 'require' attributes - // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only - var requireAttributes = type - .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) // those marking interfaces - .Concat(type.GetCustomAttributes()); // those marking the component - - // what happens in case of conflicting attributes (different strong/weak for same type) is not specified. - foreach (var attr in requireAttributes) - { - if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below) - - // requiring an interface = require any enabled component implementing that interface - // unless strong, and then require at least one enabled component implementing that interface - if (attr.RequiredType.IsInterface) - { - var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x)).ToList(); - if (implems.Count > 0) - { - if (requirements[type] == null) requirements[type] = new List(); - requirements[type].AddRange(implems); - } - else if (attr.Weak == false) // if explicitely set to !weak, is strong, else is weak - throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}."); - } - // requiring a class = require that the component is enabled - // unless weak, and then requires it if it is enabled - else - { - if (types.Contains(attr.RequiredType)) - { - if (requirements[type] == null) requirements[type] = new List(); - requirements[type].Add(attr.RequiredType); - } - else if (attr.Weak != true) // if not explicitely set to weak, is strong - throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}."); - } - } - } - - private static void GatherRequirementsFromRequiredAttribute(Type type, ICollection types, IDictionary> requirements) - { - // get 'required' attributes - // fixme explain - var requiredAttributes = type - .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) - .Concat(type.GetCustomAttributes()); - - foreach (var attr in requiredAttributes) - { - if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below) - - if (attr.RequiringType.IsInterface) - { - var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x)).ToList(); - foreach (var implem in implems) - { - if (requirements[implem] == null) requirements[implem] = new List(); - requirements[implem].Add(type); - } - } - else - { - if (types.Contains(attr.RequiringType)) - { - if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List(); - requirements[attr.RequiringType].Add(type); - } - } - } - } - - private void InstanciateComponents(IEnumerable types) - { - using (_proflog.DebugDuration("Instanciating components.", "Instanciated components.")) - { - _components = types.Select(x => (IUmbracoComponent) Activator.CreateInstance(x)).ToArray(); - } - } - - private void ComposeComponents(RuntimeLevel level) - { - using (_proflog.DebugDuration($"Composing components. (log when >{LogThresholdMilliseconds}ms)", "Composed components.")) - { - var composition = new Composition(_container, level); - foreach (var component in _components) - { - var componentType = component.GetType(); - using (_proflog.DebugDuration($"Composing {componentType.FullName}.", $"Composed {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) - { - component.Compose(composition); - } - } - } - } - - private void InitializeComponents() - { - // use a container scope to ensure that PerScope instances are disposed - // components that require instances that should not survive should register them with PerScope lifetime - using (_proflog.DebugDuration($"Initializing components. (log when >{LogThresholdMilliseconds}ms)", "Initialized components.")) - using (_container.BeginScope()) - { - foreach (var component in _components) - { - var componentType = component.GetType(); - var initializers = componentType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Where(x => x.Name == "Initialize" && x.IsGenericMethod == false); - using (_proflog.DebugDuration($"Initializing {componentType.FullName}.", $"Initialised {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) - { - foreach (var initializer in initializers) - { - var parameters = initializer.GetParameters() - .Select(x => GetParameter(componentType, x.ParameterType)) - .ToArray(); - initializer.Invoke(component, parameters); - } - } - } - } - } - - private object GetParameter(Type componentType, Type parameterType) - { - object param; - - try - { - param = _container.TryGetInstance(parameterType); - } - catch (Exception e) - { - throw new BootFailedException($"Could not get parameter of type {parameterType.FullName} for component {componentType.FullName}.", e); - } - - if (param == null) throw new BootFailedException($"Could not get parameter of type {parameterType.FullName} for component {componentType.FullName}."); - return param; - } - - public void Terminate() - { - if (_booted == false) - { - _proflog.Logger.Warn("Cannot terminate, has not booted."); - return; - } - - using (_proflog.DebugDuration($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated.")) - { - for (var i = _components.Length - 1; i >= 0; i--) // terminate components in reverse order - { - var component = _components[i]; - var componentType = component.GetType(); - using (_proflog.DebugDuration($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) - { - component.Terminate(); - } - } - } - } - } -} diff --git a/src/Umbraco.Core/Components/ComponentCollection.cs b/src/Umbraco.Core/Components/ComponentCollection.cs new file mode 100644 index 0000000000..4fa81b9760 --- /dev/null +++ b/src/Umbraco.Core/Components/ComponentCollection.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Components +{ + /// + /// Represents the collection of implementations. + /// + public class ComponentCollection : BuilderCollectionBase + { + private const int LogThresholdMilliseconds = 100; + + private readonly IProfilingLogger _logger; + + public ComponentCollection(IEnumerable items, IProfilingLogger logger) + : base(items) + { + _logger = logger; + } + + public void Initialize() + { + using (_logger.DebugDuration($"Initializing. (log components when >{LogThresholdMilliseconds}ms)", "Initialized.")) + { + foreach (var component in this.Reverse()) // terminate components in reverse order + { + var componentType = component.GetType(); + using (_logger.DebugDuration($"Initializing {componentType.FullName}.", $"Initialized {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + component.Initialize(); + } + } + } + } + + public void Terminate() + { + using (_logger.DebugDuration($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated.")) + { + foreach (var component in this.Reverse()) // terminate components in reverse order + { + var componentType = component.GetType(); + using (_logger.DebugDuration($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + component.Terminate(); + component.DisposeIfDisposable(); + } + } + } + } + } +} diff --git a/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs b/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs new file mode 100644 index 0000000000..584de7a8f2 --- /dev/null +++ b/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Components +{ + /// + /// Builds a . + /// + public class ComponentCollectionBuilder : OrderedCollectionBuilderBase + { + private const int LogThresholdMilliseconds = 100; + + private IProfilingLogger _logger; + + public ComponentCollectionBuilder() + { } + + protected override ComponentCollectionBuilder This => this; + + protected override IEnumerable CreateItems(IFactory factory) + { + _logger = factory.GetInstance(); + + using (_logger.DebugDuration($"Creating components. (log when >{LogThresholdMilliseconds}ms)", "Created.")) + { + return base.CreateItems(factory); + } + } + + protected override IComponent CreateItem(IFactory factory, Type itemType) + { + using (_logger.DebugDuration($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + return base.CreateItem(factory, itemType); + } + } + } +} diff --git a/src/Umbraco.Core/Components/ComponentComposer.cs b/src/Umbraco.Core/Components/ComponentComposer.cs new file mode 100644 index 0000000000..792790c42f --- /dev/null +++ b/src/Umbraco.Core/Components/ComponentComposer.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Core.Components +{ + /// + /// Provides a base class for composers which compose a component. + /// + /// The type of the component + public abstract class ComponentComposer : IComposer + where TComponent : IComponent + { + /// + public virtual void Compose(Composition composition) + { + composition.Components().Append(); + } + + // note: thanks to this class, a component that does not compose anything can be + // registered with one line: + // public class MyComponentComposer : ComponentComposer { } + } +} diff --git a/src/Umbraco.Core/Components/ComposeAfterAttribute.cs b/src/Umbraco.Core/Components/ComposeAfterAttribute.cs new file mode 100644 index 0000000000..a8fdfaa92b --- /dev/null +++ b/src/Umbraco.Core/Components/ComposeAfterAttribute.cs @@ -0,0 +1,59 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a composer requires another composer. + /// + /// + /// This attribute is *not* inherited. This means that a composer class inheriting from + /// another composer class does *not* inherit its requirements. However, the runtime checks + /// the *interfaces* of every composer for their requirements, so requirements declared on + /// interfaces are inherited by every composer class implementing the interface. + /// When targeting a class, indicates a dependency on the composer which must be enabled, + /// unless the requirement has explicitly been declared as weak (and then, only if the composer + /// is enabled). + /// When targeting an interface, indicates a dependency on enabled composers implementing + /// the interface. It could be no composer at all, unless the requirement has explicitly been + /// declared as strong (and at least one composer must be enabled). + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] + public sealed class ComposeAfterAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The type of the required composer. + public ComposeAfterAttribute(Type requiredType) + { + if (typeof(IComposer).IsAssignableFrom(requiredType) == false) + throw new ArgumentException($"Type {requiredType.FullName} is invalid here because it does not implement {typeof(IComposer).FullName}."); + RequiredType = requiredType; + } + + /// + /// Initializes a new instance of the class. + /// + /// The type of the required composer. + /// A value indicating whether the requirement is weak. + public ComposeAfterAttribute(Type requiredType, bool weak) + : this(requiredType) + { + Weak = weak; + } + + /// + /// Gets the required type. + /// + public Type RequiredType { get; } + + /// + /// Gets a value indicating whether the requirement is weak. + /// + /// Returns true if the requirement is weak (requires the other composer if it + /// is enabled), false if the requirement is strong (requires the other composer to be + /// enabled), and null if unspecified, in which case it is strong for classes and weak for + /// interfaces. + public bool? Weak { get; } + } +} diff --git a/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs b/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs new file mode 100644 index 0000000000..17065d1676 --- /dev/null +++ b/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs @@ -0,0 +1,40 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a component is required by another composer. + /// + /// + /// This attribute is *not* inherited. This means that a composer class inheriting from + /// another composer class does *not* inherit its requirements. However, the runtime checks + /// the *interfaces* of every composer for their requirements, so requirements declared on + /// interfaces are inherited by every composer class implementing the interface. + /// When targeting a class, indicates a dependency on the composer which must be enabled, + /// unless the requirement has explicitly been declared as weak (and then, only if the composer + /// is enabled). + /// When targeting an interface, indicates a dependency on enabled composers implementing + /// the interface. It could be no composer at all, unless the requirement has explicitly been + /// declared as strong (and at least one composer must be enabled). + /// + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] + public sealed class ComposeBeforeAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The type of the required composer. + public ComposeBeforeAttribute(Type requiringType) + { + if (typeof(IComposer).IsAssignableFrom(requiringType) == false) + throw new ArgumentException($"Type {requiringType.FullName} is invalid here because it does not implement {typeof(IComposer).FullName}."); + RequiringType = requiringType; + } + + /// + /// Gets the required type. + /// + public Type RequiringType { get; } + } +} diff --git a/src/Umbraco.Core/Components/Composers.cs b/src/Umbraco.Core/Components/Composers.cs new file mode 100644 index 0000000000..89deed934e --- /dev/null +++ b/src/Umbraco.Core/Components/Composers.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Umbraco.Core.Collections; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Components +{ + // note: this class is NOT thread-safe in any ways + + /// + /// Handles the composers. + /// + public class Composers + { + private readonly Composition _composition; + private readonly IProfilingLogger _logger; + private readonly IEnumerable _composerTypes; + + private const int LogThresholdMilliseconds = 100; + + /// + /// Initializes a new instance of the class. + /// + /// The composition. + /// The composer types. + /// A profiling logger. + public Composers(Composition composition, IEnumerable composerTypes, IProfilingLogger logger) + { + _composition = composition ?? throw new ArgumentNullException(nameof(composition)); + _composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + private class EnableInfo + { + public bool Enabled; + public int Weight = -1; + } + + /// + /// Instantiates and composes the composers. + /// + public void Compose() + { + // make sure it is there + _composition.WithCollectionBuilder(); + + IEnumerable orderedComposerTypes; + + using (_logger.DebugDuration("Preparing composer types.", "Prepared composer types.")) + { + orderedComposerTypes = PrepareComposerTypes(); + } + + var composers = InstantiateComposers(orderedComposerTypes); + + using (_logger.DebugDuration($"Composing composers. (log when >{LogThresholdMilliseconds}ms)", "Composed composers.")) + { + foreach (var composer in composers) + { + var componentType = composer.GetType(); + using (_logger.DebugDuration($"Composing {componentType.FullName}.", $"Composed {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + composer.Compose(_composition); + } + } + } + } + + private IEnumerable PrepareComposerTypes() + { + // create a list, remove those that cannot be enabled due to runtime level + var composerTypeList = _composerTypes + .Where(x => + { + // use the min/max levels specified by the attribute if any + // otherwise, min: user composers are Run, anything else is Unknown (always run) + // max: everything is Run (always run) + var attr = x.GetCustomAttribute(); + var minLevel = attr?.MinLevel ?? (x.Implements() ? RuntimeLevel.Run : RuntimeLevel.Unknown); + var maxLevel = attr?.MaxLevel ?? RuntimeLevel.Run; + return _composition.RuntimeState.Level >= minLevel && _composition.RuntimeState.Level <= maxLevel; + }) + .ToList(); + + // enable or disable composers + EnableDisableComposers(composerTypeList); + + // sort the composers according to their dependencies + var requirements = new Dictionary>(); + foreach (var type in composerTypeList) requirements[type] = null; + foreach (var type in composerTypeList) + { + GatherRequirementsFromRequireAttribute(type, composerTypeList, requirements); + GatherRequirementsFromRequiredByAttribute(type, composerTypeList, requirements); + } + + // only for debugging, this is verbose + //_logger.Debug(GetComposersReport(requirements)); + + // sort composers + var graph = new TopoGraph>>(kvp => kvp.Key, kvp => kvp.Value); + graph.AddItems(requirements); + List sortedComposerTypes; + try + { + sortedComposerTypes = graph.GetSortedItems().Select(x => x.Key).ToList(); + } + catch (Exception e) + { + // in case of an error, force-dump everything to log + _logger.Info("Composer Report:\r\n{ComposerReport}", GetComposersReport(requirements)); + _logger.Error(e, "Failed to sort composers."); + throw; + } + + // bit verbose but should help for troubleshooting + //var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine; + _logger.Debug("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes); + + return sortedComposerTypes; + } + + private static string GetComposersReport(Dictionary> requirements) + { + var text = new StringBuilder(); + text.AppendLine("Composers & Dependencies:"); + text.AppendLine(); + + foreach (var kvp in requirements) + { + var type = kvp.Key; + + text.AppendLine(type.FullName); + foreach (var attribute in type.GetCustomAttributes()) + text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue + ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")")) + : "")); + foreach (var attribute in type.GetCustomAttributes()) + text.AppendLine(" -< " + attribute.RequiringType); + foreach (var i in type.GetInterfaces()) + { + text.AppendLine(" : " + i.FullName); + foreach (var attribute in i.GetCustomAttributes()) + text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue + ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")")) + : "")); + foreach (var attribute in i.GetCustomAttributes()) + text.AppendLine(" -< " + attribute.RequiringType); + } + if (kvp.Value != null) + foreach (var t in kvp.Value) + text.AppendLine(" = " + t); + text.AppendLine(); + } + text.AppendLine("/"); + text.AppendLine(); + return text.ToString(); + } + + private static void EnableDisableComposers(ICollection types) + { + var enabled = new Dictionary(); + + // process the enable/disable attributes + // these two attributes are *not* inherited and apply to *classes* only (not interfaces). + // remote declarations (when a composer enables/disables *another* composer) + // have priority over local declarations (when a composer disables itself) so that + // ppl can enable composers that, by default, are disabled. + // what happens in case of conflicting remote declarations is unspecified. more + // precisely, the last declaration to be processed wins, but the order of the + // declarations depends on the type finder and is unspecified. + foreach (var composerType in types) + { + foreach (var attr in composerType.GetCustomAttributes()) + { + var type = attr.EnabledType ?? composerType; + if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo(); + var weight = type == composerType ? 1 : 2; + if (enableInfo.Weight > weight) continue; + + enableInfo.Enabled = true; + enableInfo.Weight = weight; + } + foreach (var attr in composerType.GetCustomAttributes()) + { + var type = attr.DisabledType ?? composerType; + if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo(); + var weight = type == composerType ? 1 : 2; + if (enableInfo.Weight > weight) continue; + + enableInfo.Enabled = false; + enableInfo.Weight = weight; + } + } + + // remove composers that end up being disabled + foreach (var kvp in enabled.Where(x => x.Value.Enabled == false)) + types.Remove(kvp.Key); + } + + private static void GatherRequirementsFromRequireAttribute(Type type, ICollection types, IDictionary> requirements) + { + // get 'require' attributes + // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only + var requireAttributes = type + .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) // those marking interfaces + .Concat(type.GetCustomAttributes()); // those marking the composer + + // what happens in case of conflicting attributes (different strong/weak for same type) is not specified. + foreach (var attr in requireAttributes) + { + if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below) + + // requiring an interface = require any enabled composer implementing that interface + // unless strong, and then require at least one enabled composer implementing that interface + if (attr.RequiredType.IsInterface) + { + var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x)).ToList(); + if (implems.Count > 0) + { + if (requirements[type] == null) requirements[type] = new List(); + requirements[type].AddRange(implems); + } + else if (attr.Weak == false) // if explicitly set to !weak, is strong, else is weak + throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}."); + } + // requiring a class = require that the composer is enabled + // unless weak, and then requires it if it is enabled + else + { + if (types.Contains(attr.RequiredType)) + { + if (requirements[type] == null) requirements[type] = new List(); + requirements[type].Add(attr.RequiredType); + } + else if (attr.Weak != true) // if not explicitly set to weak, is strong + throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}."); + } + } + } + + private static void GatherRequirementsFromRequiredByAttribute(Type type, ICollection types, IDictionary> requirements) + { + // get 'required' attributes + // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only + var requiredAttributes = type + .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) // those marking interfaces + .Concat(type.GetCustomAttributes()); // those marking the composer + + foreach (var attr in requiredAttributes) + { + if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below) + + // required by an interface = by any enabled composer implementing this that interface + if (attr.RequiringType.IsInterface) + { + var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x)).ToList(); + foreach (var implem in implems) + { + if (requirements[implem] == null) requirements[implem] = new List(); + requirements[implem].Add(type); + } + } + // required by a class + else + { + if (types.Contains(attr.RequiringType)) + { + if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List(); + requirements[attr.RequiringType].Add(type); + } + } + } + } + + private IEnumerable InstantiateComposers(IEnumerable types) + { + IComposer InstantiateComposer(Type type) + { + var ctor = type.GetConstructor(Array.Empty()); + if (ctor == null) + throw new InvalidOperationException($"Composer {type.FullName} does not have a parameter-less constructor."); + return (IComposer) ctor.Invoke(Array.Empty()); + } + + using (_logger.DebugDuration("Instantiating composers.", "Instantiated composers.")) + { + return types.Select(InstantiateComposer).ToArray(); + } + } + } +} diff --git a/src/Umbraco.Core/Components/Composition.cs b/src/Umbraco.Core/Components/Composition.cs index 671469c73a..6df86d793f 100644 --- a/src/Umbraco.Core/Components/Composition.cs +++ b/src/Umbraco.Core/Components/Composition.cs @@ -1,4 +1,8 @@ -using LightInject; +using System; +using System.Collections.Generic; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; namespace Umbraco.Core.Components { @@ -10,28 +14,233 @@ namespace Umbraco.Core.Components /// avoid accessing the container. This is because everything needs to be properly registered and with /// the proper lifecycle. These methods will take care of it. Directly registering into the container /// may cause issues. - public class Composition + public class Composition : IRegister { + private readonly Dictionary _builders = new Dictionary(); + private readonly Dictionary> _uniques = new Dictionary>(); + private readonly IRegister _register; + /// /// Initializes a new instance of the class. /// - /// A container. - /// The runtime level. - public Composition(IServiceContainer container, RuntimeLevel level) + /// A register. + /// A type loader. + /// A logger. + /// The runtime state. + /// Optional configs. + public Composition(IRegister register, TypeLoader typeLoader, IProfilingLogger logger, IRuntimeState runtimeState, Configs configs = null) { - Container = container; - RuntimeLevel = level; + _register = register; + TypeLoader = typeLoader; + Logger = logger; + RuntimeState = runtimeState; + + if (configs == null) + { + configs = new Configs(); + configs.AddCoreConfigs(); + } + + Configs = configs; + } + + #region Services + + /// + /// Gets the logger. + /// + public IProfilingLogger Logger { get; } + + /// + /// Gets the type loader. + /// + public TypeLoader TypeLoader { get; } + + /// + /// Gets the runtime state. + /// + public IRuntimeState RuntimeState { get; } + + /// + /// Gets the configurations. + /// + public Configs Configs { get; } + + #endregion + + #region IRegister + + /// + public object Concrete => _register.Concrete; + + /// + public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient) + => _register.Register(serviceType, lifetime); + + /// + public void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient) + => _register.Register(serviceType, implementingType, lifetime); + + /// + public void Register(Func factory, Lifetime lifetime = Lifetime.Transient) + where TService : class + => _register.Register(factory, lifetime); + + /// + public void Register(Type serviceType, object instance) + => _register.Register(serviceType, instance); + + /// + public void RegisterFor(Lifetime lifetime = Lifetime.Transient) + where TService : class + => _register.RegisterFor(lifetime); + + /// + public void RegisterFor(Type implementingType, Lifetime lifetime = Lifetime.Transient) + where TService : class + => _register.RegisterFor(implementingType, lifetime); + + /// + public void RegisterFor(Func factory, Lifetime lifetime = Lifetime.Transient) + where TService : class + => _register.RegisterFor(factory, lifetime); + + /// + public void RegisterFor(TService instance) + where TService : class + => _register.RegisterFor(instance); + + /// + public void RegisterAuto(Type serviceBaseType) + => _register.RegisterAuto(serviceBaseType); + + /// + public void ConfigureForWeb() + => _register.ConfigureForWeb(); + + /// + public IFactory CreateFactory() + { + foreach (var onCreating in OnCreatingFactory.Values) + onCreating(); + + foreach (var unique in _uniques.Values) + unique(_register); + _uniques.Clear(); // no point keep them around + + foreach (var builder in _builders.Values) + builder.RegisterWith(_register); + _builders.Clear(); // no point keep them around + + Configs.RegisterWith(_register); + + return _register.CreateFactory(); } /// - /// Gets the container. + /// Gets a dictionary of action to execute when creating the factory. /// - /// Use with care! - public IServiceContainer Container { get; } + public Dictionary OnCreatingFactory { get; } = new Dictionary(); + + #endregion + + #region Unique + + private string GetUniqueName() + => GetUniqueName(typeof(TService)); + + private string GetUniqueName(Type serviceType) + => serviceType.FullName; + + private string GetUniqueName() + => GetUniqueName(typeof(TService), typeof(TTarget)); + + private string GetUniqueName(Type serviceType, Type targetType) + => serviceType.FullName + "::" + targetType.FullName; /// - /// Gets the runtime level. + /// Registers a unique service as its own implementation. /// - public RuntimeLevel RuntimeLevel { get; } + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUnique(Type serviceType) + => _uniques[GetUniqueName(serviceType)] = register => register.Register(serviceType, Lifetime.Singleton); + + /// + /// Registers a unique service with an implementation type. + /// + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUnique(Type serviceType, Type implementingType) + => _uniques[GetUniqueName(serviceType)] = register => register.Register(serviceType, implementingType, Lifetime.Singleton); + + /// + /// Registers a unique service with an implementation factory. + /// + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUnique(Func factory) + where TService : class + => _uniques[GetUniqueName()] = register => register.Register(factory, Lifetime.Singleton); + + /// + /// Registers a unique service with an implementing instance. + /// + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUnique(Type serviceType, object instance) + => _uniques[GetUniqueName(serviceType)] = register => register.Register(serviceType, instance); + + /// + /// Registers a unique service for a target, as its own implementation. + /// + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUniqueFor() + where TService : class + => _uniques[GetUniqueName()] = register => register.RegisterFor(Lifetime.Singleton); + + /// + /// Registers a unique service for a target, with an implementing type. + /// + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUniqueFor(Type implementingType) + where TService : class + => _uniques[GetUniqueName()] = register => register.RegisterFor(implementingType, Lifetime.Singleton); + + /// + /// Registers a unique service for a target, with an implementation factory. + /// + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUniqueFor(Func factory) + where TService : class + => _uniques[GetUniqueName()] = register => register.RegisterFor(factory, Lifetime.Singleton); + + /// + /// Registers a unique service for a target, with an implementing instance. + /// + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUniqueFor(TService instance) + where TService : class + => _uniques[GetUniqueName()] = register => register.RegisterFor(instance); + + #endregion + + #region Collection Builders + + /// + /// Gets a collection builder (and registers the collection). + /// + /// The type of the collection builder. + /// The collection builder. + public TBuilder WithCollectionBuilder() + where TBuilder: ICollectionBuilder, new() + { + var typeOfBuilder = typeof(TBuilder); + + if (_builders.TryGetValue(typeOfBuilder, out var o)) + return (TBuilder) o; + + var builder = new TBuilder(); + _builders[typeOfBuilder] = builder; + return builder; + } + + #endregion } } diff --git a/src/Umbraco.Core/Components/CompositionExtensions.cs b/src/Umbraco.Core/Components/CompositionExtensions.cs index 7e94e4dc2b..bb23e89b81 100644 --- a/src/Umbraco.Core/Components/CompositionExtensions.cs +++ b/src/Umbraco.Core/Components/CompositionExtensions.cs @@ -1,9 +1,8 @@ using System; -using System.Runtime.CompilerServices; -using LightInject; using Umbraco.Core.Cache; using Umbraco.Core.Dictionary; using Umbraco.Core.Composing; +using Umbraco.Core.IO; using Umbraco.Core.Migrations; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Mappers; @@ -19,6 +18,46 @@ namespace Umbraco.Core.Components /// public static class CompositionExtensions { + #region FileSystems + + /// + /// Registers a filesystem. + /// + /// The type of the filesystem. + /// The implementing type. + /// The composition. + /// The register. + public static void RegisterFileSystem(this Composition composition) + where TImplementing : FileSystemWrapper, TFileSystem + where TFileSystem : class + { + composition.RegisterUnique(factory => + { + var fileSystems = factory.GetInstance(); + var supporting = factory.GetInstance(); + return fileSystems.GetFileSystem(supporting.For()); + }); + } + + /// + /// Registers a filesystem. + /// + /// The type of the filesystem. + /// The composition. + /// The register. + public static void RegisterFileSystem(this Composition composition) + where TFileSystem : FileSystemWrapper + { + composition.RegisterUnique(factory => + { + var fileSystems = factory.GetInstance(); + var supporting = factory.GetInstance(); + return fileSystems.GetFileSystem(supporting.For()); + }); + } + + #endregion + #region Collection Builders /// @@ -26,60 +65,66 @@ namespace Umbraco.Core.Components /// /// The composition. public static CacheRefresherCollectionBuilder CacheRefreshers(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the mappers collection builder. /// /// The composition. public static MapperCollectionBuilder Mappers(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the package actions collection builder. /// /// The composition. internal static PackageActionCollectionBuilder PackageActions(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the data editor collection builder. /// /// The composition. public static DataEditorCollectionBuilder DataEditors(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the property value converters collection builder. /// /// The composition. public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the url segment providers collection builder. /// /// The composition. public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the validators collection builder. /// /// The composition. internal static ManifestValueValidatorCollectionBuilder Validators(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the post-migrations collection builder. /// /// The composition. internal static PostMigrationCollectionBuilder PostMigrations(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); + + /// + /// Gets the components collection builder. + /// + public static ComponentCollectionBuilder Components(this Composition composition) + => composition.WithCollectionBuilder(); #endregion - #region Singleton + #region Uniques /// /// Sets the culture dictionary factory. @@ -89,7 +134,7 @@ namespace Umbraco.Core.Components public static void SetCultureDictionaryFactory(this Composition composition) where T : ICultureDictionaryFactory { - composition.Container.RegisterSingleton(); + composition.RegisterUnique(); } /// @@ -97,9 +142,9 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a culture dictionary factory. - public static void SetCultureDictionaryFactory(this Composition composition, Func factory) + public static void SetCultureDictionaryFactory(this Composition composition, Func factory) { - composition.Container.RegisterSingleton(factory); + composition.RegisterUnique(factory); } /// @@ -109,7 +154,7 @@ namespace Umbraco.Core.Components /// A factory. public static void SetCultureDictionaryFactory(this Composition composition, ICultureDictionaryFactory factory) { - composition.Container.RegisterSingleton(_ => factory); + composition.RegisterUnique(_ => factory); } /// @@ -120,7 +165,7 @@ namespace Umbraco.Core.Components public static void SetPublishedContentModelFactory(this Composition composition) where T : IPublishedModelFactory { - composition.Container.RegisterSingleton(); + composition.RegisterUnique(); } /// @@ -128,9 +173,9 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a published content model factory. - public static void SetPublishedContentModelFactory(this Composition composition, Func factory) + public static void SetPublishedContentModelFactory(this Composition composition, Func factory) { - composition.Container.RegisterSingleton(factory); + composition.RegisterUnique(factory); } /// @@ -140,7 +185,7 @@ namespace Umbraco.Core.Components /// A published content model factory. public static void SetPublishedContentModelFactory(this Composition composition, IPublishedModelFactory factory) { - composition.Container.RegisterSingleton(_ => factory); + composition.RegisterUnique(_ => factory); } /// @@ -151,7 +196,7 @@ namespace Umbraco.Core.Components public static void SetServerRegistrar(this Composition composition) where T : IServerRegistrar { - composition.Container.RegisterSingleton(); + composition.RegisterUnique(); } /// @@ -159,9 +204,9 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a server registar. - public static void SetServerRegistrar(this Composition composition, Func factory) + public static void SetServerRegistrar(this Composition composition, Func factory) { - composition.Container.RegisterSingleton(factory); + composition.RegisterUnique(factory); } /// @@ -171,7 +216,7 @@ namespace Umbraco.Core.Components /// A server registrar. public static void SetServerRegistrar(this Composition composition, IServerRegistrar registrar) { - composition.Container.RegisterSingleton(_ => registrar); + composition.RegisterUnique(_ => registrar); } /// @@ -182,7 +227,7 @@ namespace Umbraco.Core.Components public static void SetServerMessenger(this Composition composition) where T : IServerMessenger { - composition.Container.RegisterSingleton(); + composition.RegisterUnique(); } /// @@ -190,9 +235,9 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a server messenger. - public static void SetServerMessenger(this Composition composition, Func factory) + public static void SetServerMessenger(this Composition composition, Func factory) { - composition.Container.RegisterSingleton(factory); + composition.RegisterUnique(factory); } /// @@ -202,7 +247,7 @@ namespace Umbraco.Core.Components /// A server messenger. public static void SetServerMessenger(this Composition composition, IServerMessenger registrar) { - composition.Container.RegisterSingleton(_ => registrar); + composition.RegisterUnique(_ => registrar); } /// @@ -213,7 +258,7 @@ namespace Umbraco.Core.Components public static void SetShortStringHelper(this Composition composition) where T : IShortStringHelper { - composition.Container.RegisterSingleton(); + composition.RegisterUnique(); } /// @@ -221,9 +266,9 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a short string helper. - public static void SetShortStringHelper(this Composition composition, Func factory) + public static void SetShortStringHelper(this Composition composition, Func factory) { - composition.Container.RegisterSingleton(factory); + composition.RegisterUnique(factory); } /// @@ -233,9 +278,25 @@ namespace Umbraco.Core.Components /// A short string helper. public static void SetShortStringHelper(this Composition composition, IShortStringHelper helper) { - composition.Container.RegisterSingleton(_ => helper); + composition.RegisterUnique(_ => helper); } + /// + /// Sets the underlying media filesystem. + /// + /// A composition. + /// A filesystem factory. + public static void SetMediaFileSystem(this Composition composition, Func filesystemFactory) + => composition.RegisterUniqueFor(filesystemFactory); + + /// + /// Sets the underlying media filesystem. + /// + /// A composition. + /// A filesystem factory. + public static void SetMediaFileSystem(this Composition composition, Func filesystemFactory) + => composition.RegisterUniqueFor(_ => filesystemFactory()); + #endregion } } diff --git a/src/Umbraco.Core/Components/DisableAttribute.cs b/src/Umbraco.Core/Components/DisableAttribute.cs new file mode 100644 index 0000000000..f9a7249b89 --- /dev/null +++ b/src/Umbraco.Core/Components/DisableAttribute.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a composer should be disabled. + /// + /// + /// If a type is specified, disables the composer of that type, else disables the composer marked with the attribute. + /// This attribute is *not* inherited. + /// This attribute applies to classes only, it is not possible to enable/disable interfaces. + /// If a composer ends up being both enabled and disabled: attributes marking the composer itself have lower priority + /// than attributes on *other* composers, eg if a composer declares itself as disabled it is possible to enable it from + /// another composer. Anything else is unspecified. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class DisableAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public DisableAttribute() + { } + + /// + /// Initializes a new instance of the class. + /// + public DisableAttribute(Type disabledType) + { + DisabledType = disabledType; + } + + /// + /// Gets the disabled type, or null if it is the composer marked with the attribute. + /// + public Type DisabledType { get; } + } +} diff --git a/src/Umbraco.Core/Components/DisableComponentAttribute.cs b/src/Umbraco.Core/Components/DisableComponentAttribute.cs deleted file mode 100644 index f7ff71e119..0000000000 --- a/src/Umbraco.Core/Components/DisableComponentAttribute.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; - -namespace Umbraco.Core.Components -{ - /// - /// Indicates that a component should be disabled. - /// - /// - /// If a type is specified, disables the component of that type, else disables the component marked with the attribute. - /// This attribute is *not* inherited. - /// This attribute applies to classes only, it is not possible to enable/disable interfaces. - /// If a component ends up being both enabled and disabled: attributes marking the component itself have lower priority - /// than attributes on *other* components, eg if a component declares itself as disabled it is possible to enable it from - /// another component. Anything else is unspecified. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public class DisableComponentAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - public DisableComponentAttribute() - { } - - /// - /// Initializes a new instance of the class. - /// - public DisableComponentAttribute(Type disabledType) - { - DisabledType = disabledType; - } - - /// - /// Gets the disabled type, or null if it is the component marked with the attribute. - /// - public Type DisabledType { get; } - } -} diff --git a/src/Umbraco.Core/Components/EnableAttribute.cs b/src/Umbraco.Core/Components/EnableAttribute.cs new file mode 100644 index 0000000000..edf3cbdc2e --- /dev/null +++ b/src/Umbraco.Core/Components/EnableAttribute.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a composer should be enabled. + /// + /// + /// If a type is specified, enables the composer of that type, else enables the composer marked with the attribute. + /// This attribute is *not* inherited. + /// This attribute applies to classes only, it is not possible to enable/disable interfaces. + /// If a composer ends up being both enabled and disabled: attributes marking the composer itself have lower priority + /// than attributes on *other* composers, eg if a composer declares itself as disabled it is possible to enable it from + /// another composer. Anything else is unspecified. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class EnableAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public EnableAttribute() + { } + + /// + /// Initializes a new instance of the class. + /// + public EnableAttribute(Type enabledType) + { + EnabledType = enabledType; + } + + /// + /// Gets the enabled type, or null if it is the composer marked with the attribute. + /// + public Type EnabledType { get; } + } +} diff --git a/src/Umbraco.Core/Components/EnableComponentAttribute.cs b/src/Umbraco.Core/Components/EnableComponentAttribute.cs deleted file mode 100644 index fa76dc2404..0000000000 --- a/src/Umbraco.Core/Components/EnableComponentAttribute.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; - -namespace Umbraco.Core.Components -{ - /// - /// Indicates that a component should be enabled. - /// - /// - /// If a type is specified, enables the component of that type, else enables the component marked with the attribute. - /// This attribute is *not* inherited. - /// This attribute applies to classes only, it is not possible to enable/disable interfaces. - /// If a component ends up being both enabled and disabled: attributes marking the component itself have lower priority - /// than attributes on *other* components, eg if a component declares itself as disabled it is possible to enable it from - /// another component. Anything else is unspecified. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public class EnableComponentAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - public EnableComponentAttribute() - { } - - /// - /// Initializes a new instance of the class. - /// - public EnableComponentAttribute(Type enabledType) - { - EnabledType = enabledType; - } - - /// - /// Gets the enabled type, or null if it is the component marked with the attribute. - /// - public Type EnabledType { get; } - } -} diff --git a/src/Umbraco.Core/Components/IComponent.cs b/src/Umbraco.Core/Components/IComponent.cs new file mode 100644 index 0000000000..b1954d821b --- /dev/null +++ b/src/Umbraco.Core/Components/IComponent.cs @@ -0,0 +1,25 @@ +namespace Umbraco.Core.Components +{ + /// + /// Represents a component. + /// + /// + /// Components are created by DI and therefore must have a public constructor. + /// All components are terminated in reverse order when Umbraco terminates, and + /// disposable components are disposed. + /// The Dispose method may be invoked more than once, and components + /// should ensure they support this. + /// + public interface IComponent + { + /// + /// Initializes the component. + /// + void Initialize(); + + /// + /// Terminates the component. + /// + void Terminate(); + } +} diff --git a/src/Umbraco.Core/Components/IComposer.cs b/src/Umbraco.Core/Components/IComposer.cs new file mode 100644 index 0000000000..ce02aa4f13 --- /dev/null +++ b/src/Umbraco.Core/Components/IComposer.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Components +{ + /// + /// Represents a composer. + /// + public interface IComposer : IDiscoverable + { + /// + /// Compose. + /// + /// + void Compose(Composition composition); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/ICoreComposer.cs b/src/Umbraco.Core/Components/ICoreComposer.cs new file mode 100644 index 0000000000..94aa9953a9 --- /dev/null +++ b/src/Umbraco.Core/Components/ICoreComposer.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Components +{ + /// + /// Represents a core . + /// + /// + /// All core composers are required by (compose before) all user composers, + /// and require (compose after) all runtime composers. + /// + [ComposeAfter(typeof(IRuntimeComposer))] + public interface ICoreComposer : IComposer + { } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/IRuntimeComponent.cs b/src/Umbraco.Core/Components/IRuntimeComponent.cs deleted file mode 100644 index 7f89d21e87..0000000000 --- a/src/Umbraco.Core/Components/IRuntimeComponent.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Umbraco.Core.Components -{ - public interface IRuntimeComponent : IUmbracoComponent - { } -} diff --git a/src/Umbraco.Core/Components/IRuntimeComposer.cs b/src/Umbraco.Core/Components/IRuntimeComposer.cs new file mode 100644 index 0000000000..4b8253ee6c --- /dev/null +++ b/src/Umbraco.Core/Components/IRuntimeComposer.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Core.Components +{ + /// + /// Represents a runtime . + /// + /// + /// All runtime composers are required by (compose before) all core composers + /// + public interface IRuntimeComposer : IComposer + { } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/IUmbracoComponent.cs b/src/Umbraco.Core/Components/IUmbracoComponent.cs deleted file mode 100644 index d25b97c270..0000000000 --- a/src/Umbraco.Core/Components/IUmbracoComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Core.Components -{ - /// - /// Represents an Umbraco component. - /// - public interface IUmbracoComponent : IDiscoverable - { - /// - /// Composes the component. - /// - /// The composition. - void Compose(Composition composition); - - /// - /// Terminates the component. - /// - void Terminate(); - } -} diff --git a/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs b/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs deleted file mode 100644 index 28ff286da3..0000000000 --- a/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Core.Components -{ - [RequireComponent(typeof(IRuntimeComponent))] - public interface IUmbracoCoreComponent : IUmbracoComponent - { } -} diff --git a/src/Umbraco.Core/Components/IUmbracoUserComponent.cs b/src/Umbraco.Core/Components/IUmbracoUserComponent.cs deleted file mode 100644 index 61a07cfd48..0000000000 --- a/src/Umbraco.Core/Components/IUmbracoUserComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Core.Components -{ - [RequireComponent(typeof(UmbracoCoreComponent))] - public interface IUmbracoUserComponent : IUmbracoComponent - { } -} diff --git a/src/Umbraco.Core/Components/IUserComposer.cs b/src/Umbraco.Core/Components/IUserComposer.cs new file mode 100644 index 0000000000..59e0023635 --- /dev/null +++ b/src/Umbraco.Core/Components/IUserComposer.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Components +{ + /// + /// Represents a user . + /// + /// + /// All user composers require (compose after) all core composers. + /// + [ComposeAfter(typeof(ICoreComposer))] + public interface IUserComposer : IComposer + { } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/ManifestWatcherComponent.cs b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs index 0a8d9ccd52..2420e1d5bb 100644 --- a/src/Umbraco.Core/Components/ManifestWatcherComponent.cs +++ b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs @@ -5,16 +5,24 @@ using Umbraco.Core.Manifest; namespace Umbraco.Core.Components { - [RuntimeLevel(MinLevel = RuntimeLevel.Run)] - public class ManifestWatcherComponent : UmbracoComponentBase, IUmbracoCoreComponent + public sealed class ManifestWatcherComponent : IComponent { + private readonly IRuntimeState _runtimeState; + private readonly ILogger _logger; + // if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for // package.manifest chances and restarts the application on any change private ManifestWatcher _mw; - public void Initialize(IRuntimeState runtime, ILogger logger) + public ManifestWatcherComponent(IRuntimeState runtimeState, ILogger logger) { - if (runtime.Debug == false) return; + _runtimeState = runtimeState; + _logger = logger; + } + + public void Initialize() + { + if (_runtimeState.Debug == false) return; //if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false) // return; @@ -22,13 +30,15 @@ namespace Umbraco.Core.Components var appPlugins = IOHelper.MapPath("~/App_Plugins/"); if (Directory.Exists(appPlugins) == false) return; - _mw = new ManifestWatcher(logger); + _mw = new ManifestWatcher(_logger); _mw.Start(Directory.GetDirectories(appPlugins)); } - public override void Terminate() + public void Terminate() { - _mw?.Dispose(); + if (_mw == null) return; + + _mw.Dispose(); _mw = null; } } diff --git a/src/Umbraco.Core/Components/ManifestWatcherComposer.cs b/src/Umbraco.Core/Components/ManifestWatcherComposer.cs new file mode 100644 index 0000000000..b08680156b --- /dev/null +++ b/src/Umbraco.Core/Components/ManifestWatcherComposer.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Core.Components +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class ManifestWatcherComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs index bc66dccd31..404d385680 100644 --- a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs @@ -6,14 +6,16 @@ using Umbraco.Core.Services.Implement; namespace Umbraco.Core.Components { //TODO: This should just exist in the content service/repo! - [RuntimeLevel(MinLevel = RuntimeLevel.Run)] - public sealed class RelateOnCopyComponent : UmbracoComponentBase, IUmbracoCoreComponent + public sealed class RelateOnCopyComponent : IComponent { public void Initialize() { ContentService.Copied += ContentServiceCopied; } + public void Terminate() + { } + private static void ContentServiceCopied(IContentService sender, Events.CopyEventArgs e) { if (e.RelateToOriginal == false) return; diff --git a/src/Umbraco.Core/Components/RelateOnCopyComposer.cs b/src/Umbraco.Core/Components/RelateOnCopyComposer.cs new file mode 100644 index 0000000000..f5e9423edd --- /dev/null +++ b/src/Umbraco.Core/Components/RelateOnCopyComposer.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Core.Components +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class RelateOnCopyComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs index 8bcce50c68..6279bb98ba 100644 --- a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs @@ -7,8 +7,7 @@ using Umbraco.Core.Services.Implement; namespace Umbraco.Core.Components { - [RuntimeLevel(MinLevel = RuntimeLevel.Run)] - public sealed class RelateOnTrashComponent : UmbracoComponentBase, IUmbracoCoreComponent + public sealed class RelateOnTrashComponent : IComponent { public void Initialize() { @@ -18,6 +17,9 @@ namespace Umbraco.Core.Components MediaService.Trashed += MediaService_Trashed; } + public void Terminate() + { } + private static void ContentService_Moved(IContentService sender, MoveEventArgs e) { foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContent.ToInvariantString()))) diff --git a/src/Umbraco.Core/Components/RelateOnTrashComposer.cs b/src/Umbraco.Core/Components/RelateOnTrashComposer.cs new file mode 100644 index 0000000000..5d89bc0e37 --- /dev/null +++ b/src/Umbraco.Core/Components/RelateOnTrashComposer.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Core.Components +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class RelateOnTrashComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Core/Components/RequireComponentAttribute.cs b/src/Umbraco.Core/Components/RequireComponentAttribute.cs deleted file mode 100644 index 2d53413f99..0000000000 --- a/src/Umbraco.Core/Components/RequireComponentAttribute.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; - -namespace Umbraco.Core.Components -{ - /// - /// Indicates that a component requires another component. - /// - /// - /// This attribute is *not* inherited. This means that a component class inheriting from - /// another component class does *not* inherit its requirements. However, the bootloader checks - /// the *interfaces* of every component for their requirements, so requirements declared on - /// interfaces are inherited by every component class implementing the interface. - /// When targetting a class, indicates a dependency on the component which must be enabled, - /// unless the requirement has explicitely been declared as weak (and then, only if the component - /// is enabled). - /// When targetting an interface, indicates a dependency on enabled components implementing - /// the interface. It could be no component at all, unless the requirement has explicitely been - /// declared as strong (and at least one component must be enabled). - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] - public class RequireComponentAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The type of the required component. - public RequireComponentAttribute(Type requiredType) - { - if (typeof(IUmbracoComponent).IsAssignableFrom(requiredType) == false) - throw new ArgumentException($"Type {requiredType.FullName} is invalid here because it does not implement {typeof(IUmbracoComponent).FullName}."); - RequiredType = requiredType; - } - - /// - /// Initializes a new instance of the class. - /// - /// The type of the required component. - /// A value indicating whether the requirement is weak. - public RequireComponentAttribute(Type requiredType, bool weak) - : this(requiredType) - { - Weak = weak; - } - - /// - /// Gets the required type. - /// - public Type RequiredType { get; } - - /// - /// Gets a value indicating whether the requirement is weak. - /// - /// Returns true if the requirement is weak (requires the other component if it - /// is enabled), false if the requirement is strong (requires the other component to be - /// enabled), and null if unspecified, in which case it is strong for classes and weak for - /// interfaces. - public bool? Weak { get; } - } -} diff --git a/src/Umbraco.Core/Components/RequiredComponentAttribute.cs b/src/Umbraco.Core/Components/RequiredComponentAttribute.cs deleted file mode 100644 index 7895445179..0000000000 --- a/src/Umbraco.Core/Components/RequiredComponentAttribute.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -namespace Umbraco.Core.Components -{ - /// - /// Indicates that a component is required by another component. - /// - /// - /// fixme - /// This attribute is *not* inherited. This means that a component class inheriting from - /// another component class does *not* inherit its requirements. However, the bootloader checks - /// the *interfaces* of every component for their requirements, so requirements declared on - /// interfaces are inherited by every component class implementing the interface. - /// When targetting a class, indicates a dependency on the component which must be enabled, - /// unless the requirement has explicitely been declared as weak (and then, only if the component - /// is enabled). - /// When targetting an interface, indicates a dependency on enabled components implementing - /// the interface. It could be no component at all, unless the requirement has explicitely been - /// declared as strong (and at least one component must be enabled). - /// - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] - public class RequiredComponentAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The type of the required component. - public RequiredComponentAttribute(Type requiringType) - { - if (typeof(IUmbracoComponent).IsAssignableFrom(requiringType) == false) - throw new ArgumentException($"Type {requiringType.FullName} is invalid here because it does not implement {typeof(IUmbracoComponent).FullName}."); - RequiringType = requiringType; - } - - /// - /// Gets the required type. - /// - public Type RequiringType { get; } - } -} diff --git a/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs b/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs index 51920660d4..2c698a671a 100644 --- a/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs +++ b/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs @@ -1,13 +1,22 @@ using System; +using Umbraco.Core.Composing; namespace Umbraco.Core.Components { + /// + /// Marks a composer to indicate a minimum and/or maximum runtime level for which the composer would compose. + /// [AttributeUsage(AttributeTargets.Class /*, AllowMultiple = false, Inherited = true*/)] public class RuntimeLevelAttribute : Attribute { - //public RuntimeLevelAttribute() - //{ } + /// + /// Gets or sets the minimum runtime level for which the composer would compose. + /// + public RuntimeLevel MinLevel { get; set; } = RuntimeLevel.Install; - public RuntimeLevel MinLevel { get; set; } = RuntimeLevel.Boot; + /// + /// Gets or sets the maximum runtime level for which the composer would compose. + /// + public RuntimeLevel MaxLevel { get; set; } = RuntimeLevel.Run; } } diff --git a/src/Umbraco.Core/Components/UmbracoComponentBase.cs b/src/Umbraco.Core/Components/UmbracoComponentBase.cs deleted file mode 100644 index 476c4c6d59..0000000000 --- a/src/Umbraco.Core/Components/UmbracoComponentBase.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Umbraco.Core.Components -{ - /// - /// Provides a base class for implementations. - /// - public abstract class UmbracoComponentBase : IUmbracoComponent - { - /// - public virtual void Compose(Composition composition) - { } - - /// - public virtual void Terminate() - { } - } -} diff --git a/src/Umbraco.Core/Components/UmbracoCoreComponent.cs b/src/Umbraco.Core/Components/UmbracoCoreComponent.cs deleted file mode 100644 index 9f6709c494..0000000000 --- a/src/Umbraco.Core/Components/UmbracoCoreComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Components -{ - // the UmbracoCoreComponent requires all IUmbracoCoreComponent - // all user-level components should require the UmbracoCoreComponent - - [RequireComponent(typeof(IUmbracoCoreComponent))] - public class UmbracoCoreComponent : UmbracoComponentBase - { } -} diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs index 3fac2d3255..41038ea4e9 100644 --- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; -using LightInject; namespace Umbraco.Core.Composing { @@ -14,69 +12,34 @@ namespace Umbraco.Core.Composing /// The type of the items. public abstract class CollectionBuilderBase : ICollectionBuilder where TBuilder: CollectionBuilderBase - where TCollection : IBuilderCollection + where TCollection : class, IBuilderCollection { private readonly List _types = new List(); private readonly object _locker = new object(); - private Func, TCollection> _collectionCtor; - private ServiceRegistration[] _registrations; - - /// - /// Initializes a new instance of the - /// class with a service container. - /// - /// A service container. - protected CollectionBuilderBase(IServiceContainer container) - { - Container = container; - // ReSharper disable once DoNotCallOverridableMethodsInConstructor - Initialize(); - } - - /// - /// Gets the service container. - /// - protected IServiceContainer Container { get; } + private Type[] _registeredTypes; /// /// Gets the internal list of types as an IEnumerable (immutable). /// public IEnumerable GetTypes() => _types; - /// - /// Initializes a new instance of the builder. - /// - /// This is called by the constructor and, by default, registers the - /// collection automatically. - protected virtual void Initialize() + /// + public virtual void RegisterWith(IRegister register) { - // compile the auto-collection constructor - var argType = typeof(IEnumerable); - var ctorArgTypes = new[] { argType }; - var constructor = typeof(TCollection).GetConstructor(ctorArgTypes); - if (constructor != null) - { - var exprArg = Expression.Parameter(argType, "items"); - var exprNew = Expression.New(constructor, exprArg); - var expr = Expression.Lambda, TCollection>>(exprNew, exprArg); - _collectionCtor = expr.Compile(); - } - // else _collectionCtor remains null, assuming CreateCollection has been overriden - - // we just don't want to support re-registering collections here - var registration = Container.GetAvailableService(); - if (registration != null) - throw new InvalidOperationException("Collection builders cannot be registered once the collection itself has been registered."); + if (_registeredTypes != null) + throw new InvalidOperationException("This builder has already been registered."); // register the collection - Container.Register(_ => CreateCollection(), CollectionLifetime); + register.Register(CreateCollection, CollectionLifetime); + + // register the types + RegisterTypes(register); } /// /// Gets the collection lifetime. /// - /// Return null for transient collections. - protected virtual ILifetime CollectionLifetime => new PerContainerLifetime(); + protected virtual Lifetime CollectionLifetime => Lifetime.Singleton; /// /// Configures the internal list of types. @@ -87,8 +50,8 @@ namespace Umbraco.Core.Composing { lock (_locker) { - if (_registrations != null) - throw new InvalidOperationException("Cannot configure a collection builder after its types have been resolved."); + if (_registeredTypes != null) + throw new InvalidOperationException("Cannot configure a collection builder after it has been registered."); action(_types); } } @@ -104,55 +67,54 @@ namespace Umbraco.Core.Composing return types; } - private void RegisterTypes() + private void RegisterTypes(IRegister register) { lock (_locker) { - if (_registrations != null) return; + if (_registeredTypes != null) return; var types = GetRegisteringTypes(_types).ToArray(); + + // ensure they are safe foreach (var type in types) EnsureType(type, "register"); - var prefix = GetType().FullName + "_"; - var i = 0; + // register them foreach (var type in types) - { - var name = $"{prefix}{i++:00000}"; - Container.Register(typeof(TItem), type, name); - } + register.Register(type); - _registrations = Container.AvailableServices - .Where(x => x.ServiceName.StartsWith(prefix)) - .OrderBy(x => x.ServiceName) - .ToArray(); + _registeredTypes = types; } } /// /// Creates the collection items. /// - /// The arguments. /// The collection items. - protected virtual IEnumerable CreateItems(params object[] args) + protected virtual IEnumerable CreateItems(IFactory factory) { - RegisterTypes(); // will do it only once + if (_registeredTypes == null) + throw new InvalidOperationException("Cannot create items before the collection builder has been registered."); - var type = typeof (TItem); - return _registrations - .Select(x => (TItem) Container.GetInstanceOrThrow(type, x.ServiceName, x.ImplementingType, args)) + return _registeredTypes // respect order + .Select(x => CreateItem(factory, x)) .ToArray(); // safe } + /// + /// Creates a collection item. + /// + protected virtual TItem CreateItem(IFactory factory, Type itemType) + => (TItem) factory.GetInstance(itemType); + /// /// Creates a collection. /// /// A collection. /// Creates a new collection each time it is invoked. - public virtual TCollection CreateCollection() + public virtual TCollection CreateCollection(IFactory factory) { - if (_collectionCtor == null) throw new InvalidOperationException("Collection auto-creation is not possible."); - return _collectionCtor(CreateItems()); + return factory.CreateInstance(CreateItems(factory)); } protected Type EnsureType(Type type, string action) diff --git a/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs b/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs new file mode 100644 index 0000000000..7fba47a2cd --- /dev/null +++ b/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs @@ -0,0 +1,24 @@ +using Umbraco.Core.Components; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Core.Composing.Composers +{ + /// + /// Compose configurations. + /// + public static class ConfigurationComposer + { + public static Composition ComposeConfiguration(this Composition composition) + { + // common configurations are already registered + // register others + + composition.RegisterUnique(factory => factory.GetInstance().Content); + composition.RegisterUnique(factory => factory.GetInstance().RequestHandler); + composition.RegisterUnique(factory => factory.GetInstance().Security); + + return composition; + } + } +} diff --git a/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs b/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs new file mode 100644 index 0000000000..0274b8f1a9 --- /dev/null +++ b/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs @@ -0,0 +1,16 @@ +using AutoMapper; +using Umbraco.Core.Components; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Composing.Composers + +{ + public static class CoreMappingProfilesComposer + { + public static Composition ComposeCoreMappingProfiles(this Composition composition) + { + composition.Register(); + return composition; + } + } +} diff --git a/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs b/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs new file mode 100644 index 0000000000..4c598f27e4 --- /dev/null +++ b/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs @@ -0,0 +1,98 @@ +using Umbraco.Core.Components; +using Umbraco.Core.IO; +using Umbraco.Core.IO.MediaPathSchemes; + +namespace Umbraco.Core.Composing.Composers +{ + public static class FileSystemsComposer + { + /* + * HOW TO REPLACE THE MEDIA UNDERLYING FILESYSTEM + * ---------------------------------------------- + * + * Create a component and use it to modify the composition by adding something like: + * + * composition.RegisterUniqueFor(...); + * + * and register whatever supporting filesystem you like. + * + * + * HOW TO IMPLEMENT MY OWN FILESYSTEM + * ---------------------------------- + * + * Create your filesystem class: + * + * public class MyFileSystem : FileSystemWrapper + * { + * public MyFileSystem(IFileSystem innerFileSystem) + * : base(innerFileSystem) + * { } + * } + * + * The ctor can have more parameters, that will be resolved by the container. + * + * Register your filesystem, in a component: + * + * composition.RegisterFileSystem(); + * + * Register the underlying filesystem: + * + * composition.RegisterUniqueFor(...); + * + * And that's it, you can inject MyFileSystem wherever it's needed. + * + * + * You can also declare a filesystem interface: + * + * public interface IMyFileSystem : IFileSystem + * { } + * + * Make the class implement the interface, then + * register your filesystem, in a component: + * + * composition.RegisterFileSystem(); + * composition.RegisterUniqueFor(...); + * + * And that's it, you can inject IMyFileSystem wherever it's needed. + * + * + * WHAT IS SHADOWING + * ----------------- + * + * Shadowing is the technology used for Deploy to implement some sort of + * transaction-management on top of filesystems. The plumbing explained above, + * compared to creating your own physical filesystem, ensures that your filesystem + * would participate into such transactions. + * + * + */ + + public static Composition ComposeFileSystems(this Composition composition) + { + // register FileSystems, which manages all filesystems + // it needs to be registered (not only the interface) because it provides additional + // functionality eg for scoping, and is injected in the scope provider - whereas the + // interface is really for end-users to get access to filesystems. + composition.RegisterUnique(factory => factory.CreateInstance(factory)); + + // register IFileSystems, which gives access too all filesystems + composition.RegisterUnique(factory => factory.GetInstance()); + + // register the scheme for media paths + composition.RegisterUnique(); + + // register the IMediaFileSystem implementation + composition.RegisterFileSystem(); + + // register the supporting filesystems provider + composition.Register(factory => new SupportingFileSystems(factory), Lifetime.Singleton); + + // register the IFileSystem supporting the IMediaFileSystem + // THIS IS THE ONLY THING THAT NEEDS TO CHANGE, IN ORDER TO REPLACE THE UNDERLYING FILESYSTEM + // and, SupportingFileSystem.For() returns the underlying filesystem + composition.SetMediaFileSystem(() => new PhysicalFileSystem("~/media")); + + return composition; + } + } +} diff --git a/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs b/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs new file mode 100644 index 0000000000..62b92081c1 --- /dev/null +++ b/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs @@ -0,0 +1,54 @@ +using Umbraco.Core.Components; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; + +namespace Umbraco.Core.Composing.Composers +{ + /// + /// Composes repositories. + /// + public static class RepositoriesComposer + { + public static Composition ComposeRepositories(this Composition composition) + { + // repositories + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + + return composition; + } + } +} diff --git a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs new file mode 100644 index 0000000000..0410e72148 --- /dev/null +++ b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using System.Linq; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Packaging; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Core.Composing.Composers +{ + public static class ServicesComposer + { + public static Composition ComposeServices(this Composition composition) + { + // register a transient messages factory, which will be replaced by the web + // boot manager when running in a web context + composition.RegisterUnique(); + + // register the service context + composition.RegisterUnique(); + + // register the special idk map + composition.RegisterUnique(); + + // register the services + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.Register(SourcesFactory); + composition.RegisterUnique(factory => new LocalizedTextService( + factory.GetInstance>(), + factory.GetInstance())); + + composition.RegisterUnique(); + + composition.RegisterUnique(); + + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(factory => CreatePackageRepository(factory, "createdPackages.config")); + composition.RegisterUnique(factory => CreatePackageRepository(factory, "installedPackages.config")); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(factory => //factory required because we need to pass in a string path + new PackageInstallation( + factory.GetInstance(), factory.GetInstance(), + factory.GetInstance(), factory.GetInstance(), + new DirectoryInfo(IOHelper.GetRootDirectorySafe()))); + + return composition; + } + + /// + /// Creates an instance of PackagesRepository for either the ICreatedPackagesRepository or the IInstalledPackagesRepository + /// + /// + /// + /// + private static PackagesRepository CreatePackageRepository(IFactory factory, string packageRepoFileName) + => new PackagesRepository( + factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), + packageRepoFileName); + + private static LocalizedTextServiceFileSources SourcesFactory(IFactory container) + { + var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/")); + var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)); + var configLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config + "/lang/")); + + var pluginLangFolders = appPlugins.Exists == false + ? Enumerable.Empty() + : appPlugins.GetDirectories() + .SelectMany(x => x.GetDirectories("Lang")) + .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) + .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5) + .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); + + //user defined langs that overwrite the default, these should not be used by plugin creators + var userLangFolders = configLangFolder.Exists == false + ? Enumerable.Empty() + : configLangFolder + .GetFiles("*.user.xml", SearchOption.TopDirectoryOnly) + .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10) + .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); + + return new LocalizedTextServiceFileSources( + container.GetInstance(), + container.GetInstance(), + mainLangFolder, + pluginLangFolders.Concat(userLangFolders)); + } + } +} diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/Composing/CompositionExtensions.cs new file mode 100644 index 0000000000..c312259b82 --- /dev/null +++ b/src/Umbraco.Core/Composing/CompositionExtensions.cs @@ -0,0 +1,68 @@ +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides extension methods to the class. + /// + public static class CompositionExtensions + { + #region Essentials + + /// + /// Registers essential services. + /// + public static void RegisterEssentials(this Composition composition, + ILogger logger, IProfiler profiler, IProfilingLogger profilingLogger, + IMainDom mainDom, + AppCaches appCaches, + IUmbracoDatabaseFactory databaseFactory, + TypeLoader typeLoader, + IRuntimeState state) + { + composition.RegisterUnique(logger); + composition.RegisterUnique(profiler); + composition.RegisterUnique(profilingLogger); + composition.RegisterUnique(mainDom); + composition.RegisterUnique(appCaches); + composition.RegisterUnique(databaseFactory); + composition.RegisterUnique(factory => factory.GetInstance().SqlContext); + composition.RegisterUnique(typeLoader); + composition.RegisterUnique(state); + } + + #endregion + + #region Unique + + /// + /// Registers a unique service as its own implementation. + /// + public static void RegisterUnique(this Composition composition) + => composition.RegisterUnique(typeof(TService), typeof(TService)); + + /// + /// Registers a unique service with an implementation type. + /// + public static void RegisterUnique(this Composition composition) + => composition.RegisterUnique(typeof(TService), typeof(TImplementing)); + + /// + /// Registers a unique service with an implementation type, for a target. + /// + public static void RegisterUniqueFor(this Composition composition) + where TService : class + => composition.RegisterUniqueFor(typeof(TImplementing)); + + /// + /// Registers a unique service with an implementing instance. + /// + public static void RegisterUnique(this Composition composition, TService instance) + => composition.RegisterUnique(typeof(TService), instance); + + #endregion + } +} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs deleted file mode 100644 index 80de6d1c35..0000000000 --- a/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs +++ /dev/null @@ -1,24 +0,0 @@ -using LightInject; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; - -namespace Umbraco.Core.Composing.CompositionRoots -{ - /// - /// Sets up IoC container for Umbraco configuration classes - /// - public sealed class ConfigurationCompositionRoot : ICompositionRoot - { - public void Compose(IServiceRegistry container) - { - container.Register(factory => UmbracoConfig.For.UmbracoSettings()); - container.Register(factory => factory.GetInstance().Content); - container.Register(factory => factory.GetInstance().Templates); - container.Register(factory => factory.GetInstance().RequestHandler); - container.Register(factory => UmbracoConfig.For.GlobalSettings()); - container.Register(factory => UmbracoConfig.For.DashboardSettings()); - - // fixme - other sections we need to add? - } - } -} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs deleted file mode 100644 index 6b55a4af7e..0000000000 --- a/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs +++ /dev/null @@ -1,13 +0,0 @@ -using LightInject; -using Umbraco.Core.Models.Identity; - -namespace Umbraco.Core.Composing.CompositionRoots -{ - public sealed class CoreMappingProfilesCompositionRoot : ICompositionRoot - { - public void Compose(IServiceRegistry container) - { - container.Register(); - } - } -} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs deleted file mode 100644 index 9c36bf5cec..0000000000 --- a/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using LightInject; -using Umbraco.Core.Cache; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; - -namespace Umbraco.Core.Composing.CompositionRoots -{ - /// - /// Sets the IoC container for the umbraco data layer/repositories/sql/database/etc... - /// - public sealed class RepositoryCompositionRoot : ICompositionRoot - { - public const string DisabledCache = "DisabledCache"; - - public void Compose(IServiceRegistry container) - { - // register cache helpers - // the main cache helper is registered by CoreBootManager and is used by most repositories - // the disabled one is used by those repositories that have an annotated ctor parameter - container.RegisterSingleton(factory => CacheHelper.CreateDisabledCacheHelper(), DisabledCache); - - // resolve ctor dependency from GetInstance() runtimeArguments, if possible - 'factory' is - // the container, 'info' describes the ctor argument, and 'args' contains the args that - // were passed to GetInstance() - use first arg if it is the right type, - // - // for ... - //container.RegisterConstructorDependency((factory, info, args) => - //{ - // if (info.Member.DeclaringType != typeof(EntityContainerRepository)) return default; - // return args.Length > 0 && args[0] is Guid guid ? guid : default; - //}); - - // register repositories - // repos depend on various things, - // some repositories have an annotated ctor parameter to pick the right cache helper - - // repositories - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - - // repositories that depend on a filesystem - // these have an annotated ctor parameter to pick the right file system - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - } - } -} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs deleted file mode 100644 index 92b8139a04..0000000000 --- a/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using LightInject; -using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; - -namespace Umbraco.Core.Composing.CompositionRoots -{ - public sealed class ServicesCompositionRoot : ICompositionRoot - { - public void Compose(IServiceRegistry container) - { - // register a transient messages factory, which will be replaced by the web - // boot manager when running in a web context - container.RegisterSingleton(); - - // register the service context - container.RegisterSingleton(); - - // register the special idk map - container.RegisterSingleton(); - - // register the services - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.Register(factory => - { - var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/")); - var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)); - var configLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config + "/lang/")); - - var pluginLangFolders = appPlugins.Exists == false - ? Enumerable.Empty() - : appPlugins.GetDirectories() - .SelectMany(x => x.GetDirectories("Lang")) - .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); - - //user defined langs that overwrite the default, these should not be used by plugin creators - var userLangFolders = configLangFolder.Exists == false - ? Enumerable.Empty() - : configLangFolder - .GetFiles("*.user.xml", SearchOption.TopDirectoryOnly) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); - - return new LocalizedTextServiceFileSources( - factory.GetInstance(), - factory.GetInstance().RuntimeCache, - mainLangFolder, - pluginLangFolders.Concat(userLangFolders)); - }); - container.RegisterSingleton(factory => new LocalizedTextService( - factory.GetInstance>(), - factory.GetInstance())); - - //TODO: These are replaced in the web project - we need to declare them so that - // something is wired up, just not sure this is very nice but will work for now. - container.RegisterSingleton(); - container.RegisterSingleton(); - } - } -} diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index 9515398236..429fee3317 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -1,11 +1,11 @@ using System; -using LightInject; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Packaging; using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; @@ -20,47 +20,61 @@ namespace Umbraco.Core.Composing /// Provides a static service locator for most singletons. /// /// - /// This class is initialized with the container via LightInjectExtensions.ConfigureUmbracoCore, + /// This class is initialized with the container in UmbracoApplicationBase, /// right after the container is created in UmbracoApplicationBase.HandleApplicationStart. /// Obviously, this is a service locator, which some may consider an anti-pattern. And yet, /// practically, it works. /// public static class Current { - private static IServiceContainer _container; + private static IFactory _factory; + + // fixme - refactor + // we don't want Umbraco tests to die because the container has not been properly initialized, + // for some too-important things such as IShortStringHelper or loggers, so if it's not + // registered we setup a default one. We should really refactor our tests so that it does + // not happen. private static IShortStringHelper _shortStringHelper; private static ILogger _logger; private static IProfiler _profiler; - private static ProfilingLogger _profilingLogger; + private static IProfilingLogger _profilingLogger; private static IPublishedValueFallback _publishedValueFallback; + private static Configs _configs; /// - /// Gets or sets the DI container. + /// Gets or sets the factory. /// - public static IServiceContainer Container + public static IFactory Factory { get { - if (_container == null) throw new Exception("No container has been set."); - return _container; + if (_factory == null) throw new InvalidOperationException("No factory has been set."); + return _factory; } set { - if (_container != null) throw new Exception("A container has already been set."); - _container = value; + if (_factory != null) throw new InvalidOperationException("A factory has already been set."); + if (_configs != null) throw new InvalidOperationException("Configs are unlocked."); + _factory = value; } } - internal static bool HasContainer => _container != null; + internal static bool HasFactory => _factory != null; - // for UNIT TESTS exclusively! - // resets *everything* that is 'current' - internal static void Reset() + /// + /// Resets . Indented for testing only, and not supported in production code. + /// + /// + /// For UNIT TESTS exclusively. + /// Resets everything that is 'current'. + /// + public static void Reset() { - _container?.Dispose(); - _container = null; + _factory.DisposeIfDisposable(); + _factory = null; + _configs = null; _shortStringHelper = null; _logger = null; _profiler = null; @@ -70,97 +84,122 @@ namespace Umbraco.Core.Composing Resetted?.Invoke(null, EventArgs.Empty); } + /// + /// Unlocks . Intended for testing only, and not supported in production code. + /// + /// + /// For UNIT TESTS exclusively. + /// Unlocks so that it is possible to add configurations + /// directly to without having to wire composition. + /// + public static void UnlockConfigs() + { + if (_factory != null) + throw new InvalidOperationException("Cannot unlock configs when a factory has been set."); + _configs = new Configs(); + } + internal static event EventHandler Resetted; #region Getters - // fixme - refactor - // we don't want Umbraco to die because the container has not been properly initialized, - // for some too-important things such as IShortStringHelper or loggers, so if it's not - // registered we setup a default one. We should really refactor our tests so that it does - // not happen. Will do when we get rid of IShortStringHelper. - public static IShortStringHelper ShortStringHelper - => _shortStringHelper ?? (_shortStringHelper = _container?.TryGetInstance() - ?? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(UmbracoConfig.For.UmbracoSettings()))); + => _shortStringHelper ?? (_shortStringHelper = _factory?.TryGetInstance() + ?? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(Configs.Settings()))); public static ILogger Logger - => _logger ?? (_logger = _container?.TryGetInstance() - ?? new DebugDiagnosticsLogger()); + => _logger ?? (_logger = _factory?.TryGetInstance() + ?? new DebugDiagnosticsLogger()); public static IProfiler Profiler - => _profiler ?? (_profiler = _container?.TryGetInstance() - ?? new LogProfiler(Logger)); + => _profiler ?? (_profiler = _factory?.TryGetInstance() + ?? new LogProfiler(Logger)); - public static ProfilingLogger ProfilingLogger - => _profilingLogger ?? (_profilingLogger = _container?.TryGetInstance()) + public static IProfilingLogger ProfilingLogger + => _profilingLogger ?? (_profilingLogger = _factory?.TryGetInstance()) ?? new ProfilingLogger(Logger, Profiler); public static IRuntimeState RuntimeState - => Container.GetInstance(); + => Factory.GetInstance(); public static TypeLoader TypeLoader - => Container.GetInstance(); + => Factory.GetInstance(); - public static FileSystems FileSystems - => Container.GetInstance(); + public static Configs Configs + { + get + { + if (_configs != null) return _configs; + if (_factory == null) throw new InvalidOperationException("Can not get Current.Config during composition. Use composition.Config."); + return _factory.GetInstance(); + } + } + + public static IFileSystems FileSystems + => Factory.GetInstance(); + + public static IMediaFileSystem MediaFileSystem + => Factory.GetInstance(); public static UrlSegmentProviderCollection UrlSegmentProviders - => Container.GetInstance(); + => Factory.GetInstance(); public static CacheRefresherCollection CacheRefreshers - => Container.GetInstance(); + => Factory.GetInstance(); public static DataEditorCollection DataEditors - => Container.GetInstance(); + => Factory.GetInstance(); public static PropertyEditorCollection PropertyEditors - => Container.GetInstance(); + => Factory.GetInstance(); public static ParameterEditorCollection ParameterEditors - => Container.GetInstance(); + => Factory.GetInstance(); internal static ManifestValueValidatorCollection ManifestValidators - => Container.GetInstance(); + => Factory.GetInstance(); internal static PackageActionCollection PackageActions - => Container.GetInstance(); + => Factory.GetInstance(); + + internal static IPackageActionRunner PackageActionRunner + => Factory.GetInstance(); internal static PropertyValueConverterCollection PropertyValueConverters - => Container.GetInstance(); + => Factory.GetInstance(); internal static IPublishedModelFactory PublishedModelFactory - => Container.GetInstance(); + => Factory.GetInstance(); public static IServerMessenger ServerMessenger - => Container.GetInstance(); + => Factory.GetInstance(); public static IServerRegistrar ServerRegistrar - => Container.GetInstance(); + => Factory.GetInstance(); public static ICultureDictionaryFactory CultureDictionaryFactory - => Container.GetInstance(); + => Factory.GetInstance(); - public static CacheHelper ApplicationCache - => Container.GetInstance(); + public static AppCaches AppCaches + => Factory.GetInstance(); public static ServiceContext Services - => Container.GetInstance(); + => Factory.GetInstance(); public static IScopeProvider ScopeProvider - => Container.GetInstance(); + => Factory.GetInstance(); public static ISqlContext SqlContext - => Container.GetInstance(); + => Factory.GetInstance(); public static IPublishedContentTypeFactory PublishedContentTypeFactory - => Container.GetInstance(); + => Factory.GetInstance(); public static IPublishedValueFallback PublishedValueFallback - => _publishedValueFallback ?? Container.GetInstance() ?? new NoopPublishedValueFallback(); + => _publishedValueFallback ?? Factory.GetInstance() ?? new NoopPublishedValueFallback(); public static IVariationContextAccessor VariationContextAccessor - => Container.GetInstance(); + => Factory.GetInstance(); #endregion } diff --git a/src/Umbraco.Core/Composing/FactoryExtensions.cs b/src/Umbraco.Core/Composing/FactoryExtensions.cs new file mode 100644 index 0000000000..8027f2c7a1 --- /dev/null +++ b/src/Umbraco.Core/Composing/FactoryExtensions.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides extension methods to the class. + /// + public static class FactoryExtensions + { + /// + /// Gets an instance of a service. + /// + /// The type of the service. + /// The factory. + /// An instance of the specified type. + /// Throws an exception if the factory failed to get an instance of the specified type. + public static T GetInstance(this IFactory factory) + where T : class + => (T)factory.GetInstance(typeof(T)); + + /// + /// Tries to get an instance of a service. + /// + /// The type of the service. + /// An instance of the specified type, or null. + /// Returns null if the factory does not know how to get an instance + /// of the specified type. Throws an exception if the factory does know how + /// to get an instance of the specified type, but failed to do so. + public static T TryGetInstance(this IFactory factory) + where T : class + => (T)factory.TryGetInstance(typeof(T)); + + /// + /// Creates an instance with arguments. + /// + /// The type of the instance. + /// The factory. + /// Arguments. + /// An instance of the specified type. + /// + /// Throws an exception if the factory failed to get an instance of the specified type. + /// The arguments are used as dependencies by the factory. + /// + public static T CreateInstance(this IFactory factory, params object[] args) + where T : class + => (T)factory.CreateInstance(typeof(T), args); + + /// + /// Creates an instance of a service, with arguments. + /// + /// + /// The type of the instance. + /// Named arguments. + /// An instance of the specified type. + /// + /// The instance type does not need to be registered into the factory. + /// The arguments are used as dependencies by the factory. Other dependencies + /// are retrieved from the factory. + /// + public static object CreateInstance(this IFactory factory, Type type, params object[] args) + { + // LightInject has this, but then it requires RegisterConstructorDependency etc and has various oddities + // including the most annoying one, which is that it does not work on singletons (hard to fix) + //return factory.GetInstance(type, args); + + // this method is essentially used to build singleton instances, so it is assumed that it would be + // more expensive to build and cache a dynamic method ctor than to simply invoke the ctor, as we do + // here - this can be discussed + + // TODO: we currently try the ctor with most parameters, but we could want to fall back to others + + var ctor = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public).OrderByDescending(x => x.GetParameters().Length).FirstOrDefault(); + if (ctor == null) throw new InvalidOperationException($"Could not find a public constructor for type {type.FullName}."); + + var ctorParameters = ctor.GetParameters(); + var ctorArgs = new object[ctorParameters.Length]; + var i = 0; + foreach (var parameter in ctorParameters) + { + // no! IsInstanceOfType is not ok here + // ReSharper disable once UseMethodIsInstanceOfType + var arg = args?.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType())); + ctorArgs[i++] = arg ?? factory.GetInstance(parameter.ParameterType); + } + return ctor.Invoke(ctorArgs); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Composing/ICollectionBuilder.cs b/src/Umbraco.Core/Composing/ICollectionBuilder.cs index 5efc03c9ac..84ff3ba747 100644 --- a/src/Umbraco.Core/Composing/ICollectionBuilder.cs +++ b/src/Umbraco.Core/Composing/ICollectionBuilder.cs @@ -1,11 +1,23 @@ namespace Umbraco.Core.Composing { + /// + /// Represents a collection builder. + /// + public interface ICollectionBuilder + { + /// + /// Registers the builder so it can build the collection, by + /// registering the collection and the types. + /// + void RegisterWith(IRegister register); + } + /// /// Represents a collection builder. /// /// The type of the collection. /// The type of the items. - public interface ICollectionBuilder + public interface ICollectionBuilder : ICollectionBuilder where TCollection : IBuilderCollection { /// @@ -13,6 +25,6 @@ /// /// A collection. /// Creates a new collection each time it is invoked. - TCollection CreateCollection(); + TCollection CreateCollection(IFactory factory); } } diff --git a/src/Umbraco.Core/Composing/IFactory.cs b/src/Umbraco.Core/Composing/IFactory.cs new file mode 100644 index 0000000000..768b9207a3 --- /dev/null +++ b/src/Umbraco.Core/Composing/IFactory.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Composing +{ + /// + /// Defines a service factory for Umbraco. + /// + public interface IFactory + { + /// + /// Gets the concrete factory. + /// + object Concrete { get; } + + /// + /// Gets an instance of a service. + /// + /// The type of the service. + /// An instance of the specified type. + /// Throws an exception if the container failed to get an instance of the specified type. + object GetInstance(Type type); + + /// + /// Gets a targeted instance of a service. + /// + /// The type of the service. + /// The type of the target. + /// The instance of the specified type for the specified target. + /// Throws an exception if the container failed to get an instance of the specified type. + TService GetInstanceFor(); + + /// + /// Tries to get an instance of a service. + /// + /// The type of the service. + /// An instance of the specified type, or null. + /// Returns null if the container does not know how to get an instance + /// of the specified type. Throws an exception if the container does know how + /// to get an instance of the specified type, but failed to do so. + object TryGetInstance(Type type); + + /// + /// Gets all instances of a service. + /// + /// The type of the service. + IEnumerable GetAllInstances(Type serviceType); + + /// + /// Gets all instances of a service. + /// + /// The type of the service. + IEnumerable GetAllInstances() + where TService : class; + + /// + /// Releases an instance. + /// + /// The instance. + /// + /// See https://stackoverflow.com/questions/14072208 and http://kozmic.net/2010/08/27/must-i-release-everything-when-using-windsor/, + /// you only need to release instances you specifically resolved, and even then, if done right, that might never be needed. For + /// instance, LightInject does not require this and does not support it - should work with scopes. + /// + void Release(object instance); + + /// + /// Begins a scope. + /// + /// + /// When the scope is disposed, scoped instances that have been created during the scope are disposed. + /// Scopes can be nested. Each instance is disposed individually. + /// + IDisposable BeginScope(); + + /// + /// Enables per-request scope. + /// + /// + /// Ties scopes to web requests. + /// + void EnablePerWebRequestScope(); + } +} diff --git a/src/Umbraco.Core/Composing/IRegister.cs b/src/Umbraco.Core/Composing/IRegister.cs new file mode 100644 index 0000000000..cbf12f54a3 --- /dev/null +++ b/src/Umbraco.Core/Composing/IRegister.cs @@ -0,0 +1,106 @@ +using System; + +namespace Umbraco.Core.Composing +{ + /// + /// Defines a service register for Umbraco. + /// + public interface IRegister + { + /// + /// Gets the concrete container. + /// + object Concrete { get; } + + /// + /// Registers a service as its own implementation. + /// + void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient); + + /// + /// Registers a service with an implementation type. + /// + void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient); + + /// + /// Registers a service with an implementation factory. + /// + void Register(Func factory, Lifetime lifetime = Lifetime.Transient) + where TService : class; + + /// + /// Registers a service with an implementing instance. + /// + void Register(Type serviceType, object instance); + + /// + /// Registers a service for a target, as its own implementation. + /// + /// + /// There can only be one implementation or instanced registered for a service and target; + /// what happens if many are registered is not specified. + /// + void RegisterFor(Lifetime lifetime = Lifetime.Transient) + where TService : class; + + /// + /// Registers a service for a target, with an implementation type. + /// + /// + /// There can only be one implementation or instanced registered for a service and target; + /// what happens if many are registered is not specified. + /// + void RegisterFor(Type implementingType, Lifetime lifetime = Lifetime.Transient) + where TService : class; + + /// + /// Registers a service for a target, with an implementation factory. + /// + /// + /// There can only be one implementation or instanced registered for a service and target; + /// what happens if many are registered is not specified. + /// + void RegisterFor(Func factory, Lifetime lifetime = Lifetime.Transient) + where TService : class; + + /// + /// Registers a service for a target, with an implementing instance. + /// + /// + /// There can only be one implementation or instanced registered for a service and target; + /// what happens if many are registered is not specified. + /// + void RegisterFor(TService instance) + where TService : class; + + /// + /// Registers a base type for auto-registration. + /// + /// + /// Auto-registration means that anytime the container is asked to create an instance + /// of a type deriving from , it will first register that + /// type automatically. + /// This can be used for instance for views or controllers. Then, one just needs to + /// register a common base class or interface, and the container knows how to create instances. + /// + void RegisterAuto(Type serviceBaseType); + + #region Control + + /// + /// Configures the container for web support. + /// + /// + /// Enables support for MVC, WebAPI, but *not* per-request scope. This is used early in the boot + /// process, where anything "scoped" should not be linked to a web request. + /// + void ConfigureForWeb(); + + /// + /// Creates the factory. + /// + IFactory CreateFactory(); + + #endregion + } +} diff --git a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs index ee263f458f..46b06daf7d 100644 --- a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; namespace Umbraco.Core.Composing { @@ -13,33 +12,24 @@ namespace Umbraco.Core.Composing /// The type of the items. public abstract class LazyCollectionBuilderBase : CollectionBuilderBase where TBuilder : LazyCollectionBuilderBase - where TCollection : IBuilderCollection + where TCollection : class, IBuilderCollection { - private readonly List>> _producers1 = new List>>(); - private readonly List>> _producers2 = new List>>(); + private readonly List>> _producers = new List>>(); private readonly List _excluded = new List(); - /// - /// Initializes a new instance of the class. - /// - protected LazyCollectionBuilderBase(IServiceContainer container) - : base(container) - { } - protected abstract TBuilder This { get; } /// /// Clears all types in the collection. /// - /// The buidler. + /// The builder. public TBuilder Clear() { Configure(types => { types.Clear(); - _producers1.Clear(); - _producers2.Clear(); - _excluded.Clear(); + _producers.Clear(); + _excluded.Clear(); }); return This; } @@ -84,21 +74,7 @@ namespace Umbraco.Core.Composing { Configure(types => { - _producers1.Add(producer); - }); - return This; - } - - /// - /// Adds a types producer to the collection. - /// - /// The types producer. - /// The builder. - public TBuilder Add(Func> producer) - { - Configure(types => - { - _producers2.Add(producer); + _producers.Add(producer); }); return This; } @@ -137,8 +113,7 @@ namespace Umbraco.Core.Composing protected override IEnumerable GetRegisteringTypes(IEnumerable types) { return types - .Union(_producers1.SelectMany(x => x())) - .Union(_producers2.SelectMany(x => x(Container))) + .Union(_producers.SelectMany(x => x())) .Distinct() .Select(x => EnsureType(x, "register")) .Except(_excluded); diff --git a/src/Umbraco.Core/Composing/Lifetime.cs b/src/Umbraco.Core/Composing/Lifetime.cs new file mode 100644 index 0000000000..e1b9950c39 --- /dev/null +++ b/src/Umbraco.Core/Composing/Lifetime.cs @@ -0,0 +1,49 @@ +namespace Umbraco.Core.Composing +{ + /// + /// Specifies the lifetime of a registered instance. + /// + public enum Lifetime + { + /// + /// Always get a new instance. + /// + /// Corresponds to Transient in LightInject, Castle Windsor + /// or MS.DI, PerDependency in Autofac. + Transient, + + /// + /// One unique instance per request. + /// + // fixme - not what you think! + // currently, corresponds to 'Request' in LightInject which is 'Transient + disposed by Scope' + // but NOT (in LightInject) a per-web-request lifetime, more a TransientScoped + // + // we use it for controllers, httpContextBase and umbracoContext + // - so that they are automatically disposed at the end of the scope (ie request) + // - not sure they should not be simply 'scoped'? + // + // Castle has an extra PerWebRequest something, and others use scope + // what about Request before first request ie during application startup? + // see http://blog.ploeh.dk/2009/11/17/UsingCastleWindsor'sPerWebRequestlifestylewithASP.NETMVConIIS7/ + // Castle ends up requiring a special scope manager too + // see https://groups.google.com/forum/#!topic/castle-project-users/1E2W9LVIYR4 + // + // but maybe also - why are we requiring scoped services at startup? + Request, + + /// + /// One unique instance per container scope. + /// + /// Corresponds to Scope in LightInject, Scoped in MS.DI + /// or Castle Windsor, PerLifetimeScope in Autofac. + Scope, + + /// + /// One unique instance per container. + /// + /// Corresponds to Singleton in LightInject, Castle Windsor + /// or MS.DI and to SingleInstance in Autofac. + Singleton + } +} diff --git a/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs new file mode 100644 index 0000000000..d8a554ee8c --- /dev/null +++ b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using LightInject; + +namespace Umbraco.Core.Composing.LightInject +{ + /// + /// Implements DI with LightInject. + /// + public class LightInjectContainer : IRegister, IFactory, IDisposable + { + private int _disposed; + + /// + /// Initializes a new instance of the with a LightInject container. + /// + protected LightInjectContainer(ServiceContainer container) + { + Container = container; + } + + /// + /// Creates a new instance of the class. + /// + public static LightInjectContainer Create() + => new LightInjectContainer(CreateServiceContainer()); + + /// + /// Creates a new instance of the LightInject service container. + /// + protected static ServiceContainer CreateServiceContainer() + { + var container = new ServiceContainer(new ContainerOptions { EnablePropertyInjection = false }); + + // note: the block below is disabled, as it is too LightInject-specific + // + // supports annotated constructor injections + // eg to specify the service name on some services + //container.EnableAnnotatedConstructorInjection(); + + // note: the block below is disabled, we do not allow property injection at all anymore + // (see options in CreateServiceContainer) + // + // from the docs: "LightInject considers all read/write properties a dependency, but implements + // a loose strategy around property dependencies, meaning that it will NOT throw an exception + // in the case of an unresolved property dependency." + // + // in Umbraco we do NOT want to do property injection by default, so we have to disable it. + // from the docs, the following line will cause the container to "now only try to inject + // dependencies for properties that is annotated with the InjectAttribute." + // + // could not find it documented, but tests & code review shows that LightInject considers a + // property to be "injectable" when its setter exists and is not static, nor private, nor + // it is an index property. which means that eg protected or internal setters are OK. + //Container.EnableAnnotatedPropertyInjection(); + + // ensure that we do *not* scan assemblies + // we explicitly RegisterFrom our own composition roots and don't want them scanned + container.AssemblyScanner = new AssemblyScanner(/*container.AssemblyScanner*/); + + // see notes in MixedLightInjectScopeManagerProvider + container.ScopeManagerProvider = new MixedLightInjectScopeManagerProvider(); + + // note: the block below is disabled, because it does not work, because collection builders + // are singletons, and constructor dependencies don't work on singletons, see + // https://github.com/seesharper/LightInject/issues/294 + // + // if looking for a IContainer, and one was passed in args, use it + // this is for collection builders which require the IContainer + //container.RegisterConstructorDependency((c, i, a) => a.OfType().FirstOrDefault()); + // + // and, the block below is also disabled, because it is ugly + // + //// which means that the only way to inject the container into builders is to register it + //container.RegisterInstance(this); + // + // instead, we use an explicit GetInstance with arguments implementation + + return container; + } + + /// + /// Gets the LightInject container. + /// + protected ServiceContainer Container { get; } + + /// + /// + public object Concrete => Container; + + /// + public void Dispose() + { + if (Interlocked.Exchange(ref _disposed, 1) == 1) + return; + + Container.Dispose(); + } + + /// + public IFactory CreateFactory() => this; + + private static string GetTargetedServiceName() => "TARGET:" + typeof(TTarget).FullName; + + #region Factory + + /// + public object GetInstance(Type type) + => Container.GetInstance(type); + + /// + public TService GetInstanceFor() + => Container.GetInstance(GetTargetedServiceName()); + + /// + public object TryGetInstance(Type type) + => Container.TryGetInstance(type); + + /// + public IEnumerable GetAllInstances() + where T : class + => Container.GetAllInstances(); + + /// + public IEnumerable GetAllInstances(Type type) + => Container.GetAllInstances(type); + + /// + public void Release(object instance) + { + // nothing to release with LightInject + } + + // notes: + // we may want to look into MS code, eg: + // TypeActivatorCache in MVC at https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.cs + // which relies onto + // ActivatorUtilities at https://github.com/aspnet/DependencyInjection/blob/master/shared/Microsoft.Extensions.ActivatorUtilities.Sources/ActivatorUtilities.cs + + #endregion + + #region Registry + + /// + public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient) + => Container.Register(serviceType, GetLifetime(lifetime)); + + /// + public void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient) + { + switch (lifetime) + { + case Lifetime.Transient: + Container.Register(serviceType, implementingType, implementingType.Name); + break; + case Lifetime.Request: + case Lifetime.Scope: + case Lifetime.Singleton: + Container.Register(serviceType, implementingType, GetLifetime(lifetime)); + break; + default: + throw new NotSupportedException($"Lifetime {lifetime} is not supported."); + } + } + + /// + public void Register(Func factory, Lifetime lifetime = Lifetime.Transient) + where TService : class + { + Container.Register(f => factory(this), GetLifetime(lifetime)); + } + + /// + public void Register(Type serviceType, object instance) + => Container.RegisterInstance(serviceType, instance); + + /// + public void RegisterFor(Lifetime lifetime = Lifetime.Transient) + where TService : class + => RegisterFor(typeof(TService), lifetime); + + /// + public void RegisterFor(Type implementingType, Lifetime lifetime = Lifetime.Transient) + where TService : class + { + // note that there can only be one implementation or instance registered "for" a service + Container.Register(typeof(TService), implementingType, GetTargetedServiceName(), GetLifetime(lifetime)); + } + + /// + public void RegisterFor(Func factory, Lifetime lifetime = Lifetime.Transient) + where TService : class + { + // note that there can only be one implementation or instance registered "for" a service + Container.Register(f => factory(this), GetTargetedServiceName(), GetLifetime(lifetime)); + } + + /// + public void RegisterFor(TService instance) + where TService : class + => Container.RegisterInstance(typeof(TService), instance, GetTargetedServiceName()); + + private ILifetime GetLifetime(Lifetime lifetime) + { + switch (lifetime) + { + case Lifetime.Transient: + return null; + case Lifetime.Request: + return new PerRequestLifeTime(); + case Lifetime.Scope: + return new PerScopeLifetime(); + case Lifetime.Singleton: + return new PerContainerLifetime(); + default: + throw new NotSupportedException($"Lifetime {lifetime} is not supported."); + } + } + + /// + public void RegisterAuto(Type serviceBaseType) + { + Container.RegisterFallback((serviceType, serviceName) => + { + // https://github.com/seesharper/LightInject/issues/173 + if (serviceBaseType.IsAssignableFromGtd(serviceType)) + Container.Register(serviceType); + return false; + }, null); + } + + #endregion + + #region Control + + /// + public IDisposable BeginScope() + => Container.BeginScope(); + + /// + public virtual void ConfigureForWeb() + { } + + /// + public void EnablePerWebRequestScope() + { + if (!(Container.ScopeManagerProvider is MixedLightInjectScopeManagerProvider smp)) + throw new Exception("Container.ScopeManagerProvider is not MixedLightInjectScopeManagerProvider."); + smp.EnablePerWebRequestScope(); + } + + private class AssemblyScanner : IAssemblyScanner + { + public void Scan(Assembly assembly, IServiceRegistry serviceRegistry, Func lifetime, Func shouldRegister, Func serviceNameProvider) + { + // nothing - we don't want LightInject to scan + } + + public void Scan(Assembly assembly, IServiceRegistry serviceRegistry) + { + // nothing - we don't want LightInject to scan + } + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Exceptions/LightInjectException.cs b/src/Umbraco.Core/Composing/LightInject/LightInjectException.cs similarity index 96% rename from src/Umbraco.Core/Exceptions/LightInjectException.cs rename to src/Umbraco.Core/Composing/LightInject/LightInjectException.cs index 03fd9f2f9f..fa0aed21ca 100644 --- a/src/Umbraco.Core/Exceptions/LightInjectException.cs +++ b/src/Umbraco.Core/Composing/LightInject/LightInjectException.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -namespace Umbraco.Core.Exceptions +namespace Umbraco.Core.Composing.LightInject { /// /// Represents errors that occur due to LightInject. diff --git a/src/Umbraco.Core/Composing/MixedLightInjectScopeManagerProvider.cs b/src/Umbraco.Core/Composing/LightInject/MixedLightInjectScopeManagerProvider.cs similarity index 88% rename from src/Umbraco.Core/Composing/MixedLightInjectScopeManagerProvider.cs rename to src/Umbraco.Core/Composing/LightInject/MixedLightInjectScopeManagerProvider.cs index 05bdbc446d..897c58dd43 100644 --- a/src/Umbraco.Core/Composing/MixedLightInjectScopeManagerProvider.cs +++ b/src/Umbraco.Core/Composing/LightInject/MixedLightInjectScopeManagerProvider.cs @@ -1,7 +1,7 @@ using LightInject; using LightInject.Web; -namespace Umbraco.Core.Composing +namespace Umbraco.Core.Composing.LightInject { // by default, the container's scope manager provider is PerThreadScopeManagerProvider, // and then container.EnablePerWebRequestScope() replaces it with PerWebRequestScopeManagerProvider, @@ -13,6 +13,9 @@ namespace Umbraco.Core.Composing // of PerWebRequestScopeManagerProvider - but all delegates see is the mixed one - and therefore // they can transition without issues. // + // The PerWebRequestScopeManager maintains the scope in HttpContext and LightInject registers a + // module (PreApplicationStartMethod) which disposes it on EndRequest + // // the mixed provider is installed in container.ConfigureUmbracoCore() and then, // when doing eg container.EnableMvc() or anything that does container.EnablePerWebRequestScope() // we need to take great care to preserve the mixed scope manager provider! diff --git a/src/Umbraco.Core/Composing/LightInjectExtensions.cs b/src/Umbraco.Core/Composing/LightInjectExtensions.cs deleted file mode 100644 index 68ba48c803..0000000000 --- a/src/Umbraco.Core/Composing/LightInjectExtensions.cs +++ /dev/null @@ -1,392 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using LightInject; -using Umbraco.Core.Exceptions; - -namespace Umbraco.Core.Composing -{ - /// - /// Provides extensions to LightInject. - /// - public static class LightInjectExtensions - { - /// - /// Configure the container for Umbraco Core usage and assign to Current. - /// - /// The container. - /// The container is now the unique application container and is now accessible via Current.Container. - internal static void ConfigureUmbracoCore(this ServiceContainer container) - { - // supports annotated constructor injections - // eg to specify the service name on some services - container.EnableAnnotatedConstructorInjection(); - - // from the docs: "LightInject considers all read/write properties a dependency, but implements - // a loose strategy around property dependencies, meaning that it will NOT throw an exception - // in the case of an unresolved property dependency." - // - // in Umbraco we do NOT want to do property injection by default, so we have to disable it. - // from the docs, the following line will cause the container to "now only try to inject - // dependencies for properties that is annotated with the InjectAttribute." - // - // could not find it documented, but tests & code review shows that LightInject considers a - // property to be "injectable" when its setter exists and is not static, nor private, nor - // it is an index property. which means that eg protected or internal setters are OK. - container.EnableAnnotatedPropertyInjection(); - - // ensure that we do *not* scan assemblies - // we explicitely RegisterFrom our own composition roots and don't want them scanned - container.AssemblyScanner = new AssemblyScanner(/*container.AssemblyScanner*/); - - // see notes in MixedLightInjectScopeManagerProvider - container.ScopeManagerProvider = new MixedLightInjectScopeManagerProvider(); - - // self-register - container.Register(_ => container); - - // configure the current container - Current.Container = container; - } - - private class AssemblyScanner : IAssemblyScanner - { - //private readonly IAssemblyScanner _scanner; - - //public AssemblyScanner(IAssemblyScanner scanner) - //{ - // _scanner = scanner; - //} - - public void Scan(Assembly assembly, IServiceRegistry serviceRegistry, Func lifetime, Func shouldRegister, Func serviceNameProvider) - { - // nothing - we *could* scan non-Umbraco assemblies, though - } - - public void Scan(Assembly assembly, IServiceRegistry serviceRegistry) - { - // nothing - we *could* scan non-Umbraco assemblies, though - } - } - - /// - /// Registers a service implementation with a specified lifetime. - /// - /// The type of the service. - /// The type of the implementation. - /// The type of the lifetime. - /// The container. - public static void Register(this IServiceContainer container) - where TImplementation : TService - where TLifetime : ILifetime, new() - { - container.Register(new TLifetime()); - } - - /// - /// Registers a service implementation with a specified lifetime. - /// - /// The type of the service. - /// The type of the lifetime. - /// The container. - /// A factory. - public static void Register(this IServiceContainer container, Func factory) - where TLifetime : ILifetime, new() - { - container.Register(factory, new TLifetime()); - } - - /// - /// Registers several service implementations with a specified lifetime. - /// - /// The type of the service. - /// The type of the lifetime. - /// The container. - /// The types of the implementations. - public static void RegisterMany(this IServiceContainer container, IEnumerable implementations) - where TLifeTime : ILifetime, new() - { - foreach (var implementation in implementations) - { - // if "typeof (TService)" is there then "implementation.FullName" MUST be there too - container.Register(typeof(TService), implementation, implementation.FullName, new TLifeTime()); - } - } - - /// - /// Registers the TService with the factory that describes the dependencies of the service, as a singleton. - /// - public static void RegisterSingleton(this IServiceRegistry container, Func factory, string serviceName) - { - var registration = container.GetAvailableService(serviceName); - if (registration == null) - { - container.Register(factory, serviceName, new PerContainerLifetime()); - } - else - { - if (registration.Lifetime is PerContainerLifetime == false) - throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); - UpdateRegistration(registration, null, factory); - } - } - - /// - /// Registers the TService with the TImplementation as a singleton. - /// - public static void RegisterSingleton(this IServiceRegistry container) - where TImplementation : TService - { - var registration = container.GetAvailableService(); - - if (registration == null) - { - container.Register(new PerContainerLifetime()); - } - else - { - if (registration.Lifetime is PerContainerLifetime == false) - throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); - UpdateRegistration(registration, typeof(TImplementation), null); - } - } - - /// - /// Registers a concrete type as a singleton service. - /// - public static void RegisterSingleton(this IServiceRegistry container) - { - var registration = container.GetAvailableService(); - if (registration == null) - { - container.Register(new PerContainerLifetime()); - } - else - { - if (registration.Lifetime is PerContainerLifetime == false) - throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); - UpdateRegistration(registration, typeof(TImplementation), null); - } - - } - - /// - /// Registers the TService with the factory that describes the dependencies of the service, as a singleton. - /// - /// - /// - /// - public static void RegisterSingleton(this IServiceRegistry container, Func factory) - { - var registration = container.GetAvailableService(); - if (registration == null) - container.Register(factory, new PerContainerLifetime()); - else - UpdateRegistration(registration, null, factory); - } - - // note - what's below ALSO applies to non-singleton ie transient services - // - // see https://github.com/seesharper/LightInject/issues/133 - // - // note: we *could* use tracking lifetimes for singletons to ensure they have not been resolved - // already but that would not work for transient as the transient lifetime is null (and that is - // optimized in LightInject) - // - // also, RegisterSingleton above is dangerous because ppl could still use Register with a - // PerContainerLifetime and it will not work + the default Register would not work either for other - // lifetimes - // - // all in all, not sure we want to let ppl have direct access to the container - // we might instead want to expose some methods in UmbracoComponentBase or whatever? - - /// - /// Updates a registration. - /// - private static void UpdateRegistration(Registration registration, Type implementingType, Delegate factoryExpression) - { - // if the container has compiled already then the registrations have been captured, - // and re-registering - although updating available services - does not modify the - // output of GetInstance - // - // so we have to rely on different methods - // - // assuming the service has NOT been resolved, both methods below work, but the first - // one looks simpler. it would be good to check whether the service HAS been resolved - // but I am not sure how to do it right now, depending on the lifetime - // - // if the service HAS been resolved then updating is probably a bad idea - - // not sure which is best? that one works, though, and looks simpler - registration.ImplementingType = implementingType; - registration.FactoryExpression = factoryExpression; - - //container.Override( - // r => r.ServiceType == typeof (TService) && (registration.ServiceName == null || r.ServiceName == registration.ServiceName), - // (f, r) => - // { - // r.ImplementingType = implementingType; - // r.FactoryExpression = factoryExpression; - // return r; - // }); - } - - /// - /// Gets the available service registrations for a service type. - /// - /// The service type. - /// The container. - /// The service registrations for the service type. - public static IEnumerable GetAvailableServices(this IServiceRegistry container) - { - var typeofTService = typeof(TService); - return container.AvailableServices.Where(x => x.ServiceType == typeofTService); - } - - /// - /// Gets the unique available service registration for a service type. - /// - /// The service type. - /// The container. - /// The unique service registration for the service type. - /// Can return null, but throws if more than one registration exist for the service type. - public static ServiceRegistration GetAvailableService(this IServiceRegistry container) - { - var typeofTService = typeof(TService); - return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService); - } - - /// - /// Gets the unique available service registration for a service type and a name. - /// - /// The service type. - /// The container. - /// The name. - /// The unique service registration for the service type and the name. - /// Can return null, but throws if more than one registration exist for the service type and the name. - public static ServiceRegistration GetAvailableService(this IServiceRegistry container, string name) - { - var typeofTService = typeof(TService); - return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService && x.ServiceName == name); - } - - /// - /// Gets an instance of a TService or throws a meaningful exception. - /// - /// The service type. - /// The container. - /// The instance. - public static TService GetInstanceOrThrow(this IServiceFactory factory) - { - if (factory == null) - throw new ArgumentNullException(nameof(factory)); - - try - { - return factory.GetInstance(); - } - catch (Exception e) - { - LightInjectException.TryThrow(e); - throw; - } - } - - /// - /// Gets an instance of a TService or throws a meaningful exception. - /// - /// The container. - /// The type of the service. - /// The name of the service. - /// The implementing type. - /// Arguments. - /// The instance. - internal static object GetInstanceOrThrow(this IServiceFactory factory, Type tService, string serviceName, Type implementingType, object[] args) - { - if (factory == null) - throw new ArgumentNullException(nameof(factory)); - - // fixme temp - STOP doing this, it confuses LightInject and then we get ugly exception traces - // we HAVE to let LightInject throw - and catch at THE OUTERMOST if InvalidOperationException in LightInject.Anything! - - return factory.GetInstance(tService, serviceName, args); - //try - //{ - // return factory.GetInstance(tService, serviceName, args); - //} - //catch (Exception e) - //{ - // LightInjectException.TryThrow(e, implementingType); - // throw; - //} - } - - /// - /// Registers a base type for auto-registration. - /// - /// The base type. - /// The container. - /// - /// Any type that inherits/implements the base type will be auto-registered on-demand. - /// This methods works with actual types. Use the other overload for eg generic definitions. - /// - public static void RegisterAuto(this IServiceContainer container) - { - container.RegisterFallback((serviceType, serviceName) => - { - //Current.Logger.Debug(typeof(LightInjectExtensions), $"Fallback for type {serviceType.FullName}."); - // https://github.com/seesharper/LightInject/issues/173 - - if (typeof(T).IsAssignableFrom(serviceType)) - container.Register(serviceType); - return false; - }, null); - } - - /// - /// Registers a base type for auto-registration. - /// - /// The container. - /// The base type. - /// - /// Any type that inherits/implements the base type will be auto-registered on-demand. - /// This methods works with actual types, as well as generic definitions eg typeof(MyBase{}). - /// - public static void RegisterAuto(this IServiceContainer container, Type type) - { - container.RegisterFallback((serviceType, serviceName) => - { - //Current.Logger.Debug(typeof(LightInjectExtensions), $"Fallback for type {serviceType.FullName}."); - // https://github.com/seesharper/LightInject/issues/173 - - if (type.IsAssignableFromGtd(serviceType)) - container.Register(serviceType); - return false; - }, null); - } - - /// - /// Registers and instanciates a collection builder. - /// - /// The type of the collection builder. - /// The container. - /// The collection builder. - public static TBuilder RegisterCollectionBuilder(this IServiceContainer container) - { - // make sure it's not already registered - // we just don't want to support re-registering collection builders - var registration = container.GetAvailableService(); - if (registration != null) - throw new InvalidOperationException("Collection builders should be registered only once."); - - // register the builder - per container - var builderLifetime = new PerContainerLifetime(); - container.Register(builderLifetime); - - // return the builder - // (also initializes the builder) - return container.GetInstance(); - } - } -} diff --git a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs index 4811551cd2..241b84d8d2 100644 --- a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using LightInject; namespace Umbraco.Core.Composing { @@ -12,22 +11,14 @@ namespace Umbraco.Core.Composing /// The type of the items. public abstract class OrderedCollectionBuilderBase : CollectionBuilderBase where TBuilder : OrderedCollectionBuilderBase - where TCollection : IBuilderCollection + where TCollection : class, IBuilderCollection { - /// - /// Initializes a new instance of the class. - /// - /// - protected OrderedCollectionBuilderBase(IServiceContainer container) - : base (container) - { } - protected abstract TBuilder This { get; } /// /// Clears all types in the collection. /// - /// The buidler. + /// The builder. public TBuilder Clear() { Configure(types => types.Clear()); @@ -87,26 +78,6 @@ namespace Umbraco.Core.Composing return This; } - /// - /// Appends types to the collections. - /// - /// The types to append. - /// The builder. - public TBuilder Append(Func> types) - { - Configure(list => - { - foreach (var type in types(Container)) - { - // would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast - EnsureType(type, "register"); - if (list.Contains(type)) list.Remove(type); - list.Add(type); - } - }); - return This; - } - /// /// Appends a type after another type. /// diff --git a/src/Umbraco.Core/Composing/RegisterExtensions.cs b/src/Umbraco.Core/Composing/RegisterExtensions.cs new file mode 100644 index 0000000000..d1eacc0c0f --- /dev/null +++ b/src/Umbraco.Core/Composing/RegisterExtensions.cs @@ -0,0 +1,42 @@ +namespace Umbraco.Core.Composing +{ + /// + /// Provides extension methods to the class. + /// + public static class RegisterExtensions + { + /// + /// Registers a service with an implementation type. + /// + public static void Register(this IRegister register, Lifetime lifetime = Lifetime.Transient) + => register.Register(typeof(TService), typeof(TImplementing), lifetime); + + /// + /// Registers a service with an implementation type, for a target. + /// + public static void RegisterFor(this IRegister register, Lifetime lifetime = Lifetime.Transient) + where TService : class + => register.RegisterFor(typeof(TImplementing), lifetime); + + /// + /// Registers a service as its own implementation. + /// + public static void Register(this IRegister register, Lifetime lifetime = Lifetime.Transient) + where TService : class + => register.Register(typeof(TService), lifetime); + + /// + /// Registers a service with an implementing instance. + /// + public static void Register(this IRegister register, TService instance) + where TService : class + => register.Register(typeof(TService), instance); + + /// + /// Registers a base type for auto-registration. + /// + public static void RegisterAuto(this IRegister register) + where TServiceBase : class + => register.RegisterAuto(typeof(TServiceBase)); + } +} diff --git a/src/Umbraco.Core/Composing/RegisterFactory.cs b/src/Umbraco.Core/Composing/RegisterFactory.cs new file mode 100644 index 0000000000..8ee6e5a94c --- /dev/null +++ b/src/Umbraco.Core/Composing/RegisterFactory.cs @@ -0,0 +1,56 @@ +using System; +using System.Configuration; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Creates the container. + /// + public static class RegisterFactory + { + // cannot use typeof().AssemblyQualifiedName on the web container - we don't reference it + // a normal Umbraco site should run on the web container, but an app may run on the core one + private const string CoreLightInjectContainerTypeName = "Umbraco.Core.Composing.LightInject.LightInjectContainer,Umbraco.Core"; + private const string WebLightInjectContainerTypeName = "Umbraco.Web.Composing.LightInject.LightInjectContainer,Umbraco.Web"; + + /// + /// Creates a new instance of the configured container. + /// + /// + /// To override the default LightInjectContainer, add an appSetting named umbracoContainerType with + /// a fully qualified type name to a class with a static method "Create" returning an IRegister. + /// + public static IRegister Create() + { + Type type; + + var configuredTypeName = ConfigurationManager.AppSettings["umbracoRegisterType"]; + if (configuredTypeName.IsNullOrWhiteSpace()) + { + // try to get the web LightInject container type, + // else the core LightInject container type + type = Type.GetType(configuredTypeName = WebLightInjectContainerTypeName) ?? + Type.GetType(configuredTypeName = CoreLightInjectContainerTypeName); + } + else + { + // try to get the configured type + type = Type.GetType(configuredTypeName); + } + + if (type == null) + throw new Exception($"Cannot find register factory class '{configuredTypeName}'."); + + var factoryMethod = type.GetMethod("Create", BindingFlags.Public | BindingFlags.Static); + if (factoryMethod == null) + throw new Exception($"Register factory class '{configuredTypeName}' does not have a public static method named Create."); + + var container = factoryMethod.Invoke(null, Array.Empty()) as IRegister; + if (container == null) + throw new Exception($"Register factory '{configuredTypeName}' did not return an IRegister implementation."); + + return container; + } + } +} diff --git a/src/Umbraco.Core/Composing/TargetedServiceFactory.cs b/src/Umbraco.Core/Composing/TargetedServiceFactory.cs new file mode 100644 index 0000000000..53022c0043 --- /dev/null +++ b/src/Umbraco.Core/Composing/TargetedServiceFactory.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Composing +{ + /// + /// Provides a base class for targeted service factories. + /// + /// + public abstract class TargetedServiceFactory + { + private readonly IFactory _factory; + + protected TargetedServiceFactory(IFactory factory) + { + _factory = factory; + } + + public TService For() => _factory.GetInstanceFor(); + } +} diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index ef58671e91..b31f16f30a 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -29,12 +29,11 @@ namespace Umbraco.Core.Composing { private const string CacheKey = "umbraco-types.list"; - private readonly IRuntimeCacheProvider _runtimeCache; - private readonly IGlobalSettings _globalSettings; - private readonly ProfilingLogger _logger; + private readonly IAppPolicyCache _runtimeCache; + private readonly IProfilingLogger _logger; private readonly Dictionary _types = new Dictionary(); - private readonly object _typesLock = new object(); + private readonly object _locko = new object(); private readonly object _timerLock = new object(); private Timer _timer; @@ -43,31 +42,30 @@ namespace Umbraco.Core.Composing private string _currentAssembliesHash; private IEnumerable _assemblies; private bool _reportedChange; - private static LocalTempStorage _localTempStorage = LocalTempStorage.Unknown; + private static LocalTempStorage _localTempStorage; private static string _fileBasePath; /// /// Initializes a new instance of the class. /// /// The application runtime cache. - /// + /// Files storage mode. /// A profiling logger. - /// Used by LightInject. - public TypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, ProfilingLogger logger) - : this(runtimeCache, globalSettings, logger, true) + public TypeLoader(IAppPolicyCache runtimeCache, LocalTempStorage localTempStorage, IProfilingLogger logger) + : this(runtimeCache, localTempStorage, logger, true) { } /// /// Initializes a new instance of the class. /// /// The application runtime cache. - /// + /// Files storage mode. /// A profiling logger. /// Whether to detect changes using hashes. - internal TypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, ProfilingLogger logger, bool detectChanges) + internal TypeLoader(IAppPolicyCache runtimeCache, LocalTempStorage localTempStorage, IProfilingLogger logger, bool detectChanges) { _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); - _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); + _localTempStorage = localTempStorage == LocalTempStorage.Unknown ? LocalTempStorage.Default : localTempStorage; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); if (detectChanges) @@ -100,6 +98,13 @@ namespace Umbraco.Core.Composing } } + /// + /// Initializes a new, test/blank, instance of the class. + /// + /// The initialized instance cannot get types. + internal TypeLoader() + { } + /// /// Gets or sets the set of assemblies to scan. /// @@ -180,9 +185,7 @@ namespace Umbraco.Core.Composing // the app code folder and everything in it new Tuple(new DirectoryInfo(IOHelper.MapPath("~/App_Code")), false), // global.asax (the app domain also monitors this, if it changes will do a full restart) - new Tuple(new FileInfo(IOHelper.MapPath("~/global.asax")), false), - // trees.config - use the contents to create the hash since this gets resaved on every app startup! - new Tuple(new FileInfo(IOHelper.MapPath(SystemDirectories.Config + "/trees.config")), true) + new Tuple(new FileInfo(IOHelper.MapPath("~/global.asax")), false) }, _logger); return _currentAssembliesHash; @@ -206,7 +209,7 @@ namespace Umbraco.Core.Composing /// The hash. /// Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the /// file properties (false) or the file contents (true). - private static string GetFileHash(IEnumerable> filesAndFolders, ProfilingLogger logger) + private static string GetFileHash(IEnumerable> filesAndFolders, IProfilingLogger logger) { using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) { @@ -264,7 +267,7 @@ namespace Umbraco.Core.Composing /// A profiling logger. /// The hash. // internal for tests - internal static string GetFileHash(IEnumerable filesAndFolders, ProfilingLogger logger) + internal static string GetFileHash(IEnumerable filesAndFolders, IProfilingLogger logger) { using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) { @@ -380,14 +383,15 @@ namespace Umbraco.Core.Composing private string GetFileBasePath() { - var localTempStorage = _globalSettings.LocalTempStorageLocation; - if (_localTempStorage != localTempStorage) + lock (_locko) { - string path; - switch (_globalSettings.LocalTempStorageLocation) + if (_fileBasePath != null) + return _fileBasePath; + + switch (_localTempStorage) { case LocalTempStorage.AspNetTemp: - path = Path.Combine(HttpRuntime.CodegenDir, "UmbracoData", "umbraco-types"); + _fileBasePath = Path.Combine(HttpRuntime.CodegenDir, "UmbracoData", "umbraco-types"); break; case LocalTempStorage.EnvironmentTemp: // include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back @@ -395,33 +399,30 @@ namespace Umbraco.Core.Composing // utilizing an old path - assuming we cannot have SHA1 collisions on AppDomainAppId var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash); - path = Path.Combine(cachePath, "umbraco-types"); + _fileBasePath = Path.Combine(cachePath, "umbraco-types"); break; case LocalTempStorage.Default: default: - var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/TypesCache"); - path = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName); + var tempFolder = IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "TypesCache"); + _fileBasePath = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName); break; } - _fileBasePath = path; - _localTempStorage = localTempStorage; + // ensure that the folder exists + var directory = Path.GetDirectoryName(_fileBasePath); + if (directory == null) + throw new InvalidOperationException($"Could not determine folder for path \"{_fileBasePath}\"."); + if (Directory.Exists(directory) == false) + Directory.CreateDirectory(directory); + + return _fileBasePath; } - - // ensure that the folder exists - var directory = Path.GetDirectoryName(_fileBasePath); - if (directory == null) - throw new InvalidOperationException($"Could not determine folder for path \"{_fileBasePath}\"."); - if (Directory.Exists(directory) == false) - Directory.CreateDirectory(directory); - - return _fileBasePath; } // internal for tests internal void WriteCache() { - _logger.Logger.Debug("Writing cache file."); + _logger.Debug("Writing cache file."); var typesListFilePath = GetTypesListFilePath(); using (var stream = GetFileStream(typesListFilePath, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout)) using (var writer = new StreamWriter(stream)) @@ -475,7 +476,7 @@ namespace Umbraco.Core.Composing var typesHashFilePath = GetTypesHashFilePath(); DeleteFile(typesHashFilePath, FileDeleteTimeout); - _runtimeCache.ClearCacheItem(CacheKey); + _runtimeCache.Clear(CacheKey); } private Stream GetFileStream(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, int timeoutMilliseconds) @@ -493,7 +494,7 @@ namespace Umbraco.Core.Composing if (--attempts == 0) throw; - _logger.Logger.Debug("Attempted to get filestream for file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds); + _logger.Debug("Attempted to get filestream for file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds); Thread.Sleep(pauseMilliseconds); } } @@ -514,7 +515,7 @@ namespace Umbraco.Core.Composing if (--attempts == 0) throw; - _logger.Logger.Debug("Attempted to delete file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds); + _logger.Debug("Attempted to delete file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds); Thread.Sleep(pauseMilliseconds); } } @@ -534,6 +535,9 @@ namespace Umbraco.Core.Composing /// Caching is disabled when using specific assemblies. public IEnumerable GetTypes(bool cache = true, IEnumerable specificAssemblies = null) { + if (_logger == null) + throw new InvalidOperationException("Cannot get types from a test/blank type loader."); + // do not cache anything from specific assemblies cache &= specificAssemblies == null; @@ -541,7 +545,7 @@ namespace Umbraco.Core.Composing if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T))) { // warn - _logger.Logger.Debug("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} (slow).", typeof(T).FullName); + _logger.Debug("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} (slow).", typeof(T).FullName); return GetTypesInternal( typeof(T), null, @@ -559,7 +563,7 @@ namespace Umbraco.Core.Composing // warn if (!cache) - _logger.Logger.Debug("Running a non-cached, filter for discoverable type {TypeName} (slowish).", typeof(T).FullName); + _logger.Debug("Running a non-cached, filter for discoverable type {TypeName} (slowish).", typeof(T).FullName); // filter the cached discovered types (and maybe cache the result) return GetTypesInternal( @@ -582,13 +586,16 @@ namespace Umbraco.Core.Composing public IEnumerable GetTypesWithAttribute(bool cache = true, IEnumerable specificAssemblies = null) where TAttribute : Attribute { + if (_logger == null) + throw new InvalidOperationException("Cannot get types from a test/blank type loader."); + // do not cache anything from specific assemblies cache &= specificAssemblies == null; // if not IDiscoverable, directly get types if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T))) { - _logger.Logger.Debug("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} / attribute {AttributeName} (slow).", typeof(T).FullName, typeof(TAttribute).FullName); + _logger.Debug("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} / attribute {AttributeName} (slow).", typeof(T).FullName, typeof(TAttribute).FullName); return GetTypesInternal( typeof(T), typeof(TAttribute), @@ -606,7 +613,7 @@ namespace Umbraco.Core.Composing // warn if (!cache) - _logger.Logger.Debug("Running a non-cached, filter for discoverable type {TypeName} / attribute {AttributeName} (slowish).", typeof(T).FullName, typeof(TAttribute).FullName); + _logger.Debug("Running a non-cached, filter for discoverable type {TypeName} / attribute {AttributeName} (slowish).", typeof(T).FullName, typeof(TAttribute).FullName); // filter the cached discovered types (and maybe cache the result) return GetTypesInternal( @@ -629,11 +636,14 @@ namespace Umbraco.Core.Composing public IEnumerable GetAttributedTypes(bool cache = true, IEnumerable specificAssemblies = null) where TAttribute : Attribute { + if (_logger == null) + throw new InvalidOperationException("Cannot get types from a test/blank type loader."); + // do not cache anything from specific assemblies cache &= specificAssemblies == null; if (!cache) - _logger.Logger.Debug("Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", typeof(TAttribute).FullName); + _logger.Debug("Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", typeof(TAttribute).FullName); return GetTypesInternal( typeof (object), typeof (TAttribute), @@ -655,7 +665,7 @@ namespace Umbraco.Core.Composing var name = GetName(baseType, attributeType); - lock (_typesLock) + lock (_locko) using (_logger.TraceDuration( "Getting " + name, "Got " + name)) // cannot contain typesFound.Count as it's evaluated before the find @@ -689,7 +699,7 @@ namespace Umbraco.Core.Composing if (typeList != null) { // need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505 - _logger.Logger.Debug("Getting {TypeName}: found a cached type list.", GetName(baseType, attributeType)); + _logger.Debug("Getting {TypeName}: found a cached type list.", GetName(baseType, attributeType)); return typeList.Types; } @@ -705,7 +715,7 @@ namespace Umbraco.Core.Composing // report (only once) and scan and update the cache file if (_reportedChange == false) { - _logger.Logger.Debug("Assemblies changes detected, need to rescan everything."); + _logger.Debug("Assemblies changes detected, need to rescan everything."); _reportedChange = true; } } @@ -720,7 +730,7 @@ namespace Umbraco.Core.Composing // so in this instance there will never be a result. if (cacheResult.Exception is CachedTypeNotFoundInFileException || cacheResult.Success == false) { - _logger.Logger.Debug("Getting {TypeName}: failed to load from cache file, must scan assemblies.", GetName(baseType, attributeType)); + _logger.Debug("Getting {TypeName}: failed to load from cache file, must scan assemblies.", GetName(baseType, attributeType)); scan = true; } else @@ -739,7 +749,7 @@ namespace Umbraco.Core.Composing catch (Exception ex) { // in case of any exception, we have to exit, and revert to scanning - _logger.Logger.Error(ex, "Getting {TypeName}: failed to load cache file type {CacheType}, reverting to scanning assemblies.", GetName(baseType, attributeType), type); + _logger.Error(ex, "Getting {TypeName}: failed to load cache file type {CacheType}, reverting to scanning assemblies.", GetName(baseType, attributeType), type); scan = true; break; } @@ -747,7 +757,7 @@ namespace Umbraco.Core.Composing if (scan == false) { - _logger.Logger.Debug("Getting {TypeName}: loaded types from cache file.", GetName(baseType, attributeType)); + _logger.Debug("Getting {TypeName}: loaded types from cache file.", GetName(baseType, attributeType)); } } } @@ -755,7 +765,7 @@ namespace Umbraco.Core.Composing if (scan) { // either we had to scan, or we could not get the types from the cache file - scan now - _logger.Logger.Debug("Getting {TypeName}: " + action + ".", GetName(baseType, attributeType)); + _logger.Debug("Getting {TypeName}: " + action + ".", GetName(baseType, attributeType)); foreach (var t in finder()) typeList.Add(t); @@ -773,11 +783,11 @@ namespace Umbraco.Core.Composing UpdateCache(); } - _logger.Logger.Debug("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), added.ToString().ToLowerInvariant()); + _logger.Debug("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), added.ToString().ToLowerInvariant()); } else { - _logger.Logger.Debug("Got {TypeName}.", GetName(baseType, attributeType)); + _logger.Debug("Got {TypeName}.", GetName(baseType, attributeType)); } return typeList.Types; diff --git a/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs b/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs index 6177151a00..ba57243071 100644 --- a/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs +++ b/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs @@ -42,13 +42,5 @@ namespace Umbraco.Core.Composing { return mgr.GetTypesWithAttribute(); } - - /// - /// Gets all classes implementing ISqlSyntaxProvider and marked with the SqlSyntaxProviderAttribute. - /// - public static IEnumerable GetSqlSyntaxProviders(this TypeLoader mgr) - { - return mgr.GetTypesWithAttribute(); - } } } diff --git a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs index 99fa2d3eb9..f8ecc11d98 100644 --- a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; namespace Umbraco.Core.Composing { @@ -13,22 +12,14 @@ namespace Umbraco.Core.Composing /// The type of the items. public abstract class WeightedCollectionBuilderBase : CollectionBuilderBase where TBuilder : WeightedCollectionBuilderBase - where TCollection : IBuilderCollection + where TCollection : class, IBuilderCollection { - /// - /// Initializes a new instance of the class. - /// - /// - protected WeightedCollectionBuilderBase(IServiceContainer container) - : base(container) - { } - protected abstract TBuilder This { get; } /// /// Clears all types in the collection. /// - /// The buidler. + /// The builder. public TBuilder Clear() { Configure(types => types.Clear()); diff --git a/src/Umbraco.Core/ConfigsExtensions.cs b/src/Umbraco.Core/ConfigsExtensions.cs new file mode 100644 index 0000000000..0fcea5f430 --- /dev/null +++ b/src/Umbraco.Core/ConfigsExtensions.cs @@ -0,0 +1,52 @@ +using System.IO; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.Dashboard; +using Umbraco.Core.Configuration.Grid; +using Umbraco.Core.Configuration.HealthChecks; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; + +namespace Umbraco.Core +{ + /// + /// Provides extension methods for the class. + /// + public static class ConfigsExtensions + { + public static IGlobalSettings Global(this Configs configs) + => configs.GetConfig(); + + public static IUmbracoSettingsSection Settings(this Configs configs) + => configs.GetConfig(); + + public static IDashboardSection Dashboards(this Configs configs) + => configs.GetConfig(); + + public static IHealthChecks HealthChecks(this Configs configs) + => configs.GetConfig(); + + public static IGridConfig Grids(this Configs configs) + => configs.GetConfig(); + + internal static CoreDebug CoreDebug(this Configs configs) + => configs.GetConfig(); + + public static void AddCoreConfigs(this Configs configs) + { + var configDir = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config)); + + configs.Add(() => new GlobalSettings()); + configs.Add("umbracoConfiguration/settings"); + configs.Add("umbracoConfiguration/dashBoard"); + configs.Add("umbracoConfiguration/HealthChecks"); + + configs.Add(() => new CoreDebug()); + + // GridConfig depends on runtime caches, manifest parsers... and cannot be available during composition + configs.Add(factory => new GridConfig(factory.GetInstance(), factory.GetInstance(), configDir, factory.GetInstance().Debug)); + } + } +} diff --git a/src/Umbraco.Core/Configuration/Configs.cs b/src/Umbraco.Core/Configuration/Configs.cs new file mode 100644 index 0000000000..51e1a327a0 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Configs.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Configuration +{ + /// + /// Represents Umbraco configurations. + /// + /// + /// During composition, use composition.Configs. When running, either inject the required configuration, + /// or use Current.Configs. + /// + public class Configs + { + private readonly Dictionary> _configs = new Dictionary>(); + private Dictionary> _registerings = new Dictionary>(); + + /// + /// Gets a configuration. + /// + public TConfig GetConfig() + where TConfig : class + { + if (!_configs.TryGetValue(typeof(TConfig), out var configFactory)) + throw new InvalidOperationException($"No configuration of type {typeof(TConfig)} has been added."); + + return (TConfig) configFactory.Value; + } + + /// + /// Adds a configuration, provided by a factory. + /// + public void Add(Func configFactory) + where TConfig : class + { + // make sure it is not too late + if (_registerings == null) + throw new InvalidOperationException("Configurations have already been registered."); + + var typeOfConfig = typeof(TConfig); + + var lazyConfigFactory = _configs[typeOfConfig] = new Lazy(configFactory); + _registerings[typeOfConfig] = register => register.Register(_ => (TConfig) lazyConfigFactory.Value, Lifetime.Singleton); + } + + /// + /// Adds a configuration, provided by a factory. + /// + public void Add(Func configFactory) + where TConfig : class + { + // make sure it is not too late + if (_registerings == null) + throw new InvalidOperationException("Configurations have already been registered."); + + var typeOfConfig = typeof(TConfig); + + _configs[typeOfConfig] = new Lazy(() => + { + if (Current.HasFactory) return Current.Factory.GetInstance(); + throw new InvalidOperationException($"Cannot get configuration of type {typeOfConfig} during composition."); + }); + _registerings[typeOfConfig] = register => register.Register(configFactory, Lifetime.Singleton); + } + + /// + /// Adds a configuration, provided by a configuration section. + /// + public void Add(string sectionName) + where TConfig : class + { + Add(() => GetConfig(sectionName)); + } + + private static TConfig GetConfig(string sectionName) + where TConfig : class + { + // note: need to use SafeCallContext here because ConfigurationManager.GetSection ends up getting AppDomain.Evidence + // which will want to serialize the call context including anything that is in there - what a mess! + + using (new SafeCallContext()) + { + if ((ConfigurationManager.GetSection(sectionName) is TConfig config)) + return config; + var ex = new ConfigurationErrorsException($"Could not get configuration section \"{sectionName}\" from config files."); + Current.Logger.Error(ex, "Config error"); + throw ex; + } + } + + /// + /// Registers configurations in a register. + /// + public void RegisterWith(IRegister register) + { + // do it only once + if (_registerings == null) + throw new InvalidOperationException("Configurations have already been registered."); + + register.Register(this); + + foreach (var registering in _registerings.Values) + registering(register); + + // no need to keep them around + _registerings = null; + } + } +} diff --git a/src/Umbraco.Core/Configuration/CoreDebug.cs b/src/Umbraco.Core/Configuration/CoreDebug.cs index 71d0f24941..a71b311d7c 100644 --- a/src/Umbraco.Core/Configuration/CoreDebug.cs +++ b/src/Umbraco.Core/Configuration/CoreDebug.cs @@ -2,16 +2,6 @@ namespace Umbraco.Core.Configuration { - internal static class CoreDebugExtensions - { - private static CoreDebug _coreDebug; - - public static CoreDebug CoreDebug(this UmbracoConfig config) - { - return _coreDebug ?? (_coreDebug = new CoreDebug()); - } - } - internal class CoreDebug { public CoreDebug() diff --git a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs index beade2d6d1..b2dae09fc9 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs @@ -6,11 +6,11 @@ namespace Umbraco.Core.Configuration.Grid { class GridConfig : IGridConfig { - public GridConfig(ILogger logger, IRuntimeCacheProvider runtimeCache, DirectoryInfo appPlugins, DirectoryInfo configFolder, bool isDebug) + public GridConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, bool isDebug) { - EditorsConfig = new GridEditorsConfig(logger, runtimeCache, appPlugins, configFolder, isDebug); + EditorsConfig = new GridEditorsConfig(logger, appCaches, configFolder, isDebug); } - public IGridEditorsConfig EditorsConfig { get; private set; } + public IGridEditorsConfig EditorsConfig { get; } } } diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index 708c563d9d..e2f99a753b 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -13,16 +13,14 @@ namespace Umbraco.Core.Configuration.Grid internal class GridEditorsConfig : IGridEditorsConfig { private readonly ILogger _logger; - private readonly IRuntimeCacheProvider _runtimeCache; - private readonly DirectoryInfo _appPlugins; + private readonly AppCaches _appCaches; private readonly DirectoryInfo _configFolder; private readonly bool _isDebug; - public GridEditorsConfig(ILogger logger, IRuntimeCacheProvider runtimeCache, DirectoryInfo appPlugins, DirectoryInfo configFolder, bool isDebug) + public GridEditorsConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, bool isDebug) { _logger = logger; - _runtimeCache = runtimeCache; - _appPlugins = appPlugins; + _appCaches = appCaches; _configFolder = configFolder; _isDebug = isDebug; } @@ -31,10 +29,10 @@ namespace Umbraco.Core.Configuration.Grid { get { - Func> getResult = () => + List GetResult() { // fixme - should use the common one somehow! + ignoring _appPlugins here! - var parser = new ManifestParser(_runtimeCache, Current.ManifestValidators, _logger); + var parser = new ManifestParser(_appCaches, Current.ManifestValidators, _logger); var editors = new List(); var gridConfig = Path.Combine(_configFolder.FullName, "grid.editors.config.js"); @@ -55,20 +53,16 @@ namespace Umbraco.Core.Configuration.Grid // add manifest editors, skip duplicates foreach (var gridEditor in parser.Manifest.GridEditors) { - if (editors.Contains(gridEditor) == false) - editors.Add(gridEditor); + if (editors.Contains(gridEditor) == false) editors.Add(gridEditor); } return editors; - }; + } //cache the result if debugging is disabled var result = _isDebug - ? getResult() - : _runtimeCache.GetCacheItem>( - typeof(GridEditorsConfig) + "Editors", - () => getResult(), - TimeSpan.FromMinutes(10)); + ? GetResult() + : _appCaches.RuntimeCache.GetCacheItem>(typeof(GridEditorsConfig) + ".Editors",GetResult, TimeSpan.FromMinutes(10)); return result; } diff --git a/src/Umbraco.Core/Configuration/UmbracoConfig.cs b/src/Umbraco.Core/Configuration/UmbracoConfig.cs deleted file mode 100644 index 6a1203313e..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoConfig.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System; -using System.Configuration; -using System.IO; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.Dashboard; -using Umbraco.Core.Configuration.Grid; -using Umbraco.Core.Configuration.HealthChecks; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Configuration -{ - /// - /// The gateway to all umbraco configuration - /// - public class UmbracoConfig - { - #region Singleton - - private static readonly Lazy Lazy = new Lazy(() => new UmbracoConfig()); - - public static UmbracoConfig For => Lazy.Value; - - #endregion - - /// - /// Default constructor - /// - private UmbracoConfig() - { - // note: need to use SafeCallContext here because ConfigurationManager.GetSection ends up getting AppDomain.Evidence - // which will want to serialize the call context including anything that is in there - what a mess! - - if (_umbracoSettings == null) - { - IUmbracoSettingsSection umbracoSettings; - using (new SafeCallContext()) - { - umbracoSettings = ConfigurationManager.GetSection("umbracoConfiguration/settings") as IUmbracoSettingsSection; - } - SetUmbracoSettings(umbracoSettings); - } - - if (_dashboardSection == null) - { - IDashboardSection dashboardConfig; - using (new SafeCallContext()) - { - dashboardConfig = ConfigurationManager.GetSection("umbracoConfiguration/dashBoard") as IDashboardSection; - } - SetDashboardSettings(dashboardConfig); - } - - if (_healthChecks == null) - { - var healthCheckConfig = ConfigurationManager.GetSection("umbracoConfiguration/HealthChecks") as IHealthChecks; - SetHealthCheckSettings(healthCheckConfig); - } - } - - /// - /// Constructor - can be used for testing - /// - /// - /// - /// - /// - public UmbracoConfig(IUmbracoSettingsSection umbracoSettings, IDashboardSection dashboardSettings, IHealthChecks healthChecks, IGlobalSettings globalSettings) - { - SetHealthCheckSettings(healthChecks); - SetUmbracoSettings(umbracoSettings); - SetDashboardSettings(dashboardSettings); - SetGlobalConfig(globalSettings); - } - - private IHealthChecks _healthChecks; - private IDashboardSection _dashboardSection; - private IUmbracoSettingsSection _umbracoSettings; - private IGridConfig _gridConfig; - private IGlobalSettings _globalSettings; - - /// - /// Gets the IHealthCheck config - /// - public IHealthChecks HealthCheck() - { - if (_healthChecks == null) - { - var ex = new ConfigurationErrorsException("Could not load the " + typeof(IHealthChecks) + " from config file, ensure the web.config and healthchecks.config files are formatted correctly"); - Current.Logger.Error(ex, "Config error"); - throw ex; - } - - return _healthChecks; - } - - /// - /// Gets the IDashboardSection - /// - public IDashboardSection DashboardSettings() - { - if (_dashboardSection == null) - { - var ex = new ConfigurationErrorsException("Could not load the " + typeof(IDashboardSection) + " from config file, ensure the web.config and Dashboard.config files are formatted correctly"); - Current.Logger.Error(ex, "Config error"); - throw ex; - } - - return _dashboardSection; - } - - /// - /// Only for testing - /// - /// - public void SetDashboardSettings(IDashboardSection value) - { - _dashboardSection = value; - } - - /// - /// Only for testing - /// - /// - public void SetHealthCheckSettings(IHealthChecks value) - { - _healthChecks = value; - } - - /// - /// Only for testing - /// - /// - public void SetUmbracoSettings(IUmbracoSettingsSection value) - { - _umbracoSettings = value; - } - - /// - /// Only for testing - /// - /// - public void SetGlobalConfig(IGlobalSettings value) - { - _globalSettings = value; - } - - /// - /// Gets the IGlobalSettings - /// - public IGlobalSettings GlobalSettings() - { - return _globalSettings ?? (_globalSettings = new GlobalSettings()); - } - - /// - /// Gets the IUmbracoSettings - /// - public IUmbracoSettingsSection UmbracoSettings() - { - if (_umbracoSettings == null) - { - var ex = new ConfigurationErrorsException("Could not load the " + typeof (IUmbracoSettingsSection) + " from config file, ensure the web.config and umbracoSettings.config files are formatted correctly"); - Current.Logger.Error(ex, "Config error"); - throw ex; - } - - return _umbracoSettings; - } - - /// - /// Only for testing - /// - /// - public void SetGridConfig(IGridConfig value) - { - _gridConfig = value; - } - - /// - /// Gets the IGridConfig - /// - public IGridConfig GridConfig(ILogger logger, IRuntimeCacheProvider runtimeCache, DirectoryInfo appPlugins, DirectoryInfo configFolder, bool isDebug) - { - if (_gridConfig == null) - { - _gridConfig = new GridConfig(logger, runtimeCache, appPlugins, configFolder, isDebug); - } - - return _gridConfig; - } - - //TODO: Add other configurations here ! - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs deleted file mode 100644 index 67fd58030b..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface ITemplatesSection : IUmbracoConfigurationSection - { - RenderingEngine DefaultRenderingEngine { get; } - } -} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs index 28da03ff2d..33416d38cc 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel; namespace Umbraco.Core.Configuration.UmbracoSettings { @@ -12,8 +11,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings ISecuritySection Security { get; } IRequestHandlerSection RequestHandler { get; } - - ITemplatesSection Templates { get; } ILoggingSection Logging { get; } @@ -22,6 +19,5 @@ namespace Umbraco.Core.Configuration.UmbracoSettings IProvidersSection Providers { get; } IWebRoutingSection WebRouting { get; } - } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs deleted file mode 100644 index 8c929b02d8..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class TemplatesElement : UmbracoConfigurationElement, ITemplatesSection - { - [ConfigurationProperty("defaultRenderingEngine", IsRequired = true)] - internal InnerTextConfigurationElement DefaultRenderingEngine - { - get { return GetOptionalTextElement("defaultRenderingEngine", RenderingEngine.Mvc); } - } - - RenderingEngine ITemplatesSection.DefaultRenderingEngine - { - get { return DefaultRenderingEngine; } - } - - } -} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs index d36410f317..9ed635f6a9 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs @@ -1,11 +1,7 @@ -using System; -using System.ComponentModel; -using System.Configuration; -using System.Linq; +using System.Configuration; namespace Umbraco.Core.Configuration.UmbracoSettings { - public class UmbracoSettingsSection : ConfigurationSection, IUmbracoSettingsSection { [ConfigurationProperty("backOffice")] @@ -32,12 +28,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (RequestHandlerElement)this["requestHandler"]; } } - [ConfigurationProperty("templates")] - internal TemplatesElement Templates - { - get { return (TemplatesElement)this["templates"]; } - } - [ConfigurationProperty("logging")] internal LoggingElement Logging { @@ -77,11 +67,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return RequestHandler; } } - ITemplatesSection IUmbracoSettingsSection.Templates - { - get { return Templates; } - } - IBackOfficeSection IUmbracoSettingsSection.BackOffice { get { return BackOffice; } @@ -106,6 +91,5 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { get { return WebRouting; } } - } } diff --git a/src/Umbraco.Core/Constants-Composing.cs b/src/Umbraco.Core/Constants-Composing.cs index 1e8c9886d2..e65629a278 100644 --- a/src/Umbraco.Core/Constants-Composing.cs +++ b/src/Umbraco.Core/Constants-Composing.cs @@ -9,20 +9,6 @@ /// Defines constants for composition. /// public static class Composing - { - /// - /// Defines file system names. - /// - public static class FileSystems - { - public const string ScriptFileSystem = "ScriptFileSystem"; - public const string PartialViewFileSystem = "PartialViewFileSystem"; - public const string PartialViewMacroFileSystem = "PartialViewMacroFileSystem"; - public const string StylesheetFileSystem = "StylesheetFileSystem"; - public const string MasterpageFileSystem = "MasterpageFileSystem"; - public const string ViewFileSystem = "ViewFileSystem"; - public const string JavascriptLibraryFileSystem = "JavascriptLibraryFileSystem"; - } - } + { } } } diff --git a/src/Umbraco.Core/Constants-Packaging.cs b/src/Umbraco.Core/Constants-Packaging.cs deleted file mode 100644 index 37f2c23fa3..0000000000 --- a/src/Umbraco.Core/Constants-Packaging.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace Umbraco.Core -{ - public static partial class Constants - { - /// - /// Defines the constants used for Umbraco packages in the package.config xml - /// - public static class Packaging - { - public const string UmbPackageNodeName = "umbPackage"; - public const string DataTypesNodeName = "DataTypes"; - public const string PackageXmlFileName = "package.xml"; - public const string UmbracoPackageExtention = ".umb"; - public const string DataTypeNodeName = "DataType"; - public const string LanguagesNodeName = "Languages"; - public const string FilesNodeName = "files"; - public const string StylesheetsNodeName = "Stylesheets"; - public const string TemplatesNodeName = "Templates"; - public const string NameNodeName = "Name"; - public const string TemplateNodeName = "Template"; - public const string AliasNodeNameSmall = "alias"; - public const string AliasNodeNameCapital = "Alias"; - public const string DictionaryItemsNodeName = "DictionaryItems"; - public const string DictionaryItemNodeName = "DictionaryItem"; - public const string MacrosNodeName = "Macros"; - public const string DocumentsNodeName = "Documents"; - public const string DocumentSetNodeName = "DocumentSet"; - public const string DocumentTypesNodeName = "DocumentTypes"; - public const string DocumentTypeNodeName = "DocumentType"; - public const string FileNodeName = "file"; - public const string OrgNameNodeName = "orgName"; - public const string OrgPathNodeName = "orgPath"; - public const string GuidNodeName = "guid"; - public const string StylesheetNodeName = "styleSheet"; - public const string MacroNodeName = "macro"; - public const string InfoNodeName = "info"; - public const string PackageRequirementsMajorXpath = "./package/requirements/major"; - public const string PackageRequirementsMinorXpath = "./package/requirements/minor"; - public const string PackageRequirementsPatchXpath = "./package/requirements/patch"; - public const string PackageNameXpath = "./package/name"; - public const string PackageVersionXpath = "./package/version"; - public const string PackageUrlXpath = "./package/url"; - public const string PackageLicenseXpath = "./package/license"; - public const string PackageLicenseXpathUrlAttribute = "url"; - public const string AuthorNameXpath = "./author/name"; - public const string AuthorWebsiteXpath = "./author/website"; - public const string ReadmeXpath = "./readme"; - public const string ControlNodeName = "control"; - public const string ActionNodeName = "Action"; - public const string ActionsNodeName = "Actions"; - public const string UndoNodeAttribute = "undo"; - public const string RunatNodeAttribute = "runat"; - } - } -} diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index b15e371e87..8c27c23604 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -5,6 +5,8 @@ using System.IO; using System.Linq; using System.Web; using System.Xml.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -18,8 +20,8 @@ namespace Umbraco.Core public static class ContentExtensions { // this ain't pretty - private static MediaFileSystem _mediaFileSystem; - private static MediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem); + private static IMediaFileSystem _mediaFileSystem; + private static IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.MediaFileSystem); #region IContent @@ -189,7 +191,21 @@ namespace Umbraco.Core private static void SetUploadFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) { var property = GetProperty(content, propertyTypeAlias); - var oldpath = property.GetValue(culture, segment) is string svalue ? MediaFileSystem.GetRelativePath(svalue) : null; + + // Fixes https://github.com/umbraco/Umbraco-CMS/issues/3937 - Assigning a new file to an + // existing IMedia with extension SetValue causes exception 'Illegal characters in path' + string oldpath = null; + if (property.GetValue(culture, segment) is string svalue) + { + if (svalue.DetectIsJson()) + { + // the property value is a JSON serialized image crop data set - grab the "src" property as the file source + var jObject = JsonConvert.DeserializeObject(svalue); + svalue = jObject != null ? jObject.GetValueAsString("src") : svalue; + } + oldpath = MediaFileSystem.GetRelativePath(svalue); + } + var filepath = MediaFileSystem.StoreFile(content, property.PropertyType, filename, filestream, oldpath); property.SetValue(MediaFileSystem.GetUrl(filepath), culture, segment); } @@ -292,84 +308,45 @@ namespace Umbraco.Core /// Creates the full xml representation for the object and all of it's descendants /// /// to generate xml for - /// + /// /// Xml representation of the passed in - internal static XElement ToDeepXml(this IContent content, IPackagingService packagingService) + internal static XElement ToDeepXml(this IContent content, IEntityXmlSerializer serializer) { - return packagingService.Export(content, true, raiseEvents: false); - } - - - [Obsolete("Use the overload that declares the IPackagingService to use")] - public static XElement ToXml(this IContent content) - { - return Current.Services.PackagingService.Export(content, raiseEvents: false); + return serializer.Serialize(content, false, true); } /// /// Creates the xml representation for the object /// /// to generate xml for - /// + /// /// Xml representation of the passed in - public static XElement ToXml(this IContent content, IPackagingService packagingService) + public static XElement ToXml(this IContent content, IEntityXmlSerializer serializer) { - return packagingService.Export(content, raiseEvents: false); - } - - [Obsolete("Use the overload that declares the IPackagingService to use")] - public static XElement ToXml(this IMedia media) - { - return Current.Services.PackagingService.Export(media, raiseEvents: false); + return serializer.Serialize(content, false, false); } + /// /// Creates the xml representation for the object /// /// to generate xml for - /// + /// /// Xml representation of the passed in - public static XElement ToXml(this IMedia media, IPackagingService packagingService) + public static XElement ToXml(this IMedia media, IEntityXmlSerializer serializer) { - return packagingService.Export(media, raiseEvents: false); + return serializer.Serialize(media); } - /// - /// Creates the full xml representation for the object and all of it's descendants - /// - /// to generate xml for - /// - /// Xml representation of the passed in - internal static XElement ToDeepXml(this IMedia media, IPackagingService packagingService) - { - return packagingService.Export(media, true, raiseEvents: false); - } - - - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// - /// Boolean indicating whether the xml should be generated for preview - /// Xml representation of the passed in - public static XElement ToXml(this IContent content, IPackagingService packagingService, bool isPreview) - { - //TODO Do a proper implementation of this - //If current IContent is published we should get latest unpublished version - return content.ToXml(packagingService); - } - - /// /// Creates the xml representation for the object /// /// to generate xml for - /// + /// /// Xml representation of the passed in - public static XElement ToXml(this IMember member, IPackagingService packagingService) + public static XElement ToXml(this IMember member, IEntityXmlSerializer serializer) { - return ((PackagingService)(packagingService)).Export(member); + return serializer.Serialize(member); } #endregion diff --git a/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs index 827a902b89..15ed404d31 100644 --- a/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs @@ -22,20 +22,17 @@ namespace Umbraco.Core.Deploy /// Gets the value to be deployed from the control value as a string. /// /// The control containing the value. - /// The property where the control is located. Do not modify - only used for context /// The dependencies of the property. /// The grid cell value to be deployed. /// Note that - string GetValue(GridValue.GridControl gridControl, Property property, ICollection dependencies); + string GetValue(GridValue.GridControl gridControl, ICollection dependencies); /// /// Allows you to modify the value of a control being deployed. /// /// The control being deployed. - /// The property where the is located. Do not modify - only used for context. /// Follows the pattern of the property value connectors (). The SetValue method is used to modify the value of the . - /// Note that only the value should be modified - not the . - /// The should only be used to assist with context data relevant when setting the value. - void SetValue(GridValue.GridControl gridControl, Property property); + + void SetValue(GridValue.GridControl gridControl); } } diff --git a/src/Umbraco.Core/Deploy/IValueConnector.cs b/src/Umbraco.Core/Deploy/IValueConnector.cs index 35304e3fde..7d9f43b5fe 100644 --- a/src/Umbraco.Core/Deploy/IValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IValueConnector.cs @@ -17,19 +17,21 @@ namespace Umbraco.Core.Deploy IEnumerable PropertyEditorAliases { get; } /// - /// Gets the deploy property corresponding to a content property. + /// Gets the deploy property value corresponding to a content property value, and gather dependencies. /// - /// The content property. + /// The content property value. + /// The value property type /// The content dependencies. /// The deploy property value. - string GetValue(Property property, ICollection dependencies); + string ToArtifact(object value, PropertyType propertyType, ICollection dependencies); /// - /// Sets a content property value using a deploy property. + /// Gets the content property value corresponding to a deploy property value. /// - /// The content item. - /// The property alias. /// The deploy property value. - void SetValue(IContentBase content, string alias, string value); + /// The value property type< + /// The current content property value. + /// The content property value. + object FromArtifact(string value, PropertyType propertyType, object currentValue); } } diff --git a/src/Umbraco.Core/Events/ExportEventArgs.cs b/src/Umbraco.Core/Events/ExportEventArgs.cs deleted file mode 100644 index f46cccf05c..0000000000 --- a/src/Umbraco.Core/Events/ExportEventArgs.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; - -namespace Umbraco.Core.Events -{ - public class ExportEventArgs : CancellableObjectEventArgs>, IEquatable> - { - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public ExportEventArgs(TEntity eventObject, XElement xml, bool canCancel) - : base(new List { eventObject }, canCancel) - { - Xml = xml; - } - - /// - /// Constructor accepting a single entity instance - /// and cancellable by default - /// - /// - /// - public ExportEventArgs(TEntity eventObject, string elementName) : base(new List {eventObject}, true) - { - Xml = new XElement(elementName); - } - - protected ExportEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) - { - } - - protected ExportEventArgs(IEnumerable eventObject) : base(eventObject) - { - } - - /// - /// Returns all entities that were exported during the operation - /// - public IEnumerable ExportedEntities - { - get { return EventObject; } - } - - /// - /// Returns the xml relating to the export event - /// - public XElement Xml { get; private set; } - - public bool Equals(ExportEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && Equals(Xml, other.Xml); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((ExportEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ (Xml != null ? Xml.GetHashCode() : 0); - } - } - - public static bool operator ==(ExportEventArgs left, ExportEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(ExportEventArgs left, ExportEventArgs right) - { - return !Equals(left, right); - } - } -} diff --git a/src/Umbraco.Core/Events/ImportEventArgs.cs b/src/Umbraco.Core/Events/ImportEventArgs.cs deleted file mode 100644 index fb8f7d4936..0000000000 --- a/src/Umbraco.Core/Events/ImportEventArgs.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; - -namespace Umbraco.Core.Events -{ - public class ImportEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> - { - /// - /// Constructor accepting an XElement with the xml being imported - /// - /// - public ImportEventArgs(XElement xml) : base(new List(), true) - { - Xml = xml; - } - - /// - /// Constructor accepting a list of entities and an XElement with the imported xml - /// - /// - /// - /// - public ImportEventArgs(IEnumerable eventObject, XElement xml, bool canCancel) - : base(eventObject, canCancel) - { - Xml = xml; - } - - protected ImportEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) - { - } - - protected ImportEventArgs(IEnumerable eventObject) : base(eventObject) - { - } - - /// - /// Returns all entities that were imported during the operation - /// - public IEnumerable ImportedEntities - { - get { return EventObject; } - } - - /// - /// Returns the xml relating to the import event - /// - public XElement Xml { get; private set; } - - public bool Equals(ImportEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && Equals(Xml, other.Xml); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((ImportEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ (Xml != null ? Xml.GetHashCode() : 0); - } - } - - public static bool operator ==(ImportEventArgs left, ImportEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(ImportEventArgs left, ImportEventArgs right) - { - return !Equals(left, right); - } - } -} diff --git a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs index 2e56757a25..61369af59d 100644 --- a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs @@ -7,44 +7,28 @@ namespace Umbraco.Core.Events { public class ImportPackageEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> { - private readonly MetaData _packageMetaData; - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use the overload specifying packageMetaData instead")] - public ImportPackageEventArgs(TEntity eventObject, bool canCancel) + public ImportPackageEventArgs(TEntity eventObject, IPackageInfo packageMetaData, bool canCancel) : base(new[] { eventObject }, canCancel) { + PackageMetaData = packageMetaData ?? throw new ArgumentNullException(nameof(packageMetaData)); } - public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData, bool canCancel) - : base(new[] { eventObject }, canCancel) - { - if (packageMetaData == null) throw new ArgumentNullException("packageMetaData"); - _packageMetaData = packageMetaData; - } - - public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData) + public ImportPackageEventArgs(TEntity eventObject, IPackageInfo packageMetaData) : this(eventObject, packageMetaData, true) { } - public MetaData PackageMetaData - { - get { return _packageMetaData; } - } + public IPackageInfo PackageMetaData { get; } - public IEnumerable InstallationSummary - { - get { return EventObject; } - } + public IEnumerable InstallationSummary => EventObject; public bool Equals(ImportPackageEventArgs other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; //TODO: MetaData for package metadata has no equality operators :/ - return base.Equals(other) && _packageMetaData.Equals(other._packageMetaData); + return base.Equals(other) && PackageMetaData.Equals(other.PackageMetaData); } public override bool Equals(object obj) @@ -59,7 +43,7 @@ namespace Umbraco.Core.Events { unchecked { - return (base.GetHashCode() * 397) ^ _packageMetaData.GetHashCode(); + return (base.GetHashCode() * 397) ^ PackageMetaData.GetHashCode(); } } diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs index e0d3847c17..b31b64e435 100644 --- a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs +++ b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs @@ -32,9 +32,9 @@ namespace Umbraco.Core.Events } } - private MediaFileSystem _mediaFileSystem; + private IMediaFileSystem _mediaFileSystem; // fixme inject - private MediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem); + private IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.MediaFileSystem); } } diff --git a/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs b/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs index 13c260bf3e..63c5ceaba0 100644 --- a/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs +++ b/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs @@ -3,20 +3,13 @@ using Umbraco.Core.Models.Packaging; namespace Umbraco.Core.Events { - public class UninstallPackageEventArgs : CancellableObjectEventArgs> + public class UninstallPackageEventArgs: CancellableObjectEventArgs> { - public UninstallPackageEventArgs(TEntity eventObject, bool canCancel) - : base(new[] { eventObject }, canCancel) - { } - - public UninstallPackageEventArgs(TEntity eventObject, MetaData packageMetaData) - : base(new[] { eventObject }) + public UninstallPackageEventArgs(IEnumerable eventObject, bool canCancel) + : base(eventObject, canCancel) { - PackageMetaData = packageMetaData; } - public MetaData PackageMetaData { get; } - - public IEnumerable UninstallationSummary => EventObject; + public IEnumerable UninstallationSummary => EventObject; } } diff --git a/src/Umbraco.Core/Exceptions/BootFailedException.cs b/src/Umbraco.Core/Exceptions/BootFailedException.cs index ec07389d37..10a648fd76 100644 --- a/src/Umbraco.Core/Exceptions/BootFailedException.cs +++ b/src/Umbraco.Core/Exceptions/BootFailedException.cs @@ -1,9 +1,10 @@ using System; +using System.Text; namespace Umbraco.Core.Exceptions { /// - /// An exception that is thrown if the Umbraco application cannnot boot. + /// An exception that is thrown if the Umbraco application cannot boot. /// public class BootFailedException : Exception { @@ -29,5 +30,31 @@ namespace Umbraco.Core.Exceptions public BootFailedException(string message, Exception inner) : base(message, inner) { } + + /// + /// Rethrows a captured . + /// + /// The exception can be null, in which case a default message is used. + public static void Rethrow(BootFailedException bootFailedException) + { + if (bootFailedException == null) + throw new BootFailedException(DefaultMessage); + + // see https://stackoverflow.com/questions/57383 + // would that be the correct way to do it? + //ExceptionDispatchInfo.Capture(bootFailedException).Throw(); + + Exception e = bootFailedException; + var m = new StringBuilder(); + m.Append(DefaultMessage); + while (e != null) + { + m.Append($"\n\n-> {e.GetType().FullName}: {e.Message}"); + if (string.IsNullOrWhiteSpace(e.StackTrace) == false) + m.Append($"\n{e.StackTrace}"); + e = e.InnerException; + } + throw new BootFailedException(m.ToString()); + } } } diff --git a/src/Umbraco.Core/IMainDom.cs b/src/Umbraco.Core/IMainDom.cs new file mode 100644 index 0000000000..3a8cd13ff1 --- /dev/null +++ b/src/Umbraco.Core/IMainDom.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core +{ + /// + /// Represents the main AppDomain running for a given application. + /// + /// + /// There can be only one "main" AppDomain running for a given application at a time. + /// It is possible to register against the MainDom and be notified when it is released. + /// + public interface IMainDom + { + /// + /// Gets a value indicating whether the current domain is the main domain. + /// + bool IsMainDom { get; } + + /// + /// Registers a resource that requires the current AppDomain to be the main domain to function. + /// + /// An action to execute before the AppDomain releases the main domain status. + /// An optional weight (lower goes first). + /// A value indicating whether it was possible to register. + bool Register(Action release, int weight = 100); + + /// + /// Registers a resource that requires the current AppDomain to be the main domain to function. + /// + /// An action to execute when registering. + /// An action to execute before the AppDomain releases the main domain status. + /// An optional weight (lower goes first). + /// A value indicating whether it was possible to register. + /// If registering is successful, then the action + /// is guaranteed to execute before the AppDomain releases the main domain status. + bool Register(Action install, Action release, int weight = 100); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index 4b73e64e80..ade2c58b38 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -65,5 +65,33 @@ namespace Umbraco.Core.IO } fs.DeleteFile(tempFile); } + + /// + /// Unwraps a filesystem. + /// + /// + /// A filesystem can be wrapped in a (public) or a (internal), + /// and this method deals with the various wrappers and + /// + public static IFileSystem Unwrap(this IFileSystem filesystem) + { + var unwrapping = true; + while (unwrapping) + { + switch (filesystem) + { + case FileSystemWrapper wrapper: + filesystem = wrapper.InnerFileSystem; + break; + case ShadowWrapper shadow: + filesystem = shadow.InnerFileSystem; + break; + default: + unwrapping = false; + break; + } + } + return filesystem; + } } } diff --git a/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs b/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs deleted file mode 100644 index b3b6cb6b79..0000000000 --- a/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.CodeAnnotations; - -namespace Umbraco.Core.IO -{ - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class FileSystemProviderAttribute : Attribute - { - public string Alias { get; private set; } - - public FileSystemProviderAttribute(string alias) - { - Alias = alias; - } - } -} diff --git a/src/Umbraco.Core/IO/FileSystemWrapper.cs b/src/Umbraco.Core/IO/FileSystemWrapper.cs index 2c4377b89b..14d028c16d 100644 --- a/src/Umbraco.Core/IO/FileSystemWrapper.cs +++ b/src/Umbraco.Core/IO/FileSystemWrapper.cs @@ -16,103 +16,103 @@ namespace Umbraco.Core.IO /// public abstract class FileSystemWrapper : IFileSystem { - protected FileSystemWrapper(IFileSystem wrapped) + protected FileSystemWrapper(IFileSystem innerFileSystem) { - Wrapped = wrapped; + InnerFileSystem = innerFileSystem; } - internal IFileSystem Wrapped { get; set; } + internal IFileSystem InnerFileSystem { get; set; } public IEnumerable GetDirectories(string path) { - return Wrapped.GetDirectories(path); + return InnerFileSystem.GetDirectories(path); } public void DeleteDirectory(string path) { - Wrapped.DeleteDirectory(path); + InnerFileSystem.DeleteDirectory(path); } public void DeleteDirectory(string path, bool recursive) { - Wrapped.DeleteDirectory(path, recursive); + InnerFileSystem.DeleteDirectory(path, recursive); } public bool DirectoryExists(string path) { - return Wrapped.DirectoryExists(path); + return InnerFileSystem.DirectoryExists(path); } public void AddFile(string path, Stream stream) { - Wrapped.AddFile(path, stream); + InnerFileSystem.AddFile(path, stream); } public void AddFile(string path, Stream stream, bool overrideExisting) { - Wrapped.AddFile(path, stream, overrideExisting); + InnerFileSystem.AddFile(path, stream, overrideExisting); } public IEnumerable GetFiles(string path) { - return Wrapped.GetFiles(path); + return InnerFileSystem.GetFiles(path); } public IEnumerable GetFiles(string path, string filter) { - return Wrapped.GetFiles(path, filter); + return InnerFileSystem.GetFiles(path, filter); } public Stream OpenFile(string path) { - return Wrapped.OpenFile(path); + return InnerFileSystem.OpenFile(path); } public void DeleteFile(string path) { - Wrapped.DeleteFile(path); + InnerFileSystem.DeleteFile(path); } public bool FileExists(string path) { - return Wrapped.FileExists(path); + return InnerFileSystem.FileExists(path); } public string GetRelativePath(string fullPathOrUrl) { - return Wrapped.GetRelativePath(fullPathOrUrl); + return InnerFileSystem.GetRelativePath(fullPathOrUrl); } public string GetFullPath(string path) { - return Wrapped.GetFullPath(path); + return InnerFileSystem.GetFullPath(path); } public string GetUrl(string path) { - return Wrapped.GetUrl(path); + return InnerFileSystem.GetUrl(path); } public DateTimeOffset GetLastModified(string path) { - return Wrapped.GetLastModified(path); + return InnerFileSystem.GetLastModified(path); } public DateTimeOffset GetCreated(string path) { - return Wrapped.GetCreated(path); + return InnerFileSystem.GetCreated(path); } public long GetSize(string path) { - return Wrapped.GetSize(path); + return InnerFileSystem.GetSize(path); } - public bool CanAddPhysical => Wrapped.CanAddPhysical; + public bool CanAddPhysical => InnerFileSystem.CanAddPhysical; public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) { - Wrapped.AddFile(path, physicalPath, overrideIfExists, copy); + InnerFileSystem.AddFile(path, physicalPath, overrideIfExists, copy); } } } diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index 62ce25dff0..63a1259acb 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -1,67 +1,67 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Reflection; using System.Threading; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Composing; namespace Umbraco.Core.IO { - public class FileSystems + public class FileSystems : IFileSystems { - private readonly IFileSystemProvidersSection _config; - private readonly ConcurrentSet _wrappers = new ConcurrentSet(); + private readonly IFactory _container; private readonly ILogger _logger; - private readonly ConcurrentDictionary _providerLookup = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _filesystems = new ConcurrentDictionary(); + private readonly ConcurrentDictionary> _filesystems = new ConcurrentDictionary>(); // wrappers for shadow support private ShadowWrapper _macroPartialFileSystem; private ShadowWrapper _partialViewsFileSystem; private ShadowWrapper _stylesheetsFileSystem; private ShadowWrapper _scriptsFileSystem; - private ShadowWrapper _masterPagesFileSystem; private ShadowWrapper _mvcViewsFileSystem; - + // well-known file systems lazy initialization private object _wkfsLock = new object(); private bool _wkfsInitialized; - private object _wkfsObject; + private object _wkfsObject; // unused - private MediaFileSystem _mediaFileSystem; - + // shadow support + private readonly List _shadowWrappers = new List(); + private readonly object _shadowLocker = new object(); + private static Guid _shadowCurrentId = Guid.Empty; // static - unique!! #region Constructor // DI wants a public ctor - // but IScopeProviderInternal is not public - public FileSystems(ILogger logger) + public FileSystems(IFactory container, ILogger logger) { - // fixme inject config section => can be used by tests - _config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders"); + _container = container; _logger = logger; } // for tests only, totally unsafe internal void Reset() { - _wrappers.Clear(); - _providerLookup.Clear(); + _shadowWrappers.Clear(); _filesystems.Clear(); Volatile.Write(ref _wkfsInitialized, false); + _shadowCurrentId = Guid.Empty; } + // for tests only, totally unsafe + internal static void ResetShadowId() + { + _shadowCurrentId = Guid.Empty; + } + + // set by the scope provider when taking control of filesystems internal Func IsScoped { get; set; } = () => false; #endregion #region Well-Known FileSystems + /// public IFileSystem MacroPartialsFileSystem { get @@ -71,6 +71,7 @@ namespace Umbraco.Core.IO } } + /// public IFileSystem PartialViewsFileSystem { get @@ -80,6 +81,7 @@ namespace Umbraco.Core.IO } } + /// public IFileSystem StylesheetsFileSystem { get @@ -89,6 +91,7 @@ namespace Umbraco.Core.IO } } + /// public IFileSystem ScriptsFileSystem { get @@ -97,16 +100,8 @@ namespace Umbraco.Core.IO return _scriptsFileSystem; } } - - public IFileSystem MasterPagesFileSystem - { - get - { - if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); - return _masterPagesFileSystem;// fixme - see 7.6?! - } - } + /// public IFileSystem MvcViewsFileSystem { get @@ -116,15 +111,6 @@ namespace Umbraco.Core.IO } } - public MediaFileSystem MediaFileSystem - { - get - { - if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); - return _mediaFileSystem; - } - } - private void EnsureWellKnownFileSystems() { LazyInitializer.EnsureInitialized(ref _wkfsObject, ref _wkfsInitialized, ref _wkfsLock, CreateWellKnownFileSystems); @@ -138,18 +124,20 @@ namespace Umbraco.Core.IO var partialViewsFileSystem = new PhysicalFileSystem(SystemDirectories.PartialViews); var stylesheetsFileSystem = new PhysicalFileSystem(SystemDirectories.Css); var scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts); - var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); - _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", () => IsScoped()); - _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", () => IsScoped()); - _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", () => IsScoped()); - _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", () => IsScoped()); - _masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", () => IsScoped()); - _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", () => IsScoped()); + _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", IsScoped); + _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", IsScoped); + _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", IsScoped); + _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", IsScoped); + _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", IsScoped); - // filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again - _mediaFileSystem = GetFileSystemProvider(); + // fixme locking? + _shadowWrappers.Add(_macroPartialFileSystem); + _shadowWrappers.Add(_partialViewsFileSystem); + _shadowWrappers.Add(_stylesheetsFileSystem); + _shadowWrappers.Add(_scriptsFileSystem); + _shadowWrappers.Add(_mvcViewsFileSystem); return null; } @@ -158,156 +146,28 @@ namespace Umbraco.Core.IO #region Providers - /// - /// used to cache the lookup of how to construct this object so we don't have to reflect each time. - /// - private class ProviderConstructionInfo - { - public object[] Parameters { get; set; } - public ConstructorInfo Constructor { get; set; } - //public string ProviderAlias { get; set; } - } - - /// - /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. - /// - /// The alias of the strongly-typed filesystem. - /// The non-typed filesystem supporting the strongly-typed filesystem with the specified alias. - /// This method should not be used directly, used instead. - public IFileSystem GetUnderlyingFileSystemProvider(string alias) - { - return GetUnderlyingFileSystemProvider(alias, null); - } - - /// - /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. - /// - /// The alias of the strongly-typed filesystem. - /// A fallback creator for the filesystem. - /// The non-typed filesystem supporting the strongly-typed filesystem with the specified alias. - /// This method should not be used directly, used instead. - internal IFileSystem GetUnderlyingFileSystemProvider(string alias, Func fallback) - { - // either get the constructor info from cache or create it and add to cache - var ctorInfo = _providerLookup.GetOrAdd(alias, _ => GetUnderlyingFileSystemCtor(alias, fallback)); - return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters); - } - - private IFileSystem GetUnderlyingFileSystemNoCache(string alias, Func fallback) - { - var ctorInfo = GetUnderlyingFileSystemCtor(alias, fallback); - return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters); - } - - private ProviderConstructionInfo GetUnderlyingFileSystemCtor(string alias, Func fallback) - { - // get config - if (_config.Providers.TryGetValue(alias, out var providerConfig) == false) - { - if (fallback != null) return null; - throw new ArgumentException($"No provider found with alias {alias}."); - } - - // get the filesystem type - var providerType = Type.GetType(providerConfig.Type); - if (providerType == null) - throw new InvalidOperationException($"Could not find type {providerConfig.Type}."); - - // ensure it implements IFileSystem - if (providerType.IsAssignableFrom(typeof (IFileSystem))) - throw new InvalidOperationException($"Type {providerType.FullName} does not implement IFileSystem."); - - // find a ctor matching the config parameters - var paramCount = providerConfig.Parameters?.Count ?? 0; - var constructor = providerType.GetConstructors().SingleOrDefault(x - => x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.Keys.Contains(y.Name))); - if (constructor == null) - throw new InvalidOperationException($"Type {providerType.FullName} has no ctor matching the {paramCount} configuration parameter(s)."); - - var parameters = new object[paramCount]; - if (providerConfig.Parameters != null) // keeps ReSharper happy - { - var allKeys = providerConfig.Parameters.Keys.ToArray(); - for (var i = 0; i < paramCount; i++) - parameters[i] = providerConfig.Parameters[allKeys[i]]; - } - - return new ProviderConstructionInfo - { - Constructor = constructor, - Parameters = parameters, - //ProviderAlias = s - }; - } - /// /// Gets a strongly-typed filesystem. /// /// The type of the filesystem. /// A strongly-typed filesystem of the specified type. /// - /// Ideally, this should cache the instances, but that would break backward compatibility, so we - /// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller - /// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains - /// its own shadow and having multiple instances would lead to inconsistencies. /// Note that any filesystem created by this method *after* shadowing begins, will *not* be /// shadowing (and an exception will be thrown by the ShadowWrapper). /// - // fixme - should it change for v8? - public TFileSystem GetFileSystemProvider() + public TFileSystem GetFileSystem(IFileSystem supporting) where TFileSystem : FileSystemWrapper { - return GetFileSystemProvider(null); - } + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); - /// - /// Gets a strongly-typed filesystem. - /// - /// The type of the filesystem. - /// A fallback creator for the inner filesystem. - /// A strongly-typed filesystem of the specified type. - /// - /// The fallback creator is used only if nothing is configured. - /// Ideally, this should cache the instances, but that would break backward compatibility, so we - /// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller - /// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains - /// its own shadow and having multiple instances would lead to inconsistencies. - /// Note that any filesystem created by this method *after* shadowing begins, will *not* be - /// shadowing (and an exception will be thrown by the ShadowWrapper). - /// - public TFileSystem GetFileSystemProvider(Func fallback) - where TFileSystem : FileSystemWrapper - { - var alias = GetFileSystemAlias(); - return (TFileSystem)_filesystems.GetOrAdd(alias, _ => + return (TFileSystem) _filesystems.GetOrAdd(typeof(TFileSystem), _ => new Lazy(() => { - // gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return - // so we are double-wrapping here - // could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe - var innerFs = GetUnderlyingFileSystemNoCache(alias, fallback); - var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias, () => IsScoped()); - var fs = (IFileSystem) Activator.CreateInstance(typeof(TFileSystem), shadowWrapper); - _wrappers.Add(shadowWrapper); // keeping a reference to the wrapper - return fs; - }); - } + var name = typeof(TFileSystem).FullName; + if (name == null) throw new Exception("panic!"); - private string GetFileSystemAlias() - { - var fsType = typeof(TFileSystem); - - // validate the ctor - var constructor = fsType.GetConstructors().SingleOrDefault(x - => x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom(x.GetParameters().Single().ParameterType)); - if (constructor == null) - throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + "."); - - // find the attribute and get the alias - var attr = (FileSystemProviderAttribute)fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).SingleOrDefault(); - if (attr == null) - throw new InvalidOperationException("Type " + fsType.FullName + "is missing the required FileSystemProviderAttribute."); - - return attr.Alias; + var shadowWrapper = CreateShadowWrapper(supporting, "typed/" + name); + return _container.CreateInstance(shadowWrapper); + })).Value; } #endregion @@ -318,68 +178,75 @@ namespace Umbraco.Core.IO // shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one // global shadow for the entire application, so great care should be taken to ensure that the // application is *not* doing anything else when using a shadow. - // shadow applies to well-known filesystems *only* - at the moment, any other filesystem that would - // be created directly (via ctor) or via GetFileSystem is *not* shadowed. - - // shadow must be enabled in an app event handler before anything else ie before any filesystem - // is actually created and used - after, it is too late - enabling shadow has a neglictible perfs - // impact. - // NO! by the time an app event handler is instanciated it is already too late, see note in ctor. - //internal void EnableShadow() - //{ - // if (_mvcViewsFileSystem != null) // test one of the fs... - // throw new InvalidOperationException("Cannot enable shadow once filesystems have been created."); - // _shadowEnabled = true; - //} internal ICompletable Shadow(Guid id) { if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); - var typed = _wrappers.ToArray(); - var wrappers = new ShadowWrapper[typed.Length + 6]; - var i = 0; - while (i < typed.Length) wrappers[i] = typed[i++]; - wrappers[i++] = _macroPartialFileSystem; - wrappers[i++] = _partialViewsFileSystem; - wrappers[i++] = _stylesheetsFileSystem; - wrappers[i++] = _scriptsFileSystem; - wrappers[i++] = _masterPagesFileSystem; - wrappers[i] = _mvcViewsFileSystem; + return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow + } - return new ShadowFileSystems(id, wrappers, _logger); + internal void BeginShadow(Guid id) + { + lock (_shadowLocker) + { + // if we throw here, it means that something very wrong happened. + if (_shadowCurrentId != Guid.Empty) + throw new InvalidOperationException("Already shadowing."); + _shadowCurrentId = id; + + _logger.Debug("Shadow '{ShadowId}'", id); + + foreach (var wrapper in _shadowWrappers) + wrapper.Shadow(id); + } + } + + internal void EndShadow(Guid id, bool completed) + { + lock (_shadowLocker) + { + // if we throw here, it means that something very wrong happened. + if (_shadowCurrentId == Guid.Empty) + throw new InvalidOperationException("Not shadowing."); + if (id != _shadowCurrentId) + throw new InvalidOperationException("Not the current shadow."); + + _logger.Debug("UnShadow '{ShadowId}' {Status}", id, completed ? "complete" : "abort"); + + var exceptions = new List(); + foreach (var wrapper in _shadowWrappers) + { + try + { + // this may throw an AggregateException if some of the changes could not be applied + wrapper.UnShadow(completed); + } + catch (AggregateException ae) + { + exceptions.Add(ae); + } + } + + _shadowCurrentId = Guid.Empty; + + if (exceptions.Count > 0) + throw new AggregateException(completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions); + } + } + + private ShadowWrapper CreateShadowWrapper(IFileSystem filesystem, string shadowPath) + { + lock (_shadowLocker) + { + var wrapper = new ShadowWrapper(filesystem, shadowPath, IsScoped); + if (_shadowCurrentId != Guid.Empty) + wrapper.Shadow(_shadowCurrentId); + _shadowWrappers.Add(wrapper); + return wrapper; + } } #endregion - - private class ConcurrentSet - where T : class - { - private readonly HashSet _set = new HashSet(); - - public void Add(T item) - { - lock (_set) - { - _set.Add(item); - } - } - - public void Clear() - { - lock (_set) - { - _set.Clear(); - } - } - - public T[] ToArray() - { - lock (_set) - { - return _set.ToArray(); - } - } - } } } diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index 115cb8a5c1..14b015a468 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -5,7 +5,7 @@ using System.IO; namespace Umbraco.Core.IO { /// - /// Provides methods allowing the manipulation of files within an Umbraco application. + /// Provides methods allowing the manipulation of files. /// public interface IFileSystem { diff --git a/src/Umbraco.Core/IO/IFileSystems.cs b/src/Umbraco.Core/IO/IFileSystems.cs new file mode 100644 index 0000000000..f7d35058e3 --- /dev/null +++ b/src/Umbraco.Core/IO/IFileSystems.cs @@ -0,0 +1,33 @@ +namespace Umbraco.Core.IO +{ + /// + /// Provides the system filesystems. + /// + public interface IFileSystems + { + /// + /// Gets the macro partials filesystem. + /// + IFileSystem MacroPartialsFileSystem { get; } + + /// + /// Gets the partial views filesystem. + /// + IFileSystem PartialViewsFileSystem { get; } + + /// + /// Gets the stylesheets filesystem. + /// + IFileSystem StylesheetsFileSystem { get; } + + /// + /// Gets the scripts filesystem. + /// + IFileSystem ScriptsFileSystem { get; } + + /// + /// Gets the MVC views filesystem. + /// + IFileSystem MvcViewsFileSystem { get; } + } +} diff --git a/src/Umbraco.Core/IO/IMediaFileSystem.cs b/src/Umbraco.Core/IO/IMediaFileSystem.cs new file mode 100644 index 0000000000..ed88516135 --- /dev/null +++ b/src/Umbraco.Core/IO/IMediaFileSystem.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Umbraco.Core.Models; + +namespace Umbraco.Core.IO +{ + /// + /// Provides methods allowing the manipulation of media files. + /// + public interface IMediaFileSystem : IFileSystem + { + /// + /// Delete media files. + /// + /// Files to delete (filesystem-relative paths). + void DeleteMediaFiles(IEnumerable files); + + /// + /// Gets the file path of a media file. + /// + /// The file name. + /// The unique identifier of the content/media owning the file. + /// The unique identifier of the property type owning the file. + /// The filesystem-relative path to the media file. + /// With the old media path scheme, this CREATES a new media path each time it is invoked. + string GetMediaPath(string filename, Guid cuid, Guid puid); + + /// + /// Gets the file path of a media file. + /// + /// The file name. + /// A previous file path. + /// The unique identifier of the content/media owning the file. + /// The unique identifier of the property type owning the file. + /// The filesystem-relative path to the media file. + /// In the old, legacy, number-based scheme, we try to re-use the media folder + /// specified by . Else, we CREATE a new one. Each time we are invoked. + string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid); + + /// + /// Stores a media file associated to a property of a content item. + /// + /// The content item owning the media file. + /// The property type owning the media file. + /// The media file name. + /// A stream containing the media bytes. + /// An optional filesystem-relative filepath to the previous media file. + /// The filesystem-relative filepath to the media file. + /// + /// The file is considered "owned" by the content/propertyType. + /// If an is provided then that file (and associated thumbnails if any) is deleted + /// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new file. + /// + string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath); + + /// + /// Copies a media file as a new media file, associated to a property of a content item. + /// + /// The content item owning the copy of the media file. + /// The property type owning the copy of the media file. + /// The filesystem-relative path to the source media file. + /// The filesystem-relative path to the copy of the media file. + string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath); + } +} diff --git a/src/Umbraco.Core/IO/IMediaPathScheme.cs b/src/Umbraco.Core/IO/IMediaPathScheme.cs index 5cfb43ed77..9a38cdc74f 100644 --- a/src/Umbraco.Core/IO/IMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/IMediaPathScheme.cs @@ -7,35 +7,27 @@ namespace Umbraco.Core.IO /// public interface IMediaPathScheme { - // fixme - // to anyone finding this code: YES the Initialize() method is CompletelyBroken™ (temporal whatever) - // but at the moment, the media filesystem wants a scheme which wants a filesystem, and it's all - // convoluted due to how filesystems are managed in FileSystems - clear that part first! - - /// - /// Initialize. - /// - void Initialize(IFileSystem filesystem); - /// /// Gets a media file path. /// + /// The media filesystem. /// The (content, media) item unique identifier. /// The property type unique identifier. /// The file name. /// A previous filename. /// The filesystem-relative complete file path. - string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null); + string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null); /// /// Gets the directory that can be deleted when the file is deleted. /// + /// The media filesystem. /// The filesystem-relative path of the file. /// The filesystem-relative path of the directory. /// /// The directory, and anything below it, will be deleted. /// Can return null (or empty) when no directory should be deleted. /// - string GetDeleteDirectory(string filepath); + string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath); } } diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 7773f378a5..76e7631482 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -31,16 +31,6 @@ namespace Umbraco.Core.IO public static char DirSepChar => Path.DirectorySeparatorChar; - internal static void UnZip(string zipFilePath, string unPackDirectory, bool deleteZipFile) - { - // Unzip - var tempDir = unPackDirectory; - Directory.CreateDirectory(tempDir); - ZipFile.ExtractToDirectory(zipFilePath, unPackDirectory); - if (deleteZipFile) - File.Delete(zipFilePath); - } - //helper to try and match the old path to a new virtual one public static string FindFile(string virtualPath) { @@ -123,11 +113,6 @@ namespace Umbraco.Core.IO return MapPath(path, true); } - public static string MapPathIfVirtual(string path) - { - return path.StartsWith("~/") ? MapPath(path) : path; - } - //use a tilde character instead of the complete path internal static string ReturnPath(string settingsKey, string standardPath, bool useTilde) { @@ -156,20 +141,6 @@ namespace Umbraco.Core.IO return VerifyEditPath(filePath, new[] { validDir }); } - /// - /// Validates that the current filepath matches a directory where the user is allowed to edit a file. - /// - /// The filepath to validate. - /// The valid directory. - /// True, if the filepath is valid, else an exception is thrown. - /// The filepath is invalid. - internal static bool ValidateEditPath(string filePath, string validDir) - { - if (VerifyEditPath(filePath, validDir) == false) - throw new FileSecurityException(String.Format("The filepath '{0}' is not within an allowed directory for this type of files", filePath.Replace(MapPath(SystemDirectories.Root), ""))); - return true; - } - /// /// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file. /// @@ -221,20 +192,6 @@ namespace Umbraco.Core.IO return ext != null && validFileExtensions.Contains(ext.TrimStart('.')); } - /// - /// Validates that the current filepath has one of several authorized extensions. - /// - /// The filepath to validate. - /// The valid extensions. - /// True, if the filepath is valid, else an exception is thrown. - /// The filepath is invalid. - internal static bool ValidateFileExtension(string filePath, List validFileExtensions) - { - if (VerifyFileExtension(filePath, validFileExtensions) == false) - throw new FileSecurityException(String.Format("The extension for the current file '{0}' is not of an allowed type for this editor. This is typically controlled from either the installed MacroEngines or based on configuration in /config/umbracoSettings.config", filePath.Replace(MapPath(SystemDirectories.Root), ""))); - return true; - } - public static bool PathStartsWith(string path, string root, char separator) { // either it is identical to root, @@ -329,17 +286,6 @@ namespace Umbraco.Core.IO Directory.CreateDirectory(absolutePath); } - public static void EnsureFileExists(string path, string contents) - { - var absolutePath = IOHelper.MapPath(path); - if (File.Exists(absolutePath)) return; - - using (var writer = File.CreateText(absolutePath)) - { - writer.Write(contents); - } - } - /// /// Checks if a given path is a full path including drive letter /// diff --git a/src/Umbraco.Core/IO/MasterPageHelper.cs b/src/Umbraco.Core/IO/MasterPageHelper.cs deleted file mode 100644 index 049db04b9a..0000000000 --- a/src/Umbraco.Core/IO/MasterPageHelper.cs +++ /dev/null @@ -1,445 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Services; -using Umbraco.Core.Xml; - -namespace Umbraco.Core.IO -{ - internal class MasterPageHelper - { - private readonly IFileSystem _masterPageFileSystem; - internal static readonly string DefaultMasterTemplate = SystemDirectories.Umbraco + "/masterpages/default.master"; - //private static readonly char[] NewLineChars = Environment.NewLine.ToCharArray(); - - public MasterPageHelper(IFileSystem masterPageFileSystem) - { - if (masterPageFileSystem == null) throw new ArgumentNullException("masterPageFileSystem"); - _masterPageFileSystem = masterPageFileSystem; - } - - public bool MasterPageExists(ITemplate t) - { - return _masterPageFileSystem.FileExists(GetFilePath(t)); - } - - private string GetFilePath(ITemplate t) - { - return GetFilePath(t.Alias); - } - - private string GetFilePath(string alias) - { - return alias + ".master"; - } - - public string CreateMasterPage(ITemplate t, ITemplateRepository templateRepo, bool overWrite = false) - { - string masterpageContent = ""; - - var filePath = GetFilePath(t); - if (_masterPageFileSystem.FileExists(filePath) == false || overWrite) - { - masterpageContent = t.Content.IsNullOrWhiteSpace() ? CreateDefaultMasterPageContent(t, templateRepo) : t.Content; - - var data = Encoding.UTF8.GetBytes(masterpageContent); - var withBom = Encoding.UTF8.GetPreamble().Concat(data).ToArray(); - - using (var ms = new MemoryStream(withBom)) - { - _masterPageFileSystem.AddFile(filePath, ms, true); - } - } - else - { - using (var s = _masterPageFileSystem.OpenFile(filePath)) - using (var tr = new StreamReader(s, Encoding.UTF8)) - { - masterpageContent = tr.ReadToEnd(); - tr.Close(); - } - } - - return masterpageContent; - } - - //internal string GetFileContents(ITemplate t) - //{ - // var masterpageContent = ""; - // if (_masterPageFileSystem.FileExists(GetFilePath(t))) - // { - // using (var s = _masterPageFileSystem.OpenFile(GetFilePath(t))) - // using (var tr = new StreamReader(s)) - // { - // masterpageContent = tr.ReadToEnd(); - // tr.Close(); - // } - // } - - // return masterpageContent; - //} - - public string UpdateMasterPageFile(ITemplate t, string currentAlias, ITemplateRepository templateRepo) - { - var template = UpdateMasterPageContent(t, currentAlias); - UpdateChildTemplates(t, currentAlias, templateRepo); - var filePath = GetFilePath(t); - - var data = Encoding.UTF8.GetBytes(template); - var withBom = Encoding.UTF8.GetPreamble().Concat(data).ToArray(); - - using (var ms = new MemoryStream(withBom)) - { - _masterPageFileSystem.AddFile(filePath, ms, true); - } - return template; - } - - private string CreateDefaultMasterPageContent(ITemplate template, ITemplateRepository templateRepo) - { - var design = new StringBuilder(); - design.Append(GetMasterPageHeader(template) + Environment.NewLine); - - if (template.MasterTemplateAlias.IsNullOrWhiteSpace() == false) - { - var master = templateRepo.Get(template.MasterTemplateAlias); - if (master != null) - { - foreach (var cpId in GetContentPlaceholderIds(master)) - { - design.Append("" + - Environment.NewLine + - Environment.NewLine + - "" + - Environment.NewLine + - Environment.NewLine); - } - - return design.ToString(); - } - } - - design.Append(GetMasterContentElement(template) + Environment.NewLine); - design.Append(template.Content + Environment.NewLine); - design.Append("" + Environment.NewLine); - - return design.ToString(); - } - - public static IEnumerable GetContentPlaceholderIds(ITemplate template) - { - var retVal = new List(); - - var mp = template.Content; - var path = ""; - var r = new Regex(path, RegexOptions.IgnoreCase); - var m = r.Match(mp); - - while (m.Success) - { - var cc = m.Groups[3].Captures; - retVal.AddRange(cc.Cast().Where(c => c.Value != "server").Select(c => c.Value)); - - m = m.NextMatch(); - } - - return retVal; - } - - private static string UpdateMasterPageContent(ITemplate template, string currentAlias) - { - var masterPageContent = template.Content; - - if (string.IsNullOrEmpty(currentAlias) == false && currentAlias != template.Alias) - { - var masterHeader = - masterPageContent.Substring(0, masterPageContent.IndexOf("%>", StringComparison.Ordinal) + 2).Trim( - Environment.NewLine.ToCharArray()); - - // find the masterpagefile attribute - var m = Regex.Matches(masterHeader, "(?\\S*)=\"(?[^\"]*)\"", - RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - foreach (Match attributeSet in m) - { - if (attributeSet.Groups["attributeName"].Value.ToLower() == "masterpagefile") - { - // validate the masterpagefile - var currentMasterPageFile = attributeSet.Groups["attributeValue"].Value; - var currentMasterTemplateFile = ParentTemplatePath(template); - - if (currentMasterPageFile != currentMasterTemplateFile) - { - masterPageContent = - masterPageContent.Replace( - attributeSet.Groups["attributeName"].Value + "=\"" + currentMasterPageFile + "\"", - attributeSet.Groups["attributeName"].Value + "=\"" + currentMasterTemplateFile + - "\""); - } - } - } - } - - return masterPageContent; - } - - private void UpdateChildTemplates(ITemplate template, string currentAlias, ITemplateRepository templateRepo) - { - //if we have a Old Alias if the alias and therefor the masterpage file name has changed... - //so before we save the new masterfile, we'll clear the old one, so we don't up with - //Unused masterpage files - if (string.IsNullOrEmpty(currentAlias) == false && currentAlias != template.Alias) - { - //Ensure that child templates have the right master masterpage file name - if (template.IsMasterTemplate) - { - var children = templateRepo.GetChildren(template.Id); - foreach (var t in children) - UpdateMasterPageFile(t, null, templateRepo); - } - } - } - - - //private void SaveDesignToFile(ITemplate t, string currentAlias, string design) - //{ - // //kill the old file.. - // if (string.IsNullOrEmpty(currentAlias) == false && currentAlias != t.Alias) - // { - // var oldFile = - // IOHelper.MapPath(SystemDirectories.Masterpages + "/" + currentAlias.Replace(" ", "") + ".master"); - // if (System.IO.File.Exists(oldFile)) - // System.IO.File.Delete(oldFile); - // } - - // // save the file in UTF-8 - // System.IO.File.WriteAllText(GetFilePath(t), design, Encoding.UTF8); - //} - - //internal static void RemoveMasterPageFile(string alias) - //{ - // if (string.IsNullOrWhiteSpace(alias) == false) - // { - // string file = IOHelper.MapPath(SystemDirectories.Masterpages + "/" + alias.Replace(" ", "") + ".master"); - // if (System.IO.File.Exists(file)) - // System.IO.File.Delete(file); - // } - //} - - //internal string SaveTemplateToFile(ITemplate template, string currentAlias, ITemplateRepository templateRepo) - //{ - // var masterPageContent = template.Content; - // if (IsMasterPageSyntax(masterPageContent) == false) - // masterPageContent = ConvertToMasterPageSyntax(template); - - // // Add header to master page if it doesn't exist - // if (masterPageContent.TrimStart().StartsWith("<%@") == false) - // { - // masterPageContent = GetMasterPageHeader(template) + Environment.NewLine + masterPageContent; - // } - // else - // { - // // verify that the masterpage attribute is the same as the masterpage - // var masterHeader = - // masterPageContent.Substring(0, masterPageContent.IndexOf("%>", StringComparison.Ordinal) + 2).Trim(NewLineChars); - - // // find the masterpagefile attribute - // var m = Regex.Matches(masterHeader, "(?\\S*)=\"(?[^\"]*)\"", - // RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - // foreach (Match attributeSet in m) - // { - // if (attributeSet.Groups["attributeName"].Value.ToLower() == "masterpagefile") - // { - // // validate the masterpagefile - // var currentMasterPageFile = attributeSet.Groups["attributeValue"].Value; - // var currentMasterTemplateFile = ParentTemplatePath(template); - - // if (currentMasterPageFile != currentMasterTemplateFile) - // { - // masterPageContent = - // masterPageContent.Replace( - // attributeSet.Groups["attributeName"].Value + "=\"" + currentMasterPageFile + "\"", - // attributeSet.Groups["attributeName"].Value + "=\"" + currentMasterTemplateFile + - // "\""); - - // } - // } - // } - - // } - - // //we have a Old Alias if the alias and therefor the masterpage file name has changed... - // //so before we save the new masterfile, we'll clear the old one, so we don't up with - // //Unused masterpage files - // if (string.IsNullOrEmpty(currentAlias) == false && currentAlias != template.Alias) - // { - - // //Ensure that child templates have the right master masterpage file name - // if (template.IsMasterTemplate) - // { - // var children = templateRepo.GetChildren(template.Id); - - // foreach (var t in children) - // UpdateMasterPageFile(t, null, templateRepo); - // } - - // //then kill the old file.. - // var oldFile = GetFilePath(currentAlias); - // if (_masterPageFileSystem.FileExists(oldFile)) - // _masterPageFileSystem.DeleteFile(oldFile); - // } - - // // save the file in UTF-8 - // System.IO.File.WriteAllText(GetFilePath(template), masterPageContent, Encoding.UTF8); - - // return masterPageContent; - //} - - //internal static string ConvertToMasterPageSyntax(ITemplate template) - //{ - // string masterPageContent = GetMasterContentElement(template) + Environment.NewLine; - - // masterPageContent += template.Content; - - // // Parse the design for getitems - // masterPageContent = EnsureMasterPageSyntax(template.Alias, masterPageContent); - - // // append ending asp:content element - // masterPageContent += Environment.NewLine + "" + Environment.NewLine; - - // return masterPageContent; - //} - - public static bool IsMasterPageSyntax(string code) - { - return Regex.IsMatch(code, @"<%@\s*Master", RegexOptions.IgnoreCase) || - code.InvariantContains("", ParentTemplatePath(template)) + Environment.NewLine; - } - - private static string ParentTemplatePath(ITemplate template) - { - var masterTemplate = DefaultMasterTemplate; - if (template.MasterTemplateAlias.IsNullOrWhiteSpace() == false) - masterTemplate = SystemDirectories.Masterpages + "/" + template.MasterTemplateAlias + ".master"; - - return masterTemplate; - } - - internal static string GetMasterContentElement(ITemplate template) - { - if (template.MasterTemplateAlias.IsNullOrWhiteSpace() == false) - { - string masterAlias = template.MasterTemplateAlias; - return - String.Format("", masterAlias); - } - else - return - String.Format(""); - - } - - internal static string EnsureMasterPageSyntax(string templateAlias, string masterPageContent) - { - ReplaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", true); - ReplaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", false); - - // Parse the design for macros - ReplaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", true); - ReplaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", false); - - // Parse the design for load childs - masterPageContent = masterPageContent.Replace("", CreateDefaultPlaceHolder(templateAlias)) - .Replace("", CreateDefaultPlaceHolder(templateAlias)); - // Parse the design for aspnet forms - GetAspNetMasterPageForm(ref masterPageContent, templateAlias); - masterPageContent = masterPageContent.Replace("", ""); - // Parse the design for aspnet heads - masterPageContent = masterPageContent.Replace("", String.Format("", templateAlias.Replace(" ", ""))); - masterPageContent = masterPageContent.Replace("", ""); - return masterPageContent; - } - - - private static void GetAspNetMasterPageForm(ref string design, string templateAlias) - { - var formElement = Regex.Match(design, GetElementRegExp("?ASPNET_FORM", false), RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - if (string.IsNullOrEmpty(formElement.Value) == false) - { - string formReplace = String.Format("
", templateAlias.Replace(" ", "")); - if (formElement.Groups.Count == 0) - { - formReplace += ""; - } - design = design.Replace(formElement.Value, formReplace); - } - } - - private static string CreateDefaultPlaceHolder(string templateAlias) - { - return String.Format("", templateAlias.Replace(" ", "")); - } - - private static void ReplaceElement(ref string design, string elementName, string newElementName, bool checkForQuotes) - { - var m = - Regex.Matches(design, GetElementRegExp(elementName, checkForQuotes), - RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - foreach (Match match in m) - { - GroupCollection groups = match.Groups; - - // generate new element (compensate for a closing trail on single elements ("/")) - string elementAttributes = groups[1].Value; - // test for macro alias - if (elementName == "?UMBRACO_MACRO") - { - var tags = XmlHelper.GetAttributesFromElement(match.Value); - if (tags["macroAlias"] != null) - elementAttributes = String.Format(" Alias=\"{0}\"", tags["macroAlias"]) + elementAttributes; - else if (tags["macroalias"] != null) - elementAttributes = String.Format(" Alias=\"{0}\"", tags["macroalias"]) + elementAttributes; - } - string newElement = "<" + newElementName + " runat=\"server\" " + elementAttributes.Trim() + ">"; - if (elementAttributes.EndsWith("/")) - { - elementAttributes = elementAttributes.Substring(0, elementAttributes.Length - 1); - } - else if (groups[0].Value.StartsWith(""; - - if (checkForQuotes) - { - // if it's inside quotes, we'll change element attribute quotes to single quotes - newElement = newElement.Replace("\"", "'"); - newElement = String.Format("\"{0}\"", newElement); - } - design = design.Replace(match.Value, newElement); - } - } - - private static string GetElementRegExp(string elementName, bool checkForQuotes) - { - if (checkForQuotes) - return String.Format("\"<[^>\\s]*\\b{0}(\\b[^>]*)>\"", elementName); - else - return String.Format("<[^>\\s]*\\b{0}(\\b[^>]*)>", elementName); - - } - - } -} diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index fd58f573cc..2ce1230bcc 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -1,15 +1,15 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.IO; using System.Linq; using System.Threading.Tasks; -using LightInject; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Media; - using Umbraco.Core.Models; namespace Umbraco.Core.IO @@ -17,78 +17,32 @@ namespace Umbraco.Core.IO /// /// A custom file system provider for media /// - [FileSystemProvider("media")] - public class MediaFileSystem : FileSystemWrapper + public class MediaFileSystem : FileSystemWrapper, IMediaFileSystem { - public MediaFileSystem(IFileSystem wrapped) - : base(wrapped) - { - // due to how FileSystems is written at the moment, the ctor cannot be used to inject - // dependencies, so we have to rely on property injection for anything we might need - Current.Container.InjectProperties(this); - MediaPathScheme.Initialize(this); - } + private readonly IMediaPathScheme _mediaPathScheme; + private readonly IContentSection _contentConfig; + private readonly ILogger _logger; - [Inject] - internal IMediaPathScheme MediaPathScheme { get; set; } - - [Inject] - internal IContentSection ContentConfig { get; set; } - - [Inject] - internal ILogger Logger { get; set; } - /// - /// Deletes all files passed in. + /// Initializes a new instance of the class. /// - /// - /// - /// - internal bool DeleteFiles(IEnumerable files, Action onError = null) + public MediaFileSystem(IFileSystem innerFileSystem, IContentSection contentConfig, IMediaPathScheme mediaPathScheme, ILogger logger) + : base(innerFileSystem) { - //ensure duplicates are removed - files = files.Distinct(); - - var allsuccess = true; - var rootRelativePath = GetRelativePath("/"); - - Parallel.ForEach(files, file => - { - try - { - if (file.IsNullOrWhiteSpace()) return; - - var relativeFilePath = GetRelativePath(file); - if (FileExists(relativeFilePath) == false) return; - - var parentDirectory = Path.GetDirectoryName(relativeFilePath); - - // don't want to delete the media folder if not using directories. - if (ContentConfig.UploadAllowDirectories && parentDirectory != rootRelativePath) - { - //issue U4-771: if there is a parent directory the recursive parameter should be true - DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false); - } - else - { - DeleteFile(file); - } - } - catch (Exception e) - { - onError?.Invoke(file, e); - allsuccess = false; - } - }); - - return allsuccess; + _contentConfig = contentConfig; + _mediaPathScheme = mediaPathScheme; + _logger = logger; } + /// public void DeleteMediaFiles(IEnumerable files) { files = files.Distinct(); - Parallel.ForEach(files, file => + // kinda try to keep things under control + var options = new ParallelOptions { MaxDegreeOfParallelism = 20 }; + + Parallel.ForEach(files, options, file => { try { @@ -96,73 +50,44 @@ namespace Umbraco.Core.IO if (FileExists(file) == false) return; DeleteFile(file); - var directory = MediaPathScheme.GetDeleteDirectory(file); + var directory = _mediaPathScheme.GetDeleteDirectory(this, file); if (!directory.IsNullOrWhiteSpace()) DeleteDirectory(directory, true); } catch (Exception e) { - Logger.Error(e, "Failed to delete attached file '{File}'", file); + _logger.Error(e, "Failed to delete media file '{File}'.", file); } }); } #region Media Path - /// - /// Gets the file path of a media file. - /// - /// The file name. - /// The unique identifier of the content/media owning the file. - /// The unique identifier of the property type owning the file. - /// The filesystem-relative path to the media file. - /// With the old media path scheme, this CREATES a new media path each time it is invoked. + /// public string GetMediaPath(string filename, Guid cuid, Guid puid) { filename = Path.GetFileName(filename); if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); - return MediaPathScheme.GetFilePath(cuid, puid, filename); + return _mediaPathScheme.GetFilePath(this, cuid, puid, filename); } - /// - /// Gets the file path of a media file. - /// - /// The file name. - /// A previous file path. - /// The unique identifier of the content/media owning the file. - /// The unique identifier of the property type owning the file. - /// The filesystem-relative path to the media file. - /// In the old, legacy, number-based scheme, we try to re-use the media folder - /// specified by . Else, we CREATE a new one. Each time we are invoked. + /// public string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid) { filename = Path.GetFileName(filename); if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); - return MediaPathScheme.GetFilePath(cuid, puid, filename, prevpath); + return _mediaPathScheme.GetFilePath(this, cuid, puid, filename, prevpath); } #endregion #region Associated Media Files - /// - /// Stores a media file associated to a property of a content item. - /// - /// The content item owning the media file. - /// The property type owning the media file. - /// The media file name. - /// A stream containing the media bytes. - /// An optional filesystem-relative filepath to the previous media file. - /// The filesystem-relative filepath to the media file. - /// - /// The file is considered "owned" by the content/propertyType. - /// If an is provided then that file (and associated thumbnails if any) is deleted - /// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new file. - /// + /// public string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath) { if (content == null) throw new ArgumentNullException(nameof(content)); @@ -181,13 +106,7 @@ namespace Umbraco.Core.IO return filepath; } - /// - /// Copies a media file as a new media file, associated to a property of a content item. - /// - /// The content item owning the copy of the media file. - /// The property type owning the copy of the media file. - /// The filesystem-relative path to the source media file. - /// The filesystem-relative path to the copy of the media file. + /// public string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath) { if (content == null) throw new ArgumentNullException(nameof(content)); @@ -204,9 +123,6 @@ namespace Umbraco.Core.IO return filepath; } - - #endregion - } } diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs index 37c6a7b209..59f8213414 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs @@ -12,11 +12,7 @@ namespace Umbraco.Core.IO.MediaPathSchemes public class CombinedGuidsMediaPathScheme : IMediaPathScheme { /// - public void Initialize(IFileSystem filesystem) - { } - - /// - public string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null) + public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null) { // assumes that cuid and puid keys can be trusted - and that a single property type // for a single content cannot store two different files with the same name @@ -25,6 +21,6 @@ namespace Umbraco.Core.IO.MediaPathSchemes } /// - public string GetDeleteDirectory(string filepath) => Path.GetDirectoryName(filepath); + public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) => Path.GetDirectoryName(filepath); } } diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs index 1ed2ea59ce..1948a9f662 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.IO; using System.Threading; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; namespace Umbraco.Core.IO.MediaPathSchemes @@ -17,18 +18,11 @@ namespace Umbraco.Core.IO.MediaPathSchemes public class OriginalMediaPathScheme : IMediaPathScheme { private readonly object _folderCounterLock = new object(); - private IFileSystem _filesystem; private long _folderCounter; private bool _folderCounterInitialized; /// - public void Initialize(IFileSystem filesystem) - { - _filesystem = filesystem; - } - - /// - public string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null) + public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null) { string directory; if (previous != null) @@ -37,45 +31,45 @@ namespace Umbraco.Core.IO.MediaPathSchemes // prevpath should be "/" OR "-" // and we want to reuse the "" part, so try to find it - var sep = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories ? "/" : "-"; + var sep = Current.Configs.Settings().Content.UploadAllowDirectories ? "/" : "-"; var pos = previous.IndexOf(sep, StringComparison.Ordinal); var s = pos > 0 ? previous.Substring(0, pos) : null; - directory = pos > 0 && int.TryParse(s, out _) ? s : GetNextDirectory(); + directory = pos > 0 && int.TryParse(s, out _) ? s : GetNextDirectory(fileSystem); } else { - directory = GetNextDirectory(); + directory = GetNextDirectory(fileSystem); } if (directory == null) throw new InvalidOperationException("Cannot use a null directory."); - return UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories + return Current.Configs.Settings().Content.UploadAllowDirectories ? Path.Combine(directory, filename).Replace('\\', '/') : directory + "-" + filename; } /// - public string GetDeleteDirectory(string filepath) + public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) { return Path.GetDirectoryName(filepath); } - private string GetNextDirectory() + private string GetNextDirectory(IFileSystem fileSystem) { - EnsureFolderCounterIsInitialized(); + EnsureFolderCounterIsInitialized(fileSystem); return Interlocked.Increment(ref _folderCounter).ToString(CultureInfo.InvariantCulture); } - private void EnsureFolderCounterIsInitialized() + private void EnsureFolderCounterIsInitialized(IFileSystem fileSystem) { lock (_folderCounterLock) { if (_folderCounterInitialized) return; _folderCounter = 1000; // seed - var directories = _filesystem.GetDirectories(""); + var directories = fileSystem.GetDirectories(""); foreach (var directory in directories) { if (long.TryParse(directory, out var folderNumber) && folderNumber > _folderCounter) diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs index 4a6fdfcdbe..3c06e295e6 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs @@ -12,17 +12,13 @@ namespace Umbraco.Core.IO.MediaPathSchemes public class TwoGuidsMediaPathScheme : IMediaPathScheme { /// - public void Initialize(IFileSystem filesystem) - { } - - /// - public string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null) + public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null) { return Path.Combine(itemGuid.ToString("N"), propertyGuid.ToString("N"), filename).Replace('\\', '/'); } /// - public string GetDeleteDirectory(string filepath) + public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) { return Path.GetDirectoryName(filepath); } diff --git a/src/Umbraco.Core/IO/ShadowFileSystems.cs b/src/Umbraco.Core/IO/ShadowFileSystems.cs index 289667b0db..bce0cc6df7 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystems.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystems.cs @@ -1,158 +1,36 @@ using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; namespace Umbraco.Core.IO { + // shadow filesystems is definitively ... too convoluted + internal class ShadowFileSystems : ICompletable { - // note: taking a reference to the _manager instead of using manager.Current - // to avoid using Current everywhere but really, we support only 1 scope at - // a time, not multiple scopes in case of multiple managers (not supported) - - // fixme - why are we managing logical call context here? should be bound - // to the current scope, always => REFACTOR! but there should be something in - // place (static?) to ensure we only have one concurrent shadow FS? - // - // => yes, that's _currentId - need to cleanup this entirely - // and, we probably need a way to stop shadowing entirely without cycling the app - - private const string ItemKey = "Umbraco.Core.IO.ShadowFileSystems"; - - private static readonly object Locker = new object(); - private static Guid _currentId = Guid.Empty; - - private readonly Guid _id; - private readonly ShadowWrapper[] _wrappers; - private readonly ILogger _logger; - + private readonly FileSystems _fileSystems; private bool _completed; - //static ShadowFileSystems() - //{ - // SafeCallContext.Register( - // () => - // { - // var scope = CallContext.LogicalGetData(ItemKey); - // CallContext.FreeNamedDataSlot(ItemKey); - // return scope; - // }, - // o => - // { - // if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException(); - // if (o != null) CallContext.LogicalSetData(ItemKey, o); - // }); - //} - - public ShadowFileSystems(Guid id, ShadowWrapper[] wrappers, ILogger logger) + // invoked by the filesystems when shadowing + public ShadowFileSystems(FileSystems fileSystems, Guid id) { - lock (Locker) - { - if (_currentId != Guid.Empty) - throw new InvalidOperationException("Already shadowing."); - _currentId = id; - } + _fileSystems = fileSystems; + Id = id; - _logger = logger; - _logger.Debug("Shadow '{ShadowId}'", id); - _id = id; - - _wrappers = wrappers; - foreach (var wrapper in _wrappers) - wrapper.Shadow(id); - } - - // fixme - remove - //// internal for tests + FileSystems - //// do NOT use otherwise - //internal static ShadowFileSystems CreateScope(Guid id, ShadowWrapper[] wrappers, ILogger logger) - //{ - // lock (Locker) - // { - // if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException("Already shadowing."); - // CallContext.LogicalSetData(ItemKey, ItemKey); // value does not matter - // } - // return new ShadowFileSystems(id, wrappers, logger); - //} - - //internal static bool InScope => NoScope == false; - - //internal static bool NoScope => CallContext.LogicalGetData(ItemKey) == null; - - public void Complete() - { - _completed = true; - //lock (Locker) - //{ - // _logger.Debug("UnShadow " + _id + " (complete)."); - - // var exceptions = new List(); - // foreach (var wrapper in _wrappers) - // { - // try - // { - // // this may throw an AggregateException if some of the changes could not be applied - // wrapper.UnShadow(true); - // } - // catch (AggregateException ae) - // { - // exceptions.Add(ae); - // } - // } - - // if (exceptions.Count > 0) - // throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions); - - // // last, & *only* if successful (otherwise we'll unshadow & cleanup as best as we can) - // CallContext.FreeNamedDataSlot(ItemKey); - //} - } - - public void Dispose() - { - lock (Locker) - { - _logger.Debug("UnShadow '{ShadowId}' {Status}", _id, _completed ? "complete" : "abort"); - - var exceptions = new List(); - foreach (var wrapper in _wrappers) - { - try - { - // this may throw an AggregateException if some of the changes could not be applied - wrapper.UnShadow(_completed); - } - catch (AggregateException ae) - { - exceptions.Add(ae); - } - } - - _currentId = Guid.Empty; - - if (exceptions.Count > 0) - throw new AggregateException(_completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions); - - //if (CallContext.LogicalGetData(ItemKey) == null) return; - - //try - //{ - // _logger.Debug("UnShadow " + _id + " (abort)"); - // foreach (var wrapper in _wrappers) - // wrapper.UnShadow(false); // should not throw - //} - //finally - //{ - // // last, & always - // CallContext.FreeNamedDataSlot(ItemKey); - //} - } + _fileSystems.BeginShadow(id); } // for tests - internal static void ResetId() + public Guid Id { get; } + + // invoked by the scope when exiting, if completed + public void Complete() { - _currentId = Guid.Empty; + _completed = true; + } + + // invoked by the scope when exiting + public void Dispose() + { + _fileSystems.EndShadow(Id, _completed); } } } diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index 94bd61b162..d71f328713 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.IO { internal class ShadowWrapper : IFileSystem { - private const string ShadowFsPath = "~/App_Data/TEMP/ShadowFs"; + private static readonly string ShadowFsPath = SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"; private readonly Func _isScoped; private readonly IFileSystem _innerFileSystem; @@ -75,6 +75,8 @@ namespace Umbraco.Core.IO } } + public IFileSystem InnerFileSystem => _innerFileSystem; + private IFileSystem FileSystem { get diff --git a/src/Umbraco.Core/IO/SupportingFileSystems.cs b/src/Umbraco.Core/IO/SupportingFileSystems.cs new file mode 100644 index 0000000000..43ac2ba85a --- /dev/null +++ b/src/Umbraco.Core/IO/SupportingFileSystems.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.IO +{ + public class SupportingFileSystems : TargetedServiceFactory + { + public SupportingFileSystems(IFactory factory) + : base(factory) + { } + } +} diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index 183d48e3d9..8bea82ea4a 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -12,11 +12,11 @@ namespace Umbraco.Core.IO public static string Data => "~/App_Data"; - public static string Install => "~/install"; + public static string TempData => Data + "/TEMP"; - //fixme: remove this - [Obsolete("Master pages are obsolete and code should be removed")] - public static string Masterpages => "~/masterpages"; + public static string TempFileUploads => TempData + "/FileUploads"; + + public static string Install => "~/install"; public static string AppCode => "~/App_Code"; @@ -43,9 +43,9 @@ namespace Umbraco.Core.IO [Obsolete("Only used by legacy load balancing which is obsolete and should be removed")] public static string WebServices => IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices"); - public static string Packages => Data + IOHelper.DirSepChar + "packages"; + public static string Packages => Data + "/packages"; - public static string Preview => Data + IOHelper.DirSepChar + "preview"; + public static string Preview => Data + "/preview"; private static string _root; diff --git a/src/Umbraco.Core/IRuntime.cs b/src/Umbraco.Core/IRuntime.cs index 37b265ad77..d433dde12d 100644 --- a/src/Umbraco.Core/IRuntime.cs +++ b/src/Umbraco.Core/IRuntime.cs @@ -1,4 +1,4 @@ -using LightInject; +using Umbraco.Core.Composing; namespace Umbraco.Core { @@ -10,8 +10,14 @@ namespace Umbraco.Core /// /// Boots the runtime. /// - /// The application service container. - void Boot(ServiceContainer container); + /// The application register. + /// The application factory. + IFactory Boot(IRegister register); + + /// + /// Gets the runtime state. + /// + IRuntimeState State { get; } /// /// Terminates the runtime. diff --git a/src/Umbraco.Core/IRuntimeState.cs b/src/Umbraco.Core/IRuntimeState.cs index 5fcfab1518..30c768ab01 100644 --- a/src/Umbraco.Core/IRuntimeState.cs +++ b/src/Umbraco.Core/IRuntimeState.cs @@ -57,6 +57,11 @@ namespace Umbraco.Core /// RuntimeLevel Level { get; } + /// + /// Gets the reason for the runtime level of execution. + /// + RuntimeLevelReason Reason { get; } + /// /// Gets the current migration state. /// diff --git a/src/Umbraco.Core/Logging/IProfilingLogger.cs b/src/Umbraco.Core/Logging/IProfilingLogger.cs new file mode 100644 index 0000000000..1d7c231e95 --- /dev/null +++ b/src/Umbraco.Core/Logging/IProfilingLogger.cs @@ -0,0 +1,40 @@ +using System; + +namespace Umbraco.Core.Logging +{ + /// + /// Defines the profiling logging service. + /// + public interface IProfilingLogger : ILogger + { + /// + /// Profiles an action and log as information messages. + /// + DisposableTimer TraceDuration(string startMessage); + + /// + /// Profiles an action and log as information messages. + /// + DisposableTimer TraceDuration(string startMessage, string completeMessage, string failMessage = null); + + /// + /// Profiles an action and log as information messages. + /// + DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, string failMessage = null); + + /// + /// Profiles an action and log as debug messages. + /// + DisposableTimer DebugDuration(string startMessage); + + /// + /// Profiles an action and log as debug messages. + /// + DisposableTimer DebugDuration(string startMessage, string completeMessage, string failMessage = null, int thresholdMilliseconds = 0); + + /// + /// Profiles an action and log as debug messages. + /// + DisposableTimer DebugDuration(Type loggerType, string startMessage, string completeMessage, string failMessage = null, int thresholdMilliseconds = 0); + } +} diff --git a/src/Umbraco.Core/Logging/MessageTemplates.cs b/src/Umbraco.Core/Logging/MessageTemplates.cs index 194d47c339..47de1230ff 100644 --- a/src/Umbraco.Core/Logging/MessageTemplates.cs +++ b/src/Umbraco.Core/Logging/MessageTemplates.cs @@ -1,6 +1,10 @@ using System; +using System.IO; using System.Linq; +using System.Text; using Serilog; +using Serilog.Events; +using Serilog.Parsing; namespace Umbraco.Core.Logging { @@ -29,7 +33,23 @@ namespace Umbraco.Core.Logging if (!bound) throw new FormatException($"Could not format message \"{messageTemplate}\" with {args.Length} args."); - return parsedTemplate.Render(boundProperties.ToDictionary(x => x.Name, x => x.Value)); + var values = boundProperties.ToDictionary(x => x.Name, x => x.Value); + + // this ends up putting every string parameter between quotes + //return parsedTemplate.Render(values); + + // this does not + var tw = new StringWriter(); + foreach (var t in parsedTemplate.Tokens) + { + if (t is PropertyToken pt && + values.TryGetValue(pt.PropertyName, out var propVal) && + (propVal as ScalarValue)?.Value is string s) + tw.Write(s); + else + t.Render(values, tw); + } + return tw.ToString(); } } } diff --git a/src/Umbraco.Core/Logging/ProfilingLogger.cs b/src/Umbraco.Core/Logging/ProfilingLogger.cs index 80560e923a..d642926147 100644 --- a/src/Umbraco.Core/Logging/ProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/ProfilingLogger.cs @@ -3,14 +3,23 @@ namespace Umbraco.Core.Logging { /// - /// Provides debug or trace logging with duration management. + /// Provides logging and profiling services. /// - public sealed class ProfilingLogger + public sealed class ProfilingLogger : IProfilingLogger { + /// + /// Gets the underlying implementation. + /// public ILogger Logger { get; } + /// + /// Gets the underlying implementation. + /// public IProfiler Profiler { get; } + /// + /// Initializes a new instance of the class. + /// public ProfilingLogger(ILogger logger, IProfiler profiler) { Logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -52,5 +61,72 @@ namespace Umbraco.Core.Logging ? new DisposableTimer(Logger, LogLevel.Debug, Profiler, loggerType, startMessage, completeMessage, failMessage, thresholdMilliseconds) : null; } + + #region ILogger + + public bool IsEnabled(Type reporting, LogLevel level) + => Logger.IsEnabled(reporting, level); + + public void Fatal(Type reporting, Exception exception, string message) + => Logger.Fatal(reporting, exception, message); + + public void Fatal(Type reporting, Exception exception) + => Logger.Fatal(reporting, exception); + + public void Fatal(Type reporting, string message) + => Logger.Fatal(reporting, message); + + public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues) + => Logger.Fatal(reporting, exception, messageTemplate, propertyValues); + + public void Fatal(Type reporting, string messageTemplate, params object[] propertyValues) + => Logger.Fatal(reporting, messageTemplate, propertyValues); + + public void Error(Type reporting, Exception exception, string message) + => Logger.Error(reporting, exception, message); + + public void Error(Type reporting, Exception exception) + => Logger.Error(reporting, exception); + + public void Error(Type reporting, string message) + => Logger.Error(reporting, message); + + public void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues) + => Logger.Error(reporting, exception, messageTemplate, propertyValues); + + public void Error(Type reporting, string messageTemplate, params object[] propertyValues) + => Logger.Error(reporting, messageTemplate, propertyValues); + + public void Warn(Type reporting, string message) + => Logger.Warn(reporting, message); + + public void Warn(Type reporting, string messageTemplate, params object[] propertyValues) + => Logger.Warn(reporting, messageTemplate, propertyValues); + + public void Warn(Type reporting, Exception exception, string message) + => Logger.Warn(reporting, exception, message); + + public void Warn(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues) + => Logger.Warn(reporting, exception, messageTemplate, propertyValues); + + public void Info(Type reporting, string message) + => Logger.Info(reporting, message); + + public void Info(Type reporting, string messageTemplate, params object[] propertyValues) + => Logger.Info(reporting, messageTemplate, propertyValues); + + public void Debug(Type reporting, string message) + => Logger.Debug(reporting, message); + + public void Debug(Type reporting, string messageTemplate, params object[] propertyValues) + => Logger.Debug(reporting, messageTemplate, propertyValues); + + public void Verbose(Type reporting, string message) + => Logger.Verbose(reporting, message); + + public void Verbose(Type reporting, string messageTemplate, params object[] propertyValues) + => Logger.Verbose(reporting, messageTemplate, propertyValues); + + #endregion } } diff --git a/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs b/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs index 2d333ed916..1c6f9853a2 100644 --- a/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs +++ b/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs @@ -92,7 +92,7 @@ namespace Umbraco.Core.Logging.Serilog /// /// Reads settings from /config/serilog.user.config - /// That allows a seperate logging pipeline to be configured that wil not affect the main Umbraco log + /// That allows a separate logging pipeline to be configured that wil not affect the main Umbraco log /// /// A Serilog LoggerConfiguration public static LoggerConfiguration ReadFromUserConfigFile(this LoggerConfiguration logConfig) diff --git a/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs b/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs index c23a43a820..17d86b45e1 100644 --- a/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs +++ b/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Threading; using Serilog; using Serilog.Events; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Diagnostics; @@ -165,7 +166,7 @@ namespace Umbraco.Core.Logging.Serilog messageTemplate += "\r\nThe thread has been aborted, because the request has timed out."; // dump if configured, or if stacktrace contains Monitor.ReliableEnter - dump = UmbracoConfig.For.CoreDebug().DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception); + dump = Current.Configs.CoreDebug().DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception); // dump if it is ok to dump (might have a cap on number of dump...) dump &= MiniDump.OkToDump(); @@ -216,9 +217,9 @@ namespace Umbraco.Core.Logging.Serilog /// public void Warn(Type reporting, string message) { - LoggerFor(reporting).Warning(message); + LoggerFor(reporting).Warning(message); } - + /// public void Warn(Type reporting, string message, params object[] propertyValues) { @@ -230,7 +231,7 @@ namespace Umbraco.Core.Logging.Serilog { LoggerFor(reporting).Warning(exception, message); } - + /// public void Warn(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues) { @@ -254,7 +255,7 @@ namespace Umbraco.Core.Logging.Serilog { LoggerFor(reporting).Debug(message); } - + /// public void Debug(Type reporting, string messageTemplate, params object[] propertyValues) { diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index eb036fd441..0b4551a7cc 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -8,15 +8,13 @@ using Umbraco.Core.Logging; namespace Umbraco.Core { /// - /// Represents the main AppDomain running for a given application. + /// Provides the full implementation of . /// /// - /// There can be only one "main" AppDomain running for a given application at a time. /// When an AppDomain starts, it tries to acquire the main domain status. /// When an AppDomain stops (eg the application is restarting) it should release the main domain status. - /// It is possible to register against the MainDom and be notified when it is released. /// - internal class MainDom : IRegisteredObject + internal class MainDom : IMainDom, IRegisteredObject { #region Vars @@ -84,9 +82,7 @@ namespace Umbraco.Core /// An optional weight (lower goes first). /// A value indicating whether it was possible to register. public bool Register(Action release, int weight = 100) - { - return Register(null, release, weight); - } + => Register(null, release, weight); /// /// Registers a resource that requires the current AppDomain to be the main domain to function. @@ -195,7 +191,9 @@ namespace Umbraco.Core } } - // gets a value indicating whether we are the main domain + /// + /// Gets a value indicating whether the current domain is the main domain. + /// public bool IsMainDom => _isMainDom; // IRegisteredObject diff --git a/src/Umbraco.Core/Manifest/ManifestBackOfficeSection.cs b/src/Umbraco.Core/Manifest/ManifestBackOfficeSection.cs new file mode 100644 index 0000000000..a1b89d9a01 --- /dev/null +++ b/src/Umbraco.Core/Manifest/ManifestBackOfficeSection.cs @@ -0,0 +1,15 @@ +using System.Runtime.Serialization; +using Umbraco.Core.Models.Trees; + +namespace Umbraco.Core.Manifest +{ + [DataContract(Name = "section", Namespace = "")] + public class ManifestBackOfficeSection : IBackOfficeSection + { + [DataMember(Name = "alias")] + public string Alias { get; set; } + + [DataMember(Name = "name")] + public string Name { get; set; } + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 59753df66a..01bc0b1983 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Trees; using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Manifest @@ -20,7 +21,7 @@ namespace Umbraco.Core.Manifest { private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); - private readonly IRuntimeCacheProvider _cache; + private readonly IAppPolicyCache _cache; private readonly ILogger _logger; private readonly ManifestValueValidatorCollection _validators; @@ -29,16 +30,17 @@ namespace Umbraco.Core.Manifest /// /// Initializes a new instance of the class. /// - public ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, ILogger logger) - : this(cache, validators, "~/App_Plugins", logger) + public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ILogger logger) + : this(appCaches, validators, "~/App_Plugins", logger) { } /// /// Initializes a new instance of the class. /// - private ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, string path, ILogger logger) + private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, string path, ILogger logger) { - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + if (appCaches == null) throw new ArgumentNullException(nameof(appCaches)); + _cache = appCaches.RuntimeCache; _validators = validators ?? throw new ArgumentNullException(nameof(validators)); if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); Path = path; @@ -101,6 +103,7 @@ namespace Umbraco.Core.Manifest var gridEditors = new List(); var contentApps = new List(); var dashboards = new List(); + var sections = new List(); foreach (var manifest in manifests) { @@ -111,6 +114,7 @@ namespace Umbraco.Core.Manifest if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); if (manifest.ContentApps != null) contentApps.AddRange(manifest.ContentApps); if (manifest.Dashboards != null) dashboards.AddRange(manifest.Dashboards); + if (manifest.Sections != null) sections.AddRange(manifest.Sections.DistinctBy(x => x.Alias.ToLowerInvariant())); } return new PackageManifest @@ -121,7 +125,8 @@ namespace Umbraco.Core.Manifest ParameterEditors = parameterEditors.ToArray(), GridEditors = gridEditors.ToArray(), ContentApps = contentApps.ToArray(), - Dashboards = dashboards.ToArray() + Dashboards = dashboards.ToArray(), + Sections = sections.ToArray() }; } diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index cd806ac847..b29a28ab06 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,6 +1,5 @@ using System; using Newtonsoft.Json; -using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Manifest @@ -10,25 +9,52 @@ namespace Umbraco.Core.Manifest /// public class PackageManifest { + /// + /// Gets or sets the scripts listed in the manifest. + /// [JsonProperty("javascript")] public string[] Scripts { get; set; } = Array.Empty(); + /// + /// Gets or sets the stylesheets listed in the manifest. + /// [JsonProperty("css")] - public string[] Stylesheets { get; set; }= Array.Empty(); + public string[] Stylesheets { get; set; } = Array.Empty(); + /// + /// Gets or sets the property editors listed in the manifest. + /// [JsonProperty("propertyEditors")] public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); + /// + /// Gets or sets the parameter editors listed in the manifest. + /// [JsonProperty("parameterEditors")] public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); + /// + /// Gets or sets the grid editors listed in the manifest. + /// [JsonProperty("gridEditors")] public GridEditor[] GridEditors { get; set; } = Array.Empty(); + /// + /// Gets or sets the content apps listed in the manifest. + /// [JsonProperty("contentApps")] public ManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); + /// + /// Gets or sets the dashboards listed in the manifest. + /// [JsonProperty("dashboards")] public ManifestDashboardDefinition[] Dashboards { get; set; } = Array.Empty(); + + /// + /// Gets or sets the sections listed in the manifest. + /// + [JsonProperty("sections")] + public ManifestBackOfficeSection[] Sections { get; set; } = Array.Empty(); } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index ef6b3b720b..e2c34c31cb 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -120,7 +120,7 @@ namespace Umbraco.Core.Migrations.Install #region Configure Connection String - private const string EmbeddedDatabaseConnectionString = @"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;"; + public const string EmbeddedDatabaseConnectionString = @"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;"; /// /// Configures a connection string for the embedded database. @@ -369,52 +369,6 @@ namespace Umbraco.Core.Migrations.Install #endregion - #region Utils - - internal static void GiveLegacyAChance(IUmbracoDatabaseFactory factory, ILogger logger) - { - // look for the legacy appSettings key - var legacyConnString = ConfigurationManager.AppSettings[Constants.System.UmbracoConnectionName]; - if (string.IsNullOrWhiteSpace(legacyConnString)) return; - - var test = legacyConnString.ToLowerInvariant(); - if (test.Contains("sqlce4umbraco")) - { - // sql ce - ConfigureEmbeddedDatabaseConnection(factory, logger); - } - else if (test.Contains("tcp:")) - { - // sql azure - SaveConnectionString(legacyConnString, Constants.DbProviderNames.SqlServer, logger); - factory.Configure(legacyConnString, Constants.DbProviderNames.SqlServer); - } - else if (test.Contains("datalayer=mysql")) - { - // mysql - - // strip the datalayer part off - var connectionStringWithoutDatalayer = string.Empty; - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var variable in legacyConnString.Split(';').Where(x => x.ToLowerInvariant().StartsWith("datalayer") == false)) - connectionStringWithoutDatalayer = $"{connectionStringWithoutDatalayer}{variable};"; - - SaveConnectionString(connectionStringWithoutDatalayer, Constants.DbProviderNames.MySql, logger); - factory.Configure(connectionStringWithoutDatalayer, Constants.DbProviderNames.MySql); - } - else - { - // sql server - SaveConnectionString(legacyConnString, Constants.DbProviderNames.SqlServer, logger); - factory.Configure(legacyConnString, Constants.DbProviderNames.SqlServer); - } - - // remove the legacy connection string, so we don't end up in a loop if something goes wrong - GlobalSettings.RemoveSetting(Constants.System.UmbracoConnectionName); - } - - #endregion - #region Database Schema /// diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index eba5e61390..f9f3e5da30 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Migrations.Install /// /// Creates the initial database schema during install. /// - internal class DatabaseSchemaCreator + public class DatabaseSchemaCreator { private readonly IUmbracoDatabase _database; private readonly ILogger _logger; @@ -28,7 +28,7 @@ namespace Umbraco.Core.Migrations.Install private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax; // all tables, in order - public static readonly List OrderedTables = new List + internal static readonly List OrderedTables = new List { typeof (UserDto), typeof (NodeDto), @@ -116,8 +116,12 @@ namespace Umbraco.Core.Migrations.Install /// /// Initializes the database by creating the umbraco db schema. /// + /// This needs to execute as part of a transaction. public void InitializeDatabaseSchema() { + if (!_database.InTransaction) + throw new InvalidOperationException("Database is not in a transaction."); + var e = new DatabaseCreationEventArgs(); FireBeforeCreation(e); @@ -134,7 +138,7 @@ namespace Umbraco.Core.Migrations.Install /// /// Validates the schema of the current database. /// - public DatabaseSchemaResult ValidateSchema() + internal DatabaseSchemaResult ValidateSchema() { var result = new DatabaseSchemaResult(SqlSyntax); @@ -383,7 +387,7 @@ namespace Umbraco.Core.Migrations.Install /// If has been decorated with an , the name from that /// attribute will be used for the table name. If the attribute is not present, the name /// will be used instead. - /// + /// /// If a table with the same name already exists, the parameter will determine /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will /// not do anything if the parameter is false. @@ -405,13 +409,18 @@ namespace Umbraco.Core.Migrations.Install /// If has been decorated with an , the name from /// that attribute will be used for the table name. If the attribute is not present, the name /// will be used instead. - /// + /// /// If a table with the same name already exists, the parameter will determine /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will /// not do anything if the parameter is false. + /// + /// This need to execute as part of a transaction. /// - public void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation) + internal void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation) { + if (!_database.InTransaction) + throw new InvalidOperationException("Database is not in a transaction."); + var tableDefinition = DefinitionFactory.GetTableDefinition(modelType, SqlSyntax); var tableName = tableDefinition.Name; @@ -429,70 +438,64 @@ namespace Umbraco.Core.Migrations.Install tableExist = false; } - if (tableExist == false) - { - using (var transaction = _database.GetTransaction()) - { - //Execute the Create Table sql - var created = _database.Execute(new Sql(createSql)); - _logger.Info("Create Table '{TableName}' ({Created}): \n {Sql}", tableName, created, createSql); - - //If any statements exists for the primary key execute them here - if (string.IsNullOrEmpty(createPrimaryKeySql) == false) - { - var createdPk = _database.Execute(new Sql(createPrimaryKeySql)); - _logger.Info("Create Primary Key ({CreatedPk}):\n {Sql}", createdPk, createPrimaryKeySql); - } - - //Turn on identity insert if db provider is not mysql - if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) - _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} ON ")); - - //Call the NewTable-event to trigger the insert of base/default data - //OnNewTable(tableName, _db, e, _logger); - - dataCreation.InitializeBaseData(tableName); - - //Turn off identity insert if db provider is not mysql - if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) - _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} OFF;")); - - //Special case for MySql - if (SqlSyntax is MySqlSyntaxProvider && tableName.Equals("umbracoUser")) - { - _database.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); - } - - //Loop through index statements and execute sql - foreach (var sql in indexSql) - { - var createdIndex = _database.Execute(new Sql(sql)); - _logger.Info("Create Index ({CreatedIndex}):\n {Sql}", createdIndex, sql); - } - - //Loop through foreignkey statements and execute sql - foreach (var sql in foreignSql) - { - var createdFk = _database.Execute(new Sql(sql)); - _logger.Info("Create Foreign Key ({CreatedFk}):\n {Sql}", createdFk, sql); - } - - transaction.Complete(); - - if (overwrite) - { - _logger.Info("Table '{TableName}' was recreated", tableName); - } - else - { - _logger.Info("New table '{TableName}' was created", tableName); - } - } - } - else + if (tableExist) { // The table exists and was not recreated/overwritten. _logger.Info("Table '{TableName}' already exists - no changes were made", tableName); + return; + } + + //Execute the Create Table sql + var created = _database.Execute(new Sql(createSql)); + _logger.Info("Create Table '{TableName}' ({Created}): \n {Sql}", tableName, created, createSql); + + //If any statements exists for the primary key execute them here + if (string.IsNullOrEmpty(createPrimaryKeySql) == false) + { + var createdPk = _database.Execute(new Sql(createPrimaryKeySql)); + _logger.Info("Create Primary Key ({CreatedPk}):\n {Sql}", createdPk, createPrimaryKeySql); + } + + //Turn on identity insert if db provider is not mysql + if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) + _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} ON ")); + + //Call the NewTable-event to trigger the insert of base/default data + //OnNewTable(tableName, _db, e, _logger); + + dataCreation.InitializeBaseData(tableName); + + //Turn off identity insert if db provider is not mysql + if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) + _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} OFF;")); + + //Special case for MySql + if (SqlSyntax is MySqlSyntaxProvider && tableName.Equals("umbracoUser")) + { + _database.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); + } + + //Loop through index statements and execute sql + foreach (var sql in indexSql) + { + var createdIndex = _database.Execute(new Sql(sql)); + _logger.Info("Create Index ({CreatedIndex}):\n {Sql}", createdIndex, sql); + } + + //Loop through foreignkey statements and execute sql + foreach (var sql in foreignSql) + { + var createdFk = _database.Execute(new Sql(sql)); + _logger.Info("Create Foreign Key ({CreatedFk}):\n {Sql}", createdFk, sql); + } + + if (overwrite) + { + _logger.Info("Table '{TableName}' was recreated", tableName); + } + else + { + _logger.Info("New table '{TableName}' was created", tableName); } } diff --git a/src/Umbraco.Core/Migrations/MigrationBuilder.cs b/src/Umbraco.Core/Migrations/MigrationBuilder.cs index 3d8c88c771..d2d2b7d32a 100644 --- a/src/Umbraco.Core/Migrations/MigrationBuilder.cs +++ b/src/Umbraco.Core/Migrations/MigrationBuilder.cs @@ -1,36 +1,20 @@ using System; -using LightInject; +using Umbraco.Core.Composing; namespace Umbraco.Core.Migrations { public class MigrationBuilder : IMigrationBuilder { - private readonly IServiceContainer _container; + private readonly IFactory _container; - public MigrationBuilder(IServiceContainer container) + public MigrationBuilder(IFactory container) { _container = container; - - // because the builder should be "per container" this ctor should run only once per container. - // - // note: constructor dependencies do NOT work with lifetimes other than transient - // see https://github.com/seesharper/LightInject/issues/294 - // - // resolve ctor dependency from GetInstance() runtimeArguments, if possible - 'factory' is - // the container, 'info' describes the ctor argument, and 'args' contains the args that - // were passed to GetInstance() - use first arg if it is the right type. - // - // for IMigrationContext - container.RegisterConstructorDependency((factory, info, args) => args.Length > 0 ? args[0] as IMigrationContext : null); } public IMigration Build(Type migrationType, IMigrationContext context) { - // LightInject .Create() is a shortcut for .Register() + .GetInstance() - // but it does not support parameters, so we do it ourselves here - - _container.Register(migrationType); - return (IMigration) _container.GetInstance(migrationType, new object[] { context }); + return (IMigration) _container.CreateInstance(migrationType, context); } } } diff --git a/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs b/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs index 63cdaf4454..b23d4f1c9c 100644 --- a/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs +++ b/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs @@ -1,16 +1,11 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.Migrations { public class PostMigrationCollectionBuilder : LazyCollectionBuilderBase { - public PostMigrationCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override PostMigrationCollectionBuilder This => this; - protected override ILifetime CollectionLifetime => null; // transient + protected override Lifetime CollectionLifetime => Lifetime.Transient; } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index f7e1ee9921..833955ee6a 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -3,6 +3,7 @@ using System.Configuration; using Semver; using Umbraco.Core.Configuration; using Umbraco.Core.Migrations.Upgrade.V_7_12_0; +using Umbraco.Core.Migrations.Upgrade.V_7_14_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; namespace Umbraco.Core.Migrations.Upgrade @@ -118,7 +119,12 @@ namespace Umbraco.Core.Migrations.Upgrade To("{EE429F1B-9B26-43CA-89F8-A86017C809A3}"); To("{08919C4B-B431-449C-90EC-2B8445B5C6B1}"); To("{7EB0254C-CB8B-4C75-B15B-D48C55B449EB}"); + To("{648A2D5F-7467-48F8-B309-E99CEEE00E2A}"); // fixed version To("{C39BF2A7-1454-4047-BBFE-89E40F66ED63}"); + To("{64EBCE53-E1F0-463A-B40B-E98EFCCA8AE2}"); + To("{0009109C-A0B8-4F3F-8FEB-C137BBDDA268}"); + To("{8A027815-D5CD-4872-8B88-9A51AB5986A6}"); // from 7.14.0 + //FINAL @@ -141,12 +147,23 @@ namespace Umbraco.Core.Migrations.Upgrade // main chain, skipping the migrations // From("{init-7.12.0}"); - // start stop target + // clone start / clone stop / target ToWithClone("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); From("{init-7.12.1}").To("{init-7.10.0}"); // same as 7.12.0 From("{init-7.12.2}").To("{init-7.10.0}"); // same as 7.12.0 From("{init-7.12.3}").To("{init-7.10.0}"); // same as 7.12.0 + From("{init-7.12.4}").To("{init-7.10.0}"); // same as 7.12.0 + From("{init-7.13.0}").To("{init-7.10.0}"); // same as 7.12.0 + From("{init-7.13.1}").To("{init-7.10.0}"); // same as 7.12.0 + + // 7.14.0 has migrations, handle it... + // clone going from 7.10 to 1350617A (the last one before we started to merge 7.12 migrations), then + // clone going from CF51B39B (after 7.12 migrations) to 0009109C (the last one before we started to merge 7.12 migrations), + // ending in 8A027815 (after 7.14 migrations) + From("{init-7.14.0}") + .ToWithClone("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{9109B8AF-6B34-46EE-9484-7434196D0C79}") + .ToWithClone("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}", "{0009109C-A0B8-4F3F-8FEB-C137BBDDA268}", "{8A027815-D5CD-4872-8B88-9A51AB5986A6}"); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_14_0/UpdateMemberGroupPickerData.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_14_0/UpdateMemberGroupPickerData.cs new file mode 100644 index 0000000000..c70f42076f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_14_0/UpdateMemberGroupPickerData.cs @@ -0,0 +1,29 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_7_14_0 +{ + /// + /// Migrates member group picker properties from NVarchar to NText. See https://github.com/umbraco/Umbraco-CMS/issues/3268. + /// + public class UpdateMemberGroupPickerData : MigrationBase + { + /// + /// Migrates member group picker properties from NVarchar to NText. See https://github.com/umbraco/Umbraco-CMS/issues/3268. + /// + public UpdateMemberGroupPickerData(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Database.Execute($@"UPDATE umbracoPropertyData SET textValue = varcharValue, varcharValue = NULL + WHERE textValue IS NULL AND id IN ( + SELECT id FROM umbracoPropertyData WHERE propertyTypeId in ( + SELECT id from cmsPropertyType where dataTypeId IN ( + SELECT nodeId FROM umbracoDataType WHERE propertyEditorAlias = '{Constants.PropertyEditors.Aliases.MemberGroupPicker}' + ) + ) + )"); + + Database.Execute($"UPDATE umbracoDataType SET dbType = 'Ntext' WHERE propertyEditorAlias = '{Constants.PropertyEditors.Aliases.MemberGroupPicker}'"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentTypeIsElementColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentTypeIsElementColumn.cs new file mode 100644 index 0000000000..1df11a3e99 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentTypeIsElementColumn.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class AddContentTypeIsElementColumn : MigrationBase + { + public AddContentTypeIsElementColumn(IMigrationContext context) : base(context) + { } + + public override void Migrate() + { + AddColumn("isElement"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs index e8fd4f409e..008b3e4b5f 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs @@ -9,10 +9,10 @@ public override void Migrate() { - if (TableExists("cmsTaskType")) - Delete.Table("cmsTaskType"); if (TableExists("cmsTask")) - Delete.Table("cmsTask"); + Delete.Table("cmsTask").Do(); + if (TableExists("cmsTaskType")) + Delete.Table("cmsTaskType").Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs new file mode 100644 index 0000000000..2e366c7c14 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs @@ -0,0 +1,29 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class MakeRedirectUrlVariant : MigrationBase + { + public MakeRedirectUrlVariant(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + AddColumn("culture"); + + Delete.Index("IX_umbracoRedirectUrl").OnTable(Constants.DatabaseSchema.Tables.RedirectUrl).Do(); + Create.Index("IX_umbracoRedirectUrl").OnTable(Constants.DatabaseSchema.Tables.RedirectUrl) + .OnColumn("urlHash") + .Ascending() + .OnColumn("contentKey") + .Ascending() + .OnColumn("culture") + .Ascending() + .OnColumn("createDateUtc") + .Ascending() + .WithOptions().Unique() + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs index aa498583ff..6ddd49841d 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs @@ -11,7 +11,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - Delete.Column("edited").FromTable(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation).Do(); + if (ColumnExists(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation, "edited")) + Delete.Column("edited").FromTable(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation).Do(); // add available column diff --git a/src/Umbraco.Core/Models/ApplicationTree.cs b/src/Umbraco.Core/Models/ApplicationTree.cs deleted file mode 100644 index ccdebea724..0000000000 --- a/src/Umbraco.Core/Models/ApplicationTree.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Models -{ - [DebuggerDisplay("Tree - {Title} ({ApplicationAlias})")] - public class ApplicationTree - { - private static readonly ConcurrentDictionary ResolvedTypes = new ConcurrentDictionary(); - - /// - /// Initializes a new instance of the class. - /// - public ApplicationTree() { } - - /// - /// Initializes a new instance of the class. - /// - /// if set to true [initialize]. - /// The sort order. - /// The application alias. - /// The tree alias. - /// The tree title. - /// The icon closed. - /// The icon opened. - /// The tree type. - public ApplicationTree(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) - { - Initialize = initialize; - SortOrder = sortOrder; - ApplicationAlias = applicationAlias; - Alias = alias; - Title = title; - IconClosed = iconClosed; - IconOpened = iconOpened; - Type = type; - - } - - /// - /// Gets or sets a value indicating whether this should initialize. - /// - /// true if initialize; otherwise, false. - public bool Initialize { get; set; } - - /// - /// Gets or sets the sort order. - /// - /// The sort order. - public int SortOrder { get; set; } - - /// - /// Gets the application alias. - /// - /// The application alias. - public string ApplicationAlias { get; } - - /// - /// Gets the tree alias. - /// - /// The alias. - public string Alias { get; } - - /// - /// Gets or sets the tree title. - /// - /// The title. - public string Title { get; set; } - - /// - /// Gets or sets the icon closed. - /// - /// The icon closed. - public string IconClosed { get; set; } - - /// - /// Gets or sets the icon opened. - /// - /// The icon opened. - public string IconOpened { get; set; } - - /// - /// Gets or sets the tree type assembly name. - /// - /// The type. - public string Type { get; set; } - - /// - /// Returns the localized root node display name - /// - /// - /// - public string GetRootNodeDisplayName(ILocalizedTextService textService) - { - var label = $"[{Alias}]"; - - // try to look up a the localized tree header matching the tree alias - var localizedLabel = textService.Localize("treeHeaders/" + Alias); - - // if the localizedLabel returns [alias] then return the title attribute from the trees.config file, if it's defined - if (localizedLabel != null && localizedLabel.Equals(label, StringComparison.InvariantCultureIgnoreCase)) - { - if (string.IsNullOrEmpty(Title) == false) - label = Title; - } - else - { - // the localizedLabel translated into something that's not just [alias], so use the translation - label = localizedLabel; - } - - return label; - } - - private Type _runtimeType; - - /// - /// Returns the CLR type based on it's assembly name stored in the config - /// - /// - public Type GetRuntimeType() - { - return _runtimeType ?? (_runtimeType = System.Type.GetType(Type)); - } - - /// - /// Used to try to get and cache the tree type - /// - /// - /// - internal static Type TryGetType(string type) - { - try - { - return ResolvedTypes.GetOrAdd(type, s => - { - var result = System.Type.GetType(type); - if (result != null) - { - return result; - } - - //we need to implement a bit of a hack here due to some trees being renamed and backwards compat - var parts = type.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length != 2) - throw new InvalidOperationException("Could not resolve type"); - if (parts[1].Trim() != "Umbraco.Web" || parts[0].StartsWith("Umbraco.Web.Trees") == false || parts[0].EndsWith("Controller")) - throw new InvalidOperationException("Could not resolve type"); - - //if it's one of our controllers but it's not suffixed with "Controller" then add it and try again - var tempType = parts[0] + "Controller, Umbraco.Web"; - - result = System.Type.GetType(tempType); - if (result != null) - return result; - - throw new InvalidOperationException("Could not resolve type"); - }); - } - catch (InvalidOperationException) - { - //swallow, this is our own exception, couldn't find the type - // fixme bad use of exceptions here! - return null; - } - } - } -} diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 3f6e387dec..0c679e5e70 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Models public class Content : ContentBase, IContent { private IContentType _contentType; - private ITemplate _template; + private int? _templateId; private ContentScheduleCollection _schedule; private bool _published; private PublishedState _publishedState; @@ -83,7 +83,7 @@ namespace Umbraco.Core.Models // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { - public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); + public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.TemplateId); public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); public readonly PropertyInfo ContentScheduleSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentSchedule); public readonly PropertyInfo PublishCultureInfosSelector = ExpressionHelper.GetPropertyInfo>(x => x.PublishCultureInfos); @@ -131,10 +131,10 @@ namespace Umbraco.Core.Models /// the Default template from the ContentType will be returned. /// [DataMember] - public ITemplate Template + public int? TemplateId { - get => _template ?? _contentType.DefaultTemplate; - set => SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); + get => _templateId; + set => SetPropertyValueAndDetectChanges(value, ref _templateId, Ps.Value.TemplateSelector); } @@ -193,7 +193,7 @@ namespace Umbraco.Core.Models /// [IgnoreDataMember] - public ITemplate PublishTemplate { get; internal set; } // set by persistence + public int? PublishTemplateId { get; internal set; } // set by persistence /// [IgnoreDataMember] @@ -457,9 +457,7 @@ namespace Umbraco.Core.Models public override void ResetDirtyProperties(bool rememberDirty) { base.ResetDirtyProperties(rememberDirty); - - if (Template != null) - Template.ResetDirtyProperties(rememberDirty); + if (ContentType != null) ContentType.ResetDirtyProperties(rememberDirty); diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 88b1179f6d..b6ea9f50a0 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -26,6 +26,7 @@ namespace Umbraco.Core.Models private string _thumbnail = "folder.png"; private bool _allowedAsRoot; // note: only one that's not 'pure element type' private bool _isContainer; + private bool _isElement; private PropertyGroupCollection _propertyGroups; private PropertyTypeCollection _noGroupPropertyTypes; private IEnumerable _allowedContentTypes; @@ -90,6 +91,7 @@ namespace Umbraco.Core.Models public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); public readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.Thumbnail); public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot); + public readonly PropertyInfo IsElementSelector = ExpressionHelper.GetPropertyInfo(x => x.IsElement); public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer); public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes); public readonly PropertyInfo PropertyGroupsSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); @@ -180,6 +182,14 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector); } + /// + [DataMember] + public bool IsElement + { + get => _isElement; + set => SetPropertyValueAndDetectChanges(value, ref _isElement, Ps.Value.IsElementSelector); + } + /// /// Gets or sets a list of integer Ids for allowed ContentTypes /// diff --git a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs index 8af48bb881..adbc3de54f 100644 --- a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs @@ -15,7 +15,8 @@ namespace Umbraco.Core.Models { var type = contentType.GetType(); var itemType = PublishedItemType.Unknown; - if (typeof(IContentType).IsAssignableFrom(type)) itemType = PublishedItemType.Content; + if (contentType.IsElement) itemType = PublishedItemType.Element; + else if (typeof(IContentType).IsAssignableFrom(type)) itemType = PublishedItemType.Content; else if (typeof(IMediaType).IsAssignableFrom(type)) itemType = PublishedItemType.Media; else if (typeof(IMemberType).IsAssignableFrom(type)) itemType = PublishedItemType.Member; return itemType; diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index a414a03d2f..056602f007 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -18,9 +18,9 @@ namespace Umbraco.Core.Models ContentScheduleCollection ContentSchedule { get; set; } /// - /// Gets or sets the template used to render the content. + /// Gets or sets the template id used to render the content. /// - ITemplate Template { get; set; } + int? TemplateId { get; set; } /// /// Gets a value indicating whether the content is published. @@ -45,10 +45,10 @@ namespace Umbraco.Core.Models bool Blueprint { get; } /// - /// Gets the template used to render the published version of the content. + /// Gets the template id used to render the published version of the content. /// /// When editing the content, the template can change, but this will not until the content is published. - ITemplate PublishTemplate { get; } + int? PublishTemplateId { get; } /// /// Gets the name of the published version of the content. diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index a1d4aee02f..787e347b37 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Models /// the icon (eg. icon-home) along with an optional CSS class name representing the /// color (eg. icon-blue). Put together, the value for this scenario would be /// icon-home color-blue. - /// + /// /// If a class name for the color isn't specified, the icon color will default to black. /// string Icon { get; set; } @@ -48,6 +48,16 @@ namespace Umbraco.Core.Models /// bool IsContainer { get; set; } + /// + /// Gets or sets a value indicating whether this content type is for an element. + /// + /// + /// By default a content type is for a true media, member or document, but + /// it can also be for an element, ie a subset that can for instance be used in + /// nested content. + /// + bool IsElement { get; set; } + /// /// Gets or sets the content variation of the content type. /// diff --git a/src/Umbraco.Core/Models/IRedirectUrl.cs b/src/Umbraco.Core/Models/IRedirectUrl.cs index f3c65fe89c..e066881645 100644 --- a/src/Umbraco.Core/Models/IRedirectUrl.cs +++ b/src/Umbraco.Core/Models/IRedirectUrl.cs @@ -27,11 +27,18 @@ namespace Umbraco.Core.Models [DataMember] DateTime CreateDateUtc { get; set; } + /// + /// Gets or sets the culture. + /// + [DataMember] + string Culture { get; set; } + /// /// Gets or sets the redirect url route. /// /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. [DataMember] string Url { get; set; } + } } diff --git a/src/Umbraco.Core/Models/ITemplate.cs b/src/Umbraco.Core/Models/ITemplate.cs index 97b9324415..6548a49626 100644 --- a/src/Umbraco.Core/Models/ITemplate.cs +++ b/src/Umbraco.Core/Models/ITemplate.cs @@ -1,11 +1,9 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// - /// Defines a Template File (Masterpage or Mvc View) + /// Defines a Template File (Mvc View) /// public interface ITemplate : IFile, IRememberBeingDirty, ICanBeDirty { diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 23c232324a..dcf86a0b42 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Security.Claims; using System.Threading.Tasks; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -66,7 +67,7 @@ namespace Umbraco.Core.Models.Identity _startContentIds = new int[] { }; _groups = new IReadOnlyUserGroup[] { }; _allowedSections = new string[] { }; - _culture = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? + _culture = Current.Configs.Global().DefaultUILanguage; //fixme inject somehow? _groups = new IReadOnlyUserGroup[0]; _roles = new ObservableCollection>(); _roles.CollectionChanged += _roles_CollectionChanged; @@ -83,7 +84,7 @@ namespace Umbraco.Core.Models.Identity _startContentIds = new int[] { }; _groups = new IReadOnlyUserGroup[] { }; _allowedSections = new string[] { }; - _culture = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? + _culture = Current.Configs.Global().DefaultUILanguage; //fixme inject somehow? _groups = groups.ToArray(); _roles = new ObservableCollection>(_groups.Select(x => new IdentityUserRole { @@ -442,6 +443,6 @@ namespace Umbraco.Core.Models.Identity groups => groups.GetHashCode()); } - + } } diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index e190c8ad3b..03f8f87cd3 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -88,7 +88,7 @@ namespace Umbraco.Core.Models try { - var globalSettings = (IGlobalSettings) Composing.Current.Container.GetInstance(typeof(IGlobalSettings)); + var globalSettings = (IGlobalSettings) Composing.Current.Factory.GetInstance(typeof(IGlobalSettings)); var defaultUiCulture = CultureInfo.GetCultureInfo(globalSettings.DefaultUILanguage); Thread.CurrentThread.CurrentUICulture = defaultUiCulture; diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 0694194996..650aa6cb29 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Models.Membership { SessionTimeout = 60; _userGroups = new HashSet(); - _language = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? + _language = Current.Configs.Global().DefaultUILanguage; //fixme inject somehow? _isApproved = true; _isLockedOut = false; _startContentIds = new int[] { }; @@ -453,7 +453,7 @@ namespace Umbraco.Core.Models.Membership base.PerformDeepClone(clone); var clonedEntity = (User)clone; - + //manually clone the start node props clonedEntity._startContentIds = _startContentIds.ToArray(); clonedEntity._startMediaIds = _startMediaIds.ToArray(); @@ -483,7 +483,7 @@ namespace Umbraco.Core.Models.Membership //need to create new collections otherwise they'll get copied by ref clonedEntity._userGroups = new HashSet(_userGroups); clonedEntity._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; - + } /// diff --git a/src/Umbraco.Core/Models/Packaging/ActionRunAt.cs b/src/Umbraco.Core/Models/Packaging/ActionRunAt.cs new file mode 100644 index 0000000000..0023d4dbed --- /dev/null +++ b/src/Umbraco.Core/Models/Packaging/ActionRunAt.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Models.Packaging +{ + public enum ActionRunAt + { + Undefined = 0, + Install, + Uninstall + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs new file mode 100644 index 0000000000..f14fe47bfe --- /dev/null +++ b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace Umbraco.Core.Models.Packaging +{ + /// + /// The model of the package definition within an umbraco (zip) package file + /// + public class CompiledPackage : IPackageInfo + { + public FileInfo PackageFile { get; set; } + + public string Name { get; set; } + public string Version { get; set; } + public string Url { get; set; } + public string License { get; set; } + public string LicenseUrl { get; set; } + public Version UmbracoVersion { get; set; } + public RequirementsType UmbracoVersionRequirementsType { get; set; } + public string Author { get; set; } + public string AuthorUrl { get; set; } + public string Readme { get; set; } + public string PackageView { get; set; } + public string IconUrl { get; set; } + + public string Actions { get; set; } //fixme: Should we make this strongly typed to IEnumerable ? + + public PreInstallWarnings Warnings { get; set; } = new PreInstallWarnings(); + + public List Files { get; set; } = new List(); + + public IEnumerable Macros { get; set; } //fixme: make strongly typed + public IEnumerable Templates { get; set; } //fixme: make strongly typed + public IEnumerable Stylesheets { get; set; } //fixme: make strongly typed + public IEnumerable DataTypes { get; set; } //fixme: make strongly typed + public IEnumerable Languages { get; set; } //fixme: make strongly typed + public IEnumerable DictionaryItems { get; set; } //fixme: make strongly typed + public IEnumerable DocumentTypes { get; set; } //fixme: make strongly typed + public IEnumerable Documents { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackageDocument.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackageDocument.cs new file mode 100644 index 0000000000..c41966dfe1 --- /dev/null +++ b/src/Umbraco.Core/Models/Packaging/CompiledPackageDocument.cs @@ -0,0 +1,26 @@ +using System; +using System.Xml.Linq; + +namespace Umbraco.Core.Models.Packaging +{ + public class CompiledPackageDocument + { + public static CompiledPackageDocument Create(XElement xml) + { + if (xml.Name.LocalName != "DocumentSet") + throw new ArgumentException("The xml isn't formatted correctly, a document element is defined by ", nameof(xml)); + return new CompiledPackageDocument + { + XmlData = xml, + ImportMode = xml.AttributeValue("importMode") + }; + } + + public string ImportMode { get; set; } //this is never used + + /// + /// The serialized version of the content + /// + public XElement XmlData { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackageFile.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackageFile.cs new file mode 100644 index 0000000000..2cb989b42b --- /dev/null +++ b/src/Umbraco.Core/Models/Packaging/CompiledPackageFile.cs @@ -0,0 +1,25 @@ +using System; +using System.Xml.Linq; + +namespace Umbraco.Core.Models.Packaging +{ + public class CompiledPackageFile + { + public static CompiledPackageFile Create(XElement xml) + { + if (xml.Name.LocalName != "file") + throw new ArgumentException("The xml isn't formatted correctly, a file element is defined by ", nameof(xml)); + return new CompiledPackageFile + { + UniqueFileName = xml.Element("guid")?.Value, + OriginalName = xml.Element("orgName")?.Value, + OriginalPath = xml.Element("orgPath")?.Value + }; + } + + public string OriginalPath { get; set; } + public string UniqueFileName { get; set; } + public string OriginalName { get; set; } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Packaging/IPackageInfo.cs b/src/Umbraco.Core/Models/Packaging/IPackageInfo.cs new file mode 100644 index 0000000000..eea56549f1 --- /dev/null +++ b/src/Umbraco.Core/Models/Packaging/IPackageInfo.cs @@ -0,0 +1,24 @@ +using System; + +namespace Umbraco.Core.Models.Packaging +{ + public interface IPackageInfo + { + string Name { get; } + string Version { get; } + string Url { get; } + string License { get; } + string LicenseUrl { get; } + Version UmbracoVersion { get; } + string Author { get; } + string AuthorUrl { get; } + string Readme { get; } + + /// + /// This is the angular view path that will be loaded when the package installs + /// + string PackageView { get; } + + string IconUrl { get; } + } +} diff --git a/src/Umbraco.Core/Models/Packaging/InstallationSummary.cs b/src/Umbraco.Core/Models/Packaging/InstallationSummary.cs index 3eb397d728..1cab17e220 100644 --- a/src/Umbraco.Core/Models/Packaging/InstallationSummary.cs +++ b/src/Umbraco.Core/Models/Packaging/InstallationSummary.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.Serialization; namespace Umbraco.Core.Models.Packaging @@ -8,36 +9,19 @@ namespace Umbraco.Core.Models.Packaging [DataContract(IsReference = true)] public class InstallationSummary { - public MetaData MetaData { get; set; } - public IEnumerable DataTypesInstalled { get; set; } - public IEnumerable LanguagesInstalled { get; set; } - public IEnumerable DictionaryItemsInstalled { get; set; } - public IEnumerable MacrosInstalled { get; set; } - public IEnumerable FilesInstalled { get; set; } - public IEnumerable TemplatesInstalled { get; set; } - public IEnumerable ContentTypesInstalled { get; set; } - public IEnumerable StylesheetsInstalled { get; set; } - public IEnumerable ContentInstalled { get; set; } - public IEnumerable Actions { get; set; } - public bool PackageInstalled { get; set; } + public IPackageInfo MetaData { get; set; } + public IEnumerable DataTypesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable LanguagesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable DictionaryItemsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable MacrosInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable FilesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable TemplatesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable DocumentTypesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable StylesheetsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable ContentInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable Actions { get; set; } = Enumerable.Empty(); + public IEnumerable ActionErrors { get; set; } = Enumerable.Empty(); + } - internal static class InstallationSummaryExtentions - { - public static InstallationSummary InitEmpty(this InstallationSummary summary) - { - summary.Actions = new List(); - summary.ContentInstalled = new List(); - summary.ContentTypesInstalled = new List(); - summary.DataTypesInstalled = new List(); - summary.DictionaryItemsInstalled = new List(); - summary.FilesInstalled = new List(); - summary.LanguagesInstalled = new List(); - summary.MacrosInstalled = new List(); - summary.MetaData = new MetaData(); - summary.TemplatesInstalled = new List(); - summary.PackageInstalled = false; - return summary; - } - } } diff --git a/src/Umbraco.Core/Models/Packaging/MetaData.cs b/src/Umbraco.Core/Models/Packaging/MetaData.cs deleted file mode 100644 index a1cb450c5b..0000000000 --- a/src/Umbraco.Core/Models/Packaging/MetaData.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models.Packaging -{ - [Serializable] - [DataContract(IsReference = true)] - public class MetaData - { - public string Name { get; set; } - public string Version { get; set; } - public string Url { get; set; } - public string License { get; set; } - public string LicenseUrl { get; set; } - public int ReqMajor { get; set; } - public int ReqMinor { get; set; } - public int ReqPatch { get; set; } - public string AuthorName { get; set; } - public string AuthorUrl { get; set; } - public string Readme { get; set; } - public string Control { get; set; } - } -} diff --git a/src/Umbraco.Core/Models/Packaging/PackageAction.cs b/src/Umbraco.Core/Models/Packaging/PackageAction.cs index e941c5729a..ab7b120eae 100644 --- a/src/Umbraco.Core/Models/Packaging/PackageAction.cs +++ b/src/Umbraco.Core/Models/Packaging/PackageAction.cs @@ -4,13 +4,9 @@ using System.Xml.Linq; namespace Umbraco.Core.Models.Packaging { - public enum ActionRunAt - { - Undefined = 0, - Install, - Uninstall - } - + /// + /// Defines a package action declared within a package manifest + /// [Serializable] [DataContract(IsReference = true)] public class PackageAction diff --git a/src/Umbraco.Core/Models/Packaging/PackageDefinition.cs b/src/Umbraco.Core/Models/Packaging/PackageDefinition.cs new file mode 100644 index 0000000000..8c0ef79d0b --- /dev/null +++ b/src/Umbraco.Core/Models/Packaging/PackageDefinition.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.Packaging +{ + [DataContract(Name = "packageInstance")] + public class PackageDefinition : IPackageInfo + { + /// + /// Converts a model to a model + /// + /// + /// + /// + /// This is used only for conversions and will not 'get' a PackageDefinition from the repository with a valid ID + /// + internal static PackageDefinition FromCompiledPackage(CompiledPackage compiled) + { + return new PackageDefinition + { + Actions = compiled.Actions, + Author = compiled.Author, + AuthorUrl = compiled.AuthorUrl, + PackageView = compiled.PackageView, + IconUrl = compiled.IconUrl, + License = compiled.License, + LicenseUrl = compiled.LicenseUrl, + Name = compiled.Name, + Readme = compiled.Readme, + UmbracoVersion = compiled.UmbracoVersion, + Url = compiled.Url, + Version = compiled.Version, + Files = compiled.Files.Select(x => x.OriginalPath).ToList() + }; + } + + [DataMember(Name = "id")] + public int Id { get; set; } + + [DataMember(Name = "packageGuid")] + public Guid PackageId { get; set; } + + [DataMember(Name = "name")] + [Required] + public string Name { get; set; } = string.Empty; + + [DataMember(Name = "url")] + [Required] + [Url] + public string Url { get; set; } = string.Empty; + + /// + /// The full path to the package's zip file when it was installed (or is being installed) + /// + [ReadOnly(true)] + [DataMember(Name = "packagePath")] + public string PackagePath { get; set; } = string.Empty; + + [DataMember(Name = "version")] + [Required] + public string Version { get; set; } = "1.0.0"; + + /// + /// The minimum umbraco version that this package requires + /// + [DataMember(Name = "umbracoVersion")] + public Version UmbracoVersion { get; set; } = Configuration.UmbracoVersion.Current; + + [DataMember(Name = "author")] + [Required] + public string Author { get; set; } = string.Empty; + + [DataMember(Name = "authorUrl")] + [Required] + [Url] + public string AuthorUrl { get; set; } = string.Empty; + + [DataMember(Name = "license")] + public string License { get; set; } = "MIT License"; + + [DataMember(Name = "licenseUrl")] + public string LicenseUrl { get; set; } = "http://opensource.org/licenses/MIT"; + + [DataMember(Name = "readme")] + public string Readme { get; set; } = string.Empty; + + [DataMember(Name = "contentLoadChildNodes")] + public bool ContentLoadChildNodes { get; set; } + + [DataMember(Name = "contentNodeId")] + public string ContentNodeId { get; set; } = string.Empty; + + [DataMember(Name = "macros")] + public IList Macros { get; set; } = new List(); + + [DataMember(Name = "languages")] + public IList Languages { get; set; } = new List(); + + [DataMember(Name = "dictionaryItems")] + public IList DictionaryItems { get; set; } = new List(); + + [DataMember(Name = "templates")] + public IList Templates { get; set; } = new List(); + + [DataMember(Name = "documentTypes")] + public IList DocumentTypes { get; set; } = new List(); + + [DataMember(Name = "stylesheets")] + public IList Stylesheets { get; set; } = new List(); + + [DataMember(Name = "files")] + public IList Files { get; set; } = new List(); + + /// + [DataMember(Name = "packageView")] + public string PackageView { get; set; } = string.Empty; + + [DataMember(Name = "actions")] + public string Actions { get; set; } = ""; + + [DataMember(Name = "dataTypes")] + public IList DataTypes { get; set; } = new List(); + + [DataMember(Name = "iconUrl")] + public string IconUrl { get; set; } = string.Empty; + + + } + +} diff --git a/src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs b/src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs index 5850e2321c..f0acb2a46b 100644 --- a/src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs +++ b/src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs @@ -1,17 +1,18 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.Serialization; namespace Umbraco.Core.Models.Packaging { - [Serializable] - [DataContract(IsReference = true)] - internal class PreInstallWarnings + public class PreInstallWarnings { - public KeyValuePair[] UnsecureFiles { get; set; } - public KeyValuePair[] FilesReplaced { get; set; } - public IEnumerable ConflictingMacroAliases { get; set; } - public IEnumerable ConflictingTemplateAliases { get; set; } - public IEnumerable ConflictingStylesheetNames { get; set; } + public IEnumerable UnsecureFiles { get; set; } = Enumerable.Empty(); + public IEnumerable FilesReplaced { get; set; } = Enumerable.Empty(); + + //TODO: Shouldn't we detect other conflicting entities too ? + public IEnumerable ConflictingMacros { get; set; } = Enumerable.Empty(); + public IEnumerable ConflictingTemplates { get; set; } = Enumerable.Empty(); + public IEnumerable ConflictingStylesheets { get; set; } = Enumerable.Empty(); } } diff --git a/src/Umbraco.Web/_Legacy/Packager/RequirementsType.cs b/src/Umbraco.Core/Models/Packaging/RequirementsType.cs similarity index 62% rename from src/Umbraco.Web/_Legacy/Packager/RequirementsType.cs rename to src/Umbraco.Core/Models/Packaging/RequirementsType.cs index ea63ff6979..2b6894ed0f 100644 --- a/src/Umbraco.Web/_Legacy/Packager/RequirementsType.cs +++ b/src/Umbraco.Core/Models/Packaging/RequirementsType.cs @@ -1,4 +1,4 @@ -namespace umbraco.cms.businesslogic.packager +namespace Umbraco.Core.Models.Packaging { public enum RequirementsType { diff --git a/src/Umbraco.Core/Models/Packaging/UninstallationSummary.cs b/src/Umbraco.Core/Models/Packaging/UninstallationSummary.cs new file mode 100644 index 0000000000..fd39954f6b --- /dev/null +++ b/src/Umbraco.Core/Models/Packaging/UninstallationSummary.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.Packaging +{ + [Serializable] + [DataContract(IsReference = true)] + public class UninstallationSummary + { + public IPackageInfo MetaData { get; set; } + public IEnumerable DataTypesUninstalled { get; set; } = Enumerable.Empty(); + public IEnumerable LanguagesUninstalled { get; set; } = Enumerable.Empty(); + public IEnumerable DictionaryItemsUninstalled { get; set; } = Enumerable.Empty(); + public IEnumerable MacrosUninstalled { get; set; } = Enumerable.Empty(); + public IEnumerable FilesUninstalled { get; set; } = Enumerable.Empty(); + public IEnumerable TemplatesUninstalled { get; set; } = Enumerable.Empty(); + public IEnumerable DocumentTypesUninstalled { get; set; } = Enumerable.Empty(); + public IEnumerable StylesheetsUninstalled { get; set; } = Enumerable.Empty(); + public IEnumerable Actions { get; set; } = Enumerable.Empty(); + public IEnumerable ActionErrors { get; set; } = Enumerable.Empty(); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 0c049e81bf..4e1ce7ddd7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -62,7 +62,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the identifier of the template to use to render the content item. /// - int TemplateId { get; } + int? TemplateId { get; } /// /// Gets the identifier of the user who created the content item. diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs index df3213eb07..bfc65b70d6 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Models.PublishedContent // in order to provide a nice, "fluent" experience, this extension method // needs to access Current, which is not always initialized in tests - not // very elegant, but works - if (!Current.HasContainer) return content; + if (!Current.HasFactory) return content; // get model // if factory returns nothing, throw diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 6a69d0b9e1..36755c8944 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -73,7 +73,7 @@ namespace Umbraco.Core.Models.PublishedContent public virtual string Path => _content.Path; /// - public virtual int TemplateId => _content.TemplateId; + public virtual int? TemplateId => _content.TemplateId; /// public virtual int CreatorId => _content.CreatorId; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs index e55fe66945..42e9c9538d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs @@ -4,13 +4,18 @@ /// The type of published element. /// /// Can be a simple element, or a document, a media, a member. - public enum PublishedItemType // fixme - need to rename to PublishedElementType but then conflicts? + public enum PublishedItemType { /// /// Unknown. /// Unknown = 0, + /// + /// An element. + /// + Element, + /// /// A document. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs index 67758c1c69..8be56850d1 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs @@ -68,7 +68,7 @@ namespace Umbraco.Core.Models.PublishedContent var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; if (modelInfos.TryGetValue(typeName, out var modelInfo)) - throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); + throw new InvalidOperationException($"Both types '{type.AssemblyQualifiedName}' and '{modelInfo.ModelType.AssemblyQualifiedName}' want to be a model type for content type with alias \"{typeName}\"."); // have to use an unsafe ctor because we don't know the types, really var modelCtor = ReflectionUtilities.EmitConstructorUnsafe>(constructor); @@ -112,7 +112,7 @@ namespace Umbraco.Core.Models.PublishedContent if (ctor != null) return ctor(); var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); - ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstuctor>(declaring: listType); + ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstructor>(declaring: listType); return ctor(); } diff --git a/src/Umbraco.Core/Models/RedirectUrl.cs b/src/Umbraco.Core/Models/RedirectUrl.cs index 187d9fdd6e..55b799244e 100644 --- a/src/Umbraco.Core/Models/RedirectUrl.cs +++ b/src/Umbraco.Core/Models/RedirectUrl.cs @@ -28,12 +28,14 @@ namespace Umbraco.Core.Models public readonly PropertyInfo ContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentId); public readonly PropertyInfo ContentKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ContentKey); public readonly PropertyInfo CreateDateUtcSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDateUtc); + public readonly PropertyInfo CultureSelector = ExpressionHelper.GetPropertyInfo(x => x.Culture); public readonly PropertyInfo UrlSelector = ExpressionHelper.GetPropertyInfo(x => x.Url); } private int _contentId; private Guid _contentKey; private DateTime _createDateUtc; + private string _culture; private string _url; /// @@ -57,6 +59,13 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _createDateUtc, Ps.Value.CreateDateUtcSelector); } } + /// + public string Culture + { + get { return _culture; } + set { SetPropertyValueAndDetectChanges(value, ref _culture, Ps.Value.CultureSelector); } + } + /// public string Url { diff --git a/src/Umbraco.Core/Models/Section.cs b/src/Umbraco.Core/Models/Section.cs deleted file mode 100644 index 4b7f8309dd..0000000000 --- a/src/Umbraco.Core/Models/Section.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Umbraco.Core.Models -{ - /// - /// Represents a section defined in the app.config file. - /// - public class Section - { - public Section(string name, string @alias, int sortOrder) - { - Name = name; - Alias = alias; - SortOrder = sortOrder; - } - - public Section() - { } - - public string Name { get; set; } - public string Alias { get; set; } - public int SortOrder { get; set; } - } -} diff --git a/src/Umbraco.Core/Models/Trees/IBackOfficeSection.cs b/src/Umbraco.Core/Models/Trees/IBackOfficeSection.cs new file mode 100644 index 0000000000..86e2a18fd5 --- /dev/null +++ b/src/Umbraco.Core/Models/Trees/IBackOfficeSection.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Models.Trees +{ + /// + /// Defines a back office section. + /// + public interface IBackOfficeSection + { + /// + /// Gets the alias of the section. + /// + string Alias { get; } + + /// + /// Gets the name of the section. + /// + string Name { get; } + } +} diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 6733eea500..19726cc5a5 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -50,11 +50,11 @@ namespace Umbraco.Core.Models /// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL /// /// - /// + /// /// /// A list of 5 different sized avatar URLs /// - internal static string[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache) + internal static string[] GetUserAvatarUrls(this IUser user, IAppCache cache) { // If FIPS is required, never check the Gravatar service as it only supports MD5 hashing. // Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception @@ -71,7 +71,7 @@ namespace Umbraco.Core.Models var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash + "?d=404"; //try gravatar - var gravatarAccess = staticCache.GetCacheItem("UserAvatar" + user.Id, () => + var gravatarAccess = cache.GetCacheItem("UserAvatar" + user.Id, () => { // Test if we can reach this URL, will fail when there's network or firewall errors var request = (HttpWebRequest)WebRequest.Create(gravatarUrl); @@ -105,7 +105,7 @@ namespace Umbraco.Core.Models } //use the custom avatar - var avatarUrl = Current.FileSystems.MediaFileSystem.GetUrl(user.Avatar); + var avatarUrl = Current.MediaFileSystem.GetUrl(user.Avatar); return new[] { avatarUrl + "?width=30&height=30&mode=crop", diff --git a/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs new file mode 100644 index 0000000000..9e6339178e --- /dev/null +++ b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Packaging; +using File = System.IO.File; + +namespace Umbraco.Core.Packaging +{ + /// + /// Parses the xml document contained in a compiled (zip) Umbraco package + /// + internal class CompiledPackageXmlParser + { + private readonly ConflictingPackageData _conflictingPackageData; + + public CompiledPackageXmlParser(ConflictingPackageData conflictingPackageData) + { + _conflictingPackageData = conflictingPackageData; + } + + public CompiledPackage ToCompiledPackage(XDocument xml, FileInfo packageFile, string applicationRootFolder) + { + if (xml == null) throw new ArgumentNullException(nameof(xml)); + if (xml.Root == null) throw new ArgumentException(nameof(xml), "The xml document is invalid"); + if (xml.Root.Name != "umbPackage") throw new FormatException("The xml document is invalid"); + + var info = xml.Root.Element("info"); + if (info == null) throw new FormatException("The xml document is invalid"); + var package = info.Element("package"); + if (package == null) throw new FormatException("The xml document is invalid"); + var author = info.Element("author"); + if (author == null) throw new FormatException("The xml document is invalid"); + var requirements = package.Element("requirements"); + if (requirements == null) throw new FormatException("The xml document is invalid"); + + var def = new CompiledPackage + { + PackageFile = packageFile, + Name = package.Element("name")?.Value, + Author = author.Element("name")?.Value, + AuthorUrl = author.Element("website")?.Value, + Version = package.Element("version")?.Value, + Readme = info.Element("readme")?.Value, + License = package.Element("license")?.Value, + LicenseUrl = package.Element("license")?.AttributeValue("url"), + Url = package.Element("url")?.Value, + IconUrl = package.Element("iconUrl")?.Value, + UmbracoVersion = new Version((int)requirements.Element("major"), (int)requirements.Element("minor"), (int)requirements.Element("patch")), + UmbracoVersionRequirementsType = requirements.AttributeValue("type").IsNullOrWhiteSpace() ? RequirementsType.Legacy : Enum.Parse(requirements.AttributeValue("type"), true), + PackageView = xml.Root.Element("view")?.Value, + Actions = xml.Root.Element("Actions")?.ToString(SaveOptions.None) ?? "", //take the entire outer xml value + Files = xml.Root.Element("files")?.Elements("file")?.Select(CompiledPackageFile.Create).ToList() ?? new List(), + Macros = xml.Root.Element("Macros")?.Elements("macro") ?? Enumerable.Empty(), + Templates = xml.Root.Element("Templates")?.Elements("Template") ?? Enumerable.Empty(), + Stylesheets = xml.Root.Element("Stylesheets")?.Elements("styleSheet") ?? Enumerable.Empty(), + DataTypes = xml.Root.Element("DataTypes")?.Elements("DataType") ?? Enumerable.Empty(), + Languages = xml.Root.Element("Languages")?.Elements("Language") ?? Enumerable.Empty(), + DictionaryItems = xml.Root.Element("DictionaryItems")?.Elements("DictionaryItem") ?? Enumerable.Empty(), + DocumentTypes = xml.Root.Element("DocumentTypes")?.Elements("DocumentType") ?? Enumerable.Empty(), + Documents = xml.Root.Element("Documents")?.Elements("DocumentSet")?.Select(CompiledPackageDocument.Create) ?? Enumerable.Empty(), + }; + + def.Warnings = GetPreInstallWarnings(def, applicationRootFolder); + + return def; + } + + private PreInstallWarnings GetPreInstallWarnings(CompiledPackage package, string applicationRootFolder) + { + var sourceDestination = ExtractSourceDestinationFileInformation(package.Files); + + var installWarnings = new PreInstallWarnings + { + ConflictingMacros = _conflictingPackageData.FindConflictingMacros(package.Macros), + ConflictingTemplates = _conflictingPackageData.FindConflictingTemplates(package.Templates), + ConflictingStylesheets = _conflictingPackageData.FindConflictingStylesheets(package.Stylesheets), + UnsecureFiles = FindUnsecureFiles(sourceDestination), + FilesReplaced = FindFilesToBeReplaced(sourceDestination, applicationRootFolder) + }; + + return installWarnings; + } + + /// + /// Returns a tuple of the zip file's unique file name and it's application relative path + /// + /// + /// + public (string packageUniqueFile, string appRelativePath)[] ExtractSourceDestinationFileInformation(IEnumerable packageFiles) + { + return packageFiles + .Select(e => + { + var fileName = PrepareAsFilePathElement(e.OriginalName); + var relativeDir = UpdatePathPlaceholders(PrepareAsFilePathElement(e.OriginalPath)); + var relativePath = Path.Combine(relativeDir, fileName); + return (e.UniqueFileName, relativePath); + }).ToArray(); + } + + private IEnumerable FindFilesToBeReplaced(IEnumerable<(string packageUniqueFile, string appRelativePath)> sourceDestination, string applicationRootFolder) + { + return sourceDestination.Where(sd => File.Exists(Path.Combine(applicationRootFolder, sd.appRelativePath))) + .Select(x => x.appRelativePath) + .ToArray(); + } + + private IEnumerable FindUnsecureFiles(IEnumerable<(string packageUniqueFile, string appRelativePath)> sourceDestinationPair) + { + return sourceDestinationPair.Where(sd => IsFileDestinationUnsecure(sd.appRelativePath)) + .Select(x => x.appRelativePath) + .ToList(); + } + + private bool IsFileDestinationUnsecure(string destination) + { + var unsecureDirNames = new[] { "bin", "app_code" }; + if (unsecureDirNames.Any(ud => destination.StartsWith(ud, StringComparison.InvariantCultureIgnoreCase))) + return true; + + string extension = Path.GetExtension(destination); + return extension != null && extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); + } + + private static string PrepareAsFilePathElement(string pathElement) + { + return pathElement.TrimStart(new[] { '\\', '/', '~' }).Replace("/", "\\"); + } + + private static string UpdatePathPlaceholders(string path) + { + if (path.Contains("[$")) + { + //this is experimental and undocumented... + path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco); + path = path.Replace("[$CONFIG]", SystemDirectories.Config); + path = path.Replace("[$DATA]", SystemDirectories.Data); + } + return path; + } + + /// + /// Parses the package actions stored in the package definition + /// + /// + /// + /// + public static IEnumerable GetPackageActions(XElement actionsElement, string packageName) + { + if (actionsElement == null) return Enumerable.Empty(); + + //invariant check ... because people can realy enter anything :/ + if (!string.Equals("actions", actionsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase)) + throw new FormatException("Must be \"\" as root"); + + if (!actionsElement.HasElements) return Enumerable.Empty(); + + var actionElementName = actionsElement.Elements().First().Name.LocalName; + + //invariant check ... because people can realy enter anything :/ + if (!string.Equals("action", actionElementName, StringComparison.InvariantCultureIgnoreCase)) + throw new FormatException("Must be \" + { + var aliasAttr = e.Attribute("alias") ?? e.Attribute("Alias"); //allow both ... because people can really enter anything :/ + if (aliasAttr == null) + throw new ArgumentException("missing \"alias\" atribute in alias element", nameof(actionsElement)); + + var packageAction = new PackageAction + { + XmlData = e, + Alias = aliasAttr.Value, + PackageName = packageName, + }; + + var attr = e.Attribute("runat") ?? e.Attribute("Runat"); //allow both ... because people can really enter anything :/ + + if (attr != null && Enum.TryParse(attr.Value, true, out ActionRunAt runAt)) { packageAction.RunAt = runAt; } + + attr = e.Attribute("undo") ?? e.Attribute("Undo"); //allow both ... because people can really enter anything :/ + + if (attr != null && bool.TryParse(attr.Value, out var undo)) { packageAction.Undo = undo; } + + return packageAction; + }).ToArray(); + } + } +} diff --git a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs index b0424067bf..82693677fb 100644 --- a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs +++ b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs @@ -7,82 +7,53 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Packaging { - internal class ConflictingPackageData : IConflictingPackageData + internal class ConflictingPackageData { private readonly IMacroService _macroService; private readonly IFileService _fileService; - public ConflictingPackageData(IMacroService macroService, - IFileService fileService) + public ConflictingPackageData(IMacroService macroService, IFileService fileService) { - if (fileService != null) _fileService = fileService; - else throw new ArgumentNullException("fileService"); - if (macroService != null) _macroService = macroService; - else throw new ArgumentNullException("macroService"); + _fileService = fileService ?? throw new ArgumentNullException(nameof(fileService)); + _macroService = macroService ?? throw new ArgumentNullException(nameof(macroService)); } - public IEnumerable FindConflictingStylesheets(XElement stylesheetNotes) + public IEnumerable FindConflictingStylesheets(IEnumerable stylesheetNodes) { - if (string.Equals(Constants.Packaging.StylesheetsNodeName, stylesheetNotes.Name.LocalName) == false) - { - throw new ArgumentException("the root element must be \"" + Constants.Packaging.StylesheetsNodeName + "\"", "stylesheetNotes"); - } - - return stylesheetNotes.Elements(Constants.Packaging.StylesheetNodeName) + return stylesheetNodes .Select(n => { - XElement xElement = n.Element(Constants.Packaging.NameNodeName); + var xElement = n.Element("Name") ?? n.Element("name"); ; if (xElement == null) - { - throw new ArgumentException("Missing \"" + Constants.Packaging.NameNodeName + "\" element", - "stylesheetNotes"); - } + throw new FormatException("Missing \"Name\" element"); return _fileService.GetStylesheetByName(xElement.Value) as IFile; }) .Where(v => v != null); } - public IEnumerable FindConflictingTemplates(XElement templateNotes) + public IEnumerable FindConflictingTemplates(IEnumerable templateNodes) { - if (string.Equals(Constants.Packaging.TemplatesNodeName, templateNotes.Name.LocalName) == false) - { - throw new ArgumentException("Node must be a \"" + Constants.Packaging.TemplatesNodeName + "\" node", - "templateNotes"); - } - - return templateNotes.Elements(Constants.Packaging.TemplateNodeName) + return templateNodes .Select(n => { - XElement xElement = n.Element(Constants.Packaging.AliasNodeNameCapital) ?? n.Element(Constants.Packaging.AliasNodeNameSmall); + var xElement = n.Element("Alias") ?? n.Element("alias"); if (xElement == null) - { - throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeNameCapital + "\" element", - "templateNotes"); - } + throw new FormatException("missing a \"Alias\" element"); return _fileService.GetTemplate(xElement.Value); }) .Where(v => v != null); } - public IEnumerable FindConflictingMacros(XElement macroNodes) + public IEnumerable FindConflictingMacros(IEnumerable macroNodes) { - if (string.Equals(Constants.Packaging.MacrosNodeName, macroNodes.Name.LocalName) == false) - { - throw new ArgumentException("Node must be a \"" + Constants.Packaging.MacrosNodeName + "\" node", - "macroNodes"); - } - - return macroNodes.Elements(Constants.Packaging.MacroNodeName) + return macroNodes .Select(n => { - XElement xElement = n.Element(Constants.Packaging.AliasNodeNameSmall) ?? n.Element(Constants.Packaging.AliasNodeNameCapital); + var xElement = n.Element("alias") ?? n.Element("Alias"); if (xElement == null) - { - throw new ArgumentException(string.Format("missing a \"{0}\" element in {0} element", Constants.Packaging.AliasNodeNameSmall), - "macroNodes"); - } + throw new FormatException("missing a \"alias\" element in alias element"); return _macroService.GetByAlias(xElement.Value); }) diff --git a/src/Umbraco.Core/Packaging/IConflictingPackageData.cs b/src/Umbraco.Core/Packaging/IConflictingPackageData.cs deleted file mode 100644 index 12f3e8f8a4..0000000000 --- a/src/Umbraco.Core/Packaging/IConflictingPackageData.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using System.Xml.Linq; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Packaging -{ - internal interface IConflictingPackageData - { - IEnumerable FindConflictingStylesheets(XElement stylesheetNotes); - IEnumerable FindConflictingTemplates(XElement templateNotes); - IEnumerable FindConflictingMacros(XElement macroNodes); - } -} diff --git a/src/Umbraco.Core/Packaging/ICreatedPackagesRepository.cs b/src/Umbraco.Core/Packaging/ICreatedPackagesRepository.cs new file mode 100644 index 0000000000..42606da6ef --- /dev/null +++ b/src/Umbraco.Core/Packaging/ICreatedPackagesRepository.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Models.Packaging; + +namespace Umbraco.Core.Packaging +{ + /// + /// Manages the storage of created package definitions + /// + public interface ICreatedPackagesRepository : IPackageDefinitionRepository + { + /// + /// Creates the package file and returns it's physical path + /// + /// + string ExportPackage(PackageDefinition definition); + } +} diff --git a/src/Umbraco.Core/Packaging/IInstalledPackagesRepository.cs b/src/Umbraco.Core/Packaging/IInstalledPackagesRepository.cs new file mode 100644 index 0000000000..61f611edc5 --- /dev/null +++ b/src/Umbraco.Core/Packaging/IInstalledPackagesRepository.cs @@ -0,0 +1,11 @@ +using System; + +namespace Umbraco.Core.Packaging +{ + /// + /// Manages the storage of installed package definitions + /// + public interface IInstalledPackagesRepository : IPackageDefinitionRepository + { + } +} diff --git a/src/Umbraco.Core/Packaging/IPackageActionRunner.cs b/src/Umbraco.Core/Packaging/IPackageActionRunner.cs new file mode 100644 index 0000000000..1f8c134364 --- /dev/null +++ b/src/Umbraco.Core/Packaging/IPackageActionRunner.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Umbraco.Core.Packaging +{ + public interface IPackageActionRunner + { + /// + /// Runs the package action with the specified action alias. + /// + /// Name of the package. + /// The action alias. + /// The action XML. + /// + bool RunPackageAction(string packageName, string actionAlias, XElement actionXml, out IEnumerable errors); + + /// + /// Undos the package action with the specified action alias. + /// + /// Name of the package. + /// The action alias. + /// The action XML. + /// + bool UndoPackageAction(string packageName, string actionAlias, XElement actionXml, out IEnumerable errors); + } +} diff --git a/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs b/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs new file mode 100644 index 0000000000..343a0a05ea --- /dev/null +++ b/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Packaging; + +namespace Umbraco.Core.Packaging +{ + /// + /// Defines methods for persisting package definitions to storage + /// + public interface IPackageDefinitionRepository + { + IEnumerable GetAll(); + PackageDefinition GetById(int id); + void Delete(int id); + + /// + /// Persists a package definition to storage + /// + /// + /// true if creating/updating the package was successful, otherwise false + /// + bool SavePackage(PackageDefinition definition); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/IPackageExtraction.cs b/src/Umbraco.Core/Packaging/IPackageExtraction.cs deleted file mode 100644 index 21a5198f55..0000000000 --- a/src/Umbraco.Core/Packaging/IPackageExtraction.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Packaging -{ - /// - /// Used to access an umbraco package file - /// Remeber that filenames must be unique - /// use "FindDubletFileNames" for sanitycheck - /// - internal interface IPackageExtraction - { - /// - /// Returns the content of the file with the given filename - /// - /// Full path to the umbraco package file - /// filename of the file for wich to get the text content - /// this is the relative directory for the location of the file in the package - /// I dont know why umbraco packages contains directories in the first place?? - /// text content of the file - string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage); - - /// - /// Copies a file from package to given destination - /// - /// Full path to the ubraco package file - /// filename of the file to copy - /// destination path (including destination filename) - /// True a file was overwritten - void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath); - - /// - /// Copies a file from package to given destination - /// - /// Full path to the ubraco package file - /// Key: Source file in package. Value: Destination path inclusive file name - void CopyFilesFromArchive(string packageFilePath, IEnumerable> sourceDestination); - - /// - /// Check if given list of files can be found in the package - /// - /// Full path to the umbraco package file - /// a list of files you would like to find in the package - /// a subset if any of the files in "expectedFiles" that could not be found in the package - IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles); - - - /// - /// Sanitycheck - should return en empty collection if package is valid - /// - /// Full path to the umbraco package file - /// list of files that are found more than ones (accross directories) in the package - IEnumerable FindDubletFileNames(string packageFilePath); - - /// - /// Reads the given files from archive and returns them as a collection of byte arrays - /// - /// - /// - /// - IEnumerable ReadFilesFromArchive(string packageFilePath, IEnumerable filesToGet); - } -} diff --git a/src/Umbraco.Core/Packaging/IPackageInstallation.cs b/src/Umbraco.Core/Packaging/IPackageInstallation.cs index 1d0d46355c..c85a4ccd5f 100644 --- a/src/Umbraco.Core/Packaging/IPackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/IPackageInstallation.cs @@ -1,13 +1,43 @@ -using System.Xml.Linq; +using System.Collections.Generic; +using System.IO; +using System.Xml.Linq; using Umbraco.Core.Models.Packaging; namespace Umbraco.Core.Packaging { - internal interface IPackageInstallation + public interface IPackageInstallation { - InstallationSummary InstallPackage(string packageFilePath, int userId); - MetaData GetMetaData(string packageFilePath); - PreInstallWarnings GetPreInstallWarnings(string packageFilePath); - XElement GetConfigXmlElement(string packageFilePath); + /// + /// This will run the uninstallation sequence for this + /// + /// + /// + /// + UninstallationSummary UninstallPackage(PackageDefinition packageDefinition, int userId); + + /// + /// Installs a packages data and entities + /// + /// + /// + /// + /// + InstallationSummary InstallPackageData(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId); + + /// + /// Installs a packages files + /// + /// + /// + /// + /// + IEnumerable InstallPackageFiles(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId); + + /// + /// Reads the package (zip) file and returns the model + /// + /// + /// + CompiledPackage ReadPackage(FileInfo packageFile); } } diff --git a/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs b/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs deleted file mode 100644 index 1c31283ee8..0000000000 --- a/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Packaging; - -namespace Umbraco.Core.Packaging.Models -{ - [Serializable] - [DataContract(IsReference = true)] - public class UninstallationSummary - { - public MetaData MetaData { get; set; } - public IEnumerable DataTypesUninstalled { get; set; } - public IEnumerable LanguagesUninstalled { get; set; } - public IEnumerable DictionaryItemsUninstalled { get; set; } - public IEnumerable MacrosUninstalled { get; set; } - public IEnumerable FilesUninstalled { get; set; } - public IEnumerable TemplatesUninstalled { get; set; } - public IEnumerable ContentTypesUninstalled { get; set; } - public IEnumerable StylesheetsUninstalled { get; set; } - public IEnumerable ContentUninstalled { get; set; } - public bool PackageUninstalled { get; set; } - } - - internal static class UninstallationSummaryExtentions - { - public static UninstallationSummary InitEmpty(this UninstallationSummary summary) - { - summary.ContentUninstalled = new List(); - summary.ContentTypesUninstalled = new List(); - summary.DataTypesUninstalled = new List(); - summary.DictionaryItemsUninstalled = new List(); - summary.FilesUninstalled = new List(); - summary.LanguagesUninstalled = new List(); - summary.MacrosUninstalled = new List(); - summary.MetaData = new MetaData(); - summary.TemplatesUninstalled = new List(); - summary.PackageUninstalled = false; - return summary; - } - } -} diff --git a/src/Umbraco.Core/Packaging/PackageActionRunner.cs b/src/Umbraco.Core/Packaging/PackageActionRunner.cs new file mode 100644 index 0000000000..38275d5f0a --- /dev/null +++ b/src/Umbraco.Core/Packaging/PackageActionRunner.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core._Legacy.PackageActions; + +namespace Umbraco.Core.Packaging +{ + /// + /// Package actions are executed on packge install / uninstall. + /// + internal class PackageActionRunner : IPackageActionRunner + { + private readonly ILogger _logger; + private readonly PackageActionCollection _packageActions; + + public PackageActionRunner(ILogger logger, PackageActionCollection packageActions) + { + _logger = logger; + _packageActions = packageActions; + } + + /// + public bool RunPackageAction(string packageName, string actionAlias, XElement actionXml, out IEnumerable errors) + { + var e = new List(); + foreach (var ipa in _packageActions) + { + try + { + if (ipa.Alias() == actionAlias) + ipa.Execute(packageName, actionXml); + } + catch (Exception ex) + { + e.Add($"{ipa.Alias()} - {ex.Message}"); + _logger.Error(ex, "Error loading package action '{PackageActionAlias}' for package {PackageName}", ipa.Alias(), packageName); + } + } + + errors = e; + return e.Count == 0; + } + + /// + public bool UndoPackageAction(string packageName, string actionAlias, XElement actionXml, out IEnumerable errors) + { + var e = new List(); + foreach (var ipa in _packageActions) + { + try + { + if (ipa.Alias() == actionAlias) + ipa.Undo(packageName, actionXml); + } + catch (Exception ex) + { + e.Add($"{ipa.Alias()} - {ex.Message}"); + _logger.Error(ex, "Error undoing package action '{PackageActionAlias}' for package {PackageName}", ipa.Alias(), packageName); + } + } + errors = e; + return e.Count == 0; + } + + } +} diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs deleted file mode 100644 index 7cacb30bd3..0000000000 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ /dev/null @@ -1,295 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security; -using System.Security.Permissions; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Packaging -{ - // Note - // That class uses ReflectionOnlyLoad which does NOT handle policies (bindingRedirect) and - // therefore raised warnings when installing a package, if an exact dependency could not be - // found, though it would be found via policies. So we have to explicitely apply policies - // where appropriate. - - internal class PackageBinaryInspector : MarshalByRefObject - { - /// - /// Entry point to call from your code - /// - /// - /// - /// - /// - /// - /// Will perform the assembly scan in a separate app domain - /// - public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport) - { - // need to wrap in a safe call context in order to prevent whatever Umbraco stores - // in the logical call context from flowing to the created app domain (eg, the - // UmbracoDatabase is *not* serializable and cannot and should not flow). - using (new SafeCallContext()) - { - var appDomain = GetTempAppDomain(); - var type = typeof(PackageBinaryInspector); - try - { - var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( - type.Assembly.FullName, - type.FullName); - // do NOT turn PerformScan into static (even if ReSharper says so)! - var result = value.PerformScan(assemblys.ToArray(), out errorReport); - return result; - } - finally - { - AppDomain.Unload(appDomain); - } - } - } - - /// - /// Entry point to call from your code - /// - /// - /// - /// - /// - /// - /// Will perform the assembly scan in a separate app domain - /// - public static IEnumerable ScanAssembliesForTypeReference(string dllPath, out string[] errorReport) - { - var appDomain = GetTempAppDomain(); - var type = typeof(PackageBinaryInspector); - try - { - var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( - type.Assembly.FullName, - type.FullName); - // do NOT turn PerformScan into static (even if ReSharper says so)! - var result = value.PerformScan(dllPath, out errorReport); - return result; - } - finally - { - AppDomain.Unload(appDomain); - } - } - - /// - /// Performs the assembly scanning - /// - /// - /// - /// - /// - /// - /// This method is executed in a separate app domain - /// - private IEnumerable PerformScan(IEnumerable assemblies, out string[] errorReport) - { - //we need this handler to resolve assembly dependencies when loading below - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => - { - var name = AppDomain.CurrentDomain.ApplyPolicy(e.Name); - var a = Assembly.ReflectionOnlyLoad(name); - if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); - return a; - }; - - //First load each dll file into the context - // do NOT apply policy here: we want to scan the dlls that are in the binaries - var loaded = assemblies.Select(Assembly.ReflectionOnlyLoad).ToList(); - - //scan - return PerformScan(loaded, out errorReport); - } - - /// - /// Performs the assembly scanning - /// - /// - /// - /// - /// - /// - /// This method is executed in a separate app domain - /// - private IEnumerable PerformScan(string dllPath, out string[] errorReport) - { - if (Directory.Exists(dllPath) == false) - { - throw new DirectoryNotFoundException("Could not find directory " + dllPath); - } - - //we need this handler to resolve assembly dependencies when loading below - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => - { - var name = AppDomain.CurrentDomain.ApplyPolicy(e.Name); - var a = Assembly.ReflectionOnlyLoad(name); - if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); - return a; - }; - - //First load each dll file into the context - // do NOT apply policy here: we want to scan the dlls that are in the path - var files = Directory.GetFiles(dllPath, "*.dll"); - var loaded = files.Select(Assembly.ReflectionOnlyLoadFrom).ToList(); - - //scan - return PerformScan(loaded, out errorReport); - } - - private static IEnumerable PerformScan(IList loaded, out string[] errorReport) - { - var dllsWithReference = new List(); - var errors = new List(); - var assembliesWithErrors = new List(); - - //load each of the LoadFrom assemblies into the Load context too - foreach (var a in loaded) - { - var name = AppDomain.CurrentDomain.ApplyPolicy(a.FullName); - Assembly.ReflectionOnlyLoad(name); - } - - //get the list of assembly names to compare below - var loadedNames = loaded.Select(x => x.GetName().Name).ToArray(); - - //Then load each referenced assembly into the context - foreach (var a in loaded) - { - //don't load any referenced assemblies that are already found in the loaded array - this is based on name - // regardless of version. We'll assume that if the assembly found in the folder matches the assembly name - // being looked for, that is the version the user has shipped their package with and therefore it 'must' be correct - foreach (var assemblyName in a.GetReferencedAssemblies().Where(ass => loadedNames.Contains(ass.Name) == false)) - { - try - { - var name = AppDomain.CurrentDomain.ApplyPolicy(assemblyName.FullName); - Assembly.ReflectionOnlyLoad(name); - } - catch (FileNotFoundException) - { - //if an exception occurs it means that a referenced assembly could not be found - errors.Add( - string.Concat("This package references the assembly '", - assemblyName.Name, - "' which was not found")); - assembliesWithErrors.Add(a); - } - catch (Exception ex) - { - //if an exception occurs it means that a referenced assembly could not be found - errors.Add( - string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '", - assemblyName.Name, - "' see error log for full details.")); - assembliesWithErrors.Add(a); - Current.Logger.Error(ex, "An error occurred scanning package assembly '{AssemblyName}'", assemblyName.FullName); - } - } - } - - var contractType = GetLoadFromContractType(); - - //now that we have all referenced types into the context we can look up stuff - foreach (var a in loaded.Except(assembliesWithErrors)) - { - //now we need to see if they contain any type 'T' - var reflectedAssembly = a; - - try - { - var found = reflectedAssembly.GetExportedTypes() - .Where(contractType.IsAssignableFrom); - - if (found.Any()) - { - dllsWithReference.Add(reflectedAssembly.FullName); - } - } - catch (Exception ex) - { - //This is a hack that nobody can seem to get around, I've read everything and it seems that - // this is quite a common thing when loading types into reflection only load context, so - // we're just going to ignore this specific one for now - var typeLoadEx = ex as TypeLoadException; - if (typeLoadEx != null) - { - if (typeLoadEx.Message.InvariantContains("does not have an implementation")) - { - //ignore - continue; - } - } - else - { - errors.Add( - string.Concat("This package could not be verified for compatibility. An error occurred while scanning a packaged assembly '", - a.GetName().Name, - "' see error log for full details.")); - assembliesWithErrors.Add(a); - Current.Logger.Error(ex, "An error occurred scanning package assembly '{AssemblyName}'", a.GetName().FullName); - } - } - - } - - errorReport = errors.ToArray(); - return dllsWithReference; - } - - /// - /// In order to compare types, the types must be in the same context, this method will return the type that - /// we are checking against but from the Load context. - /// - /// - /// - private static Type GetLoadFromContractType() - { - var name = AppDomain.CurrentDomain.ApplyPolicy(typeof(T).Assembly.FullName); - var contractAssemblyLoadFrom = Assembly.ReflectionOnlyLoad(name); - - var contractType = contractAssemblyLoadFrom.GetExportedTypes() - .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName); - - if (contractType == null) - { - throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies"); - } - return contractType; - } - - /// - /// Create an app domain - /// - /// - private static AppDomain GetTempAppDomain() - { - //copy the current app domain setup but don't shadow copy files - var appName = "TempDomain" + Guid.NewGuid(); - var domainSetup = new AppDomainSetup - { - ApplicationName = appName, - ShadowCopyFiles = "false", - ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase, - ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, - DynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase, - LicenseFile = AppDomain.CurrentDomain.SetupInformation.LicenseFile, - LoaderOptimization = AppDomain.CurrentDomain.SetupInformation.LoaderOptimization, - PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, - PrivateBinPathProbe = AppDomain.CurrentDomain.SetupInformation.PrivateBinPathProbe - }; - - // create new domain with full trust - return AppDomain.CreateDomain(appName, AppDomain.CurrentDomain.Evidence, domainSetup, new PermissionSet(PermissionState.Unrestricted)); - } - } -} diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs new file mode 100644 index 0000000000..9bea527b91 --- /dev/null +++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs @@ -0,0 +1,1264 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web; +using System.Xml.Linq; +using System.Xml.XPath; +using Umbraco.Core.Collections; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Packaging; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Core.Packaging +{ + internal class PackageDataInstallation + { + private readonly ILogger _logger; + private readonly IFileService _fileService; + private readonly IMacroService _macroService; + private readonly ILocalizationService _localizationService; + private readonly IDataTypeService _dataTypeService; + private readonly PropertyEditorCollection _propertyEditors; + private readonly IEntityService _entityService; + private readonly IContentTypeService _contentTypeService; + private readonly IContentService _contentService; + + public PackageDataInstallation(ILogger logger, IFileService fileService, IMacroService macroService, ILocalizationService localizationService, + IDataTypeService dataTypeService, IEntityService entityService, IContentTypeService contentTypeService, + IContentService contentService, PropertyEditorCollection propertyEditors) + { + _logger = logger; + _fileService = fileService; + _macroService = macroService; + _localizationService = localizationService; + _dataTypeService = dataTypeService; + _propertyEditors = propertyEditors; + _entityService = entityService; + _contentTypeService = contentTypeService; + _contentService = contentService; + } + + #region Uninstall + + public UninstallationSummary UninstallPackageData(PackageDefinition package, int userId) + { + if (package == null) throw new ArgumentNullException(nameof(package)); + + var removedTemplates = new List(); + var removedMacros = new List(); + var removedContentTypes = new List(); + var removedDictionaryItems = new List(); + var removedDataTypes = new List(); + var removedLanguages = new List(); + + + //Uninstall templates + foreach (var item in package.Templates.ToArray()) + { + if (int.TryParse(item, out var nId) == false) continue; + var found = _fileService.GetTemplate(nId); + if (found != null) + { + removedTemplates.Add(found); + _fileService.DeleteTemplate(found.Alias, userId); + } + package.Templates.Remove(nId.ToString()); + } + + //Uninstall macros + foreach (var item in package.Macros.ToArray()) + { + if (int.TryParse(item, out var nId) == false) continue; + var macro = _macroService.GetById(nId); + if (macro != null) + { + removedMacros.Add(macro); + _macroService.Delete(macro, userId); + } + package.Macros.Remove(nId.ToString()); + } + + //Remove Document Types + var contentTypes = new List(); + var contentTypeService = _contentTypeService; + foreach (var item in package.DocumentTypes.ToArray()) + { + if (int.TryParse(item, out var nId) == false) continue; + var contentType = contentTypeService.Get(nId); + if (contentType == null) continue; + contentTypes.Add(contentType); + package.DocumentTypes.Remove(nId.ToString(CultureInfo.InvariantCulture)); + } + + //Order the DocumentTypes before removing them + if (contentTypes.Any()) + { + //TODO: I don't think this ordering is necessary + var orderedTypes = (from contentType in contentTypes + orderby contentType.ParentId descending, contentType.Id descending + select contentType).ToList(); + removedContentTypes.AddRange(orderedTypes); + contentTypeService.Delete(orderedTypes, userId); + } + + //Remove Dictionary items + foreach (var item in package.DictionaryItems.ToArray()) + { + if (int.TryParse(item, out var nId) == false) continue; + var di = _localizationService.GetDictionaryItemById(nId); + if (di != null) + { + removedDictionaryItems.Add(di); + _localizationService.Delete(di, userId); + } + package.DictionaryItems.Remove(nId.ToString()); + } + + //Remove Data types + foreach (var item in package.DataTypes.ToArray()) + { + if (int.TryParse(item, out var nId) == false) continue; + var dtd = _dataTypeService.GetDataType(nId); + if (dtd != null) + { + removedDataTypes.Add(dtd); + _dataTypeService.Delete(dtd, userId); + } + package.DataTypes.Remove(nId.ToString()); + } + + //Remove Langs + foreach (var item in package.Languages.ToArray()) + { + if (int.TryParse(item, out var nId) == false) continue; + var lang = _localizationService.GetLanguageById(nId); + if (lang != null) + { + removedLanguages.Add(lang); + _localizationService.Delete(lang, userId); + } + package.Languages.Remove(nId.ToString()); + } + + // create a summary of what was actually removed, for PackagingService.UninstalledPackage + var summary = new UninstallationSummary + { + MetaData = package, + TemplatesUninstalled = removedTemplates, + MacrosUninstalled = removedMacros, + DocumentTypesUninstalled = removedContentTypes, + DictionaryItemsUninstalled = removedDictionaryItems, + DataTypesUninstalled = removedDataTypes, + LanguagesUninstalled = removedLanguages, + + }; + + return summary; + + + } + + #endregion + + #region Content + + + public IEnumerable ImportContent(IEnumerable docs, IDictionary importedDocumentTypes, int userId) + { + return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId)); + } + + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// Optional parent Id for the content being imported + /// A dictionary of already imported document types (basically used as a cache) + /// Optional Id of the user performing the import + /// An enumrable list of generated content + public IEnumerable ImportContent(CompiledPackageDocument packageDocument, int parentId, IDictionary importedDocumentTypes, int userId) + { + var element = packageDocument.XmlData; + + var roots = from doc in element.Elements() + where (string)doc.Attribute("isDoc") == "" + select doc; + + var contents = ParseDocumentRootXml(roots, parentId, importedDocumentTypes).ToList(); + if (contents.Any()) + _contentService.Save(contents, userId); + + return contents; + + //var attribute = element.Attribute("isDoc"); + //if (attribute != null) + //{ + // //This is a single doc import + // var elements = new List { element }; + // var contents = ParseDocumentRootXml(elements, parentId, importedDocumentTypes).ToList(); + // if (contents.Any()) + // _contentService.Save(contents, userId); + + // return contents; + //} + + //throw new ArgumentException( + // "The passed in XElement is not valid! It does not contain a root element called " + + // "'DocumentSet' (for structured imports) nor is the first element a Document (for single document import)."); + } + + private IEnumerable ParseDocumentRootXml(IEnumerable roots, int parentId, IDictionary importedContentTypes) + { + var contents = new List(); + foreach (var root in roots) + { + var contentTypeAlias = root.Name.LocalName; + + if (!importedContentTypes.ContainsKey(contentTypeAlias)) + { + var contentType = FindContentTypeByAlias(contentTypeAlias); + importedContentTypes.Add(contentTypeAlias, contentType); + } + + var content = CreateContentFromXml(root, importedContentTypes[contentTypeAlias], null, parentId); + if (content == null) continue; + + contents.Add(content); + + var children = (from child in root.Elements() + where (string)child.Attribute("isDoc") == "" + select child) + .ToList(); + + if (children.Count > 0) + contents.AddRange(CreateContentFromXml(children, content, importedContentTypes).WhereNotNull()); + } + return contents; + } + + private IEnumerable CreateContentFromXml(IEnumerable children, IContent parent, IDictionary importedContentTypes) + { + var list = new List(); + foreach (var child in children) + { + string contentTypeAlias = child.Name.LocalName; + + if (importedContentTypes.ContainsKey(contentTypeAlias) == false) + { + var contentType = FindContentTypeByAlias(contentTypeAlias); + importedContentTypes.Add(contentTypeAlias, contentType); + } + + //Create and add the child to the list + var content = CreateContentFromXml(child, importedContentTypes[contentTypeAlias], parent, default); + list.Add(content); + + //Recursive call + var child1 = child; + var grandChildren = (from grand in child1.Elements() + where (string)grand.Attribute("isDoc") == "" + select grand).ToList(); + + if (grandChildren.Any()) + list.AddRange(CreateContentFromXml(grandChildren, content, importedContentTypes)); + } + + return list; + } + + private IContent CreateContentFromXml(XElement element, IContentType contentType, IContent parent, int parentId) + { + var key = Guid.Empty; + if (element.Attribute("key") != null && Guid.TryParse(element.Attribute("key").Value, out key)) + { + //if a Key is supplied, then we need to check if the content already exists and if so we ignore the installation for this item + if (_contentService.GetById(key) != null) + return null; + } + + var id = element.Attribute("id").Value; + var level = element.Attribute("level").Value; + var sortOrder = element.Attribute("sortOrder").Value; + var nodeName = element.Attribute("nodeName").Value; + var path = element.Attribute("path").Value; + var templateId = element.AttributeValue("template"); + + var properties = from property in element.Elements() + where property.Attribute("isDoc") == null + select property; + + var template = templateId.HasValue ? _fileService.GetTemplate(templateId.Value) : null; + + IContent content = parent == null + ? new Content(nodeName, parentId, contentType) + { + Level = int.Parse(level), + SortOrder = int.Parse(sortOrder), + TemplateId = template?.Id, + Key = key + } + : new Content(nodeName, parent, contentType) + { + Level = int.Parse(level), + SortOrder = int.Parse(sortOrder), + TemplateId = template?.Id, + Key = key + }; + + foreach (var property in properties) + { + string propertyTypeAlias = property.Name.LocalName; + if (content.HasProperty(propertyTypeAlias)) + { + var propertyValue = property.Value; + + var propertyType = contentType.PropertyTypes.FirstOrDefault(pt => pt.Alias == propertyTypeAlias); + + //set property value + content.SetValue(propertyTypeAlias, propertyValue); + } + } + + return content; + } + + #endregion + + #region DocumentTypes + + public IEnumerable ImportDocumentType(XElement docTypeElement, int userId) + { + return ImportDocumentTypes(new []{ docTypeElement }, userId); + } + + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// Optional id of the User performing the operation. Default is zero (admin). + /// An enumrable list of generated ContentTypes + public IEnumerable ImportDocumentTypes(IEnumerable docTypeElements, int userId) + { + return ImportDocumentTypes(docTypeElements.ToList(), true, userId); + } + + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// Boolean indicating whether or not to import the + /// Optional id of the User performing the operation. Default is zero (admin). + /// An enumrable list of generated ContentTypes + public IEnumerable ImportDocumentTypes(IReadOnlyCollection unsortedDocumentTypes, bool importStructure, int userId) + { + var importedContentTypes = new Dictionary(); + + //When you are importing a single doc type we have to assume that the depedencies are already there. + //Otherwise something like uSync won't work. + var graph = new TopoGraph>(x => x.Key, x => x.Dependencies); + var isSingleDocTypeImport = unsortedDocumentTypes.Count == 1; + + var importedFolders = CreateContentTypeFolderStructure(unsortedDocumentTypes); + + if (isSingleDocTypeImport == false) + { + //NOTE Here we sort the doctype XElements based on dependencies + //before creating the doc types - this should also allow for a better structure/inheritance support. + foreach (var documentType in unsortedDocumentTypes) + { + var elementCopy = documentType; + var infoElement = elementCopy.Element("Info"); + var dependencies = new HashSet(); + + //Add the Master as a dependency + if (string.IsNullOrEmpty((string)infoElement.Element("Master")) == false) + { + dependencies.Add(infoElement.Element("Master").Value); + } + + //Add compositions as dependencies + var compositionsElement = infoElement.Element("Compositions"); + if (compositionsElement != null && compositionsElement.HasElements) + { + var compositions = compositionsElement.Elements("Composition"); + if (compositions.Any()) + { + foreach (var composition in compositions) + { + dependencies.Add(composition.Value); + } + } + } + + graph.AddItem(TopoGraph.CreateNode(infoElement.Element("Alias").Value, elementCopy, dependencies.ToArray())); + } + } + + //Sorting the Document Types based on dependencies - if its not a single doc type import ref. #U4-5921 + var documentTypes = isSingleDocTypeImport + ? unsortedDocumentTypes.ToList() + : graph.GetSortedItems().Select(x => x.Item).ToList(); + + //Iterate the sorted document types and create them as IContentType objects + foreach (var documentType in documentTypes) + { + var alias = documentType.Element("Info").Element("Alias").Value; + if (importedContentTypes.ContainsKey(alias) == false) + { + var contentType = _contentTypeService.Get(alias); + importedContentTypes.Add(alias, contentType == null + ? CreateContentTypeFromXml(documentType, importedContentTypes) + : UpdateContentTypeFromXml(documentType, contentType, importedContentTypes)); + } + } + + foreach (var contentType in importedContentTypes) + { + var ct = contentType.Value; + if (importedFolders.ContainsKey(ct.Alias)) + { + ct.ParentId = importedFolders[ct.Alias]; + } + } + + //Save the newly created/updated IContentType objects + var list = importedContentTypes.Select(x => x.Value).ToList(); + _contentTypeService.Save(list, userId); + + //Now we can finish the import by updating the 'structure', + //which requires the doc types to be saved/available in the db + if (importStructure) + { + var updatedContentTypes = new List(); + //Update the structure here - we can't do it untill all DocTypes have been created + foreach (var documentType in documentTypes) + { + var alias = documentType.Element("Info").Element("Alias").Value; + var structureElement = documentType.Element("Structure"); + //Ensure that we only update ContentTypes which has actual structure-elements + if (structureElement == null || structureElement.Elements("DocumentType").Any() == false) continue; + + var updated = UpdateContentTypesStructure(importedContentTypes[alias], structureElement, importedContentTypes); + updatedContentTypes.Add(updated); + } + //Update ContentTypes with a newly added structure/list of allowed children + if (updatedContentTypes.Any()) + _contentTypeService.Save(updatedContentTypes, userId); + } + + return list; + } + + private Dictionary CreateContentTypeFolderStructure(IEnumerable unsortedDocumentTypes) + { + var importedFolders = new Dictionary(); + foreach (var documentType in unsortedDocumentTypes) + { + var foldersAttribute = documentType.Attribute("Folders"); + var infoElement = documentType.Element("Info"); + if (foldersAttribute != null && infoElement != null + //don't import any folder if this is a child doc type - the parent doc type will need to + //exist which contains it's folders + && ((string)infoElement.Element("Master")).IsNullOrWhiteSpace()) + { + var alias = documentType.Element("Info").Element("Alias").Value; + var folders = foldersAttribute.Value.Split('/'); + var rootFolder = HttpUtility.UrlDecode(folders[0]); + //level 1 = root level folders, there can only be one with the same name + var current = _contentTypeService.GetContainers(rootFolder, 1).FirstOrDefault(); + + if (current == null) + { + var tryCreateFolder = _contentTypeService.CreateContainer(-1, rootFolder); + if (tryCreateFolder == false) + { + _logger.Error(tryCreateFolder.Exception, "Could not create folder: {FolderName}", rootFolder); + throw tryCreateFolder.Exception; + } + var rootFolderId = tryCreateFolder.Result.Entity.Id; + current = _contentTypeService.GetContainer(rootFolderId); + } + + importedFolders.Add(alias, current.Id); + + for (var i = 1; i < folders.Length; i++) + { + var folderName = HttpUtility.UrlDecode(folders[i]); + current = CreateContentTypeChildFolder(folderName, current); + importedFolders[alias] = current.Id; + } + } + } + + return importedFolders; + } + + private EntityContainer CreateContentTypeChildFolder(string folderName, IUmbracoEntity current) + { + var children = _entityService.GetChildren(current.Id).ToArray(); + var found = children.Any(x => x.Name.InvariantEquals(folderName)); + if (found) + { + var containerId = children.Single(x => x.Name.InvariantEquals(folderName)).Id; + return _contentTypeService.GetContainer(containerId); + } + + var tryCreateFolder = _contentTypeService.CreateContainer(current.Id, folderName); + if (tryCreateFolder == false) + { + _logger.Error(tryCreateFolder.Exception, "Could not create folder: {FolderName}", folderName); + throw tryCreateFolder.Exception; + } + return _contentTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); + } + + private IContentType CreateContentTypeFromXml(XElement documentType, IReadOnlyDictionary importedContentTypes) + { + var infoElement = documentType.Element("Info"); + + //Name of the master corresponds to the parent + var masterElement = infoElement.Element("Master"); + IContentType parent = null; + if (masterElement != null) + { + var masterAlias = masterElement.Value; + parent = importedContentTypes.ContainsKey(masterAlias) + ? importedContentTypes[masterAlias] + : _contentTypeService.Get(masterAlias); + } + + var alias = infoElement.Element("Alias").Value; + var contentType = parent == null + ? new ContentType(-1) { Alias = alias } + : new ContentType(parent, alias); + + if (parent != null) + contentType.AddContentType(parent); + + return UpdateContentTypeFromXml(documentType, contentType, importedContentTypes); + } + + private IContentType UpdateContentTypeFromXml(XElement documentType, IContentType contentType, IReadOnlyDictionary importedContentTypes) + { + var infoElement = documentType.Element("Info"); + var defaultTemplateElement = infoElement.Element("DefaultTemplate"); + + contentType.Name = infoElement.Element("Name").Value; + contentType.Icon = infoElement.Element("Icon").Value; + contentType.Thumbnail = infoElement.Element("Thumbnail").Value; + contentType.Description = infoElement.Element("Description").Value; + + //NOTE AllowAtRoot is a new property in the package xml so we need to verify it exists before using it. + var allowAtRoot = infoElement.Element("AllowAtRoot"); + if (allowAtRoot != null) + contentType.AllowedAsRoot = allowAtRoot.Value.InvariantEquals("true"); + + //NOTE IsListView is a new property in the package xml so we need to verify it exists before using it. + var isListView = infoElement.Element("IsListView"); + if (isListView != null) + contentType.IsContainer = isListView.Value.InvariantEquals("true"); + + var isElement = infoElement.Element("IsElement"); + if (isElement != null) + contentType.IsElement = isElement.Value.InvariantEquals("true"); + + //Name of the master corresponds to the parent and we need to ensure that the Parent Id is set + var masterElement = infoElement.Element("Master"); + if (masterElement != null) + { + var masterAlias = masterElement.Value; + IContentType parent = importedContentTypes.ContainsKey(masterAlias) + ? importedContentTypes[masterAlias] + : _contentTypeService.Get(masterAlias); + + contentType.SetParent(parent); + } + + //Update Compositions on the ContentType to ensure that they are as is defined in the package xml + var compositionsElement = infoElement.Element("Compositions"); + if (compositionsElement != null && compositionsElement.HasElements) + { + var compositions = compositionsElement.Elements("Composition"); + if (compositions.Any()) + { + foreach (var composition in compositions) + { + var compositionAlias = composition.Value; + var compositionContentType = importedContentTypes.ContainsKey(compositionAlias) + ? importedContentTypes[compositionAlias] + : _contentTypeService.Get(compositionAlias); + var added = contentType.AddContentType(compositionContentType); + } + } + } + + UpdateContentTypesAllowedTemplates(contentType, infoElement.Element("AllowedTemplates"), defaultTemplateElement); + UpdateContentTypesTabs(contentType, documentType.Element("Tabs")); + UpdateContentTypesProperties(contentType, documentType.Element("GenericProperties")); + + return contentType; + } + + private void UpdateContentTypesAllowedTemplates(IContentType contentType, + XElement allowedTemplatesElement, XElement defaultTemplateElement) + { + if (allowedTemplatesElement != null && allowedTemplatesElement.Elements("Template").Any()) + { + var allowedTemplates = contentType.AllowedTemplates.ToList(); + foreach (var templateElement in allowedTemplatesElement.Elements("Template")) + { + var alias = templateElement.Value; + var template = _fileService.GetTemplate(alias.ToSafeAlias()); + if (template != null) + { + if (allowedTemplates.Any(x => x.Id == template.Id)) continue; + allowedTemplates.Add(template); + } + else + { + _logger.Warn("Packager: Error handling allowed templates. Template with alias '{TemplateAlias}' could not be found.", alias); + } + } + + contentType.AllowedTemplates = allowedTemplates; + } + + if (string.IsNullOrEmpty((string)defaultTemplateElement) == false) + { + var defaultTemplate = _fileService.GetTemplate(defaultTemplateElement.Value.ToSafeAlias()); + if (defaultTemplate != null) + { + contentType.SetDefaultTemplate(defaultTemplate); + } + else + { + _logger.Warn("Packager: Error handling default template. Default template with alias '{DefaultTemplateAlias}' could not be found.", defaultTemplateElement.Value); + } + } + } + + private void UpdateContentTypesTabs(IContentType contentType, XElement tabElement) + { + if (tabElement == null) + return; + + var tabs = tabElement.Elements("Tab"); + foreach (var tab in tabs) + { + var id = tab.Element("Id").Value;//Do we need to use this for tracking? + var caption = tab.Element("Caption").Value; + + if (contentType.PropertyGroups.Contains(caption) == false) + { + contentType.AddPropertyGroup(caption); + + } + + int sortOrder; + if (tab.Element("SortOrder") != null && int.TryParse(tab.Element("SortOrder").Value, out sortOrder)) + { + // Override the sort order with the imported value + contentType.PropertyGroups[caption].SortOrder = sortOrder; + } + } + } + + private void UpdateContentTypesProperties(IContentType contentType, XElement genericPropertiesElement) + { + var properties = genericPropertiesElement.Elements("GenericProperty"); + foreach (var property in properties) + { + var dataTypeDefinitionId = new Guid(property.Element("Definition").Value);//Unique Id for a DataTypeDefinition + + var dataTypeDefinition = _dataTypeService.GetDataType(dataTypeDefinitionId); + + //If no DataTypeDefinition with the guid from the xml wasn't found OR the ControlId on the DataTypeDefinition didn't match the DataType Id + //We look up a DataTypeDefinition that matches + + + //get the alias as a string for use below + var propertyEditorAlias = property.Element("Type").Value.Trim(); + + //If no DataTypeDefinition with the guid from the xml wasn't found OR the ControlId on the DataTypeDefinition didn't match the DataType Id + //We look up a DataTypeDefinition that matches + + if (dataTypeDefinition == null) + { + var dataTypeDefinitions = _dataTypeService.GetByEditorAlias(propertyEditorAlias); + if (dataTypeDefinitions != null && dataTypeDefinitions.Any()) + { + dataTypeDefinition = dataTypeDefinitions.FirstOrDefault(); + } + } + else if (dataTypeDefinition.EditorAlias != propertyEditorAlias) + { + var dataTypeDefinitions = _dataTypeService.GetByEditorAlias(propertyEditorAlias); + if (dataTypeDefinitions != null && dataTypeDefinitions.Any()) + { + dataTypeDefinition = dataTypeDefinitions.FirstOrDefault(); + } + } + + // For backwards compatibility, if no datatype with that ID can be found, we're letting this fail silently. + // This means that the property will not be created. + if (dataTypeDefinition == null) + { + //TODO: We should expose this to the UI during install! + _logger.Warn("Packager: Error handling creation of PropertyType '{PropertyType}'. Could not find DataTypeDefintion with unique id '{DataTypeDefinitionId}' nor one referencing the DataType with a property editor alias (or legacy control id) '{PropertyEditorAlias}'. Did the package creator forget to package up custom datatypes? This property will be converted to a label/readonly editor if one exists.", + property.Element("Name").Value, dataTypeDefinitionId, property.Element("Type").Value.Trim()); + + //convert to a label! + dataTypeDefinition = _dataTypeService.GetByEditorAlias(Constants.PropertyEditors.Aliases.NoEdit).FirstOrDefault(); + //if for some odd reason this isn't there then ignore + if (dataTypeDefinition == null) continue; + } + + var sortOrder = 0; + var sortOrderElement = property.Element("SortOrder"); + if (sortOrderElement != null) + int.TryParse(sortOrderElement.Value, out sortOrder); + var propertyType = new PropertyType(dataTypeDefinition, property.Element("Alias").Value) + { + Name = property.Element("Name").Value, + Description = (string)property.Element("Description"), + Mandatory = property.Element("Mandatory") != null ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true") : false, + ValidationRegExp = (string)property.Element("Validation"), + SortOrder = sortOrder + }; + + var tab = (string)property.Element("Tab"); + if (string.IsNullOrEmpty(tab)) + { + contentType.AddPropertyType(propertyType); + } + else + { + contentType.AddPropertyType(propertyType, tab); + } + } + } + + private IContentType UpdateContentTypesStructure(IContentType contentType, XElement structureElement, IReadOnlyDictionary importedContentTypes) + { + var allowedChildren = contentType.AllowedContentTypes.ToList(); + int sortOrder = allowedChildren.Any() ? allowedChildren.Last().SortOrder : 0; + foreach (var element in structureElement.Elements("DocumentType")) + { + var alias = element.Value; + + var allowedChild = importedContentTypes.ContainsKey(alias) ? importedContentTypes[alias] : _contentTypeService.Get(alias); + if (allowedChild == null) + { + _logger.Warn( + "Packager: Error handling DocumentType structure. DocumentType with alias '{DoctypeAlias}' could not be found and was not added to the structure for '{DoctypeStructureAlias}'.", + alias, contentType.Alias); + continue; + } + + if (allowedChildren.Any(x => x.Id.IsValueCreated && x.Id.Value == allowedChild.Id)) continue; + + allowedChildren.Add(new ContentTypeSort(new Lazy(() => allowedChild.Id), sortOrder, allowedChild.Alias)); + sortOrder++; + } + + contentType.AllowedContentTypes = allowedChildren; + return contentType; + } + + /// + /// Used during Content import to ensure that the ContentType of a content item exists + /// + /// + /// + private IContentType FindContentTypeByAlias(string contentTypeAlias) + { + var contentType = _contentTypeService.Get(contentTypeAlias); + + if (contentType == null) + throw new Exception($"ContentType matching the passed in Alias: '{contentTypeAlias}' was null"); + + return contentType; + } + + #endregion + + #region DataTypes + + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// Optional id of the user + /// An enumrable list of generated DataTypeDefinitions + public IEnumerable ImportDataTypes(IReadOnlyCollection dataTypeElements, int userId) + { + var dataTypes = new List(); + + var importedFolders = CreateDataTypeFolderStructure(dataTypeElements); + + foreach (var dataTypeElement in dataTypeElements) + { + var dataTypeDefinitionName = dataTypeElement.AttributeValue("Name"); + + var dataTypeDefinitionId = dataTypeElement.AttributeValue("Definition"); + var databaseTypeAttribute = dataTypeElement.Attribute("DatabaseType"); + + var parentId = -1; + if (importedFolders.ContainsKey(dataTypeDefinitionName)) + parentId = importedFolders[dataTypeDefinitionName]; + + var definition = _dataTypeService.GetDataType(dataTypeDefinitionId); + //If the datatypedefinition doesn't already exist we create a new new according to the one in the package xml + if (definition == null) + { + var databaseType = databaseTypeAttribute?.Value.EnumParse(true) ?? ValueStorageType.Ntext; + + // the Id field is actually the string property editor Alias + // however, the actual editor with this alias could be installed with the package, and + // therefore not yet part of the _propertyEditors collection, so we cannot try and get + // the actual editor - going with a void editor + + var editorAlias = dataTypeElement.Attribute("Id")?.Value?.Trim(); + if (!_propertyEditors.TryGet(editorAlias, out var editor)) + editor = new VoidEditor(_logger) { Alias = editorAlias }; + + var dataType = new DataType(editor) + { + Key = dataTypeDefinitionId, + Name = dataTypeDefinitionName, + DatabaseType = databaseType, + ParentId = parentId + }; + + var configurationAttributeValue = dataTypeElement.Attribute("Configuration")?.Value; + if (!string.IsNullOrWhiteSpace(configurationAttributeValue)) + dataType.Configuration = editor.GetConfigurationEditor().FromDatabase(configurationAttributeValue); + + dataTypes.Add(dataType); + } + else + { + definition.ParentId = parentId; + _dataTypeService.Save(definition, userId); + } + } + + if (dataTypes.Count > 0) + { + _dataTypeService.Save(dataTypes, userId, true); + } + + return dataTypes; + } + + private Dictionary CreateDataTypeFolderStructure(IEnumerable datatypeElements) + { + var importedFolders = new Dictionary(); + foreach (var datatypeElement in datatypeElements) + { + var foldersAttribute = datatypeElement.Attribute("Folders"); + if (foldersAttribute != null) + { + var name = datatypeElement.Attribute("Name").Value; + var folders = foldersAttribute.Value.Split('/'); + var rootFolder = HttpUtility.UrlDecode(folders[0]); + //there will only be a single result by name for level 1 (root) containers + var current = _dataTypeService.GetContainers(rootFolder, 1).FirstOrDefault(); + + if (current == null) + { + var tryCreateFolder = _dataTypeService.CreateContainer(-1, rootFolder); + if (tryCreateFolder == false) + { + _logger.Error(tryCreateFolder.Exception, "Could not create folder: {FolderName}", rootFolder); + throw tryCreateFolder.Exception; + } + current = _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); + } + + importedFolders.Add(name, current.Id); + + for (var i = 1; i < folders.Length; i++) + { + var folderName = HttpUtility.UrlDecode(folders[i]); + current = CreateDataTypeChildFolder(folderName, current); + importedFolders[name] = current.Id; + } + } + } + + return importedFolders; + } + + private EntityContainer CreateDataTypeChildFolder(string folderName, IUmbracoEntity current) + { + var children = _entityService.GetChildren(current.Id).ToArray(); + var found = children.Any(x => x.Name.InvariantEquals(folderName)); + if (found) + { + var containerId = children.Single(x => x.Name.InvariantEquals(folderName)).Id; + return _dataTypeService.GetContainer(containerId); + } + + var tryCreateFolder = _dataTypeService.CreateContainer(current.Id, folderName); + if (tryCreateFolder == false) + { + _logger.Error(tryCreateFolder.Exception, "Could not create folder: {FolderName}", folderName); + throw tryCreateFolder.Exception; + } + return _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); + } + + #endregion + + #region Dictionary Items + + /// + /// Imports and saves the 'DictionaryItems' part of the package xml as a list of + /// + /// Xml to import + /// + /// An enumerable list of dictionary items + public IEnumerable ImportDictionaryItems(IEnumerable dictionaryItemElementList, int userId) + { + var languages = _localizationService.GetAllLanguages().ToList(); + return ImportDictionaryItems(dictionaryItemElementList, languages, null, userId); + } + + private IEnumerable ImportDictionaryItems(IEnumerable dictionaryItemElementList, List languages, Guid? parentId, int userId) + { + var items = new List(); + foreach (var dictionaryItemElement in dictionaryItemElementList) + items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, parentId, userId)); + + return items; + } + + private IEnumerable ImportDictionaryItem(XElement dictionaryItemElement, List languages, Guid? parentId, int userId) + { + var items = new List(); + + IDictionaryItem dictionaryItem; + var key = dictionaryItemElement.Attribute("Key").Value; + if (_localizationService.DictionaryItemExists(key)) + dictionaryItem = GetAndUpdateDictionaryItem(key, dictionaryItemElement, languages); + else + dictionaryItem = CreateNewDictionaryItem(key, dictionaryItemElement, languages, parentId); + _localizationService.Save(dictionaryItem, userId); + items.Add(dictionaryItem); + + items.AddRange(ImportDictionaryItems(dictionaryItemElement.Elements("DictionaryItem"), languages, dictionaryItem.Key, userId)); + return items; + } + + private IDictionaryItem GetAndUpdateDictionaryItem(string key, XElement dictionaryItemElement, List languages) + { + var dictionaryItem = _localizationService.GetDictionaryItemByKey(key); + var translations = dictionaryItem.Translations.ToList(); + foreach (var valueElement in dictionaryItemElement.Elements("Value").Where(v => DictionaryValueIsNew(translations, v))) + AddDictionaryTranslation(translations, valueElement, languages); + dictionaryItem.Translations = translations; + return dictionaryItem; + } + + private static DictionaryItem CreateNewDictionaryItem(string key, XElement dictionaryItemElement, List languages, Guid? parentId) + { + var dictionaryItem = parentId.HasValue ? new DictionaryItem(parentId.Value, key) : new DictionaryItem(key); + var translations = new List(); + + foreach (var valueElement in dictionaryItemElement.Elements("Value")) + AddDictionaryTranslation(translations, valueElement, languages); + + dictionaryItem.Translations = translations; + return dictionaryItem; + } + + private static bool DictionaryValueIsNew(IEnumerable translations, XElement valueElement) + { + return translations.All(t => + String.Compare(t.Language.IsoCode, valueElement.Attribute("LanguageCultureAlias").Value, + StringComparison.InvariantCultureIgnoreCase) != 0 + ); + } + + private static void AddDictionaryTranslation(ICollection translations, XElement valueElement, IEnumerable languages) + { + var languageId = valueElement.Attribute("LanguageCultureAlias").Value; + var language = languages.SingleOrDefault(l => l.IsoCode == languageId); + if (language == null) + return; + var translation = new DictionaryTranslation(language, valueElement.Value); + translations.Add(translation); + } + + #endregion + + #region Languages + + + /// + /// Imports and saves the 'Languages' part of a package xml as a list of + /// + /// Xml to import + /// Optional id of the User performing the operation + /// An enumerable list of generated languages + public IEnumerable ImportLanguages(IEnumerable languageElements, int userId) + { + var list = new List(); + foreach (var languageElement in languageElements) + { + var isoCode = languageElement.AttributeValue("CultureAlias"); + var existingLanguage = _localizationService.GetLanguageByIsoCode(isoCode); + if (existingLanguage != null) continue; + var langauge = new Language(isoCode) + { + CultureName = languageElement.AttributeValue("FriendlyName") + }; + _localizationService.Save(langauge, userId); + list.Add(langauge); + } + + return list; + } + + #endregion + + #region Macros + + /// + /// Imports and saves the 'Macros' part of a package xml as a list of + /// + /// Xml to import + /// Optional id of the User performing the operation + /// + public IEnumerable ImportMacros(IEnumerable macroElements, int userId) + { + var macros = macroElements.Select(ParseMacroElement).ToList(); + + foreach (var macro in macros) + { + var existing = _macroService.GetByAlias(macro.Alias); + if (existing != null) + macro.Id = existing.Id; + + _macroService.Save(macro, userId); + } + + return macros; + } + + private IMacro ParseMacroElement(XElement macroElement) + { + var macroName = macroElement.Element("name").Value; + var macroAlias = macroElement.Element("alias").Value; + var macroType = Enum.Parse(macroElement.Element("macroType").Value); + var macroSource = macroElement.Element("macroSource").Value; + + //Following xml elements are treated as nullable properties + var useInEditorElement = macroElement.Element("useInEditor"); + var useInEditor = false; + if (useInEditorElement != null && string.IsNullOrEmpty((string)useInEditorElement) == false) + { + useInEditor = bool.Parse(useInEditorElement.Value); + } + var cacheDurationElement = macroElement.Element("refreshRate"); + var cacheDuration = 0; + if (cacheDurationElement != null && string.IsNullOrEmpty((string)cacheDurationElement) == false) + { + cacheDuration = int.Parse(cacheDurationElement.Value); + } + var cacheByMemberElement = macroElement.Element("cacheByMember"); + var cacheByMember = false; + if (cacheByMemberElement != null && string.IsNullOrEmpty((string)cacheByMemberElement) == false) + { + cacheByMember = bool.Parse(cacheByMemberElement.Value); + } + var cacheByPageElement = macroElement.Element("cacheByPage"); + var cacheByPage = false; + if (cacheByPageElement != null && string.IsNullOrEmpty((string)cacheByPageElement) == false) + { + cacheByPage = bool.Parse(cacheByPageElement.Value); + } + var dontRenderElement = macroElement.Element("dontRender"); + var dontRender = true; + if (dontRenderElement != null && string.IsNullOrEmpty((string)dontRenderElement) == false) + { + dontRender = bool.Parse(dontRenderElement.Value); + } + + var existingMacro = _macroService.GetByAlias(macroAlias) as Macro; + var macro = existingMacro ?? new Macro(macroAlias, macroName, macroSource, macroType, + cacheByPage, cacheByMember, dontRender, useInEditor, cacheDuration); + + var properties = macroElement.Element("properties"); + if (properties != null) + { + int sortOrder = 0; + foreach (var property in properties.Elements()) + { + var propertyName = property.Attribute("name").Value; + var propertyAlias = property.Attribute("alias").Value; + var editorAlias = property.Attribute("propertyType").Value; + var sortOrderAttribute = property.Attribute("sortOrder"); + if (sortOrderAttribute != null) + { + sortOrder = int.Parse(sortOrderAttribute.Value); + } + + if (macro.Properties.Values.Any(x => string.Equals(x.Alias, propertyAlias, StringComparison.OrdinalIgnoreCase))) continue; + macro.Properties.Add(new MacroProperty(propertyAlias, propertyName, sortOrder, editorAlias)); + sortOrder++; + } + } + return macro; + } + + + + + + #endregion + + #region Stylesheets + + public IEnumerable ImportStylesheets(IEnumerable stylesheetElements, int userId) + { + var result = new List(); + + foreach (var n in stylesheetElements) + { + var stylesheetName = n.Element("Name")?.Value; + if (stylesheetName.IsNullOrWhiteSpace()) continue; + + var s = _fileService.GetStylesheetByName(stylesheetName); + if (s == null) + { + var fileName = n.Element("FileName")?.Value; + if (fileName == null) continue; + var content = n.Element("Content")?.Value; + if (content == null) continue; + + s = new Stylesheet(fileName) { Content = content }; + _fileService.SaveStylesheet(s); + } + + foreach (var prop in n.XPathSelectElements("Properties/Property")) + { + var alias = prop.Element("Alias")?.Value; + var sp = s.Properties.SingleOrDefault(p => p != null && p.Alias == alias); + var name = prop.Element("Name")?.Value; + if (sp == null) + { + sp = new StylesheetProperty(name, "#" + name.ToSafeAlias(), ""); + s.AddProperty(sp); + } + else + { + //sp.Text = name; + //Changing the name requires removing the current property and then adding another new one + if (sp.Name != name) + { + s.RemoveProperty(sp.Name); + var newProp = new StylesheetProperty(name, sp.Alias, sp.Value); + s.AddProperty(newProp); + sp = newProp; + } + } + sp.Alias = alias; + sp.Value = prop.Element("Value")?.Value; + } + _fileService.SaveStylesheet(s); + result.Add(s); + } + + return result; + } + + #endregion + + #region Templates + + public IEnumerable ImportTemplate(XElement templateElement, int userId) + { + return ImportTemplates(new[] {templateElement}, userId); + } + + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// Optional user id + /// An enumrable list of generated Templates + public IEnumerable ImportTemplates(IReadOnlyCollection templateElements, int userId) + { + var templates = new List(); + + var graph = new TopoGraph>(x => x.Key, x => x.Dependencies); + + foreach (var tempElement in templateElements) + { + var dependencies = new List(); + var elementCopy = tempElement; + //Ensure that the Master of the current template is part of the import, otherwise we ignore this dependency as part of the dependency sorting. + if (string.IsNullOrEmpty((string)elementCopy.Element("Master")) == false && + templateElements.Any(x => (string)x.Element("Alias") == (string)elementCopy.Element("Master"))) + { + dependencies.Add((string)elementCopy.Element("Master")); + } + else if (string.IsNullOrEmpty((string)elementCopy.Element("Master")) == false && + templateElements.Any(x => (string)x.Element("Alias") == (string)elementCopy.Element("Master")) == false) + { + _logger.Info( + "Template '{TemplateAlias}' has an invalid Master '{TemplateMaster}', so the reference has been ignored.", + (string)elementCopy.Element("Alias"), + (string)elementCopy.Element("Master")); + } + + graph.AddItem(TopoGraph.CreateNode((string)elementCopy.Element("Alias"), elementCopy, dependencies)); + } + + //Sort templates by dependencies to a potential master template + var sorted = graph.GetSortedItems(); + foreach (var item in sorted) + { + var templateElement = item.Item; + + var templateName = templateElement.Element("Name").Value; + var alias = templateElement.Element("Alias").Value; + var design = templateElement.Element("Design").Value; + var masterElement = templateElement.Element("Master"); + + var existingTemplate = _fileService.GetTemplate(alias) as Template; + var template = existingTemplate ?? new Template(templateName, alias); + template.Content = design; + if (masterElement != null && string.IsNullOrEmpty((string)masterElement) == false) + { + template.MasterTemplateAlias = masterElement.Value; + var masterTemplate = templates.FirstOrDefault(x => x.Alias == masterElement.Value); + if (masterTemplate != null) + template.MasterTemplateId = new Lazy(() => masterTemplate.Id); + } + templates.Add(template); + } + + if (templates.Any()) + _fileService.SaveTemplate(templates, userId); + + return templates; + } + + private string ViewPath(string alias) + { + return SystemDirectories.MvcViews + "/" + alias.Replace(" ", "") + ".cshtml"; + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs new file mode 100644 index 0000000000..b66cdb095c --- /dev/null +++ b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Packaging; + +namespace Umbraco.Core.Packaging +{ + /// + /// Converts a to and from XML + /// + public class PackageDefinitionXmlParser + { + private readonly ILogger _logger; + + public PackageDefinitionXmlParser(ILogger logger) + { + _logger = logger; + } + + public PackageDefinition ToPackageDefinition(XElement xml) + { + if (xml == null) return null; + + var retVal = new PackageDefinition + { + Id = xml.AttributeValue("id"), + Name = xml.AttributeValue("name") ?? string.Empty, + PackagePath = xml.AttributeValue("packagePath") ?? string.Empty, + Version = xml.AttributeValue("version") ?? string.Empty, + Url = xml.AttributeValue("url") ?? string.Empty, + PackageId = xml.AttributeValue("packageGuid"), + IconUrl = xml.AttributeValue("iconUrl") ?? string.Empty, + UmbracoVersion = xml.AttributeValue("umbVersion"), + PackageView = xml.AttributeValue("view") ?? string.Empty, + License = xml.Element("license")?.Value ?? string.Empty, + LicenseUrl = xml.Element("license")?.AttributeValue("url") ?? string.Empty, + Author = xml.Element("author")?.Value ?? string.Empty, + AuthorUrl = xml.Element("author")?.AttributeValue("url") ?? string.Empty, + Readme = xml.Element("readme")?.Value ?? string.Empty, + Actions = xml.Element("actions")?.ToString(SaveOptions.None) ?? "", //take the entire outer xml value + ContentNodeId = xml.Element("content")?.AttributeValue("nodeId") ?? string.Empty, + ContentLoadChildNodes = xml.Element("content")?.AttributeValue("loadChildNodes") ?? false, + Macros = xml.Element("macros")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Templates = xml.Element("templates")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Stylesheets = xml.Element("stylesheets")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + DocumentTypes = xml.Element("documentTypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Languages = xml.Element("languages")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + DataTypes = xml.Element("datatypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Files = xml.Element("files")?.Elements("file").Select(x => x.Value).ToList() ?? new List() + }; + + return retVal; + } + + public XElement ToXml(PackageDefinition def) + { + var actionsXml = new XElement("actions"); + try + { + actionsXml = XElement.Parse(def.Actions); + } + catch (Exception e) + { + _logger.Warn(e, "Could not add package actions to the package xml definition, the xml did not parse"); + } + + var packageXml = new XElement("package", + new XAttribute("id", def.Id), + new XAttribute("version", def.Version ?? string.Empty), + new XAttribute("url", def.Url ?? string.Empty), + new XAttribute("name", def.Name ?? string.Empty), + new XAttribute("packagePath", def.PackagePath ?? string.Empty), + new XAttribute("iconUrl", def.IconUrl ?? string.Empty), + new XAttribute("umbVersion", def.UmbracoVersion), + new XAttribute("packageGuid", def.PackageId), + new XAttribute("view", def.PackageView ?? string.Empty), + + new XElement("license", + new XCData(def.License ?? string.Empty), + new XAttribute("url", def.LicenseUrl ?? string.Empty)), + + new XElement("author", + new XCData(def.Author ?? string.Empty), + new XAttribute("url", def.AuthorUrl ?? string.Empty)), + + new XElement("readme", new XCData(def.Readme ?? string.Empty)), + actionsXml, + new XElement("datatypes", string.Join(",", def.DataTypes ?? Array.Empty())), + + new XElement("content", + new XAttribute("nodeId", def.ContentNodeId ?? string.Empty), + new XAttribute("loadChildNodes", def.ContentLoadChildNodes)), + + new XElement("templates", string.Join(",", def.Templates ?? Array.Empty())), + new XElement("stylesheets", string.Join(",", def.Stylesheets ?? Array.Empty())), + new XElement("documentTypes", string.Join(",", def.DocumentTypes ?? Array.Empty())), + new XElement("macros", string.Join(",", def.Macros ?? Array.Empty())), + new XElement("files", (def.Files ?? Array.Empty()).Where(x => !x.IsNullOrWhiteSpace()).Select(x => new XElement("file", x))), + new XElement("languages", string.Join(",", def.Languages ?? Array.Empty())), + new XElement("dictionaryitems", string.Join(",", def.DictionaryItems ?? Array.Empty()))); + + return packageXml; + } + + + } +} diff --git a/src/Umbraco.Core/Packaging/PackageExtraction.cs b/src/Umbraco.Core/Packaging/PackageExtraction.cs index cc3c394732..48093da45f 100644 --- a/src/Umbraco.Core/Packaging/PackageExtraction.cs +++ b/src/Umbraco.Core/Packaging/PackageExtraction.cs @@ -6,15 +6,15 @@ using System.IO.Compression; namespace Umbraco.Core.Packaging { - internal class PackageExtraction : IPackageExtraction + internal class PackageExtraction { - public string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage) + public string ReadTextFileFromArchive(FileInfo packageFile, string fileToRead, out string directoryInPackage) { string retVal = null; bool fileFound = false; string foundDir = null; - ReadZipfileEntries(packageFilePath, entry => + ReadZipfileEntries(packageFile, entry => { string fileName = Path.GetFileName(entry.Name); @@ -36,55 +36,50 @@ namespace Umbraco.Core.Packaging if (fileFound == false) { directoryInPackage = null; - throw new FileNotFoundException(string.Format("Could not find file in package {0}", packageFilePath), fileToRead); + throw new FileNotFoundException($"Could not find file in package {packageFile}", fileToRead); } directoryInPackage = foundDir; return retVal; } - private static void CheckPackageExists(string packageFilePath) + private static void CheckPackageExists(FileInfo packageFile) { - if (string.IsNullOrEmpty(packageFilePath)) - { - throw new ArgumentNullException("packageFilePath"); - } + if (packageFile == null) throw new ArgumentNullException(nameof(packageFile)); + + if (!packageFile.Exists) + throw new ArgumentException($"Package file: {packageFile} could not be found"); - if (File.Exists(packageFilePath) == false) - { - if (File.Exists(packageFilePath) == false) - throw new ArgumentException(string.Format("Package file: {0} could not be found", packageFilePath)); - } - - string extension = Path.GetExtension(packageFilePath).ToLower(); + var extension = packageFile.Extension; var alowedExtension = new[] { ".umb", ".zip" }; // Check if the file is a valid package - if (alowedExtension.All(ae => ae.Equals(extension) == false)) + if (alowedExtension.All(ae => ae.InvariantEquals(extension) == false)) { - throw new ArgumentException( - string.Format("Error - file isn't a package. only extentions: \"{0}\" is allowed", string.Join(", ", alowedExtension))); + throw new ArgumentException("Error - file isn't a package. only extentions: \"{string.Join(", ", alowedExtension)}\" is allowed"); } } - public void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) + public void CopyFileFromArchive(FileInfo packageFile, string fileInPackageName, string destinationfilePath) { - CopyFilesFromArchive(packageFilePath, new[]{new KeyValuePair(fileInPackageName, destinationfilePath) } ); + CopyFilesFromArchive(packageFile, new[] {(fileInPackageName, destinationfilePath)}); } - public void CopyFilesFromArchive(string packageFilePath, IEnumerable> sourceDestination) + public void CopyFilesFromArchive(FileInfo packageFile, IEnumerable<(string packageUniqueFile, string appAbsolutePath)> sourceDestination) { - var d = sourceDestination.ToDictionary(k => k.Key.ToLower(), v => v.Value); + var d = sourceDestination.ToDictionary(k => k.packageUniqueFile.ToLower(), v => v.appAbsolutePath); - ReadZipfileEntries(packageFilePath, entry => + ReadZipfileEntries(packageFile, entry => { - string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); + var fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); if (fileName == string.Empty) { return true; } - string destination; - if (string.IsNullOrEmpty(fileName) == false && d.TryGetValue(fileName, out destination)) + if (string.IsNullOrEmpty(fileName) == false && d.TryGetValue(fileName, out var destination)) { + //ensure the dir exists + Directory.CreateDirectory(Path.GetDirectoryName(destination)); + using (var streamWriter = File.Open(destination, FileMode.Create)) using (var entryStream = entry.Open()) { @@ -99,15 +94,15 @@ namespace Umbraco.Core.Packaging if (d.Any()) { - throw new ArgumentException(string.Format("The following source file(s): \"{0}\" could not be found in archive: \"{1}\"", string.Join("\", \"",d.Keys), packageFilePath)); + throw new ArgumentException(string.Format("The following source file(s): \"{0}\" could not be found in archive: \"{1}\"", string.Join("\", \"",d.Keys), packageFile)); } } - public IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles) + public IEnumerable FindMissingFiles(FileInfo packageFile, IEnumerable expectedFiles) { var retVal = expectedFiles.ToList(); - ReadZipfileEntries(packageFilePath, zipEntry => + ReadZipfileEntries(packageFile, zipEntry => { string fileName = Path.GetFileName(zipEntry.Name); @@ -121,17 +116,16 @@ namespace Umbraco.Core.Packaging } - public IEnumerable FindDubletFileNames(string packageFilePath) + public IEnumerable FindDuplicateFileNames(FileInfo packageFile) { var dictionary = new Dictionary>(); - ReadZipfileEntries(packageFilePath, entry => + ReadZipfileEntries(packageFile, entry => { - string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); + var fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); - List list; - if (dictionary.TryGetValue(fileName, out list) == false) + if (dictionary.TryGetValue(fileName, out var list) == false) { list = new List(); dictionary.Add(fileName, list); @@ -145,13 +139,13 @@ namespace Umbraco.Core.Packaging return dictionary.Values.Where(v => v.Count > 1).SelectMany(v => v); } - public IEnumerable ReadFilesFromArchive(string packageFilePath, IEnumerable filesToGet) + public IEnumerable ReadFilesFromArchive(FileInfo packageFile, IEnumerable filesToGet) { - CheckPackageExists(packageFilePath); + CheckPackageExists(packageFile); var files = new HashSet(filesToGet.Select(f => f.ToLowerInvariant())); - using (var fs = File.OpenRead(packageFilePath)) + using (var fs = packageFile.OpenRead()) using (var zipArchive = new ZipArchive(fs)) { foreach (var zipEntry in zipArchive.Entries) @@ -172,11 +166,11 @@ namespace Umbraco.Core.Packaging } } - private void ReadZipfileEntries(string packageFilePath, Func entryFunc, bool skipsDirectories = true) + private void ReadZipfileEntries(FileInfo packageFile, Func entryFunc, bool skipsDirectories = true) { - CheckPackageExists(packageFilePath); + CheckPackageExists(packageFile); - using (var fs = File.OpenRead(packageFilePath)) + using (var fs = packageFile.OpenRead()) using (var zipArchive = new ZipArchive(fs)) { foreach (var zipEntry in zipArchive.Entries) diff --git a/src/Umbraco.Core/Packaging/PackageFileInstallation.cs b/src/Umbraco.Core/Packaging/PackageFileInstallation.cs new file mode 100644 index 0000000000..7c0891175b --- /dev/null +++ b/src/Umbraco.Core/Packaging/PackageFileInstallation.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Packaging; +using Umbraco.Core.Services; +using File = System.IO.File; + +namespace Umbraco.Core.Packaging +{ + /// + /// Installs package files + /// + internal class PackageFileInstallation + { + private readonly CompiledPackageXmlParser _parser; + private readonly IProfilingLogger _logger; + private readonly PackageExtraction _packageExtraction; + + public PackageFileInstallation(CompiledPackageXmlParser parser, IProfilingLogger logger) + { + _parser = parser; + _logger = logger; + _packageExtraction = new PackageExtraction(); + } + + /// + /// Returns a list of all installed file paths + /// + /// + /// + /// + /// The absolute path of where to extract the package files (normally the application root) + /// + /// + public IEnumerable InstallFiles(CompiledPackage compiledPackage, FileInfo packageFile, string targetRootFolder) + { + using (_logger.DebugDuration( + "Installing package files for package " + compiledPackage.Name, + "Package file installation complete for package " + compiledPackage.Name)) + { + var sourceAndRelativeDest = _parser.ExtractSourceDestinationFileInformation(compiledPackage.Files); + var sourceAndAbsDest = AppendRootToDestination(targetRootFolder, sourceAndRelativeDest); + + _packageExtraction.CopyFilesFromArchive(packageFile, sourceAndAbsDest); + + return sourceAndRelativeDest.Select(sd => sd.appRelativePath).ToArray(); + } + } + + public IEnumerable UninstallFiles(PackageDefinition package) + { + var removedFiles = new List(); + + foreach (var item in package.Files.ToArray()) + { + removedFiles.Add(item.GetRelativePath()); + + //here we need to try to find the file in question as most packages does not support the tilde char + var file = IOHelper.FindFile(item); + if (file != null) + { + //TODO: Surely this should be ~/ ? + file = file.EnsureStartsWith("/"); + var filePath = IOHelper.MapPath(file); + + if (File.Exists(filePath)) + File.Delete(filePath); + + } + package.Files.Remove(file); + } + + return removedFiles; + } + + private static IEnumerable<(string packageUniqueFile, string appAbsolutePath)> AppendRootToDestination(string applicationRootFolder, IEnumerable<(string packageUniqueFile, string appRelativePath)> sourceDestination) + { + return + sourceDestination.Select( + sd => (sd.packageUniqueFile, Path.Combine(applicationRootFolder, sd.appRelativePath))).ToArray(); + } + } +} diff --git a/src/Umbraco.Core/Packaging/PackageInstallType.cs b/src/Umbraco.Core/Packaging/PackageInstallType.cs new file mode 100644 index 0000000000..015b994aec --- /dev/null +++ b/src/Umbraco.Core/Packaging/PackageInstallType.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Packaging +{ + public enum PackageInstallType + { + AlreadyInstalled, + NewInstall, + Upgrade + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index 556c5203ec..d791295b38 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -1,585 +1,208 @@ using System; using System.Collections.Generic; -using System.Data; +using System.Globalization; using System.IO; using System.Linq; using System.Xml.Linq; -using System.Xml.XPath; -using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Packaging; using Umbraco.Core.Services; -using File = System.IO.File; namespace Umbraco.Core.Packaging { internal class PackageInstallation : IPackageInstallation { - private readonly IFileService _fileService; - private readonly IMacroService _macroService; - private readonly IPackagingService _packagingService; - private IConflictingPackageData _conflictingPackageData; - private readonly IPackageExtraction _packageExtraction; - private string _fullPathToRoot; - - public PackageInstallation(IPackagingService packagingService, IMacroService macroService, - IFileService fileService, IPackageExtraction packageExtraction) - : this(packagingService, macroService, fileService, packageExtraction, IOHelper.GetRootDirectorySafe()) - {} - - public PackageInstallation(IPackagingService packagingService, IMacroService macroService, - IFileService fileService, IPackageExtraction packageExtraction, string fullPathToRoot) - { - if (packageExtraction != null) _packageExtraction = packageExtraction; - else throw new ArgumentNullException("packageExtraction"); - - if (macroService != null) _macroService = macroService; - else throw new ArgumentNullException("macroService"); - - if (fileService != null) _fileService = fileService; - else throw new ArgumentNullException("fileService"); - - if (packagingService != null) _packagingService = packagingService; - else throw new ArgumentNullException("packagingService"); - - _fullPathToRoot = fullPathToRoot; - } - - public IConflictingPackageData ConflictingPackageData - { - private get - { - return _conflictingPackageData ?? - (_conflictingPackageData = new ConflictingPackageData(_macroService, _fileService)); - } - set - { - if (_conflictingPackageData != null) - { - throw new PropertyConstraintException("This property already have a value"); - } - _conflictingPackageData = value; - } - } - - public string FullPathToRoot - { - private get { return _fullPathToRoot; } - set - { - - if (_fullPathToRoot != null) - { - throw new PropertyConstraintException("This property already have a value"); - } - - _fullPathToRoot = value; - } - } - - public MetaData GetMetaData(string packageFilePath) - { - try - { - XElement rootElement = GetConfigXmlElement(packageFilePath); - return GetMetaData(rootElement); - } - catch (Exception e) - { - throw new Exception("Error reading " + packageFilePath, e); - } - } - - public PreInstallWarnings GetPreInstallWarnings(string packageFilePath) - { - try - { - XElement rootElement = GetConfigXmlElement(packageFilePath); - return GetPreInstallWarnings(rootElement); - } - catch (Exception e) - { - throw new Exception("Error reading " + packageFilePath, e); - } - } - - public InstallationSummary InstallPackage(string packageFile, int userId) - { - XElement dataTypes; - XElement languages; - XElement dictionaryItems; - XElement macroes; - XElement files; - XElement templates; - XElement documentTypes; - XElement styleSheets; - XElement documentSet; - XElement documents; - XElement actions; - MetaData metaData; - InstallationSummary installationSummary; - - try - { - XElement rootElement = GetConfigXmlElement(packageFile); - PackageSupportedCheck(rootElement); - PackageStructureSanetyCheck(packageFile, rootElement); - dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); - languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); - dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryItemsNodeName); - macroes = rootElement.Element(Constants.Packaging.MacrosNodeName); - files = rootElement.Element(Constants.Packaging.FilesNodeName); - templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); - documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName); - styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName); - documents = rootElement.Element(Constants.Packaging.DocumentsNodeName); - actions = rootElement.Element(Constants.Packaging.ActionsNodeName); - - metaData = GetMetaData(rootElement); - installationSummary = new InstallationSummary {MetaData = metaData}; - } - catch (Exception e) - { - throw new Exception("Error reading " + packageFile, e); - } - - try - { - var dataTypeDefinitions = EmptyEnumerableIfNull(dataTypes) ?? InstallDataTypes(dataTypes, userId); - installationSummary.DataTypesInstalled = dataTypeDefinitions; - - var languagesInstalled = EmptyEnumerableIfNull(languages) ?? InstallLanguages(languages, userId); - installationSummary.LanguagesInstalled = languagesInstalled; - - var dictionaryInstalled = EmptyEnumerableIfNull(dictionaryItems) ?? InstallDictionaryItems(dictionaryItems); - installationSummary.DictionaryItemsInstalled = dictionaryInstalled; - - var macros = EmptyEnumerableIfNull(macroes) ?? InstallMacros(macroes, userId); - installationSummary.MacrosInstalled = macros; - - var keyValuePairs = EmptyEnumerableIfNull(packageFile) ?? InstallFiles(packageFile, files); - installationSummary.FilesInstalled = keyValuePairs; - - var templatesInstalled = EmptyEnumerableIfNull(templates) ?? InstallTemplats(templates, userId); - installationSummary.TemplatesInstalled = templatesInstalled; - - var documentTypesInstalled = EmptyEnumerableIfNull(documentTypes) ?? InstallDocumentTypes(documentTypes, userId); - installationSummary.ContentTypesInstalled =documentTypesInstalled; - - var stylesheetsInstalled = EmptyEnumerableIfNull(styleSheets) ?? InstallStylesheets(styleSheets); - installationSummary.StylesheetsInstalled = stylesheetsInstalled; - - var documentsInstalled = documents != null ? InstallDocuments(documents, userId) - : EmptyEnumerableIfNull(documentSet) - ?? InstallDocuments(documentSet, userId); - installationSummary.ContentInstalled = documentsInstalled; - - var packageActions = EmptyEnumerableIfNull(actions) ?? GetPackageActions(actions, metaData.Name); - installationSummary.Actions = packageActions; - - installationSummary.PackageInstalled = true; - - return installationSummary; - } - catch (Exception e) - { - throw new Exception("Error installing package " + packageFile, e); - } - } + private readonly PackageExtraction _packageExtraction; + private readonly PackageDataInstallation _packageDataInstallation; + private readonly PackageFileInstallation _packageFileInstallation; + private readonly CompiledPackageXmlParser _parser; + private readonly IPackageActionRunner _packageActionRunner; + private readonly DirectoryInfo _applicationRootFolder; /// - /// Temperary check to test that we support stylesheets + /// Constructor /// - /// - private void PackageSupportedCheck(XElement rootElement) + /// + /// + /// + /// + /// + /// The root folder of the application + /// + public PackageInstallation(PackageDataInstallation packageDataInstallation, PackageFileInstallation packageFileInstallation, CompiledPackageXmlParser parser, IPackageActionRunner packageActionRunner, + DirectoryInfo applicationRootFolder) { - XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - if (styleSheets != null && styleSheets.Elements().Any()) - throw new NotSupportedException("Stylesheets is not suported in this version of umbraco"); + _packageExtraction = new PackageExtraction(); + _packageFileInstallation = packageFileInstallation ?? throw new ArgumentNullException(nameof(packageFileInstallation)); + _packageDataInstallation = packageDataInstallation ?? throw new ArgumentNullException(nameof(packageDataInstallation)); + _parser = parser ?? throw new ArgumentNullException(nameof(parser)); + _packageActionRunner = packageActionRunner ?? throw new ArgumentNullException(nameof(packageActionRunner)); + _applicationRootFolder = applicationRootFolder ?? throw new ArgumentNullException(nameof(applicationRootFolder)); } - private static T[] EmptyArrayIfNull(object obj) + public CompiledPackage ReadPackage(FileInfo packageFile) { - return obj == null ? new T[0] : null; + if (packageFile == null) throw new ArgumentNullException(nameof(packageFile)); + var doc = GetConfigXmlDoc(packageFile); + + var compiledPackage = _parser.ToCompiledPackage(doc, packageFile, _applicationRootFolder.FullName); + + ValidatePackageFile(packageFile, compiledPackage); + + return compiledPackage; } - private static IEnumerable EmptyEnumerableIfNull(object obj) + public IEnumerable InstallPackageFiles(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId) { - return obj == null ? Enumerable.Empty() : null; + if (packageDefinition == null) throw new ArgumentNullException(nameof(packageDefinition)); + if (compiledPackage == null) throw new ArgumentNullException(nameof(compiledPackage)); + + //these should be the same, TODO: we should have a better validator for this + if (packageDefinition.Name != compiledPackage.Name) + throw new InvalidOperationException("The package definition does not match the compiled package manifest"); + + var packageZipFile = compiledPackage.PackageFile; + + var files = _packageFileInstallation.InstallFiles(compiledPackage, packageZipFile, _applicationRootFolder.FullName).ToList(); + + packageDefinition.Files = files; + + return files; } - private XDocument GetConfigXmlDoc(string packageFilePath) + /// + public UninstallationSummary UninstallPackage(PackageDefinition package, int userId) { - var configXmlContent = _packageExtraction.ReadTextFileFromArchive(packageFilePath, - Constants.Packaging.PackageXmlFileName, out _); + //running this will update the PackageDefinition with the items being removed + var summary = _packageDataInstallation.UninstallPackageData(package, userId); - return XDocument.Parse(configXmlContent); + summary.Actions = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(package.Actions), package.Name); + + //run actions before files are removed + summary.ActionErrors = UndoPackageActions(package, summary.Actions).ToList(); + + var filesRemoved = _packageFileInstallation.UninstallFiles(package); + summary.FilesUninstalled = filesRemoved; + + return summary; } - public XElement GetConfigXmlElement(string packageFilePath) + public InstallationSummary InstallPackageData(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId) { - XDocument document = GetConfigXmlDoc(packageFilePath); + var installationSummary = new InstallationSummary + { + DataTypesInstalled = _packageDataInstallation.ImportDataTypes(compiledPackage.DataTypes.ToList(), userId), + LanguagesInstalled = _packageDataInstallation.ImportLanguages(compiledPackage.Languages, userId), + DictionaryItemsInstalled = _packageDataInstallation.ImportDictionaryItems(compiledPackage.DictionaryItems, userId), + MacrosInstalled = _packageDataInstallation.ImportMacros(compiledPackage.Macros, userId), + TemplatesInstalled = _packageDataInstallation.ImportTemplates(compiledPackage.Templates.ToList(), userId), + DocumentTypesInstalled = _packageDataInstallation.ImportDocumentTypes(compiledPackage.DocumentTypes, userId) + }; + + //we need a reference to the imported doc types to continue + var importedDocTypes = installationSummary.DocumentTypesInstalled.ToDictionary(x => x.Alias, x => x); + + installationSummary.StylesheetsInstalled = _packageDataInstallation.ImportStylesheets(compiledPackage.Stylesheets, userId); + installationSummary.ContentInstalled = _packageDataInstallation.ImportContent(compiledPackage.Documents, importedDocTypes, userId); + installationSummary.Actions = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(compiledPackage.Actions), compiledPackage.Name); + installationSummary.MetaData = compiledPackage; + installationSummary.FilesInstalled = packageDefinition.Files; + + //make sure the definition is up to date with everything + foreach (var x in installationSummary.DataTypesInstalled) packageDefinition.DataTypes.Add(x.Id.ToInvariantString()); + foreach (var x in installationSummary.LanguagesInstalled) packageDefinition.Languages.Add(x.Id.ToInvariantString()); + foreach (var x in installationSummary.DictionaryItemsInstalled) packageDefinition.DictionaryItems.Add(x.Id.ToInvariantString()); + foreach (var x in installationSummary.MacrosInstalled) packageDefinition.Macros.Add(x.Id.ToInvariantString()); + foreach (var x in installationSummary.TemplatesInstalled) packageDefinition.Templates.Add(x.Id.ToInvariantString()); + foreach (var x in installationSummary.DocumentTypesInstalled) packageDefinition.DocumentTypes.Add(x.Id.ToInvariantString()); + foreach (var x in installationSummary.StylesheetsInstalled) packageDefinition.Stylesheets.Add(x.Id.ToInvariantString()); + var contentInstalled = installationSummary.ContentInstalled.ToList(); + packageDefinition.ContentNodeId = contentInstalled.Count > 0 ? contentInstalled[0].Id.ToInvariantString() : null; + + //run package actions + installationSummary.ActionErrors = RunPackageActions(packageDefinition, installationSummary.Actions).ToList(); + + return installationSummary; + } + + private IEnumerable RunPackageActions(PackageDefinition packageDefinition, IEnumerable actions) + { + foreach (var n in actions) + { + //if there is an undo section then save it to the definition so we can run it at uninstallation + var undo = n.Undo; + if (undo) + packageDefinition.Actions += n.XmlData.ToString(); + + //Run the actions tagged only for 'install' + if (n.RunAt != ActionRunAt.Install) continue; + + if (n.Alias.IsNullOrWhiteSpace()) continue; + + //run the actions and report errors + if (!_packageActionRunner.RunPackageAction(packageDefinition.Name, n.Alias, n.XmlData, out var err)) + foreach (var e in err) yield return e; + } + } + + private IEnumerable UndoPackageActions(IPackageInfo packageDefinition, IEnumerable actions) + { + foreach (var n in actions) + { + //Run the actions tagged only for 'uninstall' + if (n.RunAt != ActionRunAt.Uninstall) continue; + + if (n.Alias.IsNullOrWhiteSpace()) continue; + + //run the actions and report errors + if (!_packageActionRunner.UndoPackageAction(packageDefinition.Name, n.Alias, n.XmlData, out var err)) + foreach (var e in err) yield return e; + } + } + + private XDocument GetConfigXmlDoc(FileInfo packageFile) + { + var configXmlContent = _packageExtraction.ReadTextFileFromArchive(packageFile, "package.xml", out _); + + var document = XDocument.Parse(configXmlContent); + if (document.Root == null || - document.Root.Name.LocalName.Equals(Constants.Packaging.UmbPackageNodeName) == false) + document.Root.Name.LocalName.Equals("umbPackage") == false) + throw new FormatException("xml does not have a root node called \"umbPackage\""); + + return document; + } + + private void ValidatePackageFile(FileInfo packageFile, CompiledPackage package) + { + if (!(package.Files?.Count > 0)) return; + + var sourceDestination = _parser.ExtractSourceDestinationFileInformation(package.Files).ToArray(); + + var missingFiles = _packageExtraction.FindMissingFiles(packageFile, sourceDestination.Select(i => i.packageUniqueFile)).ToArray(); + + if (missingFiles.Any()) { - throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); + throw new Exception("The following file(s) are missing in the package: " + + string.Join(", ", missingFiles.Select( + mf => + { + var (packageUniqueFile, appRelativePath) = sourceDestination.Single(fi => fi.packageUniqueFile == mf); + return $"source: \"{packageUniqueFile}\" destination: \"{appRelativePath}\""; + }))); } - return document.Root; - } - internal void PackageStructureSanetyCheck(string packageFilePath) - { - XElement rootElement = GetConfigXmlElement(packageFilePath); - PackageStructureSanetyCheck(packageFilePath, rootElement); - } + IEnumerable duplicates = _packageExtraction.FindDuplicateFileNames(packageFile).ToArray(); - private void PackageStructureSanetyCheck(string packageFilePath, XElement rootElement) - { - XElement filesElement = rootElement.Element(Constants.Packaging.FilesNodeName); - if (filesElement != null) + if (duplicates.Any()) { - var sourceDestination = ExtractSourceDestinationFileInformation(filesElement).ToArray(); - - var missingFiles = _packageExtraction.FindMissingFiles(packageFilePath, sourceDestination.Select(i => i.Key)).ToArray(); - - if (missingFiles.Any()) - { - throw new Exception("The following file(s) are missing in the package: " + - string.Join(", ", missingFiles.Select( - mf => - { - var sd = sourceDestination.Single(fi => fi.Key == mf); - return string.Format("source: \"{0}\" destination: \"{1}\"", - sd.Key, sd.Value); - }))); - } - - IEnumerable dubletFileNames = _packageExtraction.FindDubletFileNames(packageFilePath).ToArray(); - - if (dubletFileNames.Any()) - { - throw new Exception("The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " + - string.Join(", ", dubletFileNames)); - } + throw new Exception("The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " + + string.Join(", ", duplicates)); } } - private static IEnumerable GetPackageActions(XElement actionsElement, string packageName) - { - if (actionsElement == null) { return new PackageAction[0]; } - - if (string.Equals(Constants.Packaging.ActionsNodeName, actionsElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.ActionsNodeName + "\" as root", - "actionsElement"); - } - - return actionsElement.Elements(Constants.Packaging.ActionNodeName) - .Select(elemet => - { - XAttribute aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeNameCapital); - if (aliasAttr == null) - throw new ArgumentException( - "missing \"" + Constants.Packaging.AliasNodeNameCapital + "\" atribute in alias element", - "actionsElement"); - - var packageAction = new PackageAction - { - XmlData = elemet, - Alias = aliasAttr.Value, - PackageName = packageName, - }; - - - var attr = elemet.Attribute(Constants.Packaging.RunatNodeAttribute); - - if (attr != null && Enum.TryParse(attr.Value, true, out ActionRunAt runAt)) { packageAction.RunAt = runAt; } - - attr = elemet.Attribute(Constants.Packaging.UndoNodeAttribute); - - if (attr != null && bool.TryParse(attr.Value, out var undo)) { packageAction.Undo = undo; } - - - return packageAction; - }).ToArray(); - } - - private IEnumerable InstallDocuments(XElement documentsElement, int userId = 0) - { - if ((string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName) == false) - && (string.Equals(Constants.Packaging.DocumentsNodeName, documentsElement.Name.LocalName) == false)) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentsNodeName + "\" as root", - "documentsElement"); - } - - if (string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName)) - return _packagingService.ImportContent(documentsElement, -1, userId); - - return - documentsElement.Elements(Constants.Packaging.DocumentSetNodeName) - .SelectMany(documentSetElement => _packagingService.ImportContent(documentSetElement, -1, userId)) - .ToArray(); - } - - private IEnumerable InstallStylesheets(XElement styleSheetsElement) - { - if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root", - "styleSheetsElement"); - } - - // TODO: Call _packagingService when import stylesheets import has been implimentet - if (styleSheetsElement.HasElements == false) { return new List(); } - - throw new NotImplementedException("The packaging service do not yes have a method for importing stylesheets"); - } - - private IEnumerable InstallDocumentTypes(XElement documentTypes, int userId = 0) - { - if (string.Equals(Constants.Packaging.DocumentTypesNodeName, documentTypes.Name.LocalName) == false) - { - if (string.Equals(Constants.Packaging.DocumentTypeNodeName, documentTypes.Name.LocalName) == false) - throw new ArgumentException( - "Must be \"" + Constants.Packaging.DocumentTypesNodeName + "\" as root", "documentTypes"); - - documentTypes = new XElement(Constants.Packaging.DocumentTypesNodeName, documentTypes); - } - - return _packagingService.ImportContentTypes(documentTypes, userId); - } - - private IEnumerable InstallTemplats(XElement templateElement, int userId = 0) - { - if (string.Equals(Constants.Packaging.TemplatesNodeName, templateElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.TemplatesNodeName + "\" as root", - "templateElement"); - } - return _packagingService.ImportTemplates(templateElement, userId); - } - - private IEnumerable InstallFiles(string packageFilePath, XElement filesElement) - { - var sourceDestination = ExtractSourceDestinationFileInformation(filesElement); - sourceDestination = AppendRootToDestination(FullPathToRoot, sourceDestination); - - _packageExtraction.CopyFilesFromArchive(packageFilePath, sourceDestination); - - return sourceDestination.Select(sd => sd.Value).ToArray(); - } - - private KeyValuePair[] AppendRootToDestination(string fullpathToRoot, IEnumerable> sourceDestination) - { - return - sourceDestination.Select( - sd => new KeyValuePair(sd.Key, Path.Combine(fullpathToRoot, sd.Value))).ToArray(); - } - - private IEnumerable InstallMacros(XElement macroElements, int userId = 0) - { - if (string.Equals(Constants.Packaging.MacrosNodeName, macroElements.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.MacrosNodeName + "\" as root", - "macroElements"); - } - return _packagingService.ImportMacros(macroElements, userId); - } - - private IEnumerable InstallDictionaryItems(XElement dictionaryItemsElement) - { - if (string.Equals(Constants.Packaging.DictionaryItemsNodeName, dictionaryItemsElement.Name.LocalName) == - false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.DictionaryItemsNodeName + "\" as root", - "dictionaryItemsElement"); - } - return _packagingService.ImportDictionaryItems(dictionaryItemsElement); - } - - private IEnumerable InstallLanguages(XElement languageElement, int userId = 0) - { - if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.LanguagesNodeName + "\" as root", "languageElement"); - } - return _packagingService.ImportLanguages(languageElement, userId); - } - - private IEnumerable InstallDataTypes(XElement dataTypeElements, int userId = 0) - { - if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName) == false) - { - if (string.Equals(Constants.Packaging.DataTypeNodeName, dataTypeElements.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.DataTypeNodeName + "\" as root", "dataTypeElements"); - } - } - return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId); - } - - private PreInstallWarnings GetPreInstallWarnings(XElement rootElement) - { - XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); - XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); - XElement alias = rootElement.Element(Constants.Packaging.MacrosNodeName); - - var sourceDestination = EmptyArrayIfNull>(files) ?? ExtractSourceDestinationFileInformation(files); - - var installWarnings = new PreInstallWarnings(); - - var macroAliases = EmptyEnumerableIfNull(alias) ?? ConflictingPackageData.FindConflictingMacros(alias); - installWarnings.ConflictingMacroAliases = macroAliases; - - var templateAliases = EmptyEnumerableIfNull(templates) ?? ConflictingPackageData.FindConflictingTemplates(templates); - installWarnings.ConflictingTemplateAliases = templateAliases; - - var stylesheetNames = EmptyEnumerableIfNull(styleSheets) ?? ConflictingPackageData.FindConflictingStylesheets(styleSheets); - installWarnings.ConflictingStylesheetNames = stylesheetNames; - - installWarnings.UnsecureFiles = FindUnsecureFiles(sourceDestination); - installWarnings.FilesReplaced = FindFilesToBeReplaced(sourceDestination); - - return installWarnings; - } - - private KeyValuePair[] FindFilesToBeReplaced(IEnumerable> sourceDestination) - { - return sourceDestination.Where(sd => File.Exists(Path.Combine(FullPathToRoot, sd.Value))).ToArray(); - } - - private KeyValuePair[] FindUnsecureFiles(IEnumerable> sourceDestinationPair) - { - return sourceDestinationPair.Where(sd => IsFileDestinationUnsecure(sd.Value)).ToArray(); - } - - private bool IsFileDestinationUnsecure(string destination) - { - var unsecureDirNames = new[] {"bin", "app_code"}; - if(unsecureDirNames.Any(ud => destination.StartsWith(ud, StringComparison.InvariantCultureIgnoreCase))) - return true; - - string extension = Path.GetExtension(destination); - return extension != null && extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); - } - - private KeyValuePair[] ExtractSourceDestinationFileInformation(XElement filesElement) - { - if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName) == false) - { - throw new ArgumentException("the root element must be \"Files\"", "filesElement"); - } - - return filesElement.Elements(Constants.Packaging.FileNodeName) - .Select(e => - { - XElement guidElement = e.Element(Constants.Packaging.GuidNodeName); - if (guidElement == null) - { - throw new ArgumentException("Missing element \"" + Constants.Packaging.GuidNodeName + "\"", - "filesElement"); - } - - XElement orgPathElement = e.Element(Constants.Packaging.OrgPathNodeName); - if (orgPathElement == null) - { - throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgPathNodeName + "\"", - "filesElement"); - } - - XElement orgNameElement = e.Element(Constants.Packaging.OrgNameNodeName); - if (orgNameElement == null) - { - throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgNameNodeName + "\"", - "filesElement"); - } - - var fileName = PrepareAsFilePathElement(orgNameElement.Value); - var relativeDir = UpdatePathPlaceholders(PrepareAsFilePathElement(orgPathElement.Value)); - - var relativePath = Path.Combine(relativeDir, fileName); - - - return new KeyValuePair(guidElement.Value, relativePath); - }).ToArray(); - } - - private static string PrepareAsFilePathElement(string pathElement) - { - return pathElement.TrimStart(new[] {'\\', '/', '~'}).Replace("/", "\\"); - } - - private MetaData GetMetaData(XElement xRootElement) - { - XElement infoElement = xRootElement.Element(Constants.Packaging.InfoNodeName); - - if (infoElement == null) - { - throw new ArgumentException("Did not hold a \"" + Constants.Packaging.InfoNodeName + "\" element", - "xRootElement"); - } - - var majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath); - var minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath); - var patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath); - var nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath); - var versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath); - var urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath); - var licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath); - var authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath); - var authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath); - var readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath); - - XElement controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName); - - return new MetaData - { - Name = StringValue(nameElement), - Version = StringValue(versionElement), - Url = StringValue(urlElement), - License = StringValue(licenseElement), - LicenseUrl = StringAttribute(licenseElement, Constants.Packaging.PackageLicenseXpathUrlAttribute), - AuthorName = StringValue(authorNameElement), - AuthorUrl = StringValue(authorUrlElement), - Readme = StringValue(readmeElement), - Control = StringValue(controlElement), - ReqMajor = IntValue(majorElement), - ReqMinor = IntValue(minorElement), - ReqPatch = IntValue(patchElement) - }; - } - - private static string StringValue(XElement xElement, string defaultValue = "") - { - return xElement == null ? defaultValue : xElement.Value; - } - - private static string StringAttribute(XElement xElement, string attribute, string defaultValue = "") - { - return xElement == null - ? defaultValue - : xElement.HasAttributes ? xElement.AttributeValue(attribute) : defaultValue; - } - - private static int IntValue(XElement xElement, int defaultValue = 0) - { - return xElement == null ? defaultValue : int.TryParse(xElement.Value, out var val) ? val : defaultValue; - } - - private static string UpdatePathPlaceholders(string path) - { - if (path.Contains("[$")) - { - //this is experimental and undocumented... - path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco); - path = path.Replace("[$CONFIG]", SystemDirectories.Config); - path = path.Replace("[$DATA]", SystemDirectories.Data); - } - return path; - } + + } } diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs new file mode 100644 index 0000000000..1df232f62d --- /dev/null +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -0,0 +1,617 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Packaging; +using Umbraco.Core.Services; +using File = System.IO.File; + +namespace Umbraco.Core.Packaging +{ + /// + /// Manages the storage of installed/created package definitions + /// + internal class PackagesRepository : ICreatedPackagesRepository, IInstalledPackagesRepository + { + private readonly IContentService _contentService; + private readonly IContentTypeService _contentTypeService; + private readonly IDataTypeService _dataTypeService; + private readonly IFileService _fileService; + private readonly IMacroService _macroService; + private readonly ILocalizationService _languageService; + private readonly IEntityXmlSerializer _serializer; + private readonly ILogger _logger; + private readonly string _packageRepositoryFileName; + private readonly string _mediaFolderPath; + private readonly string _packagesFolderPath; + private readonly string _tempFolderPath; + private readonly PackageDefinitionXmlParser _parser; + + /// + /// Constructor + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// The file name for storing the package definitions (i.e. "createdPackages.config") + /// + /// + /// + /// + public PackagesRepository(IContentService contentService, IContentTypeService contentTypeService, + IDataTypeService dataTypeService, IFileService fileService, IMacroService macroService, + ILocalizationService languageService, + IEntityXmlSerializer serializer, ILogger logger, + string packageRepositoryFileName, + string tempFolderPath = null, string packagesFolderPath = null, string mediaFolderPath = null) + { + if (string.IsNullOrWhiteSpace(packageRepositoryFileName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(packageRepositoryFileName)); + _contentService = contentService; + _contentTypeService = contentTypeService; + _dataTypeService = dataTypeService; + _fileService = fileService; + _macroService = macroService; + _languageService = languageService; + _serializer = serializer; + _logger = logger; + _packageRepositoryFileName = packageRepositoryFileName; + + _tempFolderPath = tempFolderPath ?? SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; + _packagesFolderPath = packagesFolderPath ?? SystemDirectories.Packages; + _mediaFolderPath = mediaFolderPath ?? SystemDirectories.Media + "/created-packages"; + + _parser = new PackageDefinitionXmlParser(logger); + } + + private string CreatedPackagesFile => _packagesFolderPath.EnsureEndsWith('/') + _packageRepositoryFileName; + + public IEnumerable GetAll() + { + var packagesXml = EnsureStorage(out _); + if (packagesXml?.Root == null) + yield break;; + + foreach (var packageXml in packagesXml.Root.Elements("package")) + yield return _parser.ToPackageDefinition(packageXml); + } + + public PackageDefinition GetById(int id) + { + var packagesXml = EnsureStorage(out _); + var packageXml = packagesXml?.Root?.Elements("package").FirstOrDefault(x => x.AttributeValue("id") == id); + return packageXml == null ? null : _parser.ToPackageDefinition(packageXml); + } + + public void Delete(int id) + { + var packagesXml = EnsureStorage(out var packagesFile); + var packageXml = packagesXml?.Root?.Elements("package").FirstOrDefault(x => x.AttributeValue("id") == id); + if (packageXml == null) return; + + packageXml.Remove(); + + packagesXml.Save(packagesFile); + } + + public bool SavePackage(PackageDefinition definition) + { + if (definition == null) throw new ArgumentNullException(nameof(definition)); + + var packagesXml = EnsureStorage(out var packagesFile); + + if (packagesXml?.Root == null) + return false; + + //ensure it's valid + ValidatePackage(definition); + + if (definition.Id == default) + { + //need to gen an id and persist + // Find max id + var maxId = packagesXml.Root.Elements("package").Max(x => x.AttributeValue("id")) ?? 0; + var newId = maxId + 1; + definition.Id = newId; + definition.PackageId = definition.PackageId == default ? Guid.NewGuid() : definition.PackageId; + var packageXml = _parser.ToXml(definition); + packagesXml.Root.Add(packageXml); + } + else + { + //existing + var packageXml = packagesXml.Root.Elements("package").FirstOrDefault(x => x.AttributeValue("id") == definition.Id); + if (packageXml == null) + return false; + + var updatedXml = _parser.ToXml(definition); + packageXml.ReplaceWith(updatedXml); + } + + packagesXml.Save(packagesFile); + + return true; + } + + public string ExportPackage(PackageDefinition definition) + { + if (definition.Id == default) throw new ArgumentException("The package definition does not have an ID, it must be saved before being exported"); + if (definition.PackageId == default) throw new ArgumentException("the package definition does not have a GUID, it must be saved before being exported"); + + //ensure it's valid + ValidatePackage(definition); + + //Create a folder for building this package + var temporaryPath = IOHelper.MapPath(_tempFolderPath.EnsureEndsWith('/') + Guid.NewGuid()); + if (Directory.Exists(temporaryPath) == false) + Directory.CreateDirectory(temporaryPath); + + try + { + //Init package file + var compiledPackageXml = CreateCompiledPackageXml(out var root, out var filesXml); + + //Info section + root.Add(GetPackageInfoXml(definition)); + + PackageDocumentsAndTags(definition, root); + PackageDocumentTypes(definition, root); + PackageTemplates(definition, root); + PackageStylesheets(definition, root); + PackageMacros(definition, root, filesXml, temporaryPath); + PackageDictionaryItems(definition, root); + PackageLanguages(definition, root); + PackageDataTypes(definition, root); + + //Files + foreach (var fileName in definition.Files) + AppendFileToPackage(fileName, temporaryPath, filesXml); + + //Load view on install... + if (!string.IsNullOrEmpty(definition.PackageView)) + { + var control = new XElement("view", definition.PackageView); + AppendFileToPackage(definition.PackageView, temporaryPath, filesXml); + root.Add(control); + } + + //Actions + if (string.IsNullOrEmpty(definition.Actions) == false) + { + var actionsXml = new XElement("Actions"); + try + { + //this will be formatted like a full xml block like ... and we want the child nodes + var parsed = XElement.Parse(definition.Actions); + actionsXml.Add(parsed.Elements()); + root.Add(actionsXml); + } + catch (Exception e) + { + _logger.Warn(e, "Could not add package actions to the package, the xml did not parse"); + } + } + + var packageXmlFileName = temporaryPath + "/package.xml"; + + if (File.Exists(packageXmlFileName)) + File.Delete(packageXmlFileName); + + compiledPackageXml.Save(packageXmlFileName); + + // check if there's a packages directory below media + + if (Directory.Exists(IOHelper.MapPath(_mediaFolderPath)) == false) + Directory.CreateDirectory(IOHelper.MapPath(_mediaFolderPath)); + + var packPath = _mediaFolderPath.EnsureEndsWith('/') + (definition.Name + "_" + definition.Version).Replace(' ', '_') + ".zip"; + ZipPackage(temporaryPath, IOHelper.MapPath(packPath)); + + //we need to update the package path and save it + definition.PackagePath = packPath; + SavePackage(definition); + + return packPath; + } + finally + { + //Clean up + Directory.Delete(temporaryPath, true); + } + } + + private void ValidatePackage(PackageDefinition definition) + { + //ensure it's valid + var context = new ValidationContext(definition, serviceProvider: null, items: null); + var results = new List(); + var isValid = Validator.TryValidateObject(definition, context, results); + if (!isValid) + throw new InvalidOperationException("Validation failed, there is invalid data on the model: " + string.Join(", ", results.Select(x => x.ErrorMessage))); + } + + private void PackageDataTypes(PackageDefinition definition, XContainer root) + { + var dataTypes = new XElement("DataTypes"); + foreach (var dtId in definition.DataTypes) + { + if (!int.TryParse(dtId, out var outInt)) continue; + var dataType = _dataTypeService.GetDataType(outInt); + if (dataType == null) continue; + dataTypes.Add(_serializer.Serialize(dataType)); + } + root.Add(dataTypes); + } + + private void PackageLanguages(PackageDefinition definition, XContainer root) + { + var languages = new XElement("Languages"); + foreach (var langId in definition.Languages) + { + if (!int.TryParse(langId, out var outInt)) continue; + var lang = _languageService.GetLanguageById(outInt); + if (lang == null) continue; + languages.Add(_serializer.Serialize(lang)); + } + root.Add(languages); + } + + private void PackageDictionaryItems(PackageDefinition definition, XContainer root) + { + var dictionaryItems = new XElement("DictionaryItems"); + foreach (var dictionaryId in definition.DictionaryItems) + { + if (!int.TryParse(dictionaryId, out var outInt)) continue; + var di = _languageService.GetDictionaryItemById(outInt); + if (di == null) continue; + dictionaryItems.Add(_serializer.Serialize(di, false)); + } + root.Add(dictionaryItems); + } + + private void PackageMacros(PackageDefinition definition, XContainer root, XContainer filesXml, string temporaryPath) + { + var macros = new XElement("Macros"); + foreach (var macroId in definition.Macros) + { + if (!int.TryParse(macroId, out var outInt)) continue; + + var macroXml = GetMacroXml(outInt, out var macro); + if (macroXml == null) continue; + macros.Add(macroXml); + //if the macro has a file copy it to the xml + if (!string.IsNullOrEmpty(macro.MacroSource)) + AppendFileToPackage(macro.MacroSource, temporaryPath, filesXml); + } + root.Add(macros); + } + + private void PackageStylesheets(PackageDefinition definition, XContainer root) + { + var stylesheetsXml = new XElement("Stylesheets"); + foreach (var stylesheetName in definition.Stylesheets) + { + if (stylesheetName.IsNullOrWhiteSpace()) continue; + var xml = GetStylesheetXml(stylesheetName, true); + if (xml != null) + stylesheetsXml.Add(xml); + } + root.Add(stylesheetsXml); + } + + private void PackageTemplates(PackageDefinition definition, XContainer root) + { + var templatesXml = new XElement("Templates"); + foreach (var templateId in definition.Templates) + { + if (!int.TryParse(templateId, out var outInt)) continue; + var template = _fileService.GetTemplate(outInt); + if (template == null) continue; + templatesXml.Add(_serializer.Serialize(template)); + } + root.Add(templatesXml); + } + + private void PackageDocumentTypes(PackageDefinition definition, XContainer root) + { + var contentTypes = new HashSet(); + var docTypesXml = new XElement("DocumentTypes"); + foreach (var dtId in definition.DocumentTypes) + { + if (!int.TryParse(dtId, out var outInt)) continue; + var contentType = _contentTypeService.Get(outInt); + if (contentType == null) continue; + AddDocumentType(contentType, contentTypes); + } + foreach (var contentType in contentTypes) + docTypesXml.Add(_serializer.Serialize(contentType)); + + root.Add(docTypesXml); + } + + private void PackageDocumentsAndTags(PackageDefinition definition, XContainer root) + { + //Documents and tags + if (string.IsNullOrEmpty(definition.ContentNodeId) == false && int.TryParse(definition.ContentNodeId, out var contentNodeId)) + { + if (contentNodeId > 0) + { + //load content from umbraco. + var content = _contentService.GetById(contentNodeId); + if (content != null) + { + var contentXml = definition.ContentLoadChildNodes ? content.ToDeepXml(_serializer) : content.ToXml(_serializer); + + //Create the Documents/DocumentSet node + + root.Add( + new XElement("Documents", + new XElement("DocumentSet", + new XAttribute("importMode", "root"), + contentXml))); + + //TODO: I guess tags has been broken for a very long time for packaging, we should get this working again sometime + ////Create the TagProperties node - this is used to store a definition for all + //// document properties that are tags, this ensures that we can re-import tags properly + //XmlNode tagProps = new XElement("TagProperties"); + + ////before we try to populate this, we'll do a quick lookup to see if any of the documents + //// being exported contain published tags. + //var allExportedIds = documents.SelectNodes("//@id").Cast() + // .Select(x => x.Value.TryConvertTo()) + // .Where(x => x.Success) + // .Select(x => x.Result) + // .ToArray(); + //var allContentTags = new List(); + //foreach (var exportedId in allExportedIds) + //{ + // allContentTags.AddRange( + // Current.Services.TagService.GetTagsForEntity(exportedId)); + //} + + ////This is pretty round-about but it works. Essentially we need to get the properties that are tagged + //// but to do that we need to lookup by a tag (string) + //var allTaggedEntities = new List(); + //foreach (var group in allContentTags.Select(x => x.Group).Distinct()) + //{ + // allTaggedEntities.AddRange( + // Current.Services.TagService.GetTaggedContentByTagGroup(group)); + //} + + ////Now, we have all property Ids/Aliases and their referenced document Ids and tags + //var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId)) + // .DistinctBy(x => x.EntityId) + // .OrderBy(x => x.EntityId); + + //foreach (var taggedEntity in allExportedTaggedEntities) + //{ + // foreach (var taggedProperty in taggedEntity.TaggedProperties.Where(x => x.Tags.Any())) + // { + // XmlNode tagProp = new XElement("TagProperty"); + // var docId = packageManifest.CreateAttribute("docId", ""); + // docId.Value = taggedEntity.EntityId.ToString(CultureInfo.InvariantCulture); + // tagProp.Attributes.Append(docId); + + // var propertyAlias = packageManifest.CreateAttribute("propertyAlias", ""); + // propertyAlias.Value = taggedProperty.PropertyTypeAlias; + // tagProp.Attributes.Append(propertyAlias); + + // var group = packageManifest.CreateAttribute("group", ""); + // group.Value = taggedProperty.Tags.First().Group; + // tagProp.Attributes.Append(group); + + // tagProp.AppendChild(packageManifest.CreateCDataSection( + // JsonConvert.SerializeObject(taggedProperty.Tags.Select(x => x.Text).ToArray()))); + + // tagProps.AppendChild(tagProp); + // } + //} + + //manifestRoot.Add(tagProps); + } + } + } + } + + /// + /// Zips the package. + /// + /// The path. + /// The save path. + private static void ZipPackage(string path, string savePath) + { + if (File.Exists(savePath)) + File.Delete(savePath); + ZipFile.CreateFromDirectory(path, savePath); + } + + /// + /// Appends a file to package and copies the file to the correct folder. + /// + /// The path. + /// The package directory. + /// The files xml node + private static void AppendFileToPackage(string path, string packageDirectory, XContainer filesXml) + { + if (!path.StartsWith("~/") && !path.StartsWith("/")) + path = "~/" + path; + + var serverPath = IOHelper.MapPath(path); + + if (File.Exists(serverPath)) + AppendFileXml(new FileInfo(serverPath), path, packageDirectory, filesXml); + else if (Directory.Exists(serverPath)) + ProcessDirectory(new DirectoryInfo(serverPath), path, packageDirectory, filesXml); + } + + //Process files in directory and add them to package + private static void ProcessDirectory(DirectoryInfo directory, string dirPath, string packageDirectory, XContainer filesXml) + { + if (directory == null) throw new ArgumentNullException(nameof(directory)); + if (string.IsNullOrWhiteSpace(packageDirectory)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(packageDirectory)); + if (string.IsNullOrWhiteSpace(dirPath)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(dirPath)); + if (!directory.Exists) return; + + foreach (var file in directory.GetFiles()) + AppendFileXml(new FileInfo(Path.Combine(directory.FullName, file.Name)), dirPath + "/" + file.Name, packageDirectory, filesXml); + + foreach (var dir in directory.GetDirectories()) + ProcessDirectory(dir, dirPath + "/" + dir.Name, packageDirectory, filesXml); + } + + private static void AppendFileXml(FileInfo file, string filePath, string packageDirectory, XContainer filesXml) + { + if (file == null) throw new ArgumentNullException(nameof(file)); + if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(filePath)); + if (string.IsNullOrWhiteSpace(packageDirectory)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(packageDirectory)); + + var orgPath = filePath.Substring(0, (filePath.LastIndexOf('/'))); + var orgName = filePath.Substring((filePath.LastIndexOf('/') + 1)); + var newFileName = orgName; + + if (File.Exists(packageDirectory.EnsureEndsWith('/') + orgName)) + newFileName = Guid.NewGuid() + "_" + newFileName; + + //Copy file to directory for zipping... + File.Copy(file.FullName, packageDirectory + "/" + newFileName, true); + + filesXml.Add(new XElement("file", + new XElement("guid", newFileName), + new XElement("orgPath", orgPath == "" ? "/" : orgPath), + new XElement("orgName", orgName))); + } + + private XElement GetMacroXml(int macroId, out IMacro macro) + { + macro = _macroService.GetById(macroId); + if (macro == null) return null; + var xml = _serializer.Serialize(macro); + return xml; + } + + /// + /// Converts a umbraco stylesheet to a package xml node + /// + /// The name of the stylesheet. + /// if set to true [incluce properties]. + /// + private XElement GetStylesheetXml(string name, bool includeProperties) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); +; + var sts = _fileService.GetStylesheetByName(name); + if (sts == null) return null; + var stylesheetXml = new XElement("Stylesheet"); + stylesheetXml.Add(new XElement("Name", sts.Alias)); + stylesheetXml.Add(new XElement("FileName", sts.Name)); + stylesheetXml.Add(new XElement("Content", new XCData(sts.Content))); + + if (!includeProperties) return stylesheetXml; + + var properties = new XElement("Properties"); + foreach (var ssP in sts.Properties) + { + var property = new XElement("Property"); + property.Add(new XElement("Name", ssP.Name)); + property.Add(new XElement("Alias", ssP.Alias)); + property.Add(new XElement("Value", ssP.Value)); + } + stylesheetXml.Add(properties); + return stylesheetXml; + } + + private void AddDocumentType(IContentType dt, HashSet dtl) + { + if (dt.ParentId > 0) + { + var parent = _contentTypeService.Get(dt.ParentId); + if (parent != null) // could be a container + AddDocumentType(parent, dtl); + } + + if (!dtl.Contains(dt)) + dtl.Add(dt); + } + + private static XElement GetPackageInfoXml(PackageDefinition definition) + { + var info = new XElement("info"); + + //Package info + var package = new XElement("package"); + package.Add(new XElement("name", definition.Name)); + package.Add(new XElement("version", definition.Version)); + package.Add(new XElement("iconUrl", definition.IconUrl)); + + var license = new XElement("license", definition.License); + license.Add(new XAttribute("url", definition.LicenseUrl)); + package.Add(license); + + package.Add(new XElement("url", definition.Url)); + + var requirements = new XElement("requirements"); + + requirements.Add(new XElement("major", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Major.ToInvariantString() : definition.UmbracoVersion.Major.ToInvariantString())); + requirements.Add(new XElement("minor", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Minor.ToInvariantString() : definition.UmbracoVersion.Minor.ToInvariantString())); + requirements.Add(new XElement("patch", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Patch.ToInvariantString() : definition.UmbracoVersion.Build.ToInvariantString())); + + if (definition.UmbracoVersion != null) + requirements.Add(new XAttribute("type", RequirementsType.Strict.ToString())); + + package.Add(requirements); + info.Add(package); + + //Author + var author = new XElement("author", ""); + author.Add(new XElement("name", definition.Author)); + author.Add(new XElement("website", definition.AuthorUrl)); + info.Add(author); + + info.Add(new XElement("readme", new XCData(definition.Readme))); + + return info; + } + + private static XDocument CreateCompiledPackageXml(out XElement root, out XElement files) + { + files = new XElement("files"); + root = new XElement("umbPackage", files); + var compiledPackageXml = new XDocument(root); + return compiledPackageXml; + } + + private XDocument EnsureStorage(out string packagesFile) + { + var packagesFolder = IOHelper.MapPath(_packagesFolderPath); + //ensure it exists + Directory.CreateDirectory(packagesFolder); + + packagesFile = IOHelper.MapPath(CreatedPackagesFile); + if (!File.Exists(packagesFile)) + { + var xml = new XDocument(new XElement("packages")); + xml.Save(packagesFile); + } + + var packagesXml = XDocument.Load(packagesFile); + return packagesXml; + } + + + + + + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs index d930abc54c..4f3a67aa91 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs @@ -41,6 +41,10 @@ namespace Umbraco.Core.Persistence.Dtos [Constraint(Default = "0")] public bool IsContainer { get; set; } + [Column("isElement")] + [Constraint(Default = "0")] + public bool IsElement { get; set; } + [Column("allowAtRoot")] [Constraint(Default = "0")] public bool AllowAtRoot { get; set; } diff --git a/src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs b/src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs index b2bc990f6b..57e7138827 100644 --- a/src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Dtos // notes // - // we want a unique, non-clustered index on (url ASC, contentId ASC, createDate DESC) but the + // we want a unique, non-clustered index on (url ASC, contentId ASC, culture ASC, createDate DESC) but the // problem is that the index key must be 900 bytes max. should we run without an index? done // some perfs comparisons, and running with an index on a hash is only slightly slower on // inserts, and much faster on reads, so... we have an index on a hash. @@ -41,9 +41,13 @@ namespace Umbraco.Core.Persistence.Dtos [NullSetting(NullSetting = NullSettings.NotNull)] public string Url { get; set; } + [Column("culture")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Culture { get; set; } + [Column("urlHash")] [NullSetting(NullSetting = NullSettings.NotNull)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "urlHash, contentKey, createDateUtc")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "urlHash, contentKey, culture, createDateUtc")] [Length(40)] public string UrlHash { get; set; } } diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index c8467f47e2..7fe1d44921 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -282,7 +282,7 @@ namespace Umbraco.Core.Persistence.Factories var dto = new DocumentVersionDto { Id = entity.VersionId, - TemplateId = entity.Template?.Id, + TemplateId = entity.TemplateId, Published = false, // always building the current, unpublished one ContentVersionDto = BuildContentVersionDto(entity, contentDto) diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 38a1aa2aab..7a04a6d0d9 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -107,6 +107,7 @@ namespace Umbraco.Core.Persistence.Factories entity.CreatorId = dto.NodeDto.UserId ?? Constants.Security.UnknownUserId; entity.AllowedAsRoot = dto.AllowAtRoot; entity.IsContainer = dto.IsContainer; + entity.IsElement = dto.IsElement; entity.Trashed = dto.NodeDto.Trashed; entity.Variations = (ContentVariation) dto.Variations; } @@ -132,6 +133,7 @@ namespace Umbraco.Core.Persistence.Factories NodeId = entity.Id, AllowAtRoot = entity.AllowedAsRoot, IsContainer = entity.IsContainer, + IsElement = entity.IsElement, Variations = (byte) entity.Variations, NodeDto = BuildNodeDto(entity, nodeObjectType) }; diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index c920a18c3b..728441964a 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -93,18 +93,36 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - public static IEnumerable BuildDtos(int currentVersionId, int publishedVersionId, IEnumerable properties, ILanguageRepository languageRepository, out bool edited, out HashSet editedCultures) + /// + /// Creates a collection of from a collection of + /// + /// + /// The of the entity containing the collection of + /// + /// + /// + /// The properties to map + /// + /// out parameter indicating that one or more properties have been edited + /// out parameter containing a collection of of edited cultures when the contentVariation varies by culture + /// + public static IEnumerable BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties, + ILanguageRepository languageRepository, out bool edited, out HashSet editedCultures) { var propertyDataDtos = new List(); edited = false; editedCultures = null; // don't allocate unless necessary + string defaultCulture = null; //don't allocate unless necessary + + var entityVariesByCulture = contentVariation.VariesByCulture(); foreach (var property in properties) { if (property.PropertyType.IsPublishing) { - var editingCultures = property.PropertyType.VariesByCulture(); - if (editingCultures && editedCultures == null) editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); + //create the resulting hashset if it's not created and the entity varies by culture + if (entityVariesByCulture && editedCultures == null) + editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); // publishing = deal with edit and published values foreach (var propertyValue in property.Values) @@ -125,12 +143,24 @@ namespace Umbraco.Core.Persistence.Factories var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); edited |= !sameValues; - if (editingCultures && // cultures can be edited, ie CultureNeutral is supported - propertyValue.Culture != null && propertyValue.Segment == null && // and value is CultureNeutral - !sameValues) // and edited and published are different + if (entityVariesByCulture // cultures can be edited, ie CultureNeutral is supported + && propertyValue.Culture != null && propertyValue.Segment == null // and value is CultureNeutral + && !sameValues) // and edited and published are different { editedCultures.Add(propertyValue.Culture); // report culture as edited } + + // flag culture as edited if it contains an edited invariant property + if (propertyValue.Culture == null //invariant property + && !sameValues // and edited and published are different + && entityVariesByCulture) //only when the entity is variant + { + if (defaultCulture == null) + defaultCulture = languageRepository.GetDefaultIsoCode(); + + editedCultures.Add(defaultCulture); + } + } } else diff --git a/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs index 9ef320e187..0236fc4bd5 100644 --- a/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs @@ -18,6 +18,12 @@ namespace Umbraco.Core.Persistence /// bool Configured { get; } + /// + /// Gets the connection string. + /// + /// Throws if the factory is not configured. + string ConnectionString { get; } + /// /// Gets a value indicating whether the database can connect. /// diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs index c692a75474..a24963bace 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs @@ -35,6 +35,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Description, dto => dto.Description); CacheMap(src => src.Icon, dto => dto.Icon); CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.IsElement, dto => dto.IsElement); CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs index 6d641cab3d..80819933f5 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs @@ -1,19 +1,14 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.Persistence.Mappers { public class MapperCollectionBuilder : LazyCollectionBuilderBase { - public MapperCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override MapperCollectionBuilder This => this; - protected override void Initialize() + public override void RegisterWith(IRegister register) { - base.Initialize(); + base.RegisterWith(register); // default initializer registers // - service MapperCollectionBuilder, returns MapperCollectionBuilder @@ -21,10 +16,10 @@ namespace Umbraco.Core.Persistence.Mappers // we want to register extra // - service IMapperCollection, returns MappersCollectionBuilder's collection - Container.Register(factory => factory.GetInstance()); + register.Register(factory => factory.GetInstance()); } - public MapperCollectionBuilder AddCore() + public MapperCollectionBuilder AddCoreMappers() { Add(); Add(); diff --git a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs index 3f5a6e24bc..6cf83bc7aa 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs @@ -35,6 +35,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Description, dto => dto.Description); CacheMap(src => src.Icon, dto => dto.Icon); CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.IsElement, dto => dto.IsElement); CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs index 28dc19171f..9a4e4ec040 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs @@ -35,6 +35,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Description, dto => dto.Description); CacheMap(src => src.Icon, dto => dto.Icon); CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.IsElement, dto => dto.IsElement); CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs index 460bed71d3..57d3871e5a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs @@ -1,5 +1,5 @@ namespace Umbraco.Core.Persistence.Repositories { - interface IDataTypeContainerRepository : IEntityContainerRepository + public interface IDataTypeContainerRepository : IEntityContainerRepository { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs index ccf8df268d..ec8dfb9110 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs @@ -1,5 +1,5 @@ namespace Umbraco.Core.Persistence.Repositories { - interface IDocumentTypeContainerRepository : IEntityContainerRepository + public interface IDocumentTypeContainerRepository : IEntityContainerRepository { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs index 47f0cee52b..f1c8353a0d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs @@ -2,6 +2,6 @@ namespace Umbraco.Core.Persistence.Repositories { - interface IEntityContainerRepository : IReadRepository, IWriteRepository + public interface IEntityContainerRepository : IReadRepository, IWriteRepository { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs index eefb48933e..1ed08352ed 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - internal interface IMacroRepository : IReadWriteQueryRepository, IReadRepository + public interface IMacroRepository : IReadWriteQueryRepository, IReadRepository { //IEnumerable GetAll(params string[] aliases); diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs index a1872df0cd..6a133c053a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs @@ -1,5 +1,5 @@ namespace Umbraco.Core.Persistence.Repositories { - interface IMediaTypeContainerRepository : IEntityContainerRepository + public interface IMediaTypeContainerRepository : IEntityContainerRepository { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs index 47a56bb530..d05f4e007c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs @@ -14,8 +14,9 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The Umbraco redirect url route. /// The content unique key. + /// The culture. /// - IRedirectUrl Get(string url, Guid contentKey); + IRedirectUrl Get(string url, Guid contentKey, string culture); /// /// Deletes a redirect url. diff --git a/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs index 4bab445bee..2e9bdcfc4a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.Collections.Generic; using System.IO; using Umbraco.Core.Models; @@ -18,21 +16,6 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable GetDescendants(int masterTemplateId); IEnumerable GetDescendants(string alias); - /// - /// This checks what the default rendering engine is set in config but then also ensures that there isn't already - /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate - /// rendering engine to use. - /// - /// - /// - /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx - /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml - /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. - /// This is mostly related to installing packages since packages install file templates to the file system and then create the - /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. - /// - RenderingEngine DetermineTemplateRenderingEngine(ITemplate template); - /// /// Validates a /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs index 77759ea2da..1486935e2a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Initializes a new instance of the class. /// - public AuditEntryRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public AuditEntryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs index 6c61fe7ad5..cda89fd89a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using NPoco; using Umbraco.Core.Cache; -using Umbraco.Core.Composing.CompositionRoots; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -16,8 +14,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class AuditRepository : NPocoRepositoryBase, IAuditRepository { - public AuditRepository(IScopeAccessor scopeAccessor, [Inject(RepositoryCompositionRoot.DisabledCache)] CacheHelper cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public AuditRepository(IScopeAccessor scopeAccessor, ILogger logger) + : base(scopeAccessor, AppCaches.NoCache, logger) { } protected override void PersistNewItem(IAuditItem entity) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs index 3794bf183a..8df9bf686d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Initializes a new instance of the class. /// - public ConsentRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public ConsentRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } @@ -86,7 +86,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Update(dto); entity.ResetDirtyProperties(); - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.Id)); + IsolatedCache.Clear(RepositoryCacheKeys.GetKey(entity.Id)); } /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index bd7943ff1d..ba56c17087 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement where TEntity : class, IUmbracoEntity where TRepository : class, IRepository { - protected ContentRepositoryBase(IScopeAccessor scopeAccessor, CacheHelper cache, ILanguageRepository languageRepository, ILogger logger) + protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILanguageRepository languageRepository, ILogger logger) : base(scopeAccessor, cache, logger) { LanguageRepository = languageRepository; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs index 4bec3160a7..f608e2968d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly ITemplateRepository _templateRepository; - public ContentTypeRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, ITemplateRepository templateRepository) + public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, ITemplateRepository templateRepository) : base(scopeAccessor, cache, logger) { _templateRepository = templateRepository; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 662254d1ee..6404880a2e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -28,7 +28,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal abstract class ContentTypeRepositoryBase : NPocoRepositoryBase, IReadRepository where TEntity : class, IContentTypeComposition { - protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } @@ -1283,7 +1283,7 @@ AND umbracoNode.id <> @id", if (db == null) throw new ArgumentNullException(nameof(db)); var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.variations as ctVariations, - cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, + cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.IsElement as ctIsElement, 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.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, @@ -1384,6 +1384,7 @@ AND umbracoNode.id <> @id", Description = currCt.ctDesc, Icon = currCt.ctIcon, IsContainer = currCt.ctIsContainer, + IsElement = currCt.ctIsElement, NodeId = currCt.ctId, PrimaryKey = currCt.ctPk, Thumbnail = currCt.ctThumb, @@ -1422,7 +1423,7 @@ AND umbracoNode.id <> @id", var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.variations as ctVariations, - cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, + cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.IsElement as ctIsElement, 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.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, @@ -1559,6 +1560,7 @@ AND umbracoNode.id <> @id", Description = currCt.ctDesc, Icon = currCt.ctIcon, IsContainer = currCt.ctIsContainer, + IsElement = currCt.ctIsElement, NodeId = currCt.ctId, PrimaryKey = currCt.ctPk, Thumbnail = currCt.ctThumb, diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeContainerRepository.cs index f23b6df5e2..752b641bc3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeContainerRepository.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { class DataTypeContainerRepository : EntityContainerRepository, IDataTypeContainerRepository { - public DataTypeContainerRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public DataTypeContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger, Constants.ObjectTypes.DataTypeContainer) { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs index 4556c78fe6..28d4262763 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly Lazy _editors; // fixme temp fixing circular dependencies with LAZY but is this the right place? - public DataTypeRepository(IScopeAccessor scopeAccessor, CacheHelper cache, Lazy editors, ILogger logger) + public DataTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, Lazy editors, ILogger logger) : base(scopeAccessor, cache, logger) { _editors = editors; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs index 8f5ad70c32..be1e28fcc1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class DictionaryRepository : NPocoRepositoryBase, IDictionaryRepository { - public DictionaryRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public DictionaryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } @@ -174,8 +174,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.ResetDirtyProperties(); //Clear the cache entries that exist by uniqueid/item key - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.ItemKey)); - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.Key)); + IsolatedCache.Clear(RepositoryCacheKeys.GetKey(entity.ItemKey)); + IsolatedCache.Clear(RepositoryCacheKeys.GetKey(entity.Key)); } protected override void PersistDeletedItem(IDictionaryItem entity) @@ -186,8 +186,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Delete("WHERE id = @Id", new { Id = entity.Key }); //Clear the cache entries that exist by uniqueid/item key - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.ItemKey)); - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.Key)); + IsolatedCache.Clear(RepositoryCacheKeys.GetKey(entity.ItemKey)); + IsolatedCache.Clear(RepositoryCacheKeys.GetKey(entity.Key)); entity.DeleteDate = DateTime.Now; } @@ -203,8 +203,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Delete("WHERE id = @Id", new { Id = dto.UniqueId }); //Clear the cache entries that exist by uniqueid/item key - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(dto.Key)); - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(dto.UniqueId)); + IsolatedCache.Clear(RepositoryCacheKeys.GetKey(dto.Key)); + IsolatedCache.Clear(RepositoryCacheKeys.GetKey(dto.UniqueId)); } } @@ -224,13 +224,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public IDictionaryItem Get(Guid uniqueId) { - var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, ScopeAccessor, GlobalCache, Logger); + var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, ScopeAccessor, AppCaches, Logger); return uniqueIdRepo.Get(uniqueId); } public IDictionaryItem Get(string key) { - var keyRepo = new DictionaryByKeyRepository(this, ScopeAccessor, GlobalCache, Logger); + var keyRepo = new DictionaryByKeyRepository(this, ScopeAccessor, AppCaches, Logger); return keyRepo.Get(key); } @@ -290,7 +290,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly DictionaryRepository _dictionaryRepository; - public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { _dictionaryRepository = dictionaryRepository; @@ -343,7 +343,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly DictionaryRepository _dictionaryRepository; - public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { _dictionaryRepository = dictionaryRepository; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs index 1d24cfd2dc..09fa420f26 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs @@ -17,8 +17,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class DocumentBlueprintRepository : DocumentRepository, IDocumentBlueprintRepository { - public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, CacheHelper cacheHelper, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IContentSection settings) - : base(scopeAccessor, cacheHelper, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, settings) + public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IContentSection settings) + : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, settings) { EnsureUniqueNaming = false; // duplicates are allowed } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 35496aaba7..31c08f9124 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -25,20 +25,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly IContentTypeRepository _contentTypeRepository; private readonly ITemplateRepository _templateRepository; private readonly ITagRepository _tagRepository; - private readonly CacheHelper _cacheHelper; + private readonly AppCaches _appCaches; private PermissionRepository _permissionRepository; private readonly ContentByGuidReadRepository _contentByGuidReadRepository; private readonly IScopeAccessor _scopeAccessor; - public DocumentRepository(IScopeAccessor scopeAccessor, CacheHelper cacheHelper, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IContentSection settings) - : base(scopeAccessor, cacheHelper, languageRepository, logger) + public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IContentSection settings) + : base(scopeAccessor, appCaches, languageRepository, logger) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); - _cacheHelper = cacheHelper; + _appCaches = appCaches; _scopeAccessor = scopeAccessor; - _contentByGuidReadRepository = new ContentByGuidReadRepository(this, scopeAccessor, cacheHelper, logger); + _contentByGuidReadRepository = new ContentByGuidReadRepository(this, scopeAccessor, appCaches, logger); EnsureUniqueNaming = settings.EnsureUniqueNaming; } @@ -48,7 +48,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // note: is ok to 'new' the repo here as it's a sub-repo really private PermissionRepository PermissionRepository => _permissionRepository - ?? (_permissionRepository = new PermissionRepository(_scopeAccessor, _cacheHelper, Logger)); + ?? (_permissionRepository = new PermissionRepository(_scopeAccessor, _appCaches, Logger)); #region Repository Base @@ -273,8 +273,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var publishing = content.PublishedState == PublishedState.Publishing; // ensure that the default template is assigned - if (entity.Template == null) - entity.Template = entity.ContentType.DefaultTemplate; + if (entity.TemplateId.HasValue == false) + entity.TemplateId = entity.ContentType.DefaultTemplate?.Id; // sanitize names SanitizeNames(content, publishing); @@ -352,7 +352,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(content.VersionId, content.PublishedVersionId, entity.Properties, LanguageRepository, out var edited, out var editedCultures); + var propertyDataDtos = PropertyFactory.BuildDtos(content.ContentType.Variations, content.VersionId, content.PublishedVersionId, entity.Properties, LanguageRepository, out var edited, out var editedCultures); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); @@ -404,7 +404,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (content.PublishedState == PublishedState.Publishing) { content.Published = true; - content.PublishTemplate = content.Template; + content.PublishTemplateId = content.TemplateId; content.PublisherId = content.WriterId; content.PublishName = content.Name; content.PublishDate = content.UpdateDate; @@ -414,7 +414,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement else if (content.PublishedState == PublishedState.Unpublishing) { content.Published = false; - content.PublishTemplate = null; + content.PublishTemplateId = null; content.PublisherId = null; content.PublishName = null; content.PublishDate = null; @@ -527,7 +527,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Execute(deletePropertyDataSql); // insert property data - var propertyDataDtos = PropertyFactory.BuildDtos(content.VersionId, publishing ? content.PublishedVersionId : 0, entity.Properties, LanguageRepository, out var edited, out var editedCultures); + var propertyDataDtos = PropertyFactory.BuildDtos(content.ContentType.Variations, content.VersionId, publishing ? content.PublishedVersionId : 0, + entity.Properties, LanguageRepository, out var edited, out var editedCultures); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); @@ -608,7 +609,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (content.PublishedState == PublishedState.Publishing) { content.Published = true; - content.PublishTemplate = content.Template; + content.PublishTemplateId = content.TemplateId; content.PublisherId = content.WriterId; content.PublishName = content.Name; content.PublishDate = content.UpdateDate; @@ -618,7 +619,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement else if (content.PublishedState == PublishedState.Unpublishing) { content.Published = false; - content.PublishTemplate = null; + content.PublishTemplateId = null; content.PublisherId = null; content.PublishName = null; content.PublishDate = null; @@ -846,7 +847,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly DocumentRepository _outerRepo; - public ContentByGuidReadRepository(DocumentRepository outerRepo, IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public ContentByGuidReadRepository(DocumentRepository outerRepo, IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { _outerRepo = outerRepo; @@ -1077,18 +1078,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // assign templates and properties foreach (var temp in temps) { - // complete the item - if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template)) - temp.Content.Template = template; - if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template)) - temp.Content.PublishTemplate = template; + // set the template ID if it matches an existing template + if (temp.Template1Id.HasValue && templates.ContainsKey(temp.Template1Id.Value)) + temp.Content.TemplateId = temp.Template1Id; + if (temp.Template2Id.HasValue && templates.ContainsKey(temp.Template2Id.Value)) + temp.Content.PublishTemplateId = temp.Template2Id; + // set properties if (properties.ContainsKey(temp.VersionId)) temp.Content.Properties = properties[temp.VersionId]; else throw new InvalidOperationException($"No property data found for version: '{temp.VersionId}'."); - //load in the schedule + // load in the schedule if (schedule.TryGetValue(temp.Content.Id, out var s)) temp.Content.ContentSchedule = s; } @@ -1122,7 +1124,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // get template if (dto.DocumentVersionDto.TemplateId.HasValue && dto.DocumentVersionDto.TemplateId.Value > 0) - content.Template = _templateRepository.Get(dto.DocumentVersionDto.TemplateId.Value); + content.TemplateId = dto.DocumentVersionDto.TemplateId; // get properties - indexed by version id var versionId = dto.DocumentVersionDto.Id; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs index de8042cfcf..d50981e036 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { class DocumentTypeContainerRepository : EntityContainerRepository, IDocumentTypeContainerRepository { - public DocumentTypeContainerRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public DocumentTypeContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger, Constants.ObjectTypes.DocumentTypeContainer) { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs index f75d82bd4e..fa06216f9b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal class DomainRepository : NPocoRepositoryBase, IDomainRepository { - public DomainRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public DomainRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs index fae7ae2ebc..09fe949df1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly Guid _containerObjectType; - public EntityContainerRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, Guid containerObjectType) + public EntityContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, Guid containerObjectType) : base(scopeAccessor, cache, logger) { var allowedContainers = new[] { Constants.ObjectTypes.DocumentTypeContainer, Constants.ObjectTypes.MediaTypeContainer, Constants.ObjectTypes.DataTypeContainer }; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs index ce9a44f595..7cb78f4c9f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class ExternalLoginRepository : NPocoRepositoryBase, IExternalLoginRepository { - public ExternalLoginRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public ExternalLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index e236670e74..b3f2ff4af0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly Dictionary _codeIdMap = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _idCodeMap = new Dictionary(); - public LanguageRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public LanguageRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs index 594f26fa72..565917e078 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class MacroRepository : NPocoRepositoryBase, IMacroRepository { - public MacroRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public MacroRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index dbfdc8e980..f2c7e35395 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly ITagRepository _tagRepository; private readonly MediaByGuidReadRepository _mediaByGuidReadRepository; - public MediaRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection, ILanguageRepository languageRepository) + public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection, ILanguageRepository languageRepository) : base(scopeAccessor, cache, languageRepository, logger) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); @@ -284,7 +284,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Insert(mediaVersionDto); // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(media.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); + var propertyDataDtos = PropertyFactory.BuildDtos(media.ContentType.Variations, media.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); @@ -341,7 +341,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // replace the property data var deletePropertyDataSql = SqlContext.Sql().Delete().Where(x => x.VersionId == media.VersionId); Database.Execute(deletePropertyDataSql); - var propertyDataDtos = PropertyFactory.BuildDtos(media.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); + var propertyDataDtos = PropertyFactory.BuildDtos(media.ContentType.Variations, media.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); @@ -395,7 +395,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly MediaRepository _outerRepo; - public MediaByGuidReadRepository(MediaRepository outerRepo, IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public MediaByGuidReadRepository(MediaRepository outerRepo, IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { _outerRepo = outerRepo; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs index 318d3a9c5a..68b33e989d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { class MediaTypeContainerRepository : EntityContainerRepository, IMediaTypeContainerRepository { - public MediaTypeContainerRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public MediaTypeContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger, Constants.ObjectTypes.MediaTypeContainer) { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs index 7dc1c29396..4639871a4a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class MediaTypeRepository : ContentTypeRepositoryBase, IMediaTypeRepository { - public MediaTypeRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs index e80faaa44a..e6ee79470c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class MemberGroupRepository : NPocoRepositoryBase, IMemberGroupRepository { - public MemberGroupRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public MemberGroupRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } @@ -124,7 +124,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var result = Get(qry); return result.FirstOrDefault(); }, - //cache for 5 mins since that is the default in the RuntimeCacheProvider + //cache for 5 mins since that is the default in the Runtime app cache TimeSpan.FromMinutes(5), //sliding is true true); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index fd79b231de..2daa4abeca 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly ITagRepository _tagRepository; private readonly IMemberGroupRepository _memberGroupRepository; - public MemberRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) + public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) : base(scopeAccessor, cache, languageRepository, logger) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); @@ -310,7 +310,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Insert(dto); // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(member.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); + var propertyDataDtos = PropertyFactory.BuildDtos(member.ContentType.Variations, member.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); @@ -375,7 +375,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // replace the property data var deletePropertyDataSql = SqlContext.Sql().Delete().Where(x => x.VersionId == member.VersionId); Database.Execute(deletePropertyDataSql); - var propertyDataDtos = PropertyFactory.BuildDtos(member.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); + var propertyDataDtos = PropertyFactory.BuildDtos(member.ContentType.Variations, member.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index 023e308ad3..b81ce4010b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class MemberTypeRepository : ContentTypeRepositoryBase, IMemberTypeRepository { - public MemberTypeRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/NPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/NPocoRepositoryBase.cs index df7517469c..234693602f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/NPocoRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/NPocoRepositoryBase.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Initializes a new instance of the class. /// - protected NPocoRepositoryBase(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + protected NPocoRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewMacroRepository.cs index 741bb98e7c..d707bcee10 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewMacroRepository.cs @@ -1,14 +1,12 @@ -using LightInject; -using Umbraco.Core.IO; +using Umbraco.Core.IO; using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories.Implement { internal class PartialViewMacroRepository : PartialViewRepository, IPartialViewMacroRepository { - - public PartialViewMacroRepository([Inject("PartialViewMacroFileSystem")] IFileSystem fileSystem) - : base(fileSystem) + public PartialViewMacroRepository(IFileSystems fileSystems) + : base(fileSystems.MacroPartialsFileSystem) { } protected override PartialViewType ViewType => PartialViewType.PartialViewMacro; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewRepository.cs index 2aa63813e5..d04bc47cd8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewRepository.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using System.Text; -using LightInject; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -10,7 +9,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class PartialViewRepository : FileRepository, IPartialViewRepository { - public PartialViewRepository([Inject("PartialViewFileSystem")] IFileSystem fileSystem) + public PartialViewRepository(IFileSystems fileSystems) + : base(fileSystems.PartialViewsFileSystem) + { } + + protected PartialViewRepository(IFileSystem fileSystem) : base(fileSystem) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs index 8261ed2ea3..b4fd86c567 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal class PermissionRepository : NPocoRepositoryBase where TEntity : class, IEntity { - public PermissionRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public PermissionRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs index f49905573d..bd2580b38f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class PublicAccessRepository : NPocoRepositoryBase, IPublicAccessRepository { - public PublicAccessRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public PublicAccessRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs index 5ec7fd6f3c..baac02b6bf 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class RedirectUrlRepository : NPocoRepositoryBase, IRedirectUrlRepository { - public RedirectUrlRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public RedirectUrlRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } @@ -104,6 +104,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); ContentKey = redirectUrl.ContentKey, CreateDateUtc = redirectUrl.CreateDateUtc, Url = redirectUrl.Url, + Culture = redirectUrl.Culture, UrlHash = redirectUrl.Url.ToSHA1() }; } @@ -121,6 +122,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); url.ContentId = dto.ContentId; url.ContentKey = dto.ContentKey; url.CreateDateUtc = dto.CreateDateUtc; + url.Culture = dto.Culture; url.Url = dto.Url; return url; } @@ -130,10 +132,10 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); } } - public IRedirectUrl Get(string url, Guid contentKey) + public IRedirectUrl Get(string url, Guid contentKey, string culture) { var urlHash = url.ToSHA1(); - var sql = GetBaseQuery(false).Where(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey); + var sql = GetBaseQuery(false).Where(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey && x.Culture == culture); var dto = Database.Fetch(sql).FirstOrDefault(); return dto == null ? null : Map(dto); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index f79344028f..c5ba24f385 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using NPoco; using Umbraco.Core.Cache; -using Umbraco.Core.Composing.CompositionRoots; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -22,8 +20,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly IRelationTypeRepository _relationTypeRepository; - public RelationRepository(IScopeAccessor scopeAccessor, [Inject(RepositoryCompositionRoot.DisabledCache)] CacheHelper cache, ILogger logger, IRelationTypeRepository relationTypeRepository) - : base(scopeAccessor, cache, logger) + public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository) + : base(scopeAccessor, AppCaches.NoCache, logger) { _relationTypeRepository = relationTypeRepository; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs index e4d8396e71..4faf78bd0a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class RelationTypeRepository : NPocoRepositoryBase, IRelationTypeRepository { - public RelationTypeRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public RelationTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs index 84c76dbb53..c8329d1f32 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs @@ -19,18 +19,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private IRepositoryCachePolicy _cachePolicy; - protected RepositoryBase(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + protected RepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger) { ScopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - GlobalCache = cache ?? throw new ArgumentNullException(nameof(cache)); + AppCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches)); } protected ILogger Logger { get; } - protected CacheHelper GlobalCache { get; } + protected AppCaches AppCaches { get; } - protected IRuntimeCacheProvider GlobalIsolatedCache => GlobalCache.IsolatedRuntimeCache.GetOrCreateCache(); + protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate(); protected IScopeAccessor ScopeAccessor { get; } @@ -60,18 +60,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// Gets the isolated cache. /// /// Depends on the ambient scope cache mode. - protected IRuntimeCacheProvider IsolatedCache + protected IAppPolicyCache IsolatedCache { get { switch (AmbientScope.RepositoryCacheMode) { case RepositoryCacheMode.Default: - return GlobalCache.IsolatedRuntimeCache.GetOrCreateCache(); + return AppCaches.IsolatedCaches.GetOrCreate(); case RepositoryCacheMode.Scoped: - return AmbientScope.IsolatedRuntimeCache.GetOrCreateCache(); + return AmbientScope.IsolatedCaches.GetOrCreate(); case RepositoryCacheMode.None: - return NullCacheProvider.Instance; + return NoAppCache.Instance; default: throw new Exception("oops: cache mode."); } @@ -127,7 +127,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { get { - if (GlobalCache == CacheHelper.NoCache) + if (AppCaches == AppCaches.NoCache) return NoCacheRepositoryCachePolicy.Instance; // create the cache policy using IsolatedCache which is either global @@ -157,7 +157,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Adds or Updates an entity of type TEntity /// - /// This method is backed by an cache + /// This method is backed by an cache /// public void Save(TEntity entity) { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ScriptRepository.cs index d5719554c9..85b41a2a1c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ScriptRepository.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using LightInject; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -16,8 +15,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly IContentSection _contentConfig; - public ScriptRepository([Inject("ScriptFileSystem")] IFileSystem fileSystem, IContentSection contentConfig) - : base(fileSystem) + public ScriptRepository(IFileSystems fileSystems, IContentSection contentConfig) + : base(fileSystems.ScriptsFileSystem) { _contentConfig = contentConfig ?? throw new ArgumentNullException(nameof(contentConfig)); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs index 531df1ba13..298e503736 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs @@ -14,9 +14,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class ServerRegistrationRepository : NPocoRepositoryBase, IServerRegistrationRepository { - // fixme - should we use NoCache instead of CreateDisabledCacheHelper?! public ServerRegistrationRepository(IScopeAccessor scopeAccessor, ILogger logger) - : base(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), logger) + : base(scopeAccessor, AppCaches.NoCache, logger) { } protected override IRepositoryCachePolicy CreateCachePolicy() @@ -29,7 +28,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // and this is because the repository is special and should not participate in scopes // (cleanup in v8) // - return new FullDataSetRepositoryCachePolicy(GlobalCache.RuntimeCache, ScopeAccessor, GetEntityId, /*expires:*/ false); + return new FullDataSetRepositoryCachePolicy(AppCaches.RuntimeCache, ScopeAccessor, GetEntityId, /*expires:*/ false); } public void ClearCache() diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs index 7a3bfdb757..43233d0f31 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement where TEntity : class, IEntity where TDto: class { - protected SimpleGetRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + protected SimpleGetRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs index 73dcb44fef..4c02a8f4b5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using LightInject; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -12,8 +11,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class StylesheetRepository : FileRepository, IStylesheetRepository { - public StylesheetRepository([Inject("StylesheetFileSystem")] IFileSystem fileSystem) - : base(fileSystem) + public StylesheetRepository(IFileSystems fileSystems) + : base(fileSystems.StylesheetsFileSystem) { } #region Overrides of FileRepository diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs index 77e474be08..7dd3f03407 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class TagRepository : NPocoRepositoryBase, ITagRepository { - public TagRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public TagRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs index 83876db599..fc27c38d70 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using LightInject; using NPoco; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -24,22 +22,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class TemplateRepository : NPocoRepositoryBase, ITemplateRepository { - private readonly IFileSystem _masterpagesFileSystem; private readonly IFileSystem _viewsFileSystem; - private readonly ITemplatesSection _templateConfig; private readonly ViewHelper _viewHelper; - private readonly MasterPageHelper _masterPageHelper; - public TemplateRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, ITemplatesSection templateConfig, - [Inject(Constants.Composing.FileSystems.MasterpageFileSystem)] IFileSystem masterpageFileSystem, - [Inject(Constants.Composing.FileSystems.ViewFileSystem)] IFileSystem viewFileSystem) + public TemplateRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IFileSystems fileSystems) : base(scopeAccessor, cache, logger) { - _masterpagesFileSystem = masterpageFileSystem; - _viewsFileSystem = viewFileSystem; - _templateConfig = templateConfig; + _viewsFileSystem = fileSystems.MvcViewsFileSystem; _viewHelper = new ViewHelper(_viewsFileSystem); - _masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); } protected override IRepositoryCachePolicy CreateCachePolicy() @@ -152,7 +142,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //Save to db var template = (Template)entity; template.AddingEntity(); - + var dto = TemplateFactory.BuildDto(template, NodeObjectTypeId, template.Id); //Create the (base) node data - umbracoNode @@ -258,18 +248,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement else { // else, create or write template.Content to disk - if (DetermineTemplateRenderingEngine(template) == RenderingEngine.Mvc) - { - content = originalAlias == null - ? _viewHelper.CreateView(template, true) - : _viewHelper.UpdateViewFile(template, originalAlias); - } - else - { - content = originalAlias == null - ? _masterPageHelper.CreateMasterPage(template, this, true) - : _masterPageHelper.UpdateMasterPageFile(template, originalAlias, this); - } + content = originalAlias == null + ? _viewHelper.CreateView(template, true) + : _viewHelper.UpdateViewFile(template, originalAlias); } // once content has been set, "template on disk" are not "on disk" anymore @@ -301,16 +282,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Execute(delete, new { id = GetEntityId(entity) }); } - if (DetermineTemplateRenderingEngine(entity) == RenderingEngine.Mvc) - { - var viewName = string.Concat(entity.Alias, ".cshtml"); - _viewsFileSystem.DeleteFile(viewName); - } - else - { - var masterpageName = string.Concat(entity.Alias, ".master"); - _masterpagesFileSystem.DeleteFile(masterpageName); - } + var viewName = string.Concat(entity.Alias, ".cshtml"); + _viewsFileSystem.DeleteFile(viewName); entity.DeleteDate = DateTime.Now; } @@ -391,27 +364,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement template.VirtualPath = _viewsFileSystem.GetUrl(path); return; } - path = string.Concat(template.Alias, ".master"); - if (_masterpagesFileSystem.FileExists(path)) - { - template.VirtualPath = _masterpagesFileSystem.GetUrl(path); - return; - } } else { // we know the path already - var ext = Path.GetExtension(path); - switch (ext) - { - case ".cshtml": - case ".vbhtml": - template.VirtualPath = _viewsFileSystem.GetUrl(path); - return; - case ".master": - template.VirtualPath = _masterpagesFileSystem.GetUrl(path); - return; - } + template.VirtualPath = _viewsFileSystem.GetUrl(path); } template.VirtualPath = string.Empty; // file not found... @@ -429,25 +386,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement path = string.Concat(template.Alias, ".vbhtml"); if (_viewsFileSystem.FileExists(path)) return GetFileContent(template, _viewsFileSystem, path, init); - path = string.Concat(template.Alias, ".master"); - if (_masterpagesFileSystem.FileExists(path)) - return GetFileContent(template, _masterpagesFileSystem, path, init); } else { // we know the path already - var ext = Path.GetExtension(path); - switch (ext) - { - case ".cshtml": - case ".vbhtml": - return GetFileContent(template, _viewsFileSystem, path, init); - case ".master": - return GetFileContent(template, _masterpagesFileSystem, path, init); - } + return GetFileContent(template, _viewsFileSystem, path, init); } template.VirtualPath = string.Empty; // file not found... + return string.Empty; } @@ -517,9 +464,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case ".vbhtml": fs = _viewsFileSystem; break; - case ".master": - fs = _masterpagesFileSystem; - break; default: throw new Exception("Unsupported extension " + ext + "."); } @@ -628,56 +572,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } } - /// - /// This checks what the default rendering engine is set in config but then also ensures that there isn't already - /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate - /// rendering engine to use. - /// - /// - /// - /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx - /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml - /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. - /// This is mostly related to installing packages since packages install file templates to the file system and then create the - /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. - /// - public RenderingEngine DetermineTemplateRenderingEngine(ITemplate template) - { - var engine = _templateConfig.DefaultRenderingEngine; - var viewHelper = new ViewHelper(_viewsFileSystem); - if (viewHelper.ViewExists(template) == false) - { - if (template.Content.IsNullOrWhiteSpace() == false && MasterPageHelper.IsMasterPageSyntax(template.Content)) - { - //there is a design but its definitely a webforms design and we haven't got a MVC view already for it - return RenderingEngine.WebForms; - } - } - - var masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); - - switch (engine) - { - case RenderingEngine.Mvc: - //check if there's a view in ~/masterpages - if (masterPageHelper.MasterPageExists(template) && viewHelper.ViewExists(template) == false) - { - //change this to webforms since there's already a file there for this template alias - engine = RenderingEngine.WebForms; - } - break; - case RenderingEngine.WebForms: - //check if there's a view in ~/views - if (viewHelper.ViewExists(template) && masterPageHelper.MasterPageExists(template) == false) - { - //change this to mvc since there's already a file there for this template alias - engine = RenderingEngine.Mvc; - } - break; - } - return engine; - } - /// /// Validates a /// @@ -692,21 +586,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var path = template.VirtualPath; // get valid paths - var validDirs = _templateConfig.DefaultRenderingEngine == RenderingEngine.Mvc - ? new[] { SystemDirectories.Masterpages, SystemDirectories.MvcViews } - : new[] { SystemDirectories.Masterpages }; + var validDirs = new[] { SystemDirectories.MvcViews }; // get valid extensions var validExts = new List(); - if (_templateConfig.DefaultRenderingEngine == RenderingEngine.Mvc) - { - validExts.Add("cshtml"); - validExts.Add("vbhtml"); - } - else - { - validExts.Add("master"); - } + validExts.Add("cshtml"); + validExts.Add("vbhtml"); // validate path and extension var validFile = IOHelper.VerifyEditPath(path, validDirs); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs index 91466a0d09..c76a5de0d0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -23,11 +23,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly UserGroupWithUsersRepository _userGroupWithUsersRepository; private readonly PermissionRepository _permissionRepository; - public UserGroupRepository(IScopeAccessor scopeAccessor, CacheHelper cacheHelper, ILogger logger) - : base(scopeAccessor, cacheHelper, logger) + public UserGroupRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger) + : base(scopeAccessor, appCaches, logger) { - _userGroupWithUsersRepository = new UserGroupWithUsersRepository(this, scopeAccessor, cacheHelper, logger); - _permissionRepository = new PermissionRepository(scopeAccessor, cacheHelper, logger); + _userGroupWithUsersRepository = new UserGroupWithUsersRepository(this, scopeAccessor, appCaches, logger); + _permissionRepository = new PermissionRepository(scopeAccessor, appCaches, logger); } public const string GetByAliasCacheKeyPrefix = "UserGroupRepository_GetByAlias_"; @@ -360,7 +360,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly UserGroupRepository _userGroupRepo; - public UserGroupWithUsersRepository(UserGroupRepository userGroupRepo, IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + public UserGroupWithUsersRepository(UserGroupRepository userGroupRepo, IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { _userGroupRepo = userGroupRepo; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 17fe79a55b..918cc66cb0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -35,22 +35,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// Constructor /// /// - /// + /// /// /// /// A dictionary specifying the configuration for user passwords. If this is null then no password configuration will be persisted or read. /// /// - public UserRepository(IScopeAccessor scopeAccessor, CacheHelper cacheHelper, ILogger logger, IMapperCollection mapperCollection, IGlobalSettings globalSettings) - : base(scopeAccessor, cacheHelper, logger) + public UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IGlobalSettings globalSettings) + : base(scopeAccessor, appCaches, logger) { _mapperCollection = mapperCollection; _globalSettings = globalSettings; } // for tests - internal UserRepository(IScopeAccessor scopeAccessor, CacheHelper cacheHelper, ILogger logger, IMapperCollection mapperCollection, IDictionary passwordConfig, IGlobalSettings globalSettings) - : base(scopeAccessor, cacheHelper, logger) + internal UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IDictionary passwordConfig, IGlobalSettings globalSettings) + : base(scopeAccessor, appCaches, logger) { _mapperCollection = mapperCollection; _globalSettings = globalSettings; diff --git a/src/Umbraco.Core/Persistence/SqlContext.cs b/src/Umbraco.Core/Persistence/SqlContext.cs index d11bce166f..6f9f91114c 100644 --- a/src/Umbraco.Core/Persistence/SqlContext.cs +++ b/src/Umbraco.Core/Persistence/SqlContext.cs @@ -12,6 +12,8 @@ namespace Umbraco.Core.Persistence /// public class SqlContext : ISqlContext { + private readonly Lazy _mappers; + /// /// Initializes a new instance of the class. /// @@ -20,38 +22,20 @@ namespace Umbraco.Core.Persistence /// The database type. /// The mappers. public SqlContext(ISqlSyntaxProvider sqlSyntax, DatabaseType databaseType, IPocoDataFactory pocoDataFactory, IMapperCollection mappers = null) - { - // for tests - Mappers = mappers ?? new Mappers.MapperCollection(Enumerable.Empty()); - - SqlSyntax = sqlSyntax ?? throw new ArgumentNullException(nameof(sqlSyntax)); - PocoDataFactory = pocoDataFactory ?? throw new ArgumentNullException(nameof(pocoDataFactory)); - DatabaseType = databaseType ?? throw new ArgumentNullException(nameof(databaseType)); - Templates = new SqlTemplates(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// Initializes an empty context which must be fully initialized using the - /// method; this is done in - /// as soon as the factory is fully configured. - internal SqlContext() + : this(sqlSyntax, databaseType, pocoDataFactory, new Lazy(() => mappers ?? new Mappers.MapperCollection(Enumerable.Empty()))) { } /// - /// Initializes this . + /// Initializes a new instance of the class. /// /// The sql syntax provider. /// The Poco data factory. /// The database type. /// The mappers. - /// Fully initializes an initially empty context; this is done in - /// as soon as the factory is fully configured. - internal void Initialize(ISqlSyntaxProvider sqlSyntax, DatabaseType databaseType, IPocoDataFactory pocoDataFactory, IMapperCollection mappers = null) + public SqlContext(ISqlSyntaxProvider sqlSyntax, DatabaseType databaseType, IPocoDataFactory pocoDataFactory, Lazy mappers) { // for tests - Mappers = mappers ?? new Mappers.MapperCollection(Enumerable.Empty()); + _mappers = mappers; SqlSyntax = sqlSyntax ?? throw new ArgumentNullException(nameof(sqlSyntax)); PocoDataFactory = pocoDataFactory ?? throw new ArgumentNullException(nameof(pocoDataFactory)); @@ -60,10 +44,10 @@ namespace Umbraco.Core.Persistence } /// - public ISqlSyntaxProvider SqlSyntax { get; private set; } + public ISqlSyntaxProvider SqlSyntax { get; } /// - public DatabaseType DatabaseType { get; private set; } + public DatabaseType DatabaseType { get; } /// public Sql Sql() => NPoco.Sql.BuilderFor((ISqlContext) this); @@ -75,12 +59,12 @@ namespace Umbraco.Core.Persistence public IQuery Query() => new Query(this); /// - public SqlTemplates Templates { get; private set; } + public SqlTemplates Templates { get; } /// - public IPocoDataFactory PocoDataFactory { get; private set; } + public IPocoDataFactory PocoDataFactory { get; } /// - public IMapperCollection Mappers { get; private set; } + public IMapperCollection Mappers => _mappers.Value; } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index d69786fbfc..5cf9fa3af8 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -11,7 +11,6 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for MySql /// - [SqlSyntaxProvider(Constants.DbProviderNames.MySql)] public class MySqlSyntaxProvider : SqlSyntaxProviderBase { private readonly ILogger _logger; @@ -334,7 +333,7 @@ ORDER BY TABLE_NAME, INDEX_NAME", switch (systemMethod) { case SystemMethods.NewGuid: - return null; // NOT SUPPORTED! + return null; // NOT SUPPORTED! case SystemMethods.CurrentDateTime: return "CURRENT_TIMESTAMP"; } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 75fc9c0b69..8f39e36fb9 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -4,14 +4,12 @@ using System.Linq; using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { /// /// Represents an SqlSyntaxProvider for Sql Ce /// - [SqlSyntaxProvider(Constants.DbProviderNames.SqlCe)] public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase { public override Sql SelectTop(Sql sql, int top) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 90a2215e3d..8b8550b694 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Linq; using NPoco; +using Umbraco.Core.Logging; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Scoping; @@ -11,29 +12,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for Sql Server. /// - [SqlSyntaxProvider(Constants.DbProviderNames.SqlServer)] public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase { - // IUmbracoDatabaseFactory to be lazily injected - public SqlServerSyntaxProvider(Lazy lazyScopeProvider) - { - _serverVersion = new Lazy(() => - { - var scopeProvider = lazyScopeProvider.Value; - if (scopeProvider == null) - throw new InvalidOperationException("Failed to determine Sql Server version (no scope provider)."); - using (var scope = scopeProvider.CreateScope()) - { - var version = DetermineVersion(scope.Database); - scope.Complete(); - return version; - } - }); - } - - private readonly Lazy _serverVersion; - - internal ServerVersionInfo ServerVersion => _serverVersion.Value; + internal ServerVersionInfo ServerVersion { get; private set; } internal enum VersionName { @@ -62,19 +43,31 @@ namespace Umbraco.Core.Persistence.SqlSyntax internal class ServerVersionInfo { - public string Edition { get; set; } - public string InstanceName { get; set; } - public string ProductVersion { get; set; } - public VersionName ProductVersionName { get; private set; } - public EngineEdition EngineEdition { get; set; } - public bool IsAzure => EngineEdition == EngineEdition.Azure; - public string MachineName { get; set; } - public string ProductLevel { get; set; } - - public void Initialize() + public ServerVersionInfo() { - ProductVersionName = MapProductVersion(ProductVersion); + ProductVersionName = VersionName.Unknown; + EngineEdition = EngineEdition.Unknown; } + + public ServerVersionInfo(string edition, string instanceName, string productVersion, EngineEdition engineEdition, string machineName, string productLevel) + { + Edition = edition; + InstanceName = instanceName; + ProductVersion = productVersion; + ProductVersionName = MapProductVersion(ProductVersion); + EngineEdition = engineEdition; + MachineName = machineName; + ProductLevel = productLevel; + } + + public string Edition { get; } + public string InstanceName { get; } + public string ProductVersion { get; } + public VersionName ProductVersionName { get; } + public EngineEdition EngineEdition { get; } + public bool IsAzure => EngineEdition == EngineEdition.Azure; + public string MachineName { get; } + public string ProductLevel { get; } } private static VersionName MapProductVersion(string productVersion) @@ -105,8 +98,14 @@ namespace Umbraco.Core.Persistence.SqlSyntax } } - private static ServerVersionInfo DetermineVersion(IUmbracoDatabase database) + internal ServerVersionInfo GetSetVersion(string connectionString, string providerName, ILogger logger) { + var factory = DbProviderFactories.GetFactory(providerName); + var connection = factory.CreateConnection(); + + if (connection == null) + throw new InvalidOperationException($"Could not create a connection for provider \"{providerName}\"."); + // Edition: "Express Edition", "Windows Azure SQL Database..." // EngineEdition: 1/Desktop 2/Standard 3/Enterprise 4/Express 5/Azure // ProductLevel: RTM, SPx, CTP... @@ -123,44 +122,30 @@ namespace Umbraco.Core.Persistence.SqlSyntax SERVERPROPERTY('ResourceLastUpdateDateTime') ResourceLastUpdateDateTime, SERVERPROPERTY('ProductLevel') ProductLevel;"; - try - { - var version = database.Fetch(sql).First(); - version.Initialize(); - return version; - } - catch (Exception e) - { - // can't ignore, really - throw new Exception("Failed to determine Sql Server version (see inner exception).", e); - } - } - - internal static VersionName GetVersionName(string connectionString, string providerName) - { - var factory = DbProviderFactories.GetFactory(providerName); - var connection = factory.CreateConnection(); - - if (connection == null) - throw new InvalidOperationException($"Could not create a connection for provider \"{providerName}\"."); - connection.ConnectionString = connectionString; + ServerVersionInfo version; using (connection) { try { connection.Open(); var command = connection.CreateCommand(); - command.CommandText = "SELECT SERVERPROPERTY('ProductVersion');"; - var productVersion = command.ExecuteScalar().ToString(); + command.CommandText = sql; + using (var reader = command.ExecuteReader()) + { + reader.Read(); + version = new ServerVersionInfo(reader.GetString(0), reader.GetString(2), reader.GetString(3), (EngineEdition) reader.GetInt32(5), reader.GetString(7), reader.GetString(9)); + } connection.Close(); - return MapProductVersion(productVersion); } - catch + catch (Exception e) { - return VersionName.Unknown; + logger.Error(e, "Failed to detected SqlServer version."); + version = new ServerVersionInfo(); // all unknown } } + + return ServerVersion = version; } /// diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderAttribute.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderAttribute.cs deleted file mode 100644 index 191ee86bac..0000000000 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Umbraco.Core.Persistence.SqlSyntax -{ - /// - /// Attribute for implementations of an ISqlSyntaxProvider - /// - [AttributeUsage(AttributeTargets.Class)] - public class SqlSyntaxProviderAttribute : Attribute - { - public SqlSyntaxProviderAttribute(string providerName) - { - ProviderName = providerName; - } - - /// - /// Gets or sets the ProviderName that corresponds to the sql syntax in a provider. - /// - public string ProviderName { get; set; } - } -} diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index 1e67993895..9ed52ca148 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -1,14 +1,12 @@ using System; -using System.Collections.Generic; using System.Configuration; using System.Data.Common; -using System.Linq; using System.Threading; +using LightInject; using NPoco; using NPoco.FluentMappings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; -using Umbraco.Core.Migrations.Install; using Umbraco.Core.Persistence.FaultHandling; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; @@ -27,10 +25,8 @@ namespace Umbraco.Core.Persistence /// internal class UmbracoDatabaseFactory : DisposableObject, IUmbracoDatabaseFactory { - private readonly ISqlSyntaxProvider[] _sqlSyntaxProviders; - private readonly IMapperCollection _mappers; + private readonly Lazy _mappers; private readonly ILogger _logger; - private readonly SqlContext _sqlContext = new SqlContext(); private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private DatabaseFactory _npocoDatabaseFactory; @@ -51,24 +47,20 @@ namespace Umbraco.Core.Persistence /// /// Initializes a new instance of the . /// - /// Used by LightInject. - public UmbracoDatabaseFactory(IEnumerable sqlSyntaxProviders, ILogger logger, IMapperCollection mappers) - : this(Constants.System.UmbracoConnectionName, sqlSyntaxProviders, logger, mappers) - { - if (Configured == false) - DatabaseBuilder.GiveLegacyAChance(this, logger); - } + /// Used by core runtime. + public UmbracoDatabaseFactory(ILogger logger, Lazy mappers) + : this(Constants.System.UmbracoConnectionName, logger, mappers) + { } /// /// Initializes a new instance of the . /// /// Used by the other ctor and in tests. - public UmbracoDatabaseFactory(string connectionStringName, IEnumerable sqlSyntaxProviders, ILogger logger, IMapperCollection mappers) + public UmbracoDatabaseFactory(string connectionStringName, ILogger logger, Lazy mappers) { if (string.IsNullOrWhiteSpace(connectionStringName)) throw new ArgumentNullOrEmptyException(nameof(connectionStringName)); _mappers = mappers ?? throw new ArgumentNullException(nameof(mappers)); - _sqlSyntaxProviders = sqlSyntaxProviders?.ToArray() ?? throw new ArgumentNullException(nameof(sqlSyntaxProviders)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); var settings = ConfigurationManager.ConnectionStrings[connectionStringName]; @@ -92,10 +84,9 @@ namespace Umbraco.Core.Persistence /// Initializes a new instance of the . /// /// Used in tests. - public UmbracoDatabaseFactory(string connectionString, string providerName, IEnumerable sqlSyntaxProviders, ILogger logger, IMapperCollection mappers) + public UmbracoDatabaseFactory(string connectionString, string providerName, ILogger logger, Lazy mappers) { _mappers = mappers ?? throw new ArgumentNullException(nameof(mappers)); - _sqlSyntaxProviders = sqlSyntaxProviders?.ToArray() ?? throw new ArgumentNullException(nameof(sqlSyntaxProviders)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); if (string.IsNullOrWhiteSpace(connectionString) || string.IsNullOrWhiteSpace(providerName)) @@ -112,6 +103,16 @@ namespace Umbraco.Core.Persistence /// public bool Configured { get; private set; } + /// + public string ConnectionString + { + get + { + EnsureConfigured(); + return _connectionString; + } + } + /// public bool CanConnect { @@ -139,7 +140,7 @@ namespace Umbraco.Core.Persistence if (setting.IsNullOrWhiteSpace() || !setting.StartsWith("SqlServer.") || !Enum.TryParse(setting.Substring("SqlServer.".Length), out var versionName, true)) { - versionName = SqlServerSyntaxProvider.GetVersionName(_connectionString, _providerName); + versionName = ((SqlServerSyntaxProvider) _sqlSyntax).GetSetVersion(_connectionString, _providerName, _logger).ProductVersionName; } else { @@ -165,7 +166,7 @@ namespace Umbraco.Core.Persistence } /// - public ISqlContext SqlContext => _sqlContext; + public ISqlContext SqlContext { get; private set; } /// public void ConfigureForUpgrade() @@ -218,9 +219,7 @@ namespace Umbraco.Core.Persistence if (_npocoDatabaseFactory == null) throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance."); - // can initialize now because it is the UmbracoDatabaseFactory that determines - // the sql syntax, poco data factory, and database type - _sqlContext.Initialize(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers); + SqlContext = new SqlContext(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers); _logger.Debug("Configured."); Configured = true; @@ -245,17 +244,17 @@ namespace Umbraco.Core.Persistence // gets the sql syntax provider that corresponds, from attribute private ISqlSyntaxProvider GetSqlSyntaxProvider(string providerName) { - var name = providerName.ToLowerInvariant(); - var provider = _sqlSyntaxProviders.FirstOrDefault(x => - x.GetType() - .FirstAttribute() - .ProviderName.ToLowerInvariant() - .Equals(name)); - if (provider != null) return provider; - throw new InvalidOperationException($"Unknown provider name \"{providerName}\""); - - // previously we'd try to return SqlServerSyntaxProvider by default but this is bad - //provider = _syntaxProviders.FirstOrDefault(x => x.GetType() == typeof(SqlServerSyntaxProvider)); + switch (providerName) + { + case Constants.DbProviderNames.MySql: + return new MySqlSyntaxProvider(_logger); + case Constants.DbProviderNames.SqlCe: + return new SqlCeSyntaxProvider(); + case Constants.DbProviderNames.SqlServer: + return new SqlServerSyntaxProvider(); + default: + throw new InvalidOperationException($"Unknown provider name \"{providerName}\""); + } } // ensures that the database is configured, else throws @@ -277,7 +276,7 @@ namespace Umbraco.Core.Persistence // method used by NPoco's UmbracoDatabaseFactory to actually create the database instance private UmbracoDatabase CreateDatabaseInstance() { - return new UmbracoDatabase(_connectionString, _sqlContext, _dbProviderFactory, _logger, _connectionRetryPolicy, _commandRetryPolicy); + return new UmbracoDatabase(_connectionString, SqlContext, _dbProviderFactory, _logger, _connectionRetryPolicy, _commandRetryPolicy); } protected override void DisposeResources() diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs index 2a53142a1c..c0c0a3651e 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs @@ -1,14 +1,9 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.PropertyEditors { public class DataEditorCollectionBuilder : LazyCollectionBuilderBase { - public DataEditorCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override DataEditorCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs index d616ecf715..8f7c68c813 100644 --- a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs @@ -1,14 +1,9 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.PropertyEditors { internal class ManifestValueValidatorCollectionBuilder : LazyCollectionBuilderBase { - public ManifestValueValidatorCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override ManifestValueValidatorCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs index 5c5a8c16c8..a64fe8c43a 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs @@ -1,14 +1,9 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.PropertyEditors { public class PropertyValueConverterCollectionBuilder : OrderedCollectionBuilderBase { - public PropertyValueConverterCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override PropertyValueConverterCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs index a20694770c..6f7888aee3 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs @@ -18,17 +18,31 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { - if (source == null) return 0M; + if (source == null) + { + return 0M; + } - // in XML a decimal is a string + // is it already a decimal? + if(source is decimal) + { + return source; + } + + // is it a double? + if(source is double sourceDouble) + { + return Convert.ToDecimal(sourceDouble); + } + + // is it a string? if (source is string sourceString) { return decimal.TryParse(sourceString, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out decimal d) ? d : 0M; } - // in the database an a decimal is an a decimal - // default value is zero - return source is decimal ? source : 0M; + // couldn't convert the source value - default to zero + return 0M; } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs index 2131764ad6..29f6de0271 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.Grid; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; @@ -19,9 +20,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter(typeof(JsonValueConverter))] //this shadows the JsonValueConverter public class GridValueConverter : JsonValueConverter { - public GridValueConverter(PropertyEditorCollection propertyEditors) + private readonly IGridConfig _config; + + public GridValueConverter(PropertyEditorCollection propertyEditors, IGridConfig config) : base(propertyEditors) - { } + { + _config = config; + } public override bool IsConverter(PublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Grid); @@ -46,15 +51,6 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters //so we have the grid json... we need to merge in the grid's configuration values with the values // we've saved in the database so that when the front end gets this value, it is up-to-date. - //TODO: Change all singleton access to use ctor injection in v8!!! - //TODO: That would mean that property value converters would need to be request lifespan, hrm.... - var gridConfig = UmbracoConfig.For.GridConfig( - Current.ProfilingLogger.Logger, - Current.ApplicationCache.RuntimeCache, - new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)), - new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config)), - Current.RuntimeState.Debug); - var sections = GetArray(obj, "sections"); foreach (var section in sections.Cast()) { @@ -74,7 +70,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (alias.IsNullOrWhiteSpace() == false) { //find the alias in config - var found = gridConfig.EditorsConfig.Editors.FirstOrDefault(x => x.Alias == alias); + var found = _config.EditorsConfig.Editors.FirstOrDefault(x => x.Alias == alias); if (found != null) { //add/replace the editor value with the one from config diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index 870cb9ec13..622d81f5f2 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -295,7 +295,7 @@ namespace Umbraco.Core /// Occurs when the constructor does not exist and is true. /// Occurs when is not a Func or when /// is specified and does not match the function's returned type. - public static TLambda EmitConstuctor(bool mustExist = true, Type declaring = null) + public static TLambda EmitConstructor(bool mustExist = true, Type declaring = null) { var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs old mode 100755 new mode 100644 index dde91ad385..6918c9b423 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; -using System.Configuration; +using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Threading; using System.Web; -using LightInject; using Umbraco.Core.Cache; using Umbraco.Core.Components; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.CompositionRoots; using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; @@ -16,11 +15,9 @@ using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Scoping; using Umbraco.Core.Services.Implement; +using Umbraco.Core.Sync; namespace Umbraco.Core.Runtime { @@ -31,44 +28,38 @@ namespace Umbraco.Core.Runtime /// should be possible to use this runtime in console apps. public class CoreRuntime : IRuntime { - private BootLoader _bootLoader; + private ComponentCollection _components; + private IFactory _factory; private RuntimeState _state; /// - /// Initializes a new instance of the class. + /// Gets the logger. /// - public CoreRuntime() - { } + protected ILogger Logger { get; private set; } + + /// + /// Gets the profiler. + /// + protected IProfiler Profiler { get; private set; } + + /// + /// Gets the profiling logger. + /// + protected IProfilingLogger ProfilingLogger { get; private set; } + + /// + public IRuntimeState State => _state; /// - public virtual void Boot(ServiceContainer container) + public virtual IFactory Boot(IRegister register) { - container.ConfigureUmbracoCore(); // also sets Current.Container + // create and register the essential services + // ie the bare minimum required to boot - // register the essential stuff, - // ie the global application logger - // (profiler etc depend on boot manager) - var logger = GetLogger(); - container.RegisterInstance(logger); - // now it is ok to use Current.Logger - - ConfigureUnhandledException(logger); - ConfigureAssemblyResolve(logger); - - Compose(container); - - // prepare essential stuff - - var path = GetApplicationRootPath(); - if (string.IsNullOrWhiteSpace(path) == false) - IOHelper.SetRootDirectory(path); - - _state = (RuntimeState) container.GetInstance(); - _state.Level = RuntimeLevel.Boot; - - Logger = container.GetInstance(); - Profiler = container.GetInstance(); - ProfilingLogger = container.GetInstance(); + // loggers + var logger = Logger = GetLogger(); + var profiler = Profiler = GetProfiler(); + var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); // the boot loader boots using a container scope, so anything that is PerScope will // be disposed after the boot loader has booted, and anything else will remain. @@ -79,56 +70,125 @@ namespace Umbraco.Core.Runtime // are NOT disposed - which is not a big deal as long as they remain lightweight // objects. - using (var bootTimer = ProfilingLogger.TraceDuration( + using (var timer = profilingLogger.TraceDuration( $"Booting Umbraco {UmbracoVersion.SemanticVersion.ToSemanticString()} on {NetworkHelper.MachineName}.", "Booted.", "Boot failed.")) { - // throws if not full-trust - new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted).Demand(); + logger.Debug("Runtime: {Runtime}", GetType().FullName); - try - { - Logger.Debug("Runtime: {Runtime}", GetType().FullName); + // application environment + ConfigureUnhandledException(); + ConfigureAssemblyResolve(); + ConfigureApplicationRootPath(); - AquireMainDom(container); - DetermineRuntimeLevel(container); - var componentTypes = ResolveComponentTypes(); - _bootLoader = new BootLoader(container); - _bootLoader.Boot(componentTypes, _state.Level); - } - catch (Exception e) - { - _state.Level = RuntimeLevel.BootFailed; - var bfe = e as BootFailedException ?? new BootFailedException("Boot failed.", e); - _state.BootFailedException = bfe; - bootTimer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves - - // throwing here can cause w3wp to hard-crash and we want to avoid it. - // instead, we're logging the exception and setting level to BootFailed. - // various parts of Umbraco such as UmbracoModule and UmbracoDefaultOwinStartup - // understand this and will nullify themselves, while UmbracoModule will - // throw a BootFailedException for every requests. - } + Boot(register, timer); } - //fixme - // after Umbraco has started there is a scope in "context" and that context is - // going to stay there and never get destroyed nor reused, so we have to ensure that - // everything is cleared - //var sa = container.GetInstance(); - //sa.Scope?.Dispose(); + return _factory; } /// - /// Gets a logger. + /// Boots the runtime within a timer. /// - protected virtual ILogger GetLogger() + protected virtual IFactory Boot(IRegister register, DisposableTimer timer) { - return SerilogLogger.CreateWithDefaultConfiguration(); + Composition composition = null; + + try + { + // throws if not full-trust + new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted).Demand(); + + // application caches + var appCaches = GetAppCaches(); + + // database factory + var databaseFactory = GetDatabaseFactory(); + + // configs + var configs = GetConfigs(); + + // type loader + var localTempStorage = configs.Global().LocalTempStorageLocation; + var typeLoader = new TypeLoader(appCaches.RuntimeCache, localTempStorage, ProfilingLogger); + + // runtime state + // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' + // as the second one captures the current value (null) and therefore fails + _state = new RuntimeState(Logger, + configs.Settings(), configs.Global(), + new Lazy(() => _factory.GetInstance()), + new Lazy(() => _factory.GetInstance())) + { + Level = RuntimeLevel.Boot + }; + + // main dom + var mainDom = new MainDom(Logger); + + // create the composition + composition = new Composition(register, typeLoader, ProfilingLogger, _state, configs); + composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state); + + // register runtime-level services + // there should be none, really - this is here "just in case" + Compose(composition); + + // acquire the main domain, determine our runtime level + AcquireMainDom(mainDom); + DetermineRuntimeLevel(databaseFactory, ProfilingLogger); + + // get composers, and compose + var composerTypes = ResolveComposerTypes(typeLoader); + composition.WithCollectionBuilder(); + var composers = new Composers(composition, composerTypes, ProfilingLogger); + composers.Compose(); + + // create the factory + _factory = Current.Factory = composition.CreateFactory(); + + // create & initialize the components + _components = _factory.GetInstance(); + _components.Initialize(); + } + catch (Exception e) + { + var bfe = e as BootFailedException ?? new BootFailedException("Boot failed.", e); + + if (_state != null) + { + _state.Level = RuntimeLevel.BootFailed; + _state.BootFailedException = bfe; + } + + timer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves + + // if something goes wrong above, we may end up with no factory + // meaning nothing can get the runtime state, etc - so let's try + // to make sure we have a factory + if (_factory == null) + { + try + { + _factory = Current.Factory = composition?.CreateFactory(); + } + catch { /* yea */ } + } + + Debugger.Break(); + + // throwing here can cause w3wp to hard-crash and we want to avoid it. + // instead, we're logging the exception and setting level to BootFailed. + // various parts of Umbraco such as UmbracoModule and UmbracoDefaultOwinStartup + // understand this and will nullify themselves, while UmbracoModule will + // throw a BootFailedException for every requests. + } + + return _factory; } - protected virtual void ConfigureUnhandledException(ILogger logger) + protected virtual void ConfigureUnhandledException() { //take care of unhandled exceptions - there is nothing we can do to // prevent the launch process to go down but at least we can try @@ -141,32 +201,37 @@ namespace Umbraco.Core.Runtime var msg = "Unhandled exception in AppDomain"; if (isTerminating) msg += " (terminating)"; msg += "."; - logger.Error(exception, msg); + Logger.Error(exception, msg); }; } - protected virtual void ConfigureAssemblyResolve(ILogger logger) + protected virtual void ConfigureAssemblyResolve() { // When an assembly can't be resolved. In here we can do magic with the assembly name and try loading another. // This is used for loading a signed assembly of AutoMapper (v. 3.1+) without having to recompile old code. AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { // ensure the assembly is indeed AutoMapper and that the PublicKeyToken is null before trying to Load again - // do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stackoverflow + // do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stack overflow if (args.Name.StartsWith("AutoMapper") && args.Name.EndsWith("PublicKeyToken=null")) return Assembly.Load(args.Name.Replace(", PublicKeyToken=null", ", PublicKeyToken=be96cd2c38ef1005")); return null; }; } - - private void AquireMainDom(IServiceFactory container) + protected virtual void ConfigureApplicationRootPath() { - using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Aquired.")) + var path = GetApplicationRootPath(); + if (string.IsNullOrWhiteSpace(path) == false) + IOHelper.SetRootDirectory(path); + } + + private void AcquireMainDom(MainDom mainDom) + { + using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) { try { - var mainDom = container.GetInstance(); mainDom.Acquire(); } catch @@ -178,38 +243,39 @@ namespace Umbraco.Core.Runtime } // internal for tests - internal void DetermineRuntimeLevel(IServiceFactory container) + internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) { - using (var timer = ProfilingLogger.DebugDuration("Determining runtime level.", "Determined.")) + using (var timer = profilingLogger.DebugDuration("Determining runtime level.", "Determined.")) { try { - var dbfactory = container.GetInstance(); - SetRuntimeStateLevel(dbfactory, Logger); + _state.DetermineRuntimeLevel(databaseFactory, profilingLogger); - Logger.Debug("Runtime level: {RuntimeLevel}", _state.Level); + profilingLogger.Debug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason); if (_state.Level == RuntimeLevel.Upgrade) { - Logger.Debug("Configure database factory for upgrades."); - dbfactory.ConfigureForUpgrade(); + profilingLogger.Debug("Configure database factory for upgrades."); + databaseFactory.ConfigureForUpgrade(); } } catch { + _state.Level = RuntimeLevel.BootFailed; + _state.Reason = RuntimeLevelReason.BootFailedOnException; timer.Fail(); throw; } } } - private IEnumerable ResolveComponentTypes() + private IEnumerable ResolveComposerTypes(TypeLoader typeLoader) { - using (var timer = ProfilingLogger.TraceDuration("Resolving component types.", "Resolved.")) + using (var timer = ProfilingLogger.TraceDuration("Resolving composer types.", "Resolved.")) { try { - return GetComponentTypes(); + return GetComposerTypes(typeLoader); } catch { @@ -222,208 +288,75 @@ namespace Umbraco.Core.Runtime /// public virtual void Terminate() { - using (ProfilingLogger.DebugDuration("Terminating Umbraco.", "Terminated.")) - { - _bootLoader?.Terminate(); - } + _components.Terminate(); } /// /// Composes the runtime. /// - public virtual void Compose(ServiceContainer container) + public virtual void Compose(Composition composition) { - // compose the very essential things that are needed to bootstrap, before anything else, - // and only these things - the rest should be composed in runtime components - - // register basic things - // logging, runtime state, configuration - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterFrom(); - - // register caches - // need the deep clone runtime cache profiver to ensure entities are cached properly, ie - // are cloned in and cloned out - no request-based cache here since no web-based context, - // will be overriden later or - container.RegisterSingleton(_ => new CacheHelper( - new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), - new StaticCacheProvider(), - NullCacheProvider.Instance, - new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())))); - container.RegisterSingleton(f => f.GetInstance().RuntimeCache); - - // register the type loader - container.RegisterSingleton(); - - // register syntax providers - required by database factory - container.Register("MySqlSyntaxProvider"); - container.Register("SqlCeSyntaxProvider"); - container.Register("SqlServerSyntaxProvider"); - - // register persistence mappers - required by database factory so needs to be done here - // means the only place the collection can be modified is in a runtime - afterwards it - // has been frozen and it is too late - var mapperCollectionBuilder = container.RegisterCollectionBuilder(); - ComposeMapperCollection(mapperCollectionBuilder); - - // register database factory - required to check for migrations - // will be initialized with syntax providers and a logger, and will try to configure - // from the default connection string name, if possible, else will remain non-configured - // until properly configured (eg when installing) - container.RegisterSingleton(); - container.RegisterSingleton(f => f.GetInstance().SqlContext); - - // register the scope provider - container.RegisterSingleton(); // implements both IScopeProvider and IScopeAccessor - container.RegisterSingleton(f => f.GetInstance()); - container.RegisterSingleton(f => f.GetInstance()); - - // register MainDom - container.RegisterSingleton(); + // nothing } - protected virtual void ComposeMapperCollection(MapperCollectionBuilder builder) - { - builder.AddCore(); - } - - private void SetRuntimeStateLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger) - { - var localVersion = UmbracoVersion.LocalVersion; // the local, files, version - var codeVersion = _state.SemanticVersion; // the executing code version - var connect = false; - - // we don't know yet - _state.Level = RuntimeLevel.Unknown; - - if (localVersion == null) - { - // there is no local version, we are not installed - logger.Debug("No local version, need to install Umbraco."); - _state.Level = RuntimeLevel.Install; - } - else if (localVersion < codeVersion) - { - // there *is* a local version, but it does not match the code version - // need to upgrade - logger.Debug("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion); - _state.Level = RuntimeLevel.Upgrade; - } - else if (localVersion > codeVersion) - { - logger.Warn("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion); - _state.Level = RuntimeLevel.BootFailed; - - // in fact, this is bad enough that we want to throw - throw new BootFailedException($"Local version \"{localVersion}\" > code version \"{codeVersion}\", downgrading is not supported."); - } - else if (databaseFactory.Configured == false) - { - // local version *does* match code version, but the database is not configured - // install (again? this is a weird situation...) - logger.Debug("Database is not configured, need to install Umbraco."); - _state.Level = RuntimeLevel.Install; - } - - // install? not going to test anything else - if (_state.Level == RuntimeLevel.Install) - return; - - // else, keep going, - // anything other than install wants a database - see if we can connect - // (since this is an already existing database, assume localdb is ready) - for (var i = 0; i < 5; i++) - { - connect = databaseFactory.CanConnect; - if (connect) break; - logger.Debug("Could not immediately connect to database, trying again."); - Thread.Sleep(1000); - } - - if (connect == false) - { - // cannot connect to configured database, this is bad, fail - logger.Debug("Could not connect to database."); - _state.Level = RuntimeLevel.BootFailed; - - // in fact, this is bad enough that we want to throw - throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); - } - - // if we already know we want to upgrade, - // still run EnsureUmbracoUpgradeState to get the states - // (v7 will just get a null state, that's ok) - - // else - // look for a matching migration entry - bypassing services entirely - they are not 'up' yet - // fixme - in a LB scenario, ensure that the DB gets upgraded only once! - bool noUpgrade; - try - { - noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); - } - catch (Exception e) - { - // can connect to the database but cannot check the upgrade state... oops - logger.Warn(e, "Could not check the upgrade state."); - throw new BootFailedException("Could not check the upgrade state.", e); - } - - if (noUpgrade) - { - // the database version matches the code & files version, all clear, can run - _state.Level = RuntimeLevel.Run; - return; - } - - // the db version does not match... but we do have a migration table - // so, at least one valid table, so we quite probably are installed & need to upgrade - - // although the files version matches the code version, the database version does not - // which means the local files have been upgraded but not the database - need to upgrade - logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); - _state.Level = RuntimeLevel.Upgrade; - } - - protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) - { - var upgrader = new UmbracoUpgrader(); - var stateValueKey = upgrader.StateValueKey; - - // no scope, no service - just directly accessing the database - using (var database = databaseFactory.CreateDatabase()) - { - _state.CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); - _state.FinalMigrationState = upgrader.Plan.FinalState; - } - - logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, _state.CurrentMigrationState ?? ""); - - return _state.CurrentMigrationState == _state.FinalMigrationState; - } - - #region Locals - - protected ILogger Logger { get; private set; } - - protected IProfiler Profiler { get; private set; } - - protected ProfilingLogger ProfilingLogger { get; private set; } - - #endregion - #region Getters // getters can be implemented by runtimes inheriting from CoreRuntime - // fixme - inject! no Current! - protected virtual IEnumerable GetComponentTypes() => Current.TypeLoader.GetTypes(); + /// + /// Gets all composer types. + /// + protected virtual IEnumerable GetComposerTypes(TypeLoader typeLoader) + => typeLoader.GetTypes(); + + /// + /// Gets a logger. + /// + protected virtual ILogger GetLogger() + => SerilogLogger.CreateWithDefaultConfiguration(); + + /// + /// Gets a profiler. + /// + protected virtual IProfiler GetProfiler() + => new LogProfiler(Logger); + + /// + /// Gets the application caches. + /// + protected virtual AppCaches GetAppCaches() + { + // need the deep clone runtime cache provider to ensure entities are cached properly, ie + // are cloned in and cloned out - no request-based cache here since no web-based context, + // is overriden by the web runtime + + return new AppCaches( + new DeepCloneAppCache(new ObjectCacheAppCache()), + NoAppCache.Instance, + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); + } // by default, returns null, meaning that Umbraco should auto-detect the application root path. // override and return the absolute path to the Umbraco site/solution, if needed - protected virtual string GetApplicationRootPath() => null; + protected virtual string GetApplicationRootPath() + => null; + + /// + /// Gets the database factory. + /// + /// This is strictly internal, for tests only. + protected internal virtual IUmbracoDatabaseFactory GetDatabaseFactory() + => new UmbracoDatabaseFactory(Logger, new Lazy(() => _factory.GetInstance())); + + /// + /// Gets the configurations. + /// + protected virtual Configs GetConfigs() + { + var configs = new Configs(); + configs.AddCoreConfigs(); + return configs; + } #endregion } diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs index 90d3ece254..b9efdd6432 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs @@ -1,133 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.IO; +using System.Collections.Generic; using AutoMapper; -using LightInject; -using Umbraco.Core.Cache; using Umbraco.Core.Components; -using Umbraco.Core.Composing; -using Umbraco.Core.Composing.CompositionRoots; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; -using Umbraco.Core.IO.MediaPathSchemes; -using Umbraco.Core.Logging; -using Umbraco.Core.Manifest; -using Umbraco.Core.Migrations; -using Umbraco.Core.Migrations.Install; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.Validators; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.Core.Sync; -using Umbraco.Core._Legacy.PackageActions; -using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator; namespace Umbraco.Core.Runtime { - public class CoreRuntimeComponent : UmbracoComponentBase, IRuntimeComponent + public class CoreRuntimeComponent : IComponent { - public override void Compose(Composition composition) + private readonly IEnumerable _mapperProfiles; + + public CoreRuntimeComponent(IEnumerable mapperProfiles) { - base.Compose(composition); - - // register from roots - composition.Container.RegisterFrom(); - composition.Container.RegisterFrom(); - composition.Container.RegisterFrom(); - - // register database builder - // *not* a singleton, don't want to keep it around - composition.Container.Register(); - - // register filesystems - composition.Container.RegisterSingleton(); - composition.Container.RegisterSingleton(factory => factory.GetInstance().MediaFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().ScriptsFileSystem, Constants.Composing.FileSystems.ScriptFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().PartialViewsFileSystem, Constants.Composing.FileSystems.PartialViewFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().MacroPartialsFileSystem, Constants.Composing.FileSystems.PartialViewMacroFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().StylesheetsFileSystem, Constants.Composing.FileSystems.StylesheetFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().MasterPagesFileSystem, Constants.Composing.FileSystems.MasterpageFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().MvcViewsFileSystem, Constants.Composing.FileSystems.ViewFileSystem); - - // register manifest parser, will be injected in collection builders where needed - composition.Container.RegisterSingleton(); - - // register our predefined validators - composition.Container.RegisterCollectionBuilder() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add(); - - // properties and parameters derive from data editors - composition.Container.RegisterCollectionBuilder() - .Add(factory => factory.GetInstance().GetDataEditors()); - composition.Container.RegisterSingleton(); - composition.Container.RegisterSingleton(); - - // register a server registrar, by default it's the db registrar - composition.Container.RegisterSingleton(f => - { - if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"])) - return new SingleServerRegistrar(f.GetInstance()); - return new DatabaseServerRegistrar( - new Lazy(f.GetInstance), - new DatabaseServerRegistrarOptions()); - }); - - // by default we'll use the database server messenger with default options (no callbacks), - // this will be overridden by either the legacy thing or the db thing in the corresponding - // components in the web project - fixme - should obsolete the legacy thing - composition.Container.RegisterSingleton(factory - => new DatabaseServerMessenger( - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - true, new DatabaseServerMessengerOptions())); - - composition.Container.RegisterCollectionBuilder() - .Add(factory => factory.GetInstance().GetCacheRefreshers()); - - composition.Container.RegisterCollectionBuilder() - .Add(f => f.GetInstance().GetPackageActions()); - - composition.Container.RegisterCollectionBuilder() - .Append(factory => factory.GetInstance().GetTypes()); - - composition.Container.Register(new PerContainerLifetime()); - - composition.Container.RegisterSingleton(factory - => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance()))); - - composition.Container.RegisterCollectionBuilder() - .Append(); - - composition.Container.RegisterCollectionBuilder() - .Add(factory => factory.GetInstance().GetTypes()); - - composition.Container.RegisterSingleton(); - - // by default, register a noop factory - composition.Container.RegisterSingleton(); - - composition.Container.RegisterSingleton(); + _mapperProfiles = mapperProfiles; } - internal void Initialize(IEnumerable mapperProfiles) + public void Initialize() { // mapper profiles have been registered & are created by the container Mapper.Initialize(configuration => { - foreach (var profile in mapperProfiles) + foreach (var profile in _mapperProfiles) configuration.AddProfile(profile); }); @@ -139,5 +31,8 @@ namespace Umbraco.Core.Runtime IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/Partials"); IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/MacroPartials"); } + + public void Terminate() + { } } } diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs b/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs new file mode 100644 index 0000000000..3d959f5263 --- /dev/null +++ b/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs @@ -0,0 +1,121 @@ +using System; +using System.Configuration; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Composing; +using Umbraco.Core.Composing.Composers; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; +using Umbraco.Core.Migrations; +using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Core.Sync; +using Umbraco.Core._Legacy.PackageActions; +using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator; + +namespace Umbraco.Core.Runtime +{ + public class CoreRuntimeComposer : ComponentComposer, IRuntimeComposer + { + public override void Compose(Composition composition) + { + base.Compose(composition); + + // composers + composition + .ComposeConfiguration() + .ComposeRepositories() + .ComposeServices() + .ComposeCoreMappingProfiles() + .ComposeFileSystems(); + + // register persistence mappers - required by database factory so needs to be done here + // means the only place the collection can be modified is in a runtime - afterwards it + // has been frozen and it is too late + composition.WithCollectionBuilder().AddCoreMappers(); + + // register the scope provider + composition.RegisterUnique(); // implements both IScopeProvider and IScopeAccessor + composition.RegisterUnique(f => f.GetInstance()); + composition.RegisterUnique(f => f.GetInstance()); + + // register database builder + // *not* a singleton, don't want to keep it around + composition.Register(); + + // register manifest parser, will be injected in collection builders where needed + composition.RegisterUnique(); + + // register our predefined validators + composition.WithCollectionBuilder() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add(); + + // properties and parameters derive from data editors + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetDataEditors()); + composition.RegisterUnique(); + composition.RegisterUnique(); + + // register a server registrar, by default it's the db registrar + composition.RegisterUnique(f => + { + if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"])) + return new SingleServerRegistrar(f.GetInstance()); + return new DatabaseServerRegistrar( + new Lazy(f.GetInstance), + new DatabaseServerRegistrarOptions()); + }); + + // by default we'll use the database server messenger with default options (no callbacks), + // this will be overridden by either the legacy thing or the db thing in the corresponding + // components in the web project - fixme - should obsolete the legacy thing + composition.RegisterUnique(factory + => new DatabaseServerMessenger( + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + true, new DatabaseServerMessengerOptions())); + + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetCacheRefreshers()); + + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetPackageActions()); + + composition.WithCollectionBuilder() + .Append(composition.TypeLoader.GetTypes()); + + composition.RegisterUnique(); + + composition.RegisterUnique(factory + => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance()))); + + composition.WithCollectionBuilder() + .Append(); + + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetTypes()); + + composition.RegisterUnique(factory => new MigrationBuilder(factory)); + + // by default, register a noop factory + composition.RegisterUnique(); + } + } +} diff --git a/src/Umbraco.Core/RuntimeLevel.cs b/src/Umbraco.Core/RuntimeLevel.cs index f9ec3c90c5..2645e89226 100644 --- a/src/Umbraco.Core/RuntimeLevel.cs +++ b/src/Umbraco.Core/RuntimeLevel.cs @@ -1,5 +1,8 @@ namespace Umbraco.Core { + /// + /// Describes the levels in which the runtime can run. + /// public enum RuntimeLevel { /// diff --git a/src/Umbraco.Core/RuntimeLevelReason.cs b/src/Umbraco.Core/RuntimeLevelReason.cs new file mode 100644 index 0000000000..c10ac8b206 --- /dev/null +++ b/src/Umbraco.Core/RuntimeLevelReason.cs @@ -0,0 +1,68 @@ +namespace Umbraco.Core +{ + /// + /// Describes the reason for the runtime level. + /// + public enum RuntimeLevelReason + { + /// + /// The code version is lower than the version indicated in web.config, and + /// downgrading Umbraco is not supported. + /// + BootFailedCannotDowngrade, + + /// + /// The runtime cannot connect to the configured database. + /// + BootFailedCannotConnectToDatabase, + + /// + /// The runtime can connect to the configured database, but it cannot + /// retrieve the migrations status. + /// + BootFailedCannotCheckUpgradeState, + + /// + /// An exception was thrown during boot. + /// + BootFailedOnException, + + /// + /// Umbraco is not installed at all. + /// + InstallNoVersion, + + /// + /// A version is specified in web.config but the database is not configured. + /// + /// This is a weird state. + InstallNoDatabase, + + /// + /// A version is specified in web.config and a database is configured, but the + /// database is missing, and installing over a missing database has been enabled. + /// + InstallMissingDatabase, + + /// + /// A version is specified in web.config and a database is configured, but the + /// database is empty, and installing over an empty database has been enabled. + /// + InstallEmptyDatabase, + + /// + /// Umbraco runs an old version. + /// + UpgradeOldVersion, + + /// + /// Umbraco runs the current version but some migrations have not run. + /// + UpgradeMigrations, + + /// + /// Umbraco is running. + /// + Run + } +} diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 4f6f56531b..85e8c7370d 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -7,6 +7,9 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Upgrade; +using Umbraco.Core.Persistence; +using Umbraco.Core.Services.Implement; using Umbraco.Core.Sync; namespace Umbraco.Core @@ -17,76 +20,64 @@ namespace Umbraco.Core internal class RuntimeState : IRuntimeState { private readonly ILogger _logger; - private readonly Lazy _serverRegistrar; - private readonly Lazy _mainDom; private readonly IUmbracoSettingsSection _settings; private readonly IGlobalSettings _globalSettings; private readonly HashSet _applicationUrls = new HashSet(); - private RuntimeLevel _level; + private readonly Lazy _mainDom; + private readonly Lazy _serverRegistrar; + private RuntimeLevel _level = RuntimeLevel.Unknown; /// /// Initializes a new instance of the class. /// - /// A logger. - /// A (lazy) server registrar. - /// A (lazy) MainDom. - public RuntimeState(ILogger logger, Lazy serverRegistrar, Lazy mainDom, IUmbracoSettingsSection settings, IGlobalSettings globalSettings) + public RuntimeState(ILogger logger, IUmbracoSettingsSection settings, IGlobalSettings globalSettings, + Lazy mainDom, Lazy serverRegistrar) { _logger = logger; - _serverRegistrar = serverRegistrar; - _mainDom = mainDom; _settings = settings; _globalSettings = globalSettings; + _mainDom = mainDom; + _serverRegistrar = serverRegistrar; } + /// + /// Gets the server registrar. + /// + /// + /// This is NOT exposed in the interface. + /// private IServerRegistrar ServerRegistrar => _serverRegistrar.Value; /// /// Gets the application MainDom. /// - /// This is NOT exposed in the interface as MainDom is internal. - public MainDom MainDom => _mainDom.Value; + /// + /// This is NOT exposed in the interface as MainDom is internal. + /// + public IMainDom MainDom => _mainDom.Value; - /// - /// Gets the version of the executing code. - /// + /// public Version Version => UmbracoVersion.Current; - /// - /// Gets the version comment of the executing code. - /// + /// public string VersionComment => UmbracoVersion.Comment; - /// - /// Gets the semantic version of the executing code. - /// + /// public SemVersion SemanticVersion => UmbracoVersion.SemanticVersion; - /// - /// Gets a value indicating whether the application is running in debug mode. - /// + /// public bool Debug { get; } = GlobalSettings.DebugMode; - /// - /// Gets a value indicating whether the runtime is the current main domain. - /// + /// public bool IsMainDom => MainDom.IsMainDom; - /// - /// Get the server's current role. - /// + /// public ServerRole ServerRole => ServerRegistrar.GetCurrentServerRole(); - /// - /// Gets the Umbraco application url. - /// - /// This is eg "http://www.example.com". + /// public Uri ApplicationUrl { get; private set; } - /// - /// Gets the Umbraco application virtual path. - /// - /// This is either "/" or eg "/virtual". + /// public string ApplicationVirtualPath { get; } = HttpRuntime.AppDomainAppVirtualPath; /// @@ -95,15 +86,16 @@ namespace Umbraco.Core /// public string FinalMigrationState { get; internal set; } - /// - /// Gets the runtime level of execution. - /// + /// public RuntimeLevel Level { get => _level; internal set { _level = value; if (value == RuntimeLevel.Run) _runLevel.Set(); } } + /// + public RuntimeLevelReason Reason { get; internal set; } + /// /// Ensures that the property has a value. /// @@ -137,9 +129,149 @@ namespace Umbraco.Core return _runLevel.WaitHandle.WaitOne(timeout); } - /// - /// Gets the exception that caused the boot to fail. - /// + /// public BootFailedException BootFailedException { get; internal set; } + + /// + /// Determines the runtime level. + /// + public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + { + var localVersion = UmbracoVersion.LocalVersion; // the local, files, version + var codeVersion = SemanticVersion; // the executing code version + var connect = false; + + if (localVersion == null) + { + // there is no local version, we are not installed + logger.Debug("No local version, need to install Umbraco."); + Level = RuntimeLevel.Install; + Reason = RuntimeLevelReason.InstallNoVersion; + return; + } + + if (localVersion < codeVersion) + { + // there *is* a local version, but it does not match the code version + // need to upgrade + logger.Debug("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion); + Level = RuntimeLevel.Upgrade; + Reason = RuntimeLevelReason.UpgradeOldVersion; + } + else if (localVersion > codeVersion) + { + logger.Warn("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion); + + // in fact, this is bad enough that we want to throw + Reason = RuntimeLevelReason.BootFailedCannotDowngrade; + throw new BootFailedException($"Local version \"{localVersion}\" > code version \"{codeVersion}\", downgrading is not supported."); + } + else if (databaseFactory.Configured == false) + { + // local version *does* match code version, but the database is not configured + // install (again? this is a weird situation...) + logger.Debug("Database is not configured, need to install Umbraco."); + Level = RuntimeLevel.Install; + Reason = RuntimeLevelReason.InstallNoDatabase; + return; + } + + // else, keep going, + // anything other than install wants a database - see if we can connect + // (since this is an already existing database, assume localdb is ready) + var tries = RuntimeStateOptions.InstallMissingDatabase ? 2 : 5; + for (var i = 0;;) + { + connect = databaseFactory.CanConnect; + if (connect || ++i == tries) break; + logger.Debug("Could not immediately connect to database, trying again."); + Thread.Sleep(1000); + } + + if (connect == false) + { + // cannot connect to configured database, this is bad, fail + logger.Debug("Could not connect to database."); + + if (RuntimeStateOptions.InstallMissingDatabase) + { + // ok to install on a configured but missing database + Level = RuntimeLevel.Install; + Reason = RuntimeLevelReason.InstallMissingDatabase; + return; + } + + // else it is bad enough that we want to throw + Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase; + throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); + } + + // if we already know we want to upgrade, + // still run EnsureUmbracoUpgradeState to get the states + // (v7 will just get a null state, that's ok) + + // else + // look for a matching migration entry - bypassing services entirely - they are not 'up' yet + bool noUpgrade; + try + { + noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); + } + catch (Exception e) + { + // can connect to the database but cannot check the upgrade state... oops + logger.Warn(e, "Could not check the upgrade state."); + + if (RuntimeStateOptions.InstallEmptyDatabase) + { + // ok to install on an empty database + Level = RuntimeLevel.Install; + Reason = RuntimeLevelReason.InstallEmptyDatabase; + return; + } + + // else it is bad enough that we want to throw + Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState; + throw new BootFailedException("Could not check the upgrade state.", e); + } + + // if we already know we want to upgrade, exit here + if (Level == RuntimeLevel.Upgrade) + return; + + if (noUpgrade) + { + // the database version matches the code & files version, all clear, can run + Level = RuntimeLevel.Run; + Reason = RuntimeLevelReason.Run; + return; + } + + // the db version does not match... but we do have a migration table + // so, at least one valid table, so we quite probably are installed & need to upgrade + + // although the files version matches the code version, the database version does not + // which means the local files have been upgraded but not the database - need to upgrade + logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); + Level = RuntimeLevel.Upgrade; + Reason = RuntimeLevelReason.UpgradeMigrations; + } + + protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + { + var upgrader = new UmbracoUpgrader(); + var stateValueKey = upgrader.StateValueKey; + + // no scope, no service - just directly accessing the database + using (var database = databaseFactory.CreateDatabase()) + { + CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); + FinalMigrationState = upgrader.Plan.FinalState; + } + + logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", CurrentMigrationState, FinalMigrationState ?? ""); + + return CurrentMigrationState == FinalMigrationState; + } } } diff --git a/src/Umbraco.Core/RuntimeStateOptions.cs b/src/Umbraco.Core/RuntimeStateOptions.cs new file mode 100644 index 0000000000..9262a8a990 --- /dev/null +++ b/src/Umbraco.Core/RuntimeStateOptions.cs @@ -0,0 +1,40 @@ +using System.Configuration; + +namespace Umbraco.Core +{ + /// + /// Allows configuration of the in PreApplicationStart or in appSettings + /// + public static class RuntimeStateOptions + { + // configured statically or via app settings + private static bool BoolSetting(string key, bool missing) => ConfigurationManager.AppSettings[key]?.InvariantEquals("true") ?? missing; + + /// + /// If true the RuntimeState will continue the installation sequence when a database is missing + /// + /// + /// In this case it will be up to the implementor that is setting this value to true to take over the bootup/installation sequence + /// + public static bool InstallMissingDatabase + { + get => _installEmptyDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallMissingDatabase", false); + set => _installEmptyDatabase = value; + } + + /// + /// If true the RuntimeState will continue the installation sequence when a database is available but is empty + /// + /// + /// In this case it will be up to the implementor that is setting this value to true to take over the bootup/installation sequence + /// + public static bool InstallEmptyDatabase + { + get => _installMissingDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallEmptyDatabase", false); + set => _installMissingDatabase = value; + } + + private static bool? _installMissingDatabase; + private static bool? _installEmptyDatabase; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Scoping/IScope.cs b/src/Umbraco.Core/Scoping/IScope.cs index eefc964965..de4eef0a08 100644 --- a/src/Umbraco.Core/Scoping/IScope.cs +++ b/src/Umbraco.Core/Scoping/IScope.cs @@ -38,7 +38,7 @@ namespace Umbraco.Core.Scoping /// /// Gets the scope isolated cache. /// - IsolatedRuntimeCache IsolatedRuntimeCache { get; } + IsolatedCaches IsolatedCaches { get; } /// /// Completes the scope. diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index adc5482e68..aa08016d3c 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -1,6 +1,7 @@ using System; using System.Data; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.IO; @@ -33,7 +34,7 @@ namespace Umbraco.Core.Scoping private bool _disposed; private bool? _completed; - private IsolatedRuntimeCache _isolatedRuntimeCache; + private IsolatedCaches _isolatedCaches; private IUmbracoDatabase _database; private EventMessages _messages; private ICompletable _fscope; @@ -176,14 +177,14 @@ namespace Umbraco.Core.Scoping } /// - public IsolatedRuntimeCache IsolatedRuntimeCache + public IsolatedCaches IsolatedCaches { get { - if (ParentScope != null) return ParentScope.IsolatedRuntimeCache; + if (ParentScope != null) return ParentScope.IsolatedCaches; - return _isolatedRuntimeCache ?? (_isolatedRuntimeCache - = new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); + return _isolatedCaches ?? (_isolatedCaches + = new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); } } @@ -491,7 +492,7 @@ namespace Umbraco.Core.Scoping // caching config // true if Umbraco.CoreDebug.LogUncompletedScope appSetting is set to "true" private static bool LogUncompletedScopes => (_logUncompletedScopes - ?? (_logUncompletedScopes = UmbracoConfig.For.CoreDebug().LogUncompletedScopes)).Value; + ?? (_logUncompletedScopes = Current.Configs.CoreDebug().LogUncompletedScopes)).Value; /// public void ReadLock(params int[] lockIds) diff --git a/src/Umbraco.Core/Scoping/ScopeContext.cs b/src/Umbraco.Core/Scoping/ScopeContext.cs index dd26fda85e..4ba1999474 100644 --- a/src/Umbraco.Core/Scoping/ScopeContext.cs +++ b/src/Umbraco.Core/Scoping/ScopeContext.cs @@ -7,27 +7,32 @@ namespace Umbraco.Core.Scoping internal class ScopeContext : IScopeContext, IInstanceIdentifiable { private Dictionary _enlisted; - private bool _exiting; public void ScopeExit(bool completed) { if (_enlisted == null) return; - _exiting = true; + // fixme - can we create infinite loops? + // fixme - what about nested events? will they just be plainly ignored = really bad? List exceptions = null; - foreach (var enlisted in _enlisted.Values.OrderBy(x => x.Priority)) + List orderedEnlisted; + while ((orderedEnlisted = _enlisted.Values.OrderBy(x => x.Priority).ToList()).Count > 0) { - try + _enlisted.Clear(); + foreach (var enlisted in orderedEnlisted) { - enlisted.Execute(completed); - } - catch (Exception e) - { - if (exceptions == null) - exceptions = new List(); - exceptions.Add(e); + try + { + enlisted.Execute(completed); + } + catch (Exception e) + { + if (exceptions == null) + exceptions = new List(); + exceptions.Add(e); + } } } @@ -74,9 +79,6 @@ namespace Umbraco.Core.Scoping public T Enlist(string key, Func creator, Action action = null, int priority = 100) { - if (_exiting) - throw new InvalidOperationException("Cannot enlist now, context is exiting."); - var enlistedObjects = _enlisted ?? (_enlisted = new Dictionary()); if (enlistedObjects.TryGetValue(key, out var enlisted)) diff --git a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs index ca01330212..ff595a5d45 100644 --- a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs +++ b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Web; using System.Web.Hosting; using System.Web.Security; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -87,11 +88,11 @@ namespace Umbraco.Core.Security /// public static MembershipProvider GetUsersMembershipProvider() { - if (Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider] == null) + if (Membership.Providers[Current.Configs.Settings().Providers.DefaultBackOfficeUserProvider] == null) { - throw new InvalidOperationException("No membership provider found with name " + UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider); + throw new InvalidOperationException("No membership provider found with name " + Current.Configs.Settings().Providers.DefaultBackOfficeUserProvider); } - return Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider]; + return Membership.Providers[Current.Configs.Settings().Providers.DefaultBackOfficeUserProvider]; } /// diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs index d67f8f9200..57e1d288e4 100644 --- a/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs +++ b/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Services.Changes { - internal class ContentTypeChange + public class ContentTypeChange where TItem : class, IContentTypeComposition { public ContentTypeChange(TItem item, ContentTypeChangeTypes changeTypes) diff --git a/src/Umbraco.Core/Services/IApplicationTreeService.cs b/src/Umbraco.Core/Services/IApplicationTreeService.cs deleted file mode 100644 index 691a3a0b63..0000000000 --- a/src/Umbraco.Core/Services/IApplicationTreeService.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Services -{ - public interface IApplicationTreeService - { - /// - /// Creates a new application tree. - /// - /// if set to true [initialize]. - /// The sort order. - /// The application alias. - /// The alias. - /// The title. - /// The icon closed. - /// The icon opened. - /// The type. - void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type); - - /// - /// Saves this instance. - /// - void SaveTree(ApplicationTree tree); - - /// - /// Deletes this instance. - /// - void DeleteTree(ApplicationTree tree); - - /// - /// Gets an ApplicationTree by it's tree alias. - /// - /// The tree alias. - /// An ApplicationTree instance - ApplicationTree GetByAlias(string treeAlias); - - /// - /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. - /// - /// Returns a ApplicationTree Array - IEnumerable GetAll(); - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// Returns a ApplicationTree Array - IEnumerable GetApplicationTrees(string applicationAlias); - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// - /// Returns a ApplicationTree Array - IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized); - - /// - /// Gets the grouped application trees for the application with the specified alias - /// - /// - /// - /// - IDictionary> GetGroupedApplicationTrees(string applicationAlias, bool onlyInitialized); - } - - /// - /// Purely used to allow a service context to create the default services - /// - internal class EmptyApplicationTreeService : IApplicationTreeService - { - /// - /// Creates a new application tree. - /// - /// if set to true [initialize]. - /// The sort order. - /// The application alias. - /// The alias. - /// The title. - /// The icon closed. - /// The icon opened. - /// The type. - public void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) - { - throw new System.NotImplementedException(); - } - - /// - /// Saves this instance. - /// - public void SaveTree(ApplicationTree tree) - { - throw new System.NotImplementedException(); - } - - /// - /// Deletes this instance. - /// - public void DeleteTree(ApplicationTree tree) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets an ApplicationTree by it's tree alias. - /// - /// The tree alias. - /// An ApplicationTree instance - public ApplicationTree GetByAlias(string treeAlias) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. - /// - /// Returns a ApplicationTree Array - public IEnumerable GetAll() - { - throw new System.NotImplementedException(); - } - - public IDictionary> GetGroupedApplicationTrees(string applicationAlias, bool onlyInitialized) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// Returns a ApplicationTree Array - public IEnumerable GetApplicationTrees(string applicationAlias) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// - /// Returns a ApplicationTree Array - public IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized) - { - throw new System.NotImplementedException(); - } - } -} diff --git a/src/Umbraco.Core/Services/IEntityXmlSerializer.cs b/src/Umbraco.Core/Services/IEntityXmlSerializer.cs new file mode 100644 index 0000000000..405fc47c3a --- /dev/null +++ b/src/Umbraco.Core/Services/IEntityXmlSerializer.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.Xml.Linq; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + /// + /// Serializes entities to XML + /// + public interface IEntityXmlSerializer + { + /// + /// Exports an IContent item as an XElement. + /// + XElement Serialize(IContent content, + bool published, + bool withDescendants = false) //fixme take care of usage! only used for the packager + ; + + /// + /// Exports an IMedia item as an XElement. + /// + XElement Serialize( + IMedia media, + bool withDescendants = false); + + /// + /// Exports an IMember item as an XElement. + /// + XElement Serialize(IMember member); + + /// + /// Exports a list of Data Types + /// + /// List of data types to export + /// containing the xml representation of the IDataTypeDefinition objects + XElement Serialize(IEnumerable dataTypeDefinitions); + + XElement Serialize(IDataType dataType); + + /// + /// Exports a list of items to xml as an + /// + /// List of dictionary items to export + /// Optional boolean indicating whether or not to include children + /// containing the xml representation of the IDictionaryItem objects + XElement Serialize(IEnumerable dictionaryItem, bool includeChildren = true); + + /// + /// Exports a single item to xml as an + /// + /// Dictionary Item to export + /// Optional boolean indicating whether or not to include children + /// containing the xml representation of the IDictionaryItem object + XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren); + + XElement Serialize(Stylesheet stylesheet); + + /// + /// Exports a list of items to xml as an + /// + /// List of Languages to export + /// containing the xml representation of the ILanguage objects + XElement Serialize(IEnumerable languages); + + XElement Serialize(ILanguage language); + XElement Serialize(ITemplate template); + + /// + /// Exports a list of items to xml as an + /// + /// + /// + XElement Serialize(IEnumerable templates); + + XElement Serialize(IMediaType mediaType); + + /// + /// Exports a list of items to xml as an + /// + /// Macros to export + /// containing the xml representation of the IMacro objects + XElement Serialize(IEnumerable macros); + + XElement Serialize(IMacro macro); + XElement Serialize(IContentType contentType); + } +} diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index 76e850beb2..daf5d9b80c 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -210,21 +210,6 @@ namespace Umbraco.Core.Services /// Optional id of the user void SaveTemplate(IEnumerable templates, int userId = 0); - /// - /// This checks what the default rendering engine is set in config but then also ensures that there isn't already - /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate - /// rendering engine to use. - /// - /// - /// - /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx - /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml - /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. - /// This is mostly related to installing packages since packages install file templates to the file system and then create the - /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. - /// - RenderingEngine DetermineTemplateRenderingEngine(ITemplate template); - /// /// Gets the content of a template as a stream. /// diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index ceab6e94bf..c14882fe7a 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -1,200 +1,119 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; using System.Xml.Linq; +using Semver; using Umbraco.Core.Models; +using Umbraco.Core.Models.Packaging; +using Umbraco.Core.Packaging; namespace Umbraco.Core.Services { public interface IPackagingService : IService { - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// Optional parent Id for the content being imported - /// Optional Id of the user performing the import - /// Optional parameter indicating whether or not to raise events - /// An enumrable list of generated content - IEnumerable ImportContent(XElement element, int parentId = -1, int userId = 0, bool raiseEvents = true); + #region Package Installation /// - /// Imports and saves package xml as + /// Returns a result from an umbraco package file (zip) /// - /// Xml to import - /// Optional id of the User performing the operation. Default is zero (admin) - /// Optional parameter indicating whether or not to raise events - /// An enumrable list of generated ContentTypes - IEnumerable ImportContentTypes(XElement element, int userId = 0, bool raiseEvents = true); - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// Boolean indicating whether or not to import the - /// Optional id of the User performing the operation. Default is zero (admin) - /// Optional parameter indicating whether or not to raise events - /// An enumrable list of generated ContentTypes - IEnumerable ImportContentTypes(XElement element, bool importStructure, int userId = 0, bool raiseEvents = true); - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// Optional id of the User performing the operation. Default is zero (admin). - /// Optional parameter indicating whether or not to raise events - /// An enumrable list of generated DataTypeDefinitions - IEnumerable ImportDataTypeDefinitions(XElement element, int userId = 0, bool raiseEvents = true); - - /// - /// Imports and saves the 'DictionaryItems' part of the package xml as a list of - /// - /// Xml to import - /// Optional parameter indicating whether or not to raise events - /// An enumerable list of dictionary items - IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, bool raiseEvents = true); - - /// - /// Imports and saves the 'Languages' part of a package xml as a list of - /// - /// Xml to import - /// Optional id of the User performing the operation. Default is zero (admin) - /// Optional parameter indicating whether or not to raise events - /// An enumerable list of generated languages - IEnumerable ImportLanguages(XElement languageElementList, int userId = 0, bool raiseEvents = true); - - /// - /// Imports and saves the 'Macros' part of a package xml as a list of - /// - /// Xml to import - /// Optional id of the User performing the operation - /// Optional parameter indicating whether or not to raise events + /// /// - IEnumerable ImportMacros(XElement element, int userId = 0, bool raiseEvents = true); + CompiledPackage GetCompiledPackageInfo(FileInfo packageFile); /// - /// Imports and saves package xml as + /// Installs the package files contained in an umbraco package file (zip) /// - /// Xml to import - /// Optional id of the User performing the operation. Default is zero (admin) - /// Optional parameter indicating whether or not to raise events - /// An enumrable list of generated Templates - IEnumerable ImportTemplates(XElement element, int userId = 0, bool raiseEvents = true); + /// + /// + /// + IEnumerable InstallCompiledPackageFiles(PackageDefinition packageDefinition, FileInfo packageFile, int userId = 0); /// - /// Exports an to xml as an + /// Installs the data, entities, objects contained in an umbraco package file (zip) /// - /// ContentType to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the ContentType item - XElement Export(IContentType contentType, bool raiseEvents = true); + /// + /// + /// + InstallationSummary InstallCompiledPackageData(PackageDefinition packageDefinition, FileInfo packageFile, int userId = 0); /// - /// Exports an item to xml as an + /// Uninstalls all versions of the package by name /// - /// Content to export - /// Optional parameter indicating whether to include descendents - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the Content object - XElement Export(IContent content, bool deep = false, bool raiseEvents = true); + /// + /// + /// + UninstallationSummary UninstallPackage(string packageName, int userId = 0); + + #endregion + + #region Installed Packages + + IEnumerable GetAllInstalledPackages(); /// - /// Exports an item to xml as an + /// Returns the for the installation id /// - /// Media to export - /// Optional parameter indicating whether to include descendents - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the Media object - XElement Export(IMedia media, bool deep = false, bool raiseEvents = true); + /// + /// + PackageDefinition GetInstalledPackageById(int id); /// - /// Exports a list of items to xml as an + /// Returns all for the package by name /// - /// List of Languages to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the Language object - XElement Export(IEnumerable languages, bool raiseEvents = true); + /// + /// + /// A list of all package definitions installed for this package (i.e. original install and any upgrades) + /// + IEnumerable GetInstalledPackageByName(string name); /// - /// Exports a single item to xml as an + /// Returns a for a given package name and version /// - /// Language to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the Language object - XElement Export(ILanguage language, bool raiseEvents = true); + /// + /// + /// If the package is an upgrade, the original/current PackageDefinition is returned + /// + PackageInstallType GetPackageInstallType(string packageName, SemVersion packageVersion, out PackageDefinition alreadyInstalled); + void DeleteInstalledPackage(int packageId, int userId = 0); /// - /// Exports a list of items to xml as an + /// Persists a package definition to storage /// - /// List of dictionary items to export - /// Optional boolean indicating whether or not to include children - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IDictionaryItem objects - XElement Export(IEnumerable dictionaryItem, bool includeChildren = true, bool raiseEvents = true); + /// + bool SaveInstalledPackage(PackageDefinition definition); + + #endregion + + #region Created Packages + + IEnumerable GetAllCreatedPackages(); + PackageDefinition GetCreatedPackageById(int id); + void DeleteCreatedPackage(int id, int userId = 0); /// - /// Exports a single item to xml as an + /// Persists a package definition to storage /// - /// Dictionary Item to export - /// Optional boolean indicating whether or not to include children - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IDictionaryItem object - XElement Export(IDictionaryItem dictionaryItem, bool includeChildren, bool raiseEvents = true); + /// + bool SaveCreatedPackage(PackageDefinition definition); /// - /// Exports a list of Data Types + /// Creates the package file and returns it's physical path /// - /// List of data types to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IDataTypeDefinition objects - XElement Export(IEnumerable dataTypeDefinitions, bool raiseEvents = true); + /// + string ExportCreatedPackage(PackageDefinition definition); + + #endregion /// - /// Exports a single Data Type - /// - /// Data type to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IDataTypeDefinition object - XElement Export(IDataType dataType, bool raiseEvents = true); - - /// - /// Exports a list of items to xml as an - /// - /// List of Templates to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the ITemplate objects - XElement Export(IEnumerable templates, bool raiseEvents = true); - - /// - /// Exports a single item to xml as an - /// - /// Template to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the ITemplate object - XElement Export(ITemplate template, bool raiseEvents = true); - - /// - /// Exports a list of items to xml as an - /// - /// Macros to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IMacro objects - XElement Export(IEnumerable macros, bool raiseEvents = true); - - /// - /// Exports a single item to xml as an - /// - /// Macro to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IMacro object - XElement Export(IMacro macro, bool raiseEvents = true); - - /// - /// This will fetch an Umbraco package file from the package repository and return the relative file path to the downloaded package file + /// This will fetch an Umbraco package file from the package repository and return the file name of the downloaded package /// /// /// /// The current user id performing the operation - /// - string FetchPackageFile(Guid packageId, Version umbracoVersion, int userId); + /// + /// The file name of the downloaded package which will exist in ~/App_Data/packages + /// + Task FetchPackageFileAsync(Guid packageId, Version umbracoVersion, int userId); } } diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs index 62e59e910c..3bd9b6f2cf 100644 --- a/src/Umbraco.Core/Services/IRedirectUrlService.cs +++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs @@ -14,8 +14,9 @@ namespace Umbraco.Core.Services /// /// The Umbraco url route. /// The content unique key. + /// The culture. /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. - void Register(string url, Guid contentKey); + void Register(string url, Guid contentKey, string culture = null); /// /// Deletes all redirect urls for a given content. diff --git a/src/Umbraco.Core/Services/ISectionService.cs b/src/Umbraco.Core/Services/ISectionService.cs deleted file mode 100644 index 899ae78245..0000000000 --- a/src/Umbraco.Core/Services/ISectionService.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Services -{ - public interface ISectionService - { - /// - /// The cache storage for all applications - /// - IEnumerable
GetSections(); - - /// - /// Get the user group's allowed sections - /// - /// - /// - IEnumerable
GetAllowedSections(int userId); - - /// - /// Gets the application by its alias. - /// - /// The application alias. - /// - Section GetByAlias(string appAlias); - - /// - /// Creates a new applcation if no application with the specified alias is found. - /// - /// The application name. - /// The application alias. - /// The application icon, which has to be located in umbraco/images/tray folder. - void MakeNew(string name, string alias, string icon); - - /// - /// Makes the new. - /// - /// The name. - /// The alias. - /// The icon. - /// The sort order. - void MakeNew(string name, string alias, string icon, int sortOrder); - - /// - /// Deletes the section - /// - void DeleteSection(Section section); - } - - /// - /// Purely used to allow a service context to create the default services - /// - internal class EmptySectionService : ISectionService - { - /// - /// The cache storage for all applications - /// - public IEnumerable
GetSections() - { - throw new System.NotImplementedException(); - } - - /// - /// Get the user's allowed sections - /// - /// - /// - public IEnumerable
GetAllowedSections(int userId) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets the application by its alias. - /// - /// The application alias. - /// - public Section GetByAlias(string appAlias) - { - throw new System.NotImplementedException(); - } - - /// - /// Creates a new applcation if no application with the specified alias is found. - /// - /// The application name. - /// The application alias. - /// The application icon, which has to be located in umbraco/images/tray folder. - public void MakeNew(string name, string alias, string icon) - { - throw new System.NotImplementedException(); - } - - /// - /// Makes the new. - /// - /// The name. - /// The alias. - /// The icon. - /// The sort order. - public void MakeNew(string name, string alias, string icon, int sortOrder) - { - throw new System.NotImplementedException(); - } - - /// - /// Deletes the section - /// - public void DeleteSection(Section section) - { - throw new System.NotImplementedException(); - } - } -} diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 22ff9d5917..9943893473 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -27,13 +27,13 @@ namespace Umbraco.Core.Services.Implement private readonly IContentTypeRepository _contentTypeRepository; private readonly IDocumentBlueprintRepository _documentBlueprintRepository; private readonly ILanguageRepository _languageRepository; - private readonly MediaFileSystem _mediaFileSystem; + private readonly IMediaFileSystem _mediaFileSystem; private IQuery _queryNotTrashed; #region Constructors public ContentService(IScopeProvider provider, ILogger logger, - IEventMessagesFactory eventMessagesFactory, MediaFileSystem mediaFileSystem, + IEventMessagesFactory eventMessagesFactory, IMediaFileSystem mediaFileSystem, IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository, IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository) : base(provider, logger, eventMessagesFactory) @@ -1552,9 +1552,7 @@ namespace Umbraco.Core.Services.Implement var args = new DeleteEventArgs(c, false); // raise event & get flagged files scope.Events.Dispatch(Deleted, this, args, nameof(Deleted)); - // fixme not going to work, do it differently - _mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files - (file, e) => Logger.Error(e, "An error occurred while deleting file attached to nodes: {File}", file)); + // media files deleted by QueuingEventDispatcher } const int pageSize = 500; @@ -2735,6 +2733,8 @@ namespace Umbraco.Core.Services.Implement } } + private static readonly string[] ArrayOfOneNullString = { null }; + public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0) { if (blueprint == null) throw new ArgumentNullException(nameof(blueprint)); @@ -2746,8 +2746,23 @@ namespace Umbraco.Core.Services.Implement content.CreatorId = userId; content.WriterId = userId; - foreach (var property in blueprint.Properties) - content.SetValue(property.Alias, property.GetValue()); //fixme doesn't take into account variants + var now = DateTime.Now; + var cultures = blueprint.CultureInfos.Any() ? blueprint.CultureInfos.Select(x=>x.Key) : ArrayOfOneNullString; + foreach (var culture in cultures) + { + foreach (var property in blueprint.Properties) + { + content.SetValue(property.Alias, property.GetValue(culture), culture); + } + + content.Name = blueprint.Name; + if (!string.IsNullOrEmpty(culture)) + { + content.SetCultureInfo(culture, blueprint.GetCultureName(culture), now); + } + } + + return content; } diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeService.cs index f6498770ec..fa818496ff 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeService.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Services.Implement /// /// Represents the ContentType Service, which is an easy access to operations involving /// - internal class ContentTypeService : ContentTypeServiceBase, IContentTypeService + public class ContentTypeService : ContentTypeServiceBase, IContentTypeService { public ContentTypeService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService, IContentTypeRepository repository, IAuditRepository auditRepository, IDocumentTypeContainerRepository entityContainerRepository, IEntityRepository entityRepository) diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBase.cs index d5cdd36318..26298f171c 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBase.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Services.Implement { - internal abstract class ContentTypeServiceBase : ScopeRepositoryService + public abstract class ContentTypeServiceBase : ScopeRepositoryService { protected ContentTypeServiceBase(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(provider, logger, eventMessagesFactory) diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs index 33fb9a0894..f4457e0991 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Services.Changes; namespace Umbraco.Core.Services.Implement { - internal abstract class ContentTypeServiceBase : ContentTypeServiceBase + public abstract class ContentTypeServiceBase : ContentTypeServiceBase where TItem : class, IContentTypeComposition where TService : class, IContentTypeServiceBase { diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index be4f719bb1..8189c6524e 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -13,7 +13,7 @@ using Umbraco.Core.Services.Changes; namespace Umbraco.Core.Services.Implement { - internal abstract class ContentTypeServiceBase : ContentTypeServiceBase, IContentTypeServiceBase + public abstract class ContentTypeServiceBase : ContentTypeServiceBase, IContentTypeServiceBase where TRepository : IContentTypeRepositoryBase where TItem : class, IContentTypeComposition where TService : class, IContentTypeServiceBase diff --git a/src/Umbraco.Core/Services/Implement/DataTypeService.cs b/src/Umbraco.Core/Services/Implement/DataTypeService.cs index 79ca851de9..84d44649da 100644 --- a/src/Umbraco.Core/Services/Implement/DataTypeService.cs +++ b/src/Umbraco.Core/Services/Implement/DataTypeService.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Services.Implement /// /// Represents the DataType Service, which is an easy access to operations involving /// - internal class DataTypeService : ScopeRepositoryService, IDataTypeService + public class DataTypeService : ScopeRepositoryService, IDataTypeService { private readonly IDataTypeRepository _dataTypeRepository; private readonly IDataTypeContainerRepository _dataTypeContainerRepository; diff --git a/src/Umbraco.Core/Services/Implement/EntityService.cs b/src/Umbraco.Core/Services/Implement/EntityService.cs index 4a3db29940..37b569b814 100644 --- a/src/Umbraco.Core/Services/Implement/EntityService.cs +++ b/src/Umbraco.Core/Services/Implement/EntityService.cs @@ -258,6 +258,9 @@ namespace Umbraco.Core.Services.Implement public virtual IEnumerable GetAll(UmbracoObjectTypes objectType, params int[] ids) { var entityType = objectType.GetClrType(); + if (entityType == null) + throw new NotSupportedException($"Type \"{objectType}\" is not supported here."); + GetGetters(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs similarity index 69% rename from src/Umbraco.Core/Services/EntityXmlSerializer.cs rename to src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs index 5b64584dc6..fd1067b3e5 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs @@ -7,52 +7,64 @@ using System.Xml.Linq; using Newtonsoft.Json; using Umbraco.Core.Composing; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Strings; -namespace Umbraco.Core.Services +namespace Umbraco.Core.Services.Implement { - //TODO: Move the rest of the logic for the PackageService.Export methods to here! - /// - /// A helper class to serialize entities to XML + /// Serializes entities to XML /// - internal class EntityXmlSerializer + internal class EntityXmlSerializer : IEntityXmlSerializer { - /// - /// Exports an IContent item as an XElement. - /// - public static XElement Serialize( + private readonly IContentTypeService _contentTypeService; + private readonly IMediaService _mediaService; + private readonly IContentService _contentService; + private readonly IDataTypeService _dataTypeService; + private readonly IUserService _userService; + private readonly ILocalizationService _localizationService; + private readonly UrlSegmentProviderCollection _urlSegmentProviders; + + public EntityXmlSerializer( IContentService contentService, + IMediaService mediaService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, - IEnumerable urlSegmentProviders, - IContent content, + IContentTypeService contentTypeService, + UrlSegmentProviderCollection urlSegmentProviders) + { + _contentTypeService = contentTypeService; + _mediaService = mediaService; + _contentService = contentService; + _dataTypeService = dataTypeService; + _userService = userService; + _localizationService = localizationService; + _urlSegmentProviders = urlSegmentProviders; + } + + /// + /// Exports an IContent item as an XElement. + /// + public XElement Serialize(IContent content, bool published, bool withDescendants = false) //fixme take care of usage! only used for the packager { - if (contentService == null) throw new ArgumentNullException(nameof(contentService)); - if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); - if (userService == null) throw new ArgumentNullException(nameof(userService)); - if (localizationService == null) throw new ArgumentNullException(nameof(localizationService)); if (content == null) throw new ArgumentNullException(nameof(content)); - if (urlSegmentProviders == null) throw new ArgumentNullException(nameof(urlSegmentProviders)); // nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias); var nodeName = content.ContentType.Alias.ToSafeAliasWithForcingCheck(); - var xml = SerializeContentBase(dataTypeService, localizationService, content, content.GetUrlSegment(urlSegmentProviders), nodeName, published); + var xml = SerializeContentBase(content, content.GetUrlSegment(_urlSegmentProviders), nodeName, published); xml.Add(new XAttribute("nodeType", content.ContentType.Id)); xml.Add(new XAttribute("nodeTypeAlias", content.ContentType.Alias)); - xml.Add(new XAttribute("creatorName", content.GetCreatorProfile(userService)?.Name ?? "??")); + xml.Add(new XAttribute("creatorName", content.GetCreatorProfile(_userService)?.Name ?? "??")); //xml.Add(new XAttribute("creatorID", content.CreatorId)); - xml.Add(new XAttribute("writerName", content.GetWriterProfile(userService)?.Name ?? "??")); + xml.Add(new XAttribute("writerName", content.GetWriterProfile(_userService)?.Name ?? "??")); xml.Add(new XAttribute("writerID", content.WriterId)); - xml.Add(new XAttribute("template", content.Template?.Id.ToString(CultureInfo.InvariantCulture) ?? "0")); + xml.Add(new XAttribute("template", content.TemplateId?.ToString(CultureInfo.InvariantCulture) ?? "")); xml.Add(new XAttribute("isPublished", content.Published)); @@ -63,8 +75,8 @@ namespace Umbraco.Core.Services var total = long.MaxValue; while(page * pageSize < total) { - var children = contentService.GetPagedChildren(content.Id, page++, pageSize, out total); - SerializeChildren(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, children, xml, published); + var children = _contentService.GetPagedChildren(content.Id, page++, pageSize, out total); + SerializeChildren(children, xml, published); } } @@ -75,34 +87,29 @@ namespace Umbraco.Core.Services /// /// Exports an IMedia item as an XElement. /// - public static XElement Serialize( - IMediaService mediaService, - IDataTypeService dataTypeService, - IUserService userService, - ILocalizationService localizationService, - IEnumerable urlSegmentProviders, + public XElement Serialize( IMedia media, bool withDescendants = false) { - if (mediaService == null) throw new ArgumentNullException(nameof(mediaService)); - if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); - if (userService == null) throw new ArgumentNullException(nameof(userService)); - if (localizationService == null) throw new ArgumentNullException(nameof(localizationService)); + if (_mediaService == null) throw new ArgumentNullException(nameof(_mediaService)); + if (_dataTypeService == null) throw new ArgumentNullException(nameof(_dataTypeService)); + if (_userService == null) throw new ArgumentNullException(nameof(_userService)); + if (_localizationService == null) throw new ArgumentNullException(nameof(_localizationService)); if (media == null) throw new ArgumentNullException(nameof(media)); - if (urlSegmentProviders == null) throw new ArgumentNullException(nameof(urlSegmentProviders)); + if (_urlSegmentProviders == null) throw new ArgumentNullException(nameof(_urlSegmentProviders)); // nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias); var nodeName = media.ContentType.Alias.ToSafeAliasWithForcingCheck(); const bool published = false; // always false for media - var xml = SerializeContentBase(dataTypeService, localizationService, media, media.GetUrlSegment(urlSegmentProviders), nodeName, published); + var xml = SerializeContentBase(media, media.GetUrlSegment(_urlSegmentProviders), nodeName, published); xml.Add(new XAttribute("nodeType", media.ContentType.Id)); xml.Add(new XAttribute("nodeTypeAlias", media.ContentType.Alias)); //xml.Add(new XAttribute("creatorName", media.GetCreatorProfile(userService).Name)); //xml.Add(new XAttribute("creatorID", media.CreatorId)); - xml.Add(new XAttribute("writerName", media.GetWriterProfile(userService)?.Name ?? string.Empty)); + xml.Add(new XAttribute("writerName", media.GetWriterProfile(_userService)?.Name ?? string.Empty)); xml.Add(new XAttribute("writerID", media.WriterId)); //xml.Add(new XAttribute("template", 0)); // no template for media @@ -114,8 +121,8 @@ namespace Umbraco.Core.Services var total = long.MaxValue; while (page * pageSize < total) { - var children = mediaService.GetPagedChildren(media.Id, page++, pageSize, out total); - SerializeChildren(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, children, xml); + var children = _mediaService.GetPagedChildren(media.Id, page++, pageSize, out total); + SerializeChildren(children, xml); } } @@ -125,16 +132,13 @@ namespace Umbraco.Core.Services /// /// Exports an IMember item as an XElement. /// - public static XElement Serialize( - IDataTypeService dataTypeService, - ILocalizationService localizationService, - IMember member) + public XElement Serialize(IMember member) { // nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias); var nodeName = member.ContentType.Alias.ToSafeAliasWithForcingCheck(); const bool published = false; // always false for member - var xml = SerializeContentBase(dataTypeService, localizationService, member, "", nodeName, published); + var xml = SerializeContentBase(member, "", nodeName, published); xml.Add(new XAttribute("nodeType", member.ContentType.Id)); xml.Add(new XAttribute("nodeTypeAlias", member.ContentType.Alias)); @@ -148,7 +152,22 @@ namespace Umbraco.Core.Services return xml; } - public XElement Serialize(IDataTypeService dataTypeService, IDataType dataType) + /// + /// Exports a list of Data Types + /// + /// List of data types to export + /// containing the xml representation of the IDataTypeDefinition objects + public XElement Serialize(IEnumerable dataTypeDefinitions) + { + var container = new XElement("DataTypes"); + foreach (var dataTypeDefinition in dataTypeDefinitions) + { + container.Add(Serialize(dataTypeDefinition)); + } + return container; + } + + public XElement Serialize(IDataType dataType) { var xml = new XElement("DataType"); xml.Add(new XAttribute("Name", dataType.Name)); @@ -162,7 +181,7 @@ namespace Umbraco.Core.Services if (dataType.Level != 1) { //get url encoded folder names - var folders = dataTypeService.GetContainers(dataType) + var folders = _dataTypeService.GetContainers(dataType) .OrderBy(x => x.Level) .Select(x => HttpUtility.UrlEncode(x.Name)); @@ -175,7 +194,45 @@ namespace Umbraco.Core.Services return xml; } - public XElement Serialize(IDictionaryItem dictionaryItem) + /// + /// Exports a list of items to xml as an + /// + /// List of dictionary items to export + /// Optional boolean indicating whether or not to include children + /// containing the xml representation of the IDictionaryItem objects + public XElement Serialize(IEnumerable dictionaryItem, bool includeChildren = true) + { + var xml = new XElement("DictionaryItems"); + foreach (var item in dictionaryItem) + { + xml.Add(Serialize(item, includeChildren)); + } + return xml; + } + + /// + /// Exports a single item to xml as an + /// + /// Dictionary Item to export + /// Optional boolean indicating whether or not to include children + /// containing the xml representation of the IDictionaryItem object + public XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren) + { + var xml = Serialize(dictionaryItem); + + if (includeChildren) + { + var children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key); + foreach (var child in children) + { + xml.Add(Serialize(child, true)); + } + } + + return xml; + } + + private XElement Serialize(IDictionaryItem dictionaryItem) { var xml = new XElement("DictionaryItem", new XAttribute("Key", dictionaryItem.ItemKey)); foreach (var translation in dictionaryItem.Translations) @@ -210,6 +267,21 @@ namespace Umbraco.Core.Services return xml; } + /// + /// Exports a list of items to xml as an + /// + /// List of Languages to export + /// containing the xml representation of the ILanguage objects + public XElement Serialize(IEnumerable languages) + { + var xml = new XElement("Languages"); + foreach (var language in languages) + { + xml.Add(Serialize(language)); + } + return xml; + } + public XElement Serialize(ILanguage language) { var xml = new XElement("Language", @@ -240,7 +312,22 @@ namespace Umbraco.Core.Services return xml; } - public XElement Serialize(IDataTypeService dataTypeService, IMediaType mediaType) + /// + /// Exports a list of items to xml as an + /// + /// + /// + public XElement Serialize(IEnumerable templates) + { + var xml = new XElement("Templates"); + foreach (var item in templates) + { + xml.Add(Serialize(item)); + } + return xml; + } + + public XElement Serialize(IMediaType mediaType) { var info = new XElement("Info", new XElement("Name", mediaType.Name), @@ -263,7 +350,7 @@ namespace Umbraco.Core.Services var genericProperties = new XElement("GenericProperties"); // actually, all of them foreach (var propertyType in mediaType.PropertyTypes) { - var definition = dataTypeService.GetDataType(propertyType.DataTypeId); + var definition = _dataTypeService.GetDataType(propertyType.DataTypeId); var propertyGroup = propertyType.PropertyGroupId == null // true generic property ? null @@ -301,6 +388,21 @@ namespace Umbraco.Core.Services return xml; } + /// + /// Exports a list of items to xml as an + /// + /// Macros to export + /// containing the xml representation of the IMacro objects + public XElement Serialize(IEnumerable macros) + { + var xml = new XElement("Macros"); + foreach (var item in macros) + { + xml.Add(Serialize(item)); + } + return xml; + } + public XElement Serialize(IMacro macro) { var xml = new XElement("macro"); @@ -328,7 +430,7 @@ namespace Umbraco.Core.Services return xml; } - public XElement Serialize(IDataTypeService dataTypeService, IContentTypeService contentTypeService, IContentType contentType) + public XElement Serialize(IContentType contentType) { var info = new XElement("Info", new XElement("Name", contentType.Name), @@ -337,7 +439,8 @@ namespace Umbraco.Core.Services new XElement("Thumbnail", contentType.Thumbnail), new XElement("Description", contentType.Description), new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()), - new XElement("IsListView", contentType.IsContainer.ToString())); + new XElement("IsListView", contentType.IsContainer.ToString()), + new XElement("IsElement", contentType.IsElement.ToString())); var masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId); if(masterContentType != null) @@ -372,7 +475,7 @@ namespace Umbraco.Core.Services var genericProperties = new XElement("GenericProperties"); // actually, all of them foreach (var propertyType in contentType.PropertyTypes) { - var definition = dataTypeService.GetDataType(propertyType.DataTypeId); + var definition = _dataTypeService.GetDataType(propertyType.DataTypeId); var propertyGroup = propertyType.PropertyGroupId == null // true generic property ? null @@ -413,7 +516,7 @@ namespace Umbraco.Core.Services if (contentType.Level != 1 && masterContentType == null) { //get url encoded folder names - var folders = contentTypeService.GetContainers(contentType) + var folders = _contentTypeService.GetContainers(contentType) .OrderBy(x => x.Level) .Select(x => HttpUtility.UrlEncode(x.Name)); @@ -427,7 +530,7 @@ namespace Umbraco.Core.Services } // exports an IContentBase (IContent, IMedia or IMember) as an XElement. - private static XElement SerializeContentBase(IDataTypeService dataTypeService, ILocalizationService localizationService, IContentBase contentBase, string urlValue, string nodeName, bool published) + private XElement SerializeContentBase(IContentBase contentBase, string urlValue, string nodeName, bool published) { var xml = new XElement(nodeName, new XAttribute("id", contentBase.Id), @@ -444,13 +547,13 @@ namespace Umbraco.Core.Services new XAttribute("isDoc", "")); foreach (var property in contentBase.Properties) - xml.Add(SerializeProperty(dataTypeService, localizationService, property, published)); + xml.Add(SerializeProperty(property, published)); return xml; } // exports a property as XElements. - private static IEnumerable SerializeProperty(IDataTypeService dataTypeService, ILocalizationService localizationService, Property property, bool published) + private IEnumerable SerializeProperty(Property property, bool published) { var propertyType = property.PropertyType; @@ -458,16 +561,16 @@ namespace Umbraco.Core.Services var propertyEditor = Current.PropertyEditors[propertyType.PropertyEditorAlias]; return propertyEditor == null ? Array.Empty() - : propertyEditor.GetValueEditor().ConvertDbToXml(property, dataTypeService, localizationService, published); + : propertyEditor.GetValueEditor().ConvertDbToXml(property, _dataTypeService, _localizationService, published); } // exports an IContent item descendants. - private static void SerializeChildren(IContentService contentService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IEnumerable children, XElement xml, bool published) + private void SerializeChildren(IEnumerable children, XElement xml, bool published) { foreach (var child in children) { // add the child xml - var childXml = Serialize(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, child, published); + var childXml = Serialize(child, published); xml.Add(childXml); const int pageSize = 500; @@ -475,20 +578,20 @@ namespace Umbraco.Core.Services var total = long.MaxValue; while(page * pageSize < total) { - var grandChildren = contentService.GetPagedChildren(child.Id, page++, pageSize, out total); + var grandChildren = _contentService.GetPagedChildren(child.Id, page++, pageSize, out total); // recurse - SerializeChildren(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, grandChildren, childXml, published); + SerializeChildren(grandChildren, childXml, published); } } } // exports an IMedia item descendants. - private static void SerializeChildren(IMediaService mediaService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IEnumerable children, XElement xml) + private void SerializeChildren(IEnumerable children, XElement xml) { foreach (var child in children) { // add the child xml - var childXml = Serialize(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, child); + var childXml = Serialize(child); xml.Add(childXml); const int pageSize = 500; @@ -496,9 +599,9 @@ namespace Umbraco.Core.Services var total = long.MaxValue; while (page * pageSize < total) { - var grandChildren = mediaService.GetPagedChildren(child.Id, page++, pageSize, out total); + var grandChildren = _mediaService.GetPagedChildren(child.Id, page++, pageSize, out total); // recurse - SerializeChildren(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, grandChildren, childXml); + SerializeChildren(grandChildren, childXml); } } } diff --git a/src/Umbraco.Core/Services/Implement/FileService.cs b/src/Umbraco.Core/Services/Implement/FileService.cs index f15f0d7d47..0c08404ab3 100644 --- a/src/Umbraco.Core/Services/Implement/FileService.cs +++ b/src/Umbraco.Core/Services/Implement/FileService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -556,27 +555,6 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// This checks what the default rendering engine is set in config but then also ensures that there isn't already - /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate - /// rendering engine to use. - /// - /// - /// - /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx - /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml - /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. - /// This is mostly related to installing packages since packages install file templates to the file system and then create the - /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. - /// - public RenderingEngine DetermineTemplateRenderingEngine(ITemplate template) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - return _templateRepository.DetermineTemplateRenderingEngine(template); - } - } - /// /// Deletes a template by its alias /// @@ -1043,7 +1021,7 @@ namespace Umbraco.Core.Services.Implement _auditRepository.Save(new AuditItem(objectId, type, userId, entityType)); } - //TODO Method to change name and/or alias of view/masterpage template + //TODO Method to change name and/or alias of view template #region Event Handlers diff --git a/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs index 3b3f90a412..430c2b3d3c 100644 --- a/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs +++ b/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Services.Implement public class LocalizedTextServiceFileSources { private readonly ILogger _logger; - private readonly IRuntimeCacheProvider _cache; + private readonly IAppPolicyCache _cache; private readonly IEnumerable _supplementFileSources; private readonly DirectoryInfo _fileSourceFolder; @@ -37,16 +37,16 @@ namespace Umbraco.Core.Services.Implement /// public LocalizedTextServiceFileSources( ILogger logger, - IRuntimeCacheProvider cache, + AppCaches appCaches, DirectoryInfo fileSourceFolder, IEnumerable supplementFileSources) { if (logger == null) throw new ArgumentNullException("logger"); - if (cache == null) throw new ArgumentNullException("cache"); + if (appCaches == null) throw new ArgumentNullException("cache"); if (fileSourceFolder == null) throw new ArgumentNullException("fileSourceFolder"); _logger = logger; - _cache = cache; + _cache = appCaches.RuntimeCache; //Create the lazy source for the _xmlSources _xmlSources = new Lazy>>(() => @@ -137,14 +137,9 @@ namespace Umbraco.Core.Services.Implement /// /// Constructor /// - /// - /// - /// - public LocalizedTextServiceFileSources(ILogger logger, IRuntimeCacheProvider cache, DirectoryInfo fileSourceFolder) - : this(logger, cache, fileSourceFolder, Enumerable.Empty()) - { - - } + public LocalizedTextServiceFileSources(ILogger logger, AppCaches appCaches, DirectoryInfo fileSourceFolder) + : this(logger, appCaches, fileSourceFolder, Enumerable.Empty()) + { } /// /// returns all xml sources for all culture files found in the folder diff --git a/src/Umbraco.Core/Services/Implement/MacroService.cs b/src/Umbraco.Core/Services/Implement/MacroService.cs index 5176e2eb22..d4f2d95bbb 100644 --- a/src/Umbraco.Core/Services/Implement/MacroService.cs +++ b/src/Umbraco.Core/Services/Implement/MacroService.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Services.Implement /// /// Represents the Macro Service, which is an easy access to operations involving /// - internal class MacroService : ScopeRepositoryService, IMacroService + public class MacroService : ScopeRepositoryService, IMacroService { private readonly IMacroRepository _macroRepository; private readonly IAuditRepository _auditRepository; diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index 1d04462836..f8c6badb37 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -26,11 +26,11 @@ namespace Umbraco.Core.Services.Implement private readonly IAuditRepository _auditRepository; private readonly IEntityRepository _entityRepository; - private readonly MediaFileSystem _mediaFileSystem; + private readonly IMediaFileSystem _mediaFileSystem; #region Constructors - public MediaService(IScopeProvider provider, MediaFileSystem mediaFileSystem, ILogger logger, IEventMessagesFactory eventMessagesFactory, + public MediaService(IScopeProvider provider, IMediaFileSystem mediaFileSystem, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMediaRepository mediaRepository, IAuditRepository auditRepository, IMediaTypeRepository mediaTypeRepository, IEntityRepository entityRepository) : base(provider, logger, eventMessagesFactory) @@ -757,8 +757,7 @@ namespace Umbraco.Core.Services.Implement var args = new DeleteEventArgs(c, false); // raise event & get flagged files scope.Events.Dispatch(Deleted, this, args); - _mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files - (file, e) => Logger.Error(e, "An error occurred while deleting file attached to nodes: {File}", file)); + // media files deleted by QueuingEventDispatcher } const int pageSize = 500; diff --git a/src/Umbraco.Core/Services/Implement/MediaTypeService.cs b/src/Umbraco.Core/Services/Implement/MediaTypeService.cs index fe8c78e137..8cb69a655d 100644 --- a/src/Umbraco.Core/Services/Implement/MediaTypeService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaTypeService.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Services.Implement { - internal class MediaTypeService : ContentTypeServiceBase, IMediaTypeService + public class MediaTypeService : ContentTypeServiceBase, IMediaTypeService { public MediaTypeService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMediaService mediaService, IMediaTypeRepository mediaTypeRepository, IAuditRepository auditRepository, IMediaTypeContainerRepository entityContainerRepository, diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index e191493736..2f8c2f9a79 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -28,14 +28,14 @@ namespace Umbraco.Core.Services.Implement private readonly IAuditRepository _auditRepository; private readonly IMemberGroupService _memberGroupService; - private readonly MediaFileSystem _mediaFileSystem; + private readonly IMediaFileSystem _mediaFileSystem; //only for unit tests! internal MembershipProviderBase MembershipProvider { get; set; } #region Constructor - public MemberService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, MediaFileSystem mediaFileSystem, + public MemberService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IMediaFileSystem mediaFileSystem, IMemberRepository memberRepository, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, IAuditRepository auditRepository) : base(provider, logger, eventMessagesFactory) { @@ -929,10 +929,7 @@ namespace Umbraco.Core.Services.Implement args.CanCancel = false; scope.Events.Dispatch(Deleted, this, args); - // fixme - this is MOOT because the event will not trigger immediately - // it's been refactored already (think it's the dispatcher that deals with it?) - _mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files - (file, e) => Logger.Error(e, "An error occurred while deleting file attached to nodes: {File}", file)); + // media files deleted by QueuingEventDispatcher } #endregion diff --git a/src/Umbraco.Core/Services/Implement/MemberTypeService.cs b/src/Umbraco.Core/Services/Implement/MemberTypeService.cs index 6cc7d03cfb..05f32dc99c 100644 --- a/src/Umbraco.Core/Services/Implement/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberTypeService.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Services.Implement { - internal class MemberTypeService : ContentTypeServiceBase, IMemberTypeService + public class MemberTypeService : ContentTypeServiceBase, IMemberTypeService { private readonly IMemberTypeRepository _memberTypeRepository; diff --git a/src/Umbraco.Core/Services/Implement/PackagingService.cs b/src/Umbraco.Core/Services/Implement/PackagingService.cs index fff865e097..24ef818624 100644 --- a/src/Umbraco.Core/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Core/Services/Implement/PackagingService.cs @@ -1,15 +1,14 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; +using System.Threading.Tasks; using System.Web; using System.Xml.Linq; +using Semver; using Umbraco.Core.Collections; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; @@ -18,8 +17,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Packaging; using Umbraco.Core.Packaging; -using Umbraco.Core.Packaging.Models; -using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.PropertyEditors; @@ -35,1855 +32,227 @@ namespace Umbraco.Core.Services.Implement /// public class PackagingService : IPackagingService { - private readonly ILogger _logger; - private readonly IContentService _contentService; - private readonly IContentTypeService _contentTypeService; - private readonly IMediaService _mediaService; - private readonly IMacroService _macroService; - private readonly IDataTypeService _dataTypeService; - private readonly IFileService _fileService; - private readonly ILocalizationService _localizationService; - private readonly IEntityService _entityService; - private readonly IScopeProvider _scopeProvider; - private readonly IEnumerable _urlSegmentProviders; - private Dictionary _importedContentTypes; - private IPackageInstallation _packageInstallation; - private readonly IUserService _userService; - private readonly IAuditRepository _auditRepository; - private readonly IContentTypeRepository _contentTypeRepository; - private readonly PropertyEditorCollection _propertyEditors; + + private readonly IPackageInstallation _packageInstallation; + private readonly IAuditService _auditService; + private readonly ICreatedPackagesRepository _createdPackages; + private readonly IInstalledPackagesRepository _installedPackages; private static HttpClient _httpClient; public PackagingService( - ILogger logger, - IContentService contentService, - IContentTypeService contentTypeService, - IMediaService mediaService, - IMacroService macroService, - IDataTypeService dataTypeService, - IFileService fileService, - ILocalizationService localizationService, - IEntityService entityService, - IUserService userService, - IScopeProvider scopeProvider, - IEnumerable urlSegmentProviders, - IAuditRepository auditRepository, IContentTypeRepository contentTypeRepository, - PropertyEditorCollection propertyEditors) + IAuditService auditService, + ICreatedPackagesRepository createdPackages, + IInstalledPackagesRepository installedPackages, + IPackageInstallation packageInstallation) { - _logger = logger; - _contentService = contentService; - _contentTypeService = contentTypeService; - _mediaService = mediaService; - _macroService = macroService; - _dataTypeService = dataTypeService; - _fileService = fileService; - _localizationService = localizationService; - _entityService = entityService; - _scopeProvider = scopeProvider; - _urlSegmentProviders = urlSegmentProviders; - _auditRepository = auditRepository; - _contentTypeRepository = contentTypeRepository; - _propertyEditors = propertyEditors; - _userService = userService; - _importedContentTypes = new Dictionary(); + _auditService = auditService; + _createdPackages = createdPackages; + _installedPackages = installedPackages; + _packageInstallation = packageInstallation; } - protected IQuery Query() => _scopeProvider.SqlContext.Query(); - - #region Content - - /// - /// Exports an item to xml as an - /// - /// Content to export - /// Optional parameter indicating whether to include descendents - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the Content object - public XElement Export(IContent content, bool deep = false, bool raiseEvents = true) - { - var nodeName = content.ContentType.Alias.ToSafeAliasWithForcingCheck(); - - if (raiseEvents) - { - if (ExportingContent.IsRaisedEventCancelled(new ExportEventArgs(content, nodeName), this)) - return new XElement(nodeName); - } - - const bool published = false; // fixme - what shall we export? - var xml = EntityXmlSerializer.Serialize(_contentService, _dataTypeService, _userService, _localizationService, _urlSegmentProviders, content, published, deep); - - if (raiseEvents) - ExportedContent.RaiseEvent(new ExportEventArgs(content, xml, false), this); - - return xml; - } - - - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// Optional parent Id for the content being imported - /// Optional Id of the user performing the import - /// Optional parameter indicating whether or not to raise events - /// An enumrable list of generated content - public IEnumerable ImportContent(XElement element, int parentId = -1, int userId = 0, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ImportingContent.IsRaisedEventCancelled(new ImportEventArgs(element), this)) - return Enumerable.Empty(); - } - - var name = element.Name.LocalName; - if (name.Equals("DocumentSet")) - { - //This is a regular deep-structured import - var roots = from doc in element.Elements() - where (string)doc.Attribute("isDoc") == "" - select doc; - - var contents = ParseDocumentRootXml(roots, parentId).ToList(); - if (contents.Any()) - _contentService.Save(contents, userId); - - if (raiseEvents) - ImportedContent.RaiseEvent(new ImportEventArgs(contents, element, false), this); - return contents; - } - - var attribute = element.Attribute("isDoc"); - if (attribute != null) - { - //This is a single doc import - var elements = new List { element }; - var contents = ParseDocumentRootXml(elements, parentId).ToList(); - if (contents.Any()) - _contentService.Save(contents, userId); - - if (raiseEvents) - ImportedContent.RaiseEvent(new ImportEventArgs(contents, element, false), this); - return contents; - } - - throw new ArgumentException( - "The passed in XElement is not valid! It does not contain a root element called " + - "'DocumentSet' (for structured imports) nor is the first element a Document (for single document import)."); - } - - private IEnumerable ParseDocumentRootXml(IEnumerable roots, int parentId) - { - var contents = new List(); - foreach (var root in roots) - { - var contentTypeAlias = root.Name.LocalName; - - if (_importedContentTypes.ContainsKey(contentTypeAlias) == false) - { - var contentType = FindContentTypeByAlias(contentTypeAlias); - _importedContentTypes.Add(contentTypeAlias, contentType); - } - - var content = CreateContentFromXml(root, _importedContentTypes[contentTypeAlias], null, parentId); - contents.Add(content); - - var children = (from child in root.Elements() - where (string)child.Attribute("isDoc") == "" - select child) - .ToList(); - if (children.Any()) - contents.AddRange(CreateContentFromXml(children, content)); - } - return contents; - } - - private IEnumerable CreateContentFromXml(IEnumerable children, IContent parent) - { - var list = new List(); - foreach (var child in children) - { - string contentTypeAlias = child.Name.LocalName; - - if (_importedContentTypes.ContainsKey(contentTypeAlias) == false) - { - var contentType = FindContentTypeByAlias(contentTypeAlias); - _importedContentTypes.Add(contentTypeAlias, contentType); - } - - //Create and add the child to the list - var content = CreateContentFromXml(child, _importedContentTypes[contentTypeAlias], parent, default(int)); - list.Add(content); - - //Recursive call - XElement child1 = child; - var grandChildren = (from grand in child1.Elements() - where (string) grand.Attribute("isDoc") == "" - select grand).ToList(); - - if (grandChildren.Any()) - list.AddRange(CreateContentFromXml(grandChildren, content)); - } - - return list; - } - - private IContent CreateContentFromXml(XElement element, IContentType contentType, IContent parent, int parentId) - { - var id = element.Attribute("id").Value; - var level = element.Attribute("level").Value; - var sortOrder = element.Attribute("sortOrder").Value; - var nodeName = element.Attribute("nodeName").Value; - var path = element.Attribute("path").Value; - //TODO: Shouldn't we be using this value??? - var template = element.Attribute("template").Value; - var key = Guid.Empty; - - var properties = from property in element.Elements() - where property.Attribute("isDoc") == null - select property; - - IContent content = parent == null - ? new Content(nodeName, parentId, contentType) - { - Level = int.Parse(level), - SortOrder = int.Parse(sortOrder) - } - : new Content(nodeName, parent, contentType) - { - Level = int.Parse(level), - SortOrder = int.Parse(sortOrder) - }; - - if (element.Attribute("key") != null && Guid.TryParse(element.Attribute("key").Value, out key)) - { - // update the Guid (for UDI support) - content.Key = key; - } - - using (var scope = _scopeProvider.CreateScope(autoComplete: true)) - { - foreach (var property in properties) - { - string propertyTypeAlias = property.Name.LocalName; - if (content.HasProperty(propertyTypeAlias)) - { - var propertyValue = property.Value; - - var propertyType = contentType.PropertyTypes.FirstOrDefault(pt => pt.Alias == propertyTypeAlias); - - //TODO: It would be heaps nicer if we didn't have to hard code references to specific property editors - // we'd have to modify the packaging format to denote how to parse/store the value instead of relying on this - - if (propertyType != null) - { - // fixme - wtf is this very specific thing here?! - //if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.CheckBoxList) - //{ - - // //TODO: We need to refactor this so the packager isn't making direct db calls for an 'edge' case - // var database = scope.Database; - // var dtos = database.Fetch("WHERE datatypeNodeId = @Id", new { Id = propertyType.DataTypeId }); - - // var propertyValueList = new List(); - // foreach (var preValue in propertyValue.Split(',')) - // { - // propertyValueList.Add(dtos.Single(x => x.Value == preValue).Id.ToString(CultureInfo.InvariantCulture)); - // } - - // propertyValue = string.Join(",", propertyValueList.ToArray()); - - //} - } - //set property value - content.SetValue(propertyTypeAlias, propertyValue); - } - } - } - - return content; - } - - #endregion - - #region ContentTypes - - /// - /// Exports an to xml as an - /// - /// ContentType to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the ContentType item. - public XElement Export(IContentType contentType, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ExportingContentType.IsRaisedEventCancelled(new ExportEventArgs(contentType, "DocumentType"), this)) - return new XElement("DocumentType"); - } - - var exporter = new EntityXmlSerializer(); - var xml = exporter.Serialize(_dataTypeService, _contentTypeService, contentType); - - if (raiseEvents) - ExportedContentType.RaiseEvent(new ExportEventArgs(contentType, xml, false), this); - - return xml; - } - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// Optional id of the User performing the operation. Default is zero (admin). - /// Optional parameter indicating whether or not to raise events - /// An enumrable list of generated ContentTypes - public IEnumerable ImportContentTypes(XElement element, int userId = 0, bool raiseEvents = true) - { - return ImportContentTypes(element, true, userId); - } - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// Boolean indicating whether or not to import the - /// Optional id of the User performing the operation. Default is zero (admin). - /// Optional parameter indicating whether or not to raise events - /// An enumrable list of generated ContentTypes - public IEnumerable ImportContentTypes(XElement element, bool importStructure, int userId = 0, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ImportingContentType.IsRaisedEventCancelled(new ImportEventArgs(element), this)) - return Enumerable.Empty(); - } - - var name = element.Name.LocalName; - if (name.Equals("DocumentTypes") == false && name.Equals("DocumentType") == false) - { - throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'DocumentTypes' for multiple imports or 'DocumentType' for a single import."); - } - - _importedContentTypes = new Dictionary(); - var unsortedDocumentTypes = name.Equals("DocumentTypes") - ? (from doc in element.Elements("DocumentType") select doc).ToList() - : new List { element }; - - //When you are importing a single doc type we have to assume that the depedencies are already there. - //Otherwise something like uSync won't work. - var graph = new TopoGraph>(x => x.Key, x => x.Dependencies); - var isSingleDocTypeImport = unsortedDocumentTypes.Count == 1; - - var importedFolders = CreateContentTypeFolderStructure(unsortedDocumentTypes); - - if (isSingleDocTypeImport == false) - { - //NOTE Here we sort the doctype XElements based on dependencies - //before creating the doc types - this should also allow for a better structure/inheritance support. - foreach (var documentType in unsortedDocumentTypes) - { - var elementCopy = documentType; - var infoElement = elementCopy.Element("Info"); - var dependencies = new HashSet(); - - //Add the Master as a dependency - if (string.IsNullOrEmpty((string)infoElement.Element("Master")) == false) - { - dependencies.Add(infoElement.Element("Master").Value); - } - - //Add compositions as dependencies - var compositionsElement = infoElement.Element("Compositions"); - if (compositionsElement != null && compositionsElement.HasElements) - { - var compositions = compositionsElement.Elements("Composition"); - if (compositions.Any()) - { - foreach (var composition in compositions) - { - dependencies.Add(composition.Value); - } - } - } - - graph.AddItem(TopoGraph.CreateNode(infoElement.Element("Alias").Value, elementCopy, dependencies.ToArray())); - } - } - - //Sorting the Document Types based on dependencies - if its not a single doc type import ref. #U4-5921 - var documentTypes = isSingleDocTypeImport - ? unsortedDocumentTypes.ToList() - : graph.GetSortedItems().Select(x => x.Item).ToList(); - - //Iterate the sorted document types and create them as IContentType objects - foreach (var documentType in documentTypes) - { - var alias = documentType.Element("Info").Element("Alias").Value; - if (_importedContentTypes.ContainsKey(alias) == false) - { - var contentType = _contentTypeService.Get(alias); - _importedContentTypes.Add(alias, contentType == null - ? CreateContentTypeFromXml(documentType) - : UpdateContentTypeFromXml(documentType, contentType)); - } - } - - foreach (var contentType in _importedContentTypes) - { - var ct = contentType.Value; - if (importedFolders.ContainsKey(ct.Alias)) - { - ct.ParentId = importedFolders[ct.Alias]; - } - } - - //Save the newly created/updated IContentType objects - var list = _importedContentTypes.Select(x => x.Value).ToList(); - _contentTypeService.Save(list, userId); - - //Now we can finish the import by updating the 'structure', - //which requires the doc types to be saved/available in the db - if (importStructure) - { - var updatedContentTypes = new List(); - //Update the structure here - we can't do it untill all DocTypes have been created - foreach (var documentType in documentTypes) - { - var alias = documentType.Element("Info").Element("Alias").Value; - var structureElement = documentType.Element("Structure"); - //Ensure that we only update ContentTypes which has actual structure-elements - if (structureElement == null || structureElement.Elements("DocumentType").Any() == false) continue; - - var updated = UpdateContentTypesStructure(_importedContentTypes[alias], structureElement); - updatedContentTypes.Add(updated); - } - //Update ContentTypes with a newly added structure/list of allowed children - if (updatedContentTypes.Any()) - _contentTypeService.Save(updatedContentTypes, userId); - } - - if (raiseEvents) - ImportedContentType.RaiseEvent(new ImportEventArgs(list, element, false), this); - - return list; - } - - private Dictionary CreateContentTypeFolderStructure(IEnumerable unsortedDocumentTypes) - { - var importedFolders = new Dictionary(); - foreach (var documentType in unsortedDocumentTypes) - { - var foldersAttribute = documentType.Attribute("Folders"); - var infoElement = documentType.Element("Info"); - if (foldersAttribute != null && infoElement != null - //don't import any folder if this is a child doc type - the parent doc type will need to - //exist which contains it's folders - && ((string)infoElement.Element("Master")).IsNullOrWhiteSpace()) - { - var alias = documentType.Element("Info").Element("Alias").Value; - var folders = foldersAttribute.Value.Split('/'); - var rootFolder = HttpUtility.UrlDecode(folders[0]); - //level 1 = root level folders, there can only be one with the same name - var current = _contentTypeService.GetContainers(rootFolder, 1).FirstOrDefault(); - - if (current == null) - { - var tryCreateFolder = _contentTypeService.CreateContainer(-1, rootFolder); - if (tryCreateFolder == false) - { - _logger.Error(tryCreateFolder.Exception, "Could not create folder: {FolderName}", rootFolder); - throw tryCreateFolder.Exception; - } - var rootFolderId = tryCreateFolder.Result.Entity.Id; - current = _contentTypeService.GetContainer(rootFolderId); - } - - importedFolders.Add(alias, current.Id); - - for (var i = 1; i < folders.Length; i++) - { - var folderName = HttpUtility.UrlDecode(folders[i]); - current = CreateContentTypeChildFolder(folderName, current); - importedFolders[alias] = current.Id; - } - } - } - - return importedFolders; - } - - private EntityContainer CreateContentTypeChildFolder(string folderName, IUmbracoEntity current) - { - var children = _entityService.GetChildren(current.Id).ToArray(); - var found = children.Any(x => x.Name.InvariantEquals(folderName)); - if (found) - { - var containerId = children.Single(x => x.Name.InvariantEquals(folderName)).Id; - return _contentTypeService.GetContainer(containerId); - } - - var tryCreateFolder = _contentTypeService.CreateContainer(current.Id, folderName); - if (tryCreateFolder == false) - { - _logger.Error(tryCreateFolder.Exception, "Could not create folder: {FolderName}", folderName); - throw tryCreateFolder.Exception; - } - return _contentTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); - } - - private IContentType CreateContentTypeFromXml(XElement documentType) - { - var infoElement = documentType.Element("Info"); - - //Name of the master corresponds to the parent - var masterElement = infoElement.Element("Master"); - IContentType parent = null; - if (masterElement != null) - { - var masterAlias = masterElement.Value; - parent = _importedContentTypes.ContainsKey(masterAlias) - ? _importedContentTypes[masterAlias] - : _contentTypeService.Get(masterAlias); - } - - var alias = infoElement.Element("Alias").Value; - var contentType = parent == null - ? new ContentType(-1) { Alias = alias } - : new ContentType(parent, alias); - - if (parent != null) - contentType.AddContentType(parent); - - return UpdateContentTypeFromXml(documentType, contentType); - } - - private IContentType UpdateContentTypeFromXml(XElement documentType, IContentType contentType) - { - var infoElement = documentType.Element("Info"); - var defaultTemplateElement = infoElement.Element("DefaultTemplate"); - - contentType.Name = infoElement.Element("Name").Value; - contentType.Icon = infoElement.Element("Icon").Value; - contentType.Thumbnail = infoElement.Element("Thumbnail").Value; - contentType.Description = infoElement.Element("Description").Value; - - //NOTE AllowAtRoot is a new property in the package xml so we need to verify it exists before using it. - var allowAtRoot = infoElement.Element("AllowAtRoot"); - if (allowAtRoot != null) - contentType.AllowedAsRoot = allowAtRoot.Value.InvariantEquals("true"); - - //NOTE IsListView is a new property in the package xml so we need to verify it exists before using it. - var isListView = infoElement.Element("IsListView"); - if (isListView != null) - contentType.IsContainer = isListView.Value.InvariantEquals("true"); - - //Name of the master corresponds to the parent and we need to ensure that the Parent Id is set - var masterElement = infoElement.Element("Master"); - if (masterElement != null) - { - var masterAlias = masterElement.Value; - IContentType parent = _importedContentTypes.ContainsKey(masterAlias) - ? _importedContentTypes[masterAlias] - : _contentTypeService.Get(masterAlias); - - contentType.SetParent(parent); - } - - //Update Compositions on the ContentType to ensure that they are as is defined in the package xml - var compositionsElement = infoElement.Element("Compositions"); - if (compositionsElement != null && compositionsElement.HasElements) - { - var compositions = compositionsElement.Elements("Composition"); - if (compositions.Any()) - { - foreach (var composition in compositions) - { - var compositionAlias = composition.Value; - var compositionContentType = _importedContentTypes.ContainsKey(compositionAlias) - ? _importedContentTypes[compositionAlias] - : _contentTypeService.Get(compositionAlias); - var added = contentType.AddContentType(compositionContentType); - } - } - } - - UpdateContentTypesAllowedTemplates(contentType, infoElement.Element("AllowedTemplates"), defaultTemplateElement); - UpdateContentTypesTabs(contentType, documentType.Element("Tabs")); - UpdateContentTypesProperties(contentType, documentType.Element("GenericProperties")); - - return contentType; - } - - private void UpdateContentTypesAllowedTemplates(IContentType contentType, - XElement allowedTemplatesElement, XElement defaultTemplateElement) - { - if (allowedTemplatesElement != null && allowedTemplatesElement.Elements("Template").Any()) - { - var allowedTemplates = contentType.AllowedTemplates.ToList(); - foreach (var templateElement in allowedTemplatesElement.Elements("Template")) - { - var alias = templateElement.Value; - var template = _fileService.GetTemplate(alias.ToSafeAlias()); - if (template != null) - { - if (allowedTemplates.Any(x => x.Id == template.Id)) continue; - allowedTemplates.Add(template); - } - else - { - _logger.Warn("Packager: Error handling allowed templates. Template with alias '{TemplateAlias}' could not be found.", alias); - } - } - - contentType.AllowedTemplates = allowedTemplates; - } - - if (string.IsNullOrEmpty((string)defaultTemplateElement) == false) - { - var defaultTemplate = _fileService.GetTemplate(defaultTemplateElement.Value.ToSafeAlias()); - if (defaultTemplate != null) - { - contentType.SetDefaultTemplate(defaultTemplate); - } - else - { - _logger.Warn("Packager: Error handling default template. Default template with alias '{DefaultTemplateAlias}' could not be found.", defaultTemplateElement.Value); - } - } - } - - private void UpdateContentTypesTabs(IContentType contentType, XElement tabElement) - { - if (tabElement == null) - return; - - var tabs = tabElement.Elements("Tab"); - foreach (var tab in tabs) - { - var id = tab.Element("Id").Value;//Do we need to use this for tracking? - var caption = tab.Element("Caption").Value; - - if (contentType.PropertyGroups.Contains(caption) == false) - { - contentType.AddPropertyGroup(caption); - - } - - int sortOrder; - if (tab.Element("SortOrder") != null && int.TryParse(tab.Element("SortOrder").Value, out sortOrder)) - { - // Override the sort order with the imported value - contentType.PropertyGroups[caption].SortOrder = sortOrder; - } - } - } - - private void UpdateContentTypesProperties(IContentType contentType, XElement genericPropertiesElement) - { - var properties = genericPropertiesElement.Elements("GenericProperty"); - foreach (var property in properties) - { - var dataTypeDefinitionId = new Guid(property.Element("Definition").Value);//Unique Id for a DataTypeDefinition - - var dataTypeDefinition = _dataTypeService.GetDataType(dataTypeDefinitionId); - - //If no DataTypeDefinition with the guid from the xml wasn't found OR the ControlId on the DataTypeDefinition didn't match the DataType Id - //We look up a DataTypeDefinition that matches - - - //get the alias as a string for use below - var propertyEditorAlias = property.Element("Type").Value.Trim(); - - //If no DataTypeDefinition with the guid from the xml wasn't found OR the ControlId on the DataTypeDefinition didn't match the DataType Id - //We look up a DataTypeDefinition that matches - - if (dataTypeDefinition == null) - { - var dataTypeDefinitions = _dataTypeService.GetByEditorAlias(propertyEditorAlias); - if (dataTypeDefinitions != null && dataTypeDefinitions.Any()) - { - dataTypeDefinition = dataTypeDefinitions.FirstOrDefault(); - } - } - else if (dataTypeDefinition.EditorAlias != propertyEditorAlias) - { - var dataTypeDefinitions = _dataTypeService.GetByEditorAlias(propertyEditorAlias); - if (dataTypeDefinitions != null && dataTypeDefinitions.Any()) - { - dataTypeDefinition = dataTypeDefinitions.FirstOrDefault(); - } - } - - // For backwards compatibility, if no datatype with that ID can be found, we're letting this fail silently. - // This means that the property will not be created. - if (dataTypeDefinition == null) - { - _logger.Warn("Packager: Error handling creation of PropertyType '{PropertyType}'. Could not find DataTypeDefintion with unique id '{DataTypeDefinitionId}' nor one referencing the DataType with a property editor alias (or legacy control id) '{PropertyEditorAlias}'. Did the package creator forget to package up custom datatypes? This property will be converted to a label/readonly editor if one exists.", - property.Element("Name").Value, dataTypeDefinitionId, property.Element("Type").Value.Trim()); - - //convert to a label! - dataTypeDefinition = _dataTypeService.GetByEditorAlias(Constants.PropertyEditors.Aliases.NoEdit).FirstOrDefault(); - //if for some odd reason this isn't there then ignore - if (dataTypeDefinition == null) continue; - } - - var sortOrder = 0; - var sortOrderElement = property.Element("SortOrder"); - if (sortOrderElement != null) - int.TryParse(sortOrderElement.Value, out sortOrder); - var propertyType = new PropertyType(dataTypeDefinition, property.Element("Alias").Value) - { - Name = property.Element("Name").Value, - Description = (string)property.Element("Description"), - Mandatory = property.Element("Mandatory") != null ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true") : false, - ValidationRegExp = (string)property.Element("Validation"), - SortOrder = sortOrder - }; - - var tab = (string)property.Element("Tab"); - if (string.IsNullOrEmpty(tab)) - { - contentType.AddPropertyType(propertyType); - } - else - { - contentType.AddPropertyType(propertyType, tab); - } - } - } - - private IContentType UpdateContentTypesStructure(IContentType contentType, XElement structureElement) - { - var allowedChildren = contentType.AllowedContentTypes.ToList(); - int sortOrder = allowedChildren.Any() ? allowedChildren.Last().SortOrder : 0; - foreach (var element in structureElement.Elements("DocumentType")) - { - var alias = element.Value; - - var allowedChild = _importedContentTypes.ContainsKey(alias) ? _importedContentTypes[alias] : _contentTypeService.Get(alias); - if (allowedChild == null) - { - _logger.Warn( - "Packager: Error handling DocumentType structure. DocumentType with alias '{DoctypeAlias}' could not be found and was not added to the structure for '{DoctypeStructureAlias}'.", - alias, contentType.Alias); - continue; - } - - if (allowedChildren.Any(x => x.Id.IsValueCreated && x.Id.Value == allowedChild.Id)) continue; - - allowedChildren.Add(new ContentTypeSort(new Lazy(() => allowedChild.Id), sortOrder, allowedChild.Alias)); - sortOrder++; - } - - contentType.AllowedContentTypes = allowedChildren; - return contentType; - } - - /// - /// Used during Content import to ensure that the ContentType of a content item exists - /// - /// - /// - private IContentType FindContentTypeByAlias(string contentTypeAlias) - { - using (var scope = _scopeProvider.CreateScope()) - { - var query = Query().Where(x => x.Alias == contentTypeAlias); - var contentType = _contentTypeRepository.Get(query).FirstOrDefault(); - - if (contentType == null) - throw new Exception($"ContentType matching the passed in Alias: '{contentTypeAlias}' was null"); - - scope.Complete(); - return contentType; - } - } - - #endregion - - #region DataTypes - - /// - /// Exports a list of Data Types - /// - /// List of data types to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IDataTypeDefinition objects - public XElement Export(IEnumerable dataTypeDefinitions, bool raiseEvents = true) - { - var container = new XElement("DataTypes"); - foreach (var dataTypeDefinition in dataTypeDefinitions) - { - container.Add(Export(dataTypeDefinition, raiseEvents)); - } - return container; - } - - /// - /// Exports a single Data Type - /// - /// Data type to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IDataTypeDefinition object - public XElement Export(IDataType dataType, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ExportingDataType.IsRaisedEventCancelled(new ExportEventArgs(dataType, "DataType"), this)) - return new XElement("DataType"); - } - - var exporter = new EntityXmlSerializer(); - var xml = exporter.Serialize(_dataTypeService, dataType); - - if (raiseEvents) - ExportedDataType.RaiseEvent(new ExportEventArgs(dataType, xml, false), this); - - return xml; - } - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// Optional id of the user - /// Optional parameter indicating whether or not to raise events - /// An enumrable list of generated DataTypeDefinitions - public IEnumerable ImportDataTypeDefinitions(XElement element, int userId = 0, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ImportingDataType.IsRaisedEventCancelled(new ImportEventArgs(element), this)) - return Enumerable.Empty(); - } - - var name = element.Name.LocalName; - if (name.Equals("DataTypes") == false && name.Equals("DataType") == false) - { - throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'DataTypes' for multiple imports or 'DataType' for a single import."); - } - - var dataTypes = new List(); - var dataTypeElements = name.Equals("DataTypes") - ? (from doc in element.Elements("DataType") select doc).ToList() - : new List { element }; - - var importedFolders = CreateDataTypeFolderStructure(dataTypeElements); - - foreach (var dataTypeElement in dataTypeElements) - { - var dataTypeDefinitionName = dataTypeElement.Attribute("Name").Value; - - var dataTypeDefinitionId = new Guid(dataTypeElement.Attribute("Definition").Value); - var databaseTypeAttribute = dataTypeElement.Attribute("DatabaseType"); - - var parentId = -1; - if (importedFolders.ContainsKey(dataTypeDefinitionName)) - parentId = importedFolders[dataTypeDefinitionName]; - - var definition = _dataTypeService.GetDataType(dataTypeDefinitionId); - //If the datatypedefinition doesn't already exist we create a new new according to the one in the package xml - if (definition == null) - { - var databaseType = databaseTypeAttribute != null - ? databaseTypeAttribute.Value.EnumParse(true) - : ValueStorageType.Ntext; - - // the Id field is actually the string property editor Alias - // however, the actual editor with this alias could be installed with the package, and - // therefore not yet part of the _propertyEditors collection, so we cannot try and get - // the actual editor - going with a void editor - - var editorAlias = dataTypeElement.Attribute("Id")?.Value?.Trim(); - if (!_propertyEditors.TryGet(editorAlias, out var editor)) - editor = new VoidEditor(_logger) { Alias = editorAlias }; - - var dataType = new DataType(editor) - { - Key = dataTypeDefinitionId, - Name = dataTypeDefinitionName, - DatabaseType = databaseType, - ParentId = parentId - }; - - var configurationAttributeValue = dataTypeElement.Attribute("Configuration")?.Value; - if (!string.IsNullOrWhiteSpace(configurationAttributeValue)) - dataType.Configuration = editor.GetConfigurationEditor().FromDatabase(configurationAttributeValue); - - dataTypes.Add(dataType); - } - else - { - definition.ParentId = parentId; - _dataTypeService.Save(definition, userId); - } - } - - if (dataTypes.Count > 0) - { - _dataTypeService.Save(dataTypes, userId, true); - } - - if (raiseEvents) - ImportedDataType.RaiseEvent(new ImportEventArgs(dataTypes, element, false), this); - - return dataTypes; - } - - private Dictionary CreateDataTypeFolderStructure(IEnumerable datatypeElements) - { - var importedFolders = new Dictionary(); - foreach (var datatypeElement in datatypeElements) - { - var foldersAttribute = datatypeElement.Attribute("Folders"); - if (foldersAttribute != null) - { - var name = datatypeElement.Attribute("Name").Value; - var folders = foldersAttribute.Value.Split('/'); - var rootFolder = HttpUtility.UrlDecode(folders[0]); - //there will only be a single result by name for level 1 (root) containers - var current = _dataTypeService.GetContainers(rootFolder, 1).FirstOrDefault(); - - if (current == null) - { - var tryCreateFolder = _dataTypeService.CreateContainer(-1, rootFolder); - if (tryCreateFolder == false) - { - _logger.Error(tryCreateFolder.Exception, "Could not create folder: {FolderName}", rootFolder); - throw tryCreateFolder.Exception; - } - current = _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); - } - - importedFolders.Add(name, current.Id); - - for (var i = 1; i < folders.Length; i++) - { - var folderName = HttpUtility.UrlDecode(folders[i]); - current = CreateDataTypeChildFolder(folderName, current); - importedFolders[name] = current.Id; - } - } - } - - return importedFolders; - } - - private EntityContainer CreateDataTypeChildFolder(string folderName, IUmbracoEntity current) - { - var children = _entityService.GetChildren(current.Id).ToArray(); - var found = children.Any(x => x.Name.InvariantEquals(folderName)); - if (found) - { - var containerId = children.Single(x => x.Name.InvariantEquals(folderName)).Id; - return _dataTypeService.GetContainer(containerId); - } - - var tryCreateFolder = _dataTypeService.CreateContainer(current.Id, folderName); - if (tryCreateFolder == false) - { - _logger.Error(tryCreateFolder.Exception, "Could not create folder: {FolderName}", folderName); - throw tryCreateFolder.Exception; - } - return _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); - } - - #endregion - - #region Dictionary Items - - /// - /// Exports a list of items to xml as an - /// - /// List of dictionary items to export - /// Optional boolean indicating whether or not to include children - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IDictionaryItem objects - public XElement Export(IEnumerable dictionaryItem, bool includeChildren = true, bool raiseEvents = true) - { - var xml = new XElement("DictionaryItems"); - foreach (var item in dictionaryItem) - { - xml.Add(Export(item, includeChildren, raiseEvents)); - } - return xml; - } - - /// - /// Exports a single item to xml as an - /// - /// Dictionary Item to export - /// Optional boolean indicating whether or not to include children - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IDictionaryItem object - public XElement Export(IDictionaryItem dictionaryItem, bool includeChildren, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ExportingDictionaryItem.IsRaisedEventCancelled(new ExportEventArgs(dictionaryItem, "DictionaryItem"), this)) - return new XElement("DictionaryItem"); - } - - var exporter = new EntityXmlSerializer(); - var xml = exporter.Serialize(dictionaryItem); - - if (includeChildren) - { - var children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key); - foreach (var child in children) - { - xml.Add(Export(child, true)); - } - } - - if (raiseEvents) - ExportedDictionaryItem.RaiseEvent(new ExportEventArgs(dictionaryItem, xml, false), this); - - return xml; - } - - /// - /// Imports and saves the 'DictionaryItems' part of the package xml as a list of - /// - /// Xml to import - /// Optional parameter indicating whether or not to raise events - /// An enumerable list of dictionary items - public IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ImportingDictionaryItem.IsRaisedEventCancelled(new ImportEventArgs(dictionaryItemElementList), this)) - return Enumerable.Empty(); - } - - var languages = _localizationService.GetAllLanguages().ToList(); - return ImportDictionaryItems(dictionaryItemElementList, languages, raiseEvents); - } - - private IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, List languages, bool raiseEvents, Guid? parentId = null) - { - var items = new List(); - foreach (var dictionaryItemElement in dictionaryItemElementList.Elements("DictionaryItem")) - items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, raiseEvents, parentId)); - - - if (raiseEvents) - ImportedDictionaryItem.RaiseEvent(new ImportEventArgs(items, dictionaryItemElementList, false), this); - - return items; - } - - private IEnumerable ImportDictionaryItem(XElement dictionaryItemElement, List languages, bool raiseEvents, Guid? parentId) - { - var items = new List(); - - IDictionaryItem dictionaryItem; - var key = dictionaryItemElement.Attribute("Key").Value; - if (_localizationService.DictionaryItemExists(key)) - dictionaryItem = GetAndUpdateDictionaryItem(key, dictionaryItemElement, languages); - else - dictionaryItem = CreateNewDictionaryItem(key, dictionaryItemElement, languages, parentId); - _localizationService.Save(dictionaryItem); - items.Add(dictionaryItem); - - items.AddRange(ImportDictionaryItems(dictionaryItemElement, languages, raiseEvents, dictionaryItem.Key)); - return items; - } - - private IDictionaryItem GetAndUpdateDictionaryItem(string key, XElement dictionaryItemElement, List languages) - { - var dictionaryItem = _localizationService.GetDictionaryItemByKey(key); - var translations = dictionaryItem.Translations.ToList(); - foreach (var valueElement in dictionaryItemElement.Elements("Value").Where(v => DictionaryValueIsNew(translations, v))) - AddDictionaryTranslation(translations, valueElement, languages); - dictionaryItem.Translations = translations; - return dictionaryItem; - } - - private static DictionaryItem CreateNewDictionaryItem(string key, XElement dictionaryItemElement, List languages, Guid? parentId) - { - var dictionaryItem = parentId.HasValue ? new DictionaryItem(parentId.Value, key) : new DictionaryItem(key); - var translations = new List(); - - foreach (var valueElement in dictionaryItemElement.Elements("Value")) - AddDictionaryTranslation(translations, valueElement, languages); - - dictionaryItem.Translations = translations; - return dictionaryItem; - } - - private static bool DictionaryValueIsNew(IEnumerable translations, XElement valueElement) - { - return translations.All(t => - String.Compare(t.Language.IsoCode, valueElement.Attribute("LanguageCultureAlias").Value, - StringComparison.InvariantCultureIgnoreCase) != 0 - ); - } - - private static void AddDictionaryTranslation(ICollection translations, XElement valueElement, IEnumerable languages) - { - var languageId = valueElement.Attribute("LanguageCultureAlias").Value; - var language = languages.SingleOrDefault(l => l.IsoCode == languageId); - if (language == null) - return; - var translation = new DictionaryTranslation(language, valueElement.Value); - translations.Add(translation); - } - - #endregion - - #region Files - #endregion - - #region Languages - - /// - /// Exports a list of items to xml as an - /// - /// List of Languages to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the ILanguage objects - public XElement Export(IEnumerable languages, bool raiseEvents = true) - { - var xml = new XElement("Languages"); - foreach (var language in languages) - { - xml.Add(Export(language, raiseEvents)); - } - return xml; - } - - /// - /// Exports a single item to xml as an - /// - /// Language to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the ILanguage object - public XElement Export(ILanguage language, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ExportingLanguage.IsRaisedEventCancelled(new ExportEventArgs(language, "Language"), this)) - return new XElement("Language"); - } - - var exporter = new EntityXmlSerializer(); - var xml = exporter.Serialize(language); - - if (raiseEvents) - ExportedLanguage.RaiseEvent(new ExportEventArgs(language, xml, false), this); - - return xml; - } - - /// - /// Imports and saves the 'Languages' part of a package xml as a list of - /// - /// Xml to import - /// Optional id of the User performing the operation - /// Optional parameter indicating whether or not to raise events - /// An enumerable list of generated languages - public IEnumerable ImportLanguages(XElement languageElementList, int userId = 0, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ImportingLanguage.IsRaisedEventCancelled(new ImportEventArgs(languageElementList), this)) - return Enumerable.Empty(); - } - - var list = new List(); - foreach (var languageElement in languageElementList.Elements("Language")) - { - var isoCode = languageElement.Attribute("CultureAlias").Value; - var existingLanguage = _localizationService.GetLanguageByIsoCode(isoCode); - if (existingLanguage == null) - { - var langauge = new Language(isoCode) - { - CultureName = languageElement.Attribute("FriendlyName").Value - }; - _localizationService.Save(langauge); - list.Add(langauge); - } - } - - if (raiseEvents) - ImportedLanguage.RaiseEvent(new ImportEventArgs(list, languageElementList, false), this); - - return list; - } - - #endregion - - #region Macros - - /// - /// Imports and saves the 'Macros' part of a package xml as a list of - /// - /// Xml to import - /// Optional id of the User performing the operation - /// Optional parameter indicating whether or not to raise events - /// - public IEnumerable ImportMacros(XElement element, int userId = 0, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ImportingMacro.IsRaisedEventCancelled(new ImportEventArgs(element), this)) - return Enumerable.Empty(); - } - - var name = element.Name.LocalName; - if (name.Equals("Macros") == false && name.Equals("macro") == false) - { - throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'Macros' for multiple imports or 'macro' for a single import."); - } - - var macroElements = name.Equals("Macros") - ? (from doc in element.Elements("macro") select doc).ToList() - : new List { element }; - - var macros = macroElements.Select(ParseMacroElement).ToList(); - - foreach (var macro in macros) - { - var existing = _macroService.GetByAlias(macro.Alias); - if (existing != null) - macro.Id = existing.Id; - - _macroService.Save(macro, userId); - } - - if (raiseEvents) - ImportedMacro.RaiseEvent(new ImportEventArgs(macros, element, false), this); - - return macros; - } - - private IMacro ParseMacroElement(XElement macroElement) - { - var macroName = macroElement.Element("name").Value; - var macroAlias = macroElement.Element("alias").Value; - var macroType = Enum.Parse(macroElement.Element("macroType").Value); - var macroSource = macroElement.Element("macroSource").Value; - - //Following xml elements are treated as nullable properties - var useInEditorElement = macroElement.Element("useInEditor"); - var useInEditor = false; - if (useInEditorElement != null && string.IsNullOrEmpty((string)useInEditorElement) == false) - { - useInEditor = bool.Parse(useInEditorElement.Value); - } - var cacheDurationElement = macroElement.Element("refreshRate"); - var cacheDuration = 0; - if (cacheDurationElement != null && string.IsNullOrEmpty((string)cacheDurationElement) == false) - { - cacheDuration = int.Parse(cacheDurationElement.Value); - } - var cacheByMemberElement = macroElement.Element("cacheByMember"); - var cacheByMember = false; - if (cacheByMemberElement != null && string.IsNullOrEmpty((string)cacheByMemberElement) == false) - { - cacheByMember = bool.Parse(cacheByMemberElement.Value); - } - var cacheByPageElement = macroElement.Element("cacheByPage"); - var cacheByPage = false; - if (cacheByPageElement != null && string.IsNullOrEmpty((string)cacheByPageElement) == false) - { - cacheByPage = bool.Parse(cacheByPageElement.Value); - } - var dontRenderElement = macroElement.Element("dontRender"); - var dontRender = true; - if (dontRenderElement != null && string.IsNullOrEmpty((string)dontRenderElement) == false) - { - dontRender = bool.Parse(dontRenderElement.Value); - } - - var existingMacro = _macroService.GetByAlias(macroAlias) as Macro; - var macro = existingMacro ?? new Macro(macroAlias, macroName, macroSource, macroType, - cacheByPage, cacheByMember, dontRender, useInEditor, cacheDuration); - - var properties = macroElement.Element("properties"); - if (properties != null) - { - int sortOrder = 0; - foreach (var property in properties.Elements()) - { - var propertyName = property.Attribute("name").Value; - var propertyAlias = property.Attribute("alias").Value; - var editorAlias = property.Attribute("propertyType").Value; - var sortOrderAttribute = property.Attribute("sortOrder"); - if (sortOrderAttribute != null) - { - sortOrder = int.Parse(sortOrderAttribute.Value); - } - - if (macro.Properties.Values.Any(x => string.Equals(x.Alias, propertyAlias, StringComparison.OrdinalIgnoreCase))) continue; - macro.Properties.Add(new MacroProperty(propertyAlias, propertyName, sortOrder, editorAlias)); - sortOrder++; - } - } - return macro; - } - - /// - /// Exports a list of items to xml as an - /// - /// Macros to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IMacro objects - public XElement Export(IEnumerable macros, bool raiseEvents = true) - { - var xml = new XElement("Macros"); - foreach (var item in macros) - { - xml.Add(Export(item, raiseEvents)); - } - return xml; - } - - /// - /// Exports a single item to xml as an - /// - /// Macro to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the IMacro object - public XElement Export(IMacro macro, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ExportingMacro.IsRaisedEventCancelled(new ExportEventArgs(macro, "macro"), this)) - return new XElement("macro"); - } - - var exporter = new EntityXmlSerializer(); - var xml = exporter.Serialize(macro); - - if (raiseEvents) - ExportedMacro.RaiseEvent(new ExportEventArgs(macro, xml, false), this); - - return xml; - } - - #endregion - - #region Members - - /// - /// Exports an item to xml as an - /// - /// Member to export - /// containing the xml representation of the Member object - public XElement Export(IMember member) - { - return EntityXmlSerializer.Serialize(_dataTypeService, _localizationService, member); - } - - #endregion - - #region Media - - /// - /// Exports an item to xml as an - /// - /// Media to export - /// Optional parameter indicating whether to include descendents - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the Media object - public XElement Export(IMedia media, bool deep = false, bool raiseEvents = true) - { - var nodeName = media.ContentType.Alias.ToSafeAliasWithForcingCheck(); - - if (raiseEvents) - { - if (ExportingMedia.IsRaisedEventCancelled(new ExportEventArgs(media, nodeName), this)) - return new XElement(nodeName); - } - - var xml = EntityXmlSerializer.Serialize(_mediaService, _dataTypeService, _userService, _localizationService, _urlSegmentProviders, media, deep); - - if (raiseEvents) - ExportedMedia.RaiseEvent(new ExportEventArgs(media, xml, false), this); - - return xml; - } - - - #endregion - - #region MediaTypes - - /// - /// Exports an to xml as an - /// - /// MediaType to export - /// containing the xml representation of the MediaType item. - internal XElement Export(IMediaType mediaType) - { - var exporter = new EntityXmlSerializer(); - var xml = exporter.Serialize(_dataTypeService, mediaType); - - return xml; - } - - #endregion - - #region Package Manifest - #endregion - #region Package Files - /// - /// This will fetch an Umbraco package file from the package repository and return the relative file path to the downloaded package file - /// - /// - /// - /// /// The current user id performing the operation - /// - public string FetchPackageFile(Guid packageId, Version umbracoVersion, int userId) + /// + public async Task FetchPackageFileAsync(Guid packageId, Version umbracoVersion, int userId) { - using (var scope = _scopeProvider.CreateScope()) + //includeHidden = true because we don't care if it's hidden we want to get the file regardless + var url = $"{Constants.PackageRepository.RestApiBaseUrl}/{packageId}?version={umbracoVersion.ToString(3)}&includeHidden=true&asFile=true"; + byte[] bytes; + try { - //includeHidden = true because we don't care if it's hidden we want to get the file regardless - var url = $"{Constants.PackageRepository.RestApiBaseUrl}/{packageId}?version={umbracoVersion.ToString(3)}&includeHidden=true&asFile=true"; - byte[] bytes; - try + if (_httpClient == null) { - if (_httpClient == null) - { - _httpClient = new HttpClient(); - } - bytes = _httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult(); + _httpClient = new HttpClient(); } - catch (HttpRequestException ex) + bytes = await _httpClient.GetByteArrayAsync(url); + } + catch (HttpRequestException ex) + { + throw new ConnectionException("An error occuring downloading the package from " + url, ex); + } + + //successfull + if (bytes.Length > 0) + { + var packagePath = IOHelper.MapPath(SystemDirectories.Packages); + + // Check for package directory + if (Directory.Exists(packagePath) == false) + Directory.CreateDirectory(packagePath); + + var packageFilePath = Path.Combine(packagePath, packageId + ".umb"); + + using (var fs1 = new FileStream(packageFilePath, FileMode.Create)) { - throw new ConnectionException("An error occuring downloading the package from " + url, ex); + fs1.Write(bytes, 0, bytes.Length); + return new FileInfo(packageFilePath); } - - //successfull - if (bytes.Length > 0) - { - var packagePath = IOHelper.MapPath(SystemDirectories.Packages); - - // Check for package directory - if (Directory.Exists(packagePath) == false) - Directory.CreateDirectory(packagePath); - - var packageFilePath = Path.Combine(packagePath, packageId + ".umb"); - - using (var fs1 = new FileStream(packageFilePath, FileMode.Create)) - { - fs1.Write(bytes, 0, bytes.Length); - return "packages\\" + packageId + ".umb"; - } - } - - Audit(AuditType.PackagerInstall, $"Package {packageId} fetched from {Constants.PackageRepository.DefaultRepositoryId}", userId, -1); - return null; - } - } - - private void Audit(AuditType type, string message, int userId, int objectId) - { - _auditRepository.Save(new AuditItem(objectId, type, userId, "Package", message)); - } - - #endregion - - #region Templates - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// Optional user id - /// Optional parameter indicating whether or not to raise events - /// An enumrable list of generated Templates - public IEnumerable ImportTemplates(XElement element, int userId = 0, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ImportingTemplate.IsRaisedEventCancelled(new ImportEventArgs(element), this)) - return Enumerable.Empty(); } - var name = element.Name.LocalName; - if (name.Equals("Templates") == false && name.Equals("Template") == false) - { - throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'Templates' for multiple imports or 'Template' for a single import."); - } - - var templates = new List(); - var templateElements = name.Equals("Templates") - ? (from doc in element.Elements("Template") select doc).ToList() - : new List { element }; - - var graph = new TopoGraph>(x => x.Key, x => x.Dependencies); - - foreach (XElement tempElement in templateElements) - { - var dependencies = new List(); - var elementCopy = tempElement; - //Ensure that the Master of the current template is part of the import, otherwise we ignore this dependency as part of the dependency sorting. - if (string.IsNullOrEmpty((string)elementCopy.Element("Master")) == false && - templateElements.Any(x => (string)x.Element("Alias") == (string)elementCopy.Element("Master"))) - { - dependencies.Add((string)elementCopy.Element("Master")); - } - else if (string.IsNullOrEmpty((string)elementCopy.Element("Master")) == false && - templateElements.Any(x => (string)x.Element("Alias") == (string)elementCopy.Element("Master")) == false) - { - _logger.Info( - "Template '{TemplateAlias}' has an invalid Master '{TemplateMaster}', so the reference has been ignored.", - (string) elementCopy.Element("Alias"), - (string) elementCopy.Element("Master")); - } - - graph.AddItem(TopoGraph.CreateNode((string) elementCopy.Element("Alias"), elementCopy, dependencies)); - } - - //Sort templates by dependencies to a potential master template - var sorted = graph.GetSortedItems(); - foreach (var item in sorted) - { - var templateElement = item.Item; - - var templateName = templateElement.Element("Name").Value; - var alias = templateElement.Element("Alias").Value; - var design = templateElement.Element("Design").Value; - var masterElement = templateElement.Element("Master"); - - var isMasterPage = IsMasterPageSyntax(design); - var path = isMasterPage ? MasterpagePath(alias) : ViewPath(alias); - - var existingTemplate = _fileService.GetTemplate(alias) as Template; - var template = existingTemplate ?? new Template(templateName, alias); - template.Content = design; - if (masterElement != null && string.IsNullOrEmpty((string)masterElement) == false) - { - template.MasterTemplateAlias = masterElement.Value; - var masterTemplate = templates.FirstOrDefault(x => x.Alias == masterElement.Value); - if (masterTemplate != null) - template.MasterTemplateId = new Lazy(() => masterTemplate.Id); - } - templates.Add(template); - } - - if (templates.Any()) - _fileService.SaveTemplate(templates, userId); - - if (raiseEvents) - ImportedTemplate.RaiseEvent(new ImportEventArgs(templates, element, false), this); - - return templates; + _auditService.Add(AuditType.PackagerInstall, userId, -1, "Package", $"Package {packageId} fetched from {Constants.PackageRepository.DefaultRepositoryId}"); + return null; } - - public IEnumerable ImportStylesheets(XElement element, int userId = 0, bool raiseEvents = true) - { - - if (raiseEvents) - { - if (ImportingStylesheets.IsRaisedEventCancelled(new ImportEventArgs(element), this)) - return Enumerable.Empty(); - } - - IEnumerable styleSheets = Enumerable.Empty(); - - if (element.Elements().Any()) - throw new NotImplementedException("This needs to be implimentet"); - - - if (raiseEvents) - ImportingStylesheets.RaiseEvent(new ImportEventArgs(styleSheets, element, false), this); - - return styleSheets; - - } - - - private bool IsMasterPageSyntax(string code) - { - return Regex.IsMatch(code, @"<%@\s*Master", RegexOptions.IgnoreCase) || - code.InvariantContains(" - /// Exports a list of items to xml as an - ///
- /// List of Templates to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the ITemplate objects - public XElement Export(IEnumerable templates, bool raiseEvents = true) - { - var xml = new XElement("Templates"); - foreach (var item in templates) - { - xml.Add(Export(item, raiseEvents)); - } - return xml; - } - - /// - /// Exports a single item to xml as an - /// - /// Template to export - /// Optional parameter indicating whether or not to raise events - /// containing the xml representation of the ITemplate object - public XElement Export(ITemplate template, bool raiseEvents = true) - { - if (raiseEvents) - { - if (ExportingTemplate.IsRaisedEventCancelled(new ExportEventArgs(template, "Template"), this)) - return new XElement("Template"); - } - - var exporter = new EntityXmlSerializer(); - var xml = exporter.Serialize(template); - - if (raiseEvents) - ExportedTemplate.RaiseEvent(new ExportEventArgs(template, xml, false), this); - - return xml; - } - - #endregion - - #region Stylesheets #endregion #region Installation - internal IPackageInstallation PackageInstallation + public CompiledPackage GetCompiledPackageInfo(FileInfo packageFile) => _packageInstallation.ReadPackage(packageFile); + + public IEnumerable InstallCompiledPackageFiles(PackageDefinition packageDefinition, FileInfo packageFile, int userId = 0) { - private get { return _packageInstallation ?? new PackageInstallation(this, _macroService, _fileService, new PackageExtraction()); } - set { _packageInstallation = value; } + if (packageDefinition == null) throw new ArgumentNullException(nameof(packageDefinition)); + if (packageDefinition.Id == default) throw new ArgumentException("The package definition has not been persisted"); + if (packageDefinition.Name == default) throw new ArgumentException("The package definition has incomplete information"); + + var compiledPackage = GetCompiledPackageInfo(packageFile); + if (compiledPackage == null) throw new InvalidOperationException("Could not read the package file " + packageFile); + + var files = _packageInstallation.InstallPackageFiles(packageDefinition, compiledPackage, userId).ToList(); + + SaveInstalledPackage(packageDefinition); + + _auditService.Add(AuditType.PackagerInstall, userId, -1, "Package", $"Package files installed for package '{compiledPackage.Name}'."); + + return files; } - internal InstallationSummary InstallPackage(string packageFilePath, int userId = 0, bool raiseEvents = false) + public InstallationSummary InstallCompiledPackageData(PackageDefinition packageDefinition, FileInfo packageFile, int userId = 0) { - var metaData = GetPackageMetaData(packageFilePath); + if (packageDefinition == null) throw new ArgumentNullException(nameof(packageDefinition)); + if (packageDefinition.Id == default) throw new ArgumentException("The package definition has not been persisted"); + if (packageDefinition.Name == default) throw new ArgumentException("The package definition has incomplete information"); - if (raiseEvents) - { - if (ImportingPackage.IsRaisedEventCancelled(new ImportPackageEventArgs(packageFilePath, metaData), this)) - { - var initEmpty = new InstallationSummary().InitEmpty(); - initEmpty.MetaData = metaData; - return initEmpty; - } - } - var installationSummary = PackageInstallation.InstallPackage(packageFilePath, userId); + var compiledPackage = GetCompiledPackageInfo(packageFile); + if (compiledPackage == null) throw new InvalidOperationException("Could not read the package file " + packageFile); - if (raiseEvents) + if (ImportingPackage.IsRaisedEventCancelled(new ImportPackageEventArgs(packageFile.Name, compiledPackage), this)) + return new InstallationSummary { MetaData = compiledPackage }; + + var summary = _packageInstallation.InstallPackageData(packageDefinition, compiledPackage, userId); + + SaveInstalledPackage(packageDefinition); + + _auditService.Add(AuditType.PackagerInstall, userId, -1, "Package", $"Package data installed for package '{compiledPackage.Name}'."); + + ImportedPackage.RaiseEvent(new ImportPackageEventArgs(summary, compiledPackage, false), this); + + return summary; + } + + public UninstallationSummary UninstallPackage(string packageName, int userId = 0) + { + //this is ordered by descending version + var allPackageVersions = GetInstalledPackageByName(packageName)?.ToList(); + if (allPackageVersions == null || allPackageVersions.Count == 0) + throw new InvalidOperationException("No installed package found by name " + packageName); + + var summary = new UninstallationSummary { - ImportedPackage.RaiseEvent(new ImportPackageEventArgs(installationSummary, metaData, false), this); + MetaData = allPackageVersions[0] + }; + + var allSummaries = new List(); + + foreach (var packageVersion in allPackageVersions) + { + var versionUninstallSummary = _packageInstallation.UninstallPackage(packageVersion, userId); + + allSummaries.Add(versionUninstallSummary); + + //merge the summary + summary.ActionErrors = summary.ActionErrors.Concat(versionUninstallSummary.ActionErrors).Distinct().ToList(); + summary.Actions = summary.Actions.Concat(versionUninstallSummary.Actions).Distinct().ToList(); + summary.DataTypesUninstalled = summary.DataTypesUninstalled.Concat(versionUninstallSummary.DataTypesUninstalled).Distinct().ToList(); + summary.DictionaryItemsUninstalled = summary.DictionaryItemsUninstalled.Concat(versionUninstallSummary.DictionaryItemsUninstalled).Distinct().ToList(); + summary.DocumentTypesUninstalled = summary.DocumentTypesUninstalled.Concat(versionUninstallSummary.DocumentTypesUninstalled).Distinct().ToList(); + summary.FilesUninstalled = summary.FilesUninstalled.Concat(versionUninstallSummary.FilesUninstalled).Distinct().ToList(); + summary.LanguagesUninstalled = summary.LanguagesUninstalled.Concat(versionUninstallSummary.LanguagesUninstalled).Distinct().ToList(); + summary.MacrosUninstalled = summary.MacrosUninstalled.Concat(versionUninstallSummary.MacrosUninstalled).Distinct().ToList(); + summary.StylesheetsUninstalled = summary.StylesheetsUninstalled.Concat(versionUninstallSummary.StylesheetsUninstalled).Distinct().ToList(); + summary.TemplatesUninstalled = summary.TemplatesUninstalled.Concat(versionUninstallSummary.TemplatesUninstalled).Distinct().ToList(); + + SaveInstalledPackage(packageVersion); + DeleteInstalledPackage(packageVersion.Id, userId); } - return installationSummary; - } + // trigger the UninstalledPackage event + UninstalledPackage.RaiseEvent(new UninstallPackageEventArgs(allSummaries, false), this); - internal PreInstallWarnings GetPackageWarnings(string packageFilePath) - { - return PackageInstallation.GetPreInstallWarnings(packageFilePath); - } - - internal MetaData GetPackageMetaData(string packageFilePath) - { - return PackageInstallation.GetMetaData(packageFilePath); + return summary; } #endregion - #region Package Building + #region Created/Installed Package Repositories + + public void DeleteCreatedPackage(int id, int userId = 0) + { + var package = GetCreatedPackageById(id); + if (package == null) return; + + _auditService.Add(AuditType.PackagerUninstall, userId, -1, "Package", $"Created package '{package.Name}' deleted. Package id: {package.Id}"); + _createdPackages.Delete(id); + } + + public IEnumerable GetAllCreatedPackages() => _createdPackages.GetAll(); + + public PackageDefinition GetCreatedPackageById(int id) => _createdPackages.GetById(id); + + public bool SaveCreatedPackage(PackageDefinition definition) => _createdPackages.SavePackage(definition); + + public string ExportCreatedPackage(PackageDefinition definition) => _createdPackages.ExportPackage(definition); + + + public IEnumerable GetAllInstalledPackages() => _installedPackages.GetAll(); + + public PackageDefinition GetInstalledPackageById(int id) => _installedPackages.GetById(id); + + public IEnumerable GetInstalledPackageByName(string name) + { + var found = _installedPackages.GetAll().Where(x => x.Name.InvariantEquals(name)).OrderByDescending(x => SemVersion.Parse(x.Version)); + return found; + } + + public PackageInstallType GetPackageInstallType(string packageName, SemVersion packageVersion, out PackageDefinition alreadyInstalled) + { + if (packageName == null) throw new ArgumentNullException(nameof(packageName)); + if (packageVersion == null) throw new ArgumentNullException(nameof(packageVersion)); + + //get the latest version installed + alreadyInstalled = GetInstalledPackageByName(packageName)?.OrderByDescending(x => SemVersion.Parse(x.Version)).FirstOrDefault(); + if (alreadyInstalled == null) return PackageInstallType.NewInstall; + + if (!SemVersion.TryParse(alreadyInstalled.Version, out var installedVersion)) + throw new InvalidOperationException("Could not parse the currently installed package version " + alreadyInstalled.Version); + + //compare versions + if (installedVersion >= packageVersion) return PackageInstallType.AlreadyInstalled; + + //it's an upgrade + return PackageInstallType.Upgrade; + } + + public bool SaveInstalledPackage(PackageDefinition definition) => _installedPackages.SavePackage(definition); + + public void DeleteInstalledPackage(int packageId, int userId = 0) + { + var package = GetInstalledPackageById(packageId); + if (package == null) return; + + _auditService.Add(AuditType.PackagerUninstall, userId, -1, "Package", $"Installed package '{package.Name}' deleted. Package id: {package.Id}"); + _installedPackages.Delete(packageId); + } + #endregion - /// - /// This method can be used to trigger the 'ImportedPackage' event when a package is installed by something else but this service. - /// - /// - internal static void OnImportedPackage(ImportPackageEventArgs args) - { - ImportedPackage.RaiseEvent(args, null); - } - - /// - /// This method can be used to trigger the 'UninstalledPackage' event when a package is uninstalled by something else but this service. - /// - /// - internal static void OnUninstalledPackage(UninstallPackageEventArgs args) - { - UninstalledPackage.RaiseEvent(args, null); - } - #region Event Handlers - /// - /// Occurs before Importing Content - /// - public static event TypedEventHandler> ImportingContent; - - /// - /// Occurs after Content is Imported and Saved - /// - public static event TypedEventHandler> ImportedContent; - - - public static event TypedEventHandler> ExportingContent; - - /// - /// Occurs after Content is Exported to Xml - /// - public static event TypedEventHandler> ExportedContent; - - /// - /// Occurs before Exporting Media - /// - public static event TypedEventHandler> ExportingMedia; - - /// - /// Occurs after Media is Exported to Xml - /// - public static event TypedEventHandler> ExportedMedia; - - /// - /// Occurs before Importing ContentType - /// - public static event TypedEventHandler> ImportingContentType; - - /// - /// Occurs after ContentType is Imported and Saved - /// - public static event TypedEventHandler> ImportedContentType; - - /// - /// Occurs before Exporting ContentType - /// - public static event TypedEventHandler> ExportingContentType; - - /// - /// Occurs after ContentType is Exported to Xml - /// - public static event TypedEventHandler> ExportedContentType; - - /// - /// Occurs before Importing DataType - /// - public static event TypedEventHandler> ImportingDataType; - - /// - /// Occurs after DataType is Imported and Saved - /// - public static event TypedEventHandler> ImportedDataType; - - /// - /// Occurs before Exporting DataType - /// - public static event TypedEventHandler> ExportingDataType; - - /// - /// Occurs after DataType is Exported to Xml - /// - public static event TypedEventHandler> ExportedDataType; - - /// - /// Occurs before Importing DictionaryItem - /// - public static event TypedEventHandler> ImportingDictionaryItem; - - /// - /// Occurs after DictionaryItem is Imported and Saved - /// - public static event TypedEventHandler> ImportedDictionaryItem; - - /// - /// Occurs before Exporting DictionaryItem - /// - public static event TypedEventHandler> ExportingDictionaryItem; - - /// - /// Occurs after DictionaryItem is Exported to Xml - /// - public static event TypedEventHandler> ExportedDictionaryItem; - - /// - /// Occurs before Importing Macro - /// - public static event TypedEventHandler> ImportingMacro; - - /// - /// Occurs after Macro is Imported and Saved - /// - public static event TypedEventHandler> ImportedMacro; - - /// - /// Occurs before Exporting Macro - /// - public static event TypedEventHandler> ExportingMacro; - - /// - /// Occurs after Macro is Exported to Xml - /// - public static event TypedEventHandler> ExportedMacro; - - /// - /// Occurs before Importing Language - /// - public static event TypedEventHandler> ImportingLanguage; - - /// - /// Occurs after Language is Imported and Saved - /// - public static event TypedEventHandler> ImportedLanguage; - - /// - /// Occurs before Exporting Language - /// - public static event TypedEventHandler> ExportingLanguage; - - /// - /// Occurs after Language is Exported to Xml - /// - public static event TypedEventHandler> ExportedLanguage; - - /// - /// Occurs before Importing Template - /// - public static event TypedEventHandler> ImportingTemplate; - - /// - /// Occurs before Importing Stylesheets - /// - public static event TypedEventHandler> ImportingStylesheets; - - /// - /// Occurs after Template is Imported and Saved - /// - public static event TypedEventHandler> ImportedTemplate; - - /// - /// Occurs before Exporting Template - /// - public static event TypedEventHandler> ExportingTemplate; - - /// - /// Occurs after Template is Exported to Xml - /// - public static event TypedEventHandler> ExportedTemplate; /// /// Occurs before Importing umbraco package /// - internal static event TypedEventHandler> ImportingPackage; + public static event TypedEventHandler> ImportingPackage; /// /// Occurs after a package is imported @@ -1893,8 +262,10 @@ namespace Umbraco.Core.Services.Implement /// /// Occurs after a package is uninstalled /// - public static event TypedEventHandler> UninstalledPackage; + public static event TypedEventHandler UninstalledPackage; #endregion + + } } diff --git a/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs b/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs index e9703bd85c..80816961fc 100644 --- a/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs +++ b/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs @@ -19,15 +19,15 @@ namespace Umbraco.Core.Services.Implement _redirectUrlRepository = redirectUrlRepository; } - public void Register(string url, Guid contentKey) + public void Register(string url, Guid contentKey, string culture = null) { using (var scope = ScopeProvider.CreateScope()) { - var redir = _redirectUrlRepository.Get(url, contentKey); + var redir = _redirectUrlRepository.Get(url, contentKey, culture); if (redir != null) redir.CreateDateUtc = DateTime.UtcNow; else - redir = new RedirectUrl { Key = Guid.NewGuid(), Url = url, ContentKey = contentKey }; + redir = new RedirectUrl { Key = Guid.NewGuid(), Url = url, ContentKey = contentKey, Culture = culture}; _redirectUrlRepository.Save(redir); scope.Complete(); } diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index f90b1d8d64..6d7ac8a5e7 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -25,8 +25,6 @@ namespace Umbraco.Core.Services private readonly Lazy _serverRegistrationService; private readonly Lazy _entityService; private readonly Lazy _relationService; - private readonly Lazy _treeService; - private readonly Lazy _sectionService; private readonly Lazy _macroService; private readonly Lazy _memberTypeService; private readonly Lazy _memberGroupService; @@ -38,8 +36,7 @@ namespace Umbraco.Core.Services /// /// Initializes a new instance of the class with lazy services. /// - /// Used by IoC. Note that LightInject will favor lazy args when picking a constructor. - public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy treeService, Lazy sectionService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService) + public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService) { _publicAccessService = publicAccessService; _domainService = domainService; @@ -59,8 +56,6 @@ namespace Umbraco.Core.Services _serverRegistrationService = serverRegistrationService; _entityService = entityService; _relationService = relationService; - _treeService = treeService; - _sectionService = sectionService; _macroService = macroService; _memberTypeService = memberTypeService; _memberGroupService = memberGroupService; @@ -71,10 +66,13 @@ namespace Umbraco.Core.Services } /// - /// Initializes a new instance of the class with services. + /// Creates a partial service context with only some services (for tests). /// - /// Used in tests. All items are optional and remain null if not specified. - public ServiceContext(IContentService contentService = null, + /// + /// Using a true constructor for this confuses DI containers. + /// + public static ServiceContext CreatePartial( + IContentService contentService = null, IMediaService mediaService = null, IContentTypeService contentTypeService = null, IMediaTypeService mediaTypeService = null, @@ -88,8 +86,6 @@ namespace Umbraco.Core.Services IMemberTypeService memberTypeService = null, IMemberService memberService = null, IUserService userService = null, - ISectionService sectionService = null, - IApplicationTreeService treeService = null, ITagService tagService = null, INotificationService notificationService = null, ILocalizedTextService localizedTextService = null, @@ -102,40 +98,41 @@ namespace Umbraco.Core.Services IRedirectUrlService redirectUrlService = null, IConsentService consentService = null) { - if (serverRegistrationService != null) _serverRegistrationService = new Lazy(() => serverRegistrationService); - if (externalLoginService != null) _externalLoginService = new Lazy(() => externalLoginService); - if (auditService != null) _auditService = new Lazy(() => auditService); - if (localizedTextService != null) _localizedTextService = new Lazy(() => localizedTextService); - if (tagService != null) _tagService = new Lazy(() => tagService); - if (contentService != null) _contentService = new Lazy(() => contentService); - if (mediaService != null) _mediaService = new Lazy(() => mediaService); - if (contentTypeService != null) _contentTypeService = new Lazy(() => contentTypeService); - if (mediaTypeService != null) _mediaTypeService = new Lazy(() => mediaTypeService); - if (dataTypeService != null) _dataTypeService = new Lazy(() => dataTypeService); - if (fileService != null) _fileService = new Lazy(() => fileService); - if (localizationService != null) _localizationService = new Lazy(() => localizationService); - if (packagingService != null) _packagingService = new Lazy(() => packagingService); - if (entityService != null) _entityService = new Lazy(() => entityService); - if (relationService != null) _relationService = new Lazy(() => relationService); - if (sectionService != null) _sectionService = new Lazy(() => sectionService); - if (memberGroupService != null) _memberGroupService = new Lazy(() => memberGroupService); - if (memberTypeService != null) _memberTypeService = new Lazy(() => memberTypeService); - if (treeService != null) _treeService = new Lazy(() => treeService); - if (memberService != null) _memberService = new Lazy(() => memberService); - if (userService != null) _userService = new Lazy(() => userService); - if (notificationService != null) _notificationService = new Lazy(() => notificationService); - if (domainService != null) _domainService = new Lazy(() => domainService); - if (macroService != null) _macroService = new Lazy(() => macroService); - if (publicAccessService != null) _publicAccessService = new Lazy(() => publicAccessService); - if (redirectUrlService != null) _redirectUrlService = new Lazy(() => redirectUrlService); - if (consentService != null) _consentService = new Lazy(() => consentService); + Lazy Lazy(T service) => service == null ? null : new Lazy(() => service); + + return new ServiceContext( + Lazy(publicAccessService), + Lazy(domainService), + Lazy(auditService), + Lazy(localizedTextService), + Lazy(tagService), + Lazy(contentService), + Lazy(userService), + Lazy(memberService), + Lazy(mediaService), + Lazy(contentTypeService), + Lazy(mediaTypeService), + Lazy(dataTypeService), + Lazy(fileService), + Lazy(localizationService), + Lazy(packagingService), + Lazy(serverRegistrationService), + Lazy(entityService), + Lazy(relationService), + Lazy(macroService), + Lazy(memberTypeService), + Lazy(memberGroupService), + Lazy(notificationService), + Lazy(externalLoginService), + Lazy(redirectUrlService), + Lazy(consentService)); } - + /// /// Gets the /// public IPublicAccessService PublicAccessService => _publicAccessService.Value; - + /// /// Gets the /// @@ -231,16 +228,6 @@ namespace Umbraco.Core.Services /// public IMemberService MemberService => _memberService.Value; - /// - /// Gets the - /// - public ISectionService SectionService => _sectionService.Value; - - /// - /// Gets the - /// - public IApplicationTreeService ApplicationTreeService => _treeService.Value; - /// /// Gets the MemberTypeService /// diff --git a/src/Umbraco.Core/SimpleMainDom.cs b/src/Umbraco.Core/SimpleMainDom.cs new file mode 100644 index 0000000000..87cc7bcff1 --- /dev/null +++ b/src/Umbraco.Core/SimpleMainDom.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core +{ + /// + /// Provides a simple implementation of . + /// + public class SimpleMainDom : IMainDom + { + private readonly object _locko = new object(); + private readonly List> _callbacks = new List>(); + private bool _isStopping; + + /// + public bool IsMainDom { get; private set; } = true; + + /// + public bool Register(Action release, int weight = 100) + => Register(null, release, weight); + + /// + public bool Register(Action install, Action release, int weight = 100) + { + lock (_locko) + { + if (_isStopping) return false; + install?.Invoke(); + if (release != null) + _callbacks.Add(new KeyValuePair(weight, release)); + return true; + } + } + + public void Stop() + { + lock (_locko) + { + if (_isStopping) return; + if (IsMainDom == false) return; // probably not needed + _isStopping = true; + } + + try + { + foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) + { + callback(); // no timeout on callbacks + } + } + finally + { + // in any case... + IsMainDom = false; + } + } + } +} diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 03a371204c..b6a1103097 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -187,7 +187,6 @@ namespace Umbraco.Core outputArray[i] = char.IsLetterOrDigit(inputArray[i]) ? inputArray[i] : replacement; return new string(outputArray); } - private static readonly char[] CleanForXssChars = "*?(){}[];:%<>/\\|&'\"".ToCharArray(); /// @@ -893,7 +892,7 @@ namespace Umbraco.Core } /// - /// Ensures that the folder path ends with a DirectorySeperatorChar + /// Ensures that the folder path ends with a DirectorySeparatorChar /// /// /// @@ -1115,7 +1114,7 @@ namespace Umbraco.Core /// Checks UmbracoSettings.ForceSafeAliases to determine whether it should filter the text. public static string ToSafeAliasWithForcingCheck(this string alias) { - return UmbracoConfig.For.UmbracoSettings().Content.ForceSafeAliases ? alias.ToSafeAlias() : alias; + return Current.Configs.Settings().Content.ForceSafeAliases ? alias.ToSafeAlias() : alias; } /// @@ -1127,7 +1126,7 @@ namespace Umbraco.Core /// Checks UmbracoSettings.ForceSafeAliases to determine whether it should filter the text. public static string ToSafeAliasWithForcingCheck(this string alias, string culture) { - return UmbracoConfig.For.UmbracoSettings().Content.ForceSafeAliases ? alias.ToSafeAlias(culture) : alias; + return Current.Configs.Settings().Content.ForceSafeAliases ? alias.ToSafeAlias(culture) : alias; } // the new methods to get a url segment diff --git a/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs b/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs index a9b8234e14..5183c28e15 100644 --- a/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs @@ -1,14 +1,9 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.Strings { public class UrlSegmentProviderCollectionBuilder : OrderedCollectionBuilderBase { - public UrlSegmentProviderCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override UrlSegmentProviderCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 20f9276ba1..6844e6e75c 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Sync private readonly IRuntimeState _runtime; private readonly ManualResetEvent _syncIdle; private readonly object _locko = new object(); - private readonly ProfilingLogger _profilingLogger; + private readonly IProfilingLogger _profilingLogger; private readonly ISqlContext _sqlContext; private readonly Lazy _distCacheFilePath; private int _lastId = -1; @@ -46,7 +46,7 @@ namespace Umbraco.Core.Sync public DatabaseServerMessengerOptions Options { get; } public DatabaseServerMessenger( - IRuntimeState runtime, IScopeProvider scopeProvider, ISqlContext sqlContext, ProfilingLogger proflog, IGlobalSettings globalSettings, + IRuntimeState runtime, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings, bool distributedEnabled, DatabaseServerMessengerOptions options) : base(distributedEnabled) { @@ -54,7 +54,7 @@ namespace Umbraco.Core.Sync _sqlContext = sqlContext; _runtime = runtime; _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); - Logger = proflog.Logger; + Logger = proflog; Options = options ?? throw new ArgumentNullException(nameof(options)); _lastPruned = _lastSync = DateTime.UtcNow; _syncIdle = new ManualResetEvent(true); @@ -551,7 +551,7 @@ namespace Umbraco.Core.Sync break; case LocalTempStorage.Default: default: - var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/DistCache"); + var tempFolder = IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "DistCache"); distCacheFilePath = Path.Combine(tempFolder, fileName); break; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8ef76ade37..ec9b2d8eb0 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -109,38 +109,39 @@ + - + - + - + - - + + - - - + + + - - + + - - + + - + @@ -154,35 +155,49 @@ - + + + - - - - - - - - + + + + + + + + + + + + - - + + - - - - + + + + + + + + + + - - + + + + @@ -191,6 +206,8 @@ + + @@ -244,7 +261,6 @@ - @@ -276,7 +292,6 @@ - @@ -289,7 +304,6 @@ - @@ -306,7 +320,6 @@ - @@ -314,18 +327,24 @@ + + + + + + @@ -335,6 +354,7 @@ + @@ -347,6 +367,7 @@ + @@ -356,6 +377,7 @@ + @@ -367,6 +389,7 @@ + @@ -409,6 +432,12 @@ + + + + + + @@ -420,6 +449,20 @@ + + + + + + + + + + + + + + @@ -470,6 +513,8 @@ + + @@ -534,13 +579,11 @@ - - @@ -568,7 +611,7 @@ - + @@ -585,12 +628,10 @@ - - @@ -638,7 +679,6 @@ - @@ -741,7 +781,6 @@ - @@ -836,7 +875,6 @@ - @@ -861,11 +899,7 @@ - - - - - + @@ -1220,7 +1254,6 @@ - @@ -1279,6 +1312,7 @@ + @@ -1332,6 +1366,7 @@ + @@ -1351,10 +1386,9 @@ - + - @@ -1384,7 +1418,6 @@ - @@ -1423,6 +1456,7 @@ + diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index d4f2d40656..e058858799 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Xml /// /// The XmlHelper class contains general helper methods for working with xml in umbraco. /// - public class XmlHelper + internal class XmlHelper { /// /// Creates or sets an attribute on the XmlNode if an Attributes collection is available @@ -126,44 +126,6 @@ namespace Umbraco.Core.Xml return false; } - /// - /// Tries to create a new XElement from a property value. - /// - /// The value of the property. - /// The Xml element. - /// A value indicating whether it has been possible to create the element. - /// The value can be anything... Performance-wise, this is bad. - public static bool TryCreateXElementFromPropertyValue(object value, out XElement elt) - { - // see note above in TryCreateXPathDocumentFromPropertyValue... - - elt = null; - var xml = value as string; - if (xml == null) return false; // not a string - if (CouldItBeXml(xml) == false) return false; // string does not look like it's xml - if (IsXmlWhitespace(xml)) return false; // string is whitespace, xml-wise - - try - { - elt = XElement.Parse(xml, LoadOptions.None); - } - catch - { - elt = null; - return false; // string can't be parsed into xml - } - - //SD: This used to do this but the razor macros and the entire razor macros section is gone, it was all legacy, it seems this method isn't even - // used apart from for tests so don't think this matters. In any case, we no longer check for this! - - //var name = elt.Name.LocalName; // must not match an excluded tag - //if (UmbracoConfig.For.UmbracoSettings().Scripting.NotDynamicXmlDocumentElements.All(x => x.Element.InvariantEquals(name) == false)) - // return true; - //elt = null; - //return false; - - return true; - } /// /// Sorts the children of a parentNode. @@ -186,47 +148,6 @@ namespace Umbraco.Core.Xml parentNode.AppendChild(node); // moves the node to the last position } - /// - /// Sorts the children of a parentNode if needed. - /// - /// The parent node. - /// An XPath expression to select children of to sort. - /// A function returning the value to order the nodes by. - /// A value indicating whether sorting was needed. - /// same as SortNodes but will do nothing if nodes are already sorted - should improve performances. - internal static bool SortNodesIfNeeded( - XmlNode parentNode, - string childNodesXPath, - Func orderBy) - { - // ensure orderBy runs only once per node - // checks whether nodes are already ordered - // and actually sorts only if needed - - var childNodesAndOrder = parentNode.SelectNodes(childNodesXPath).Cast() - .Select(x => Tuple.Create(x, orderBy(x))).ToArray(); - - var a = 0; - foreach (var x in childNodesAndOrder) - { - if (a > x.Item2) - { - a = -1; - break; - } - a = x.Item2; - } - - if (a >= 0) - return false; - - // append child nodes to last position, in sort-order - // so all child nodes will go after the property nodes - foreach (var x in childNodesAndOrder.OrderBy(x => x.Item2)) - parentNode.AppendChild(x.Item1); // moves the node to the last position - - return true; - } /// /// Sorts a single child node of a parentNode. @@ -281,72 +202,6 @@ namespace Umbraco.Core.Xml return false; } - // used by DynamicNode only, see note in TryCreateXPathDocumentFromPropertyValue - public static string StripDashesInElementOrAttributeNames(string xml) - { - using (var outputms = new MemoryStream()) - { - using (TextWriter outputtw = new StreamWriter(outputms)) - { - using (var ms = new MemoryStream()) - { - using (var tw = new StreamWriter(ms)) - { - tw.Write(xml); - tw.Flush(); - ms.Position = 0; - using (var tr = new StreamReader(ms)) - { - bool IsInsideElement = false, IsInsideQuotes = false; - int ic = 0; - while ((ic = tr.Read()) != -1) - { - if (ic == (int)'<' && !IsInsideQuotes) - { - if (tr.Peek() != (int)'!') - { - IsInsideElement = true; - } - } - if (ic == (int)'>' && !IsInsideQuotes) - { - IsInsideElement = false; - } - if (ic == (int)'"') - { - IsInsideQuotes = !IsInsideQuotes; - } - if (!IsInsideElement || ic != (int)'-' || IsInsideQuotes) - { - outputtw.Write((char)ic); - } - } - - } - } - } - outputtw.Flush(); - outputms.Position = 0; - using (TextReader outputtr = new StreamReader(outputms)) - { - return outputtr.ReadToEnd(); - } - } - } - } - - - /// - /// Imports a XML node from text. - /// - /// The text. - /// The XML doc. - /// - public static XmlNode ImportXmlNodeFromText(string text, ref XmlDocument xmlDoc) - { - xmlDoc.LoadXml(text); - return xmlDoc.FirstChild; - } /// /// Opens a file as a XmlDocument. @@ -355,16 +210,14 @@ namespace Umbraco.Core.Xml /// Returns a XmlDocument class public static XmlDocument OpenAsXmlDocument(string filePath) { - - var reader = new XmlTextReader(IOHelper.MapPath(filePath)) {WhitespaceHandling = WhitespaceHandling.All}; - - var xmlDoc = new XmlDocument(); - //Load the file into the XmlDocument - xmlDoc.Load(reader); - //Close off the connection to the file. - reader.Close(); - - return xmlDoc; + using (var reader = new XmlTextReader(IOHelper.MapPath(filePath)) {WhitespaceHandling = WhitespaceHandling.All}) + { + var xmlDoc = new XmlDocument(); + //Load the file into the XmlDocument + xmlDoc.Load(reader); + + return xmlDoc; + } } /// diff --git a/src/Umbraco.Core/_Legacy/PackageActions/IPackageAction.cs b/src/Umbraco.Core/_Legacy/PackageActions/IPackageAction.cs index 502aee36b3..b58ea34a60 100644 --- a/src/Umbraco.Core/_Legacy/PackageActions/IPackageAction.cs +++ b/src/Umbraco.Core/_Legacy/PackageActions/IPackageAction.cs @@ -1,13 +1,13 @@ using System.Xml; +using System.Xml.Linq; using Umbraco.Core.Composing; namespace Umbraco.Core._Legacy.PackageActions { public interface IPackageAction : IDiscoverable { - bool Execute(string packageName, XmlNode xmlData); + bool Execute(string packageName, XElement xmlData); string Alias(); - bool Undo(string packageName, XmlNode xmlData); - XmlNode SampleXml(); + bool Undo(string packageName, XElement xmlData); } } diff --git a/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollection.cs b/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollection.cs index 46ea3c1e39..a38c8cea56 100644 --- a/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollection.cs +++ b/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollection.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Composing; namespace Umbraco.Core._Legacy.PackageActions { - internal class PackageActionCollection : BuilderCollectionBase + public sealed class PackageActionCollection : BuilderCollectionBase { public PackageActionCollection(IEnumerable items) : base(items) diff --git a/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollectionBuilder.cs b/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollectionBuilder.cs index 42ab3ec7c2..2f73a2b489 100644 --- a/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollectionBuilder.cs +++ b/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollectionBuilder.cs @@ -1,14 +1,9 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core._Legacy.PackageActions { internal class PackageActionCollectionBuilder : LazyCollectionBuilderBase { - public PackageActionCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override PackageActionCollectionBuilder This => this; } } diff --git a/src/Umbraco.Examine/Config/ConfigIndexCriteria.cs b/src/Umbraco.Examine/Config/ConfigIndexCriteria.cs deleted file mode 100644 index de2a5ced36..0000000000 --- a/src/Umbraco.Examine/Config/ConfigIndexCriteria.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Examine; - -namespace Umbraco.Examine.Config -{ - /// - /// a data structure for storing indexing/searching instructions based on config based indexers - /// - public class ConfigIndexCriteria - { - /// - /// Constructor - /// - /// - /// - /// - /// - /// - public ConfigIndexCriteria(IEnumerable standardFields, IEnumerable userFields, IEnumerable includeNodeTypes, IEnumerable excludeNodeTypes, int? parentNodeId) - { - UserFields = userFields.ToList(); - StandardFields = standardFields.ToList(); - IncludeItemTypes = includeNodeTypes; - ExcludeItemTypes = excludeNodeTypes; - ParentNodeId = parentNodeId; - } - - public IEnumerable StandardFields { get; internal set; } - public IEnumerable UserFields { get; internal set; } - - public IEnumerable IncludeItemTypes { get; internal set; } - public IEnumerable ExcludeItemTypes { get; internal set; } - public int? ParentNodeId { get; internal set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Examine/Config/ConfigIndexField.cs b/src/Umbraco.Examine/Config/ConfigIndexField.cs deleted file mode 100644 index ec9cbf797e..0000000000 --- a/src/Umbraco.Examine/Config/ConfigIndexField.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Configuration; - -namespace Umbraco.Examine.Config -{ - /// - /// A configuration item representing a field to index - /// - public sealed class ConfigIndexField : ConfigurationElement - { - [ConfigurationProperty("Name", IsRequired = true)] - public string Name - { - get => (string)this["Name"]; - set => this["Name"] = value; - } - - [ConfigurationProperty("EnableSorting", IsRequired = false)] - public bool EnableSorting - { - get => (bool)this["EnableSorting"]; - set => this["EnableSorting"] = value; - } - - [ConfigurationProperty("Type", IsRequired = false, DefaultValue = "String")] - public string Type - { - get => (string)this["Type"]; - set => this["Type"] = value; - } - - public bool Equals(ConfigIndexField other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return string.Equals(Name, other.Name); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((ConfigIndexField)obj); - } - - public override int GetHashCode() - { - return Name.GetHashCode(); - } - - public static bool operator ==(ConfigIndexField left, ConfigIndexField right) - { - return Equals(left, right); - } - - public static bool operator !=(ConfigIndexField left, ConfigIndexField right) - { - return !Equals(left, right); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Examine/Config/IndexFieldCollection.cs b/src/Umbraco.Examine/Config/IndexFieldCollection.cs deleted file mode 100644 index 063c157dbe..0000000000 --- a/src/Umbraco.Examine/Config/IndexFieldCollection.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Configuration; - -namespace Umbraco.Examine.Config -{ - public sealed class IndexFieldCollection : ConfigurationElementCollection - { - #region Overridden methods to define collection - protected override ConfigurationElement CreateNewElement() - { - return new ConfigIndexField(); - } - protected override object GetElementKey(ConfigurationElement element) - { - ConfigIndexField field = (ConfigIndexField)element; - return field.Name; - } - - public override bool IsReadOnly() - { - return false; - } - #endregion - - /// - /// Adds an index field to the collection - /// - /// - public void Add(ConfigIndexField field) - { - BaseAdd(field, true); - } - - /// - /// Default property for accessing an IndexField definition - /// - /// Field Name - /// - public new ConfigIndexField this[string name] - { - get - { - return (ConfigIndexField)this.BaseGet(name); - } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Examine/Config/IndexFieldCollectionExtensions.cs b/src/Umbraco.Examine/Config/IndexFieldCollectionExtensions.cs deleted file mode 100644 index eea117ce05..0000000000 --- a/src/Umbraco.Examine/Config/IndexFieldCollectionExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Examine.Config -{ - public static class IndexFieldCollectionExtensions - { - public static List ToList(this IndexFieldCollection indexes) - { - List fields = new List(); - foreach (ConfigIndexField field in indexes) - fields.Add(field); - return fields; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Examine/Config/IndexSet.cs b/src/Umbraco.Examine/Config/IndexSet.cs deleted file mode 100644 index 65b1cd5aec..0000000000 --- a/src/Umbraco.Examine/Config/IndexSet.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Configuration; -using System.IO; -using System.Web; -using System.Web.Hosting; - -namespace Umbraco.Examine.Config -{ - public sealed class IndexSet : ConfigurationElement - { - - [ConfigurationProperty("SetName", IsRequired = true, IsKey = true)] - public string SetName => (string)this["SetName"]; - - /// - /// When this property is set, the indexing will only index documents that are descendants of this node. - /// - [ConfigurationProperty("IndexParentId", IsRequired = false, IsKey = false)] - public int? IndexParentId - { - get - { - if (this["IndexParentId"] == null) - return null; - - return (int)this["IndexParentId"]; - } - } - - /// - /// The collection of node types to index, if not specified, all node types will be indexed (apart from the ones specified in the ExcludeNodeTypes collection). - /// - [ConfigurationCollection(typeof(IndexFieldCollection))] - [ConfigurationProperty("IncludeNodeTypes", IsDefaultCollection = false, IsRequired = false)] - public IndexFieldCollection IncludeNodeTypes => (IndexFieldCollection)base["IncludeNodeTypes"]; - - /// - /// The collection of node types to not index. If specified, these node types will not be indexed. - /// - [ConfigurationCollection(typeof(IndexFieldCollection))] - [ConfigurationProperty("ExcludeNodeTypes", IsDefaultCollection = false, IsRequired = false)] - public IndexFieldCollection ExcludeNodeTypes => (IndexFieldCollection)base["ExcludeNodeTypes"]; - - /// - /// A collection of user defined umbraco fields to index - /// - /// - /// If this property is not specified, or if it's an empty collection, the default user fields will be all user fields defined in Umbraco - /// - [ConfigurationCollection(typeof(IndexFieldCollection))] - [ConfigurationProperty("IndexUserFields", IsDefaultCollection = false, IsRequired = false)] - public IndexFieldCollection IndexUserFields => (IndexFieldCollection)base["IndexUserFields"]; - - /// - /// The fields umbraco values that will be indexed. i.e. id, nodeTypeAlias, writer, etc... - /// - /// - /// If this is not specified, or if it's an empty collection, the default optins will be specified: - /// - id - /// - version - /// - parentID - /// - level - /// - writerID - /// - creatorID - /// - nodeType - /// - template - /// - sortOrder - /// - createDate - /// - updateDate - /// - nodeName - /// - urlName - /// - writerName - /// - creatorName - /// - nodeTypeAlias - /// - path - /// - [ConfigurationCollection(typeof(IndexFieldCollection))] - [ConfigurationProperty("IndexAttributeFields", IsDefaultCollection = false, IsRequired = false)] - public IndexFieldCollection IndexAttributeFields => (IndexFieldCollection)base["IndexAttributeFields"]; - } -} diff --git a/src/Umbraco.Examine/Config/IndexSetCollection.cs b/src/Umbraco.Examine/Config/IndexSetCollection.cs deleted file mode 100644 index bfce8e4cce..0000000000 --- a/src/Umbraco.Examine/Config/IndexSetCollection.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Configuration; - - -namespace Umbraco.Examine.Config -{ - public sealed class IndexSetCollection : ConfigurationElementCollection - { - #region Overridden methods to define collection - protected override ConfigurationElement CreateNewElement() - { - return new IndexSet(); - } - protected override object GetElementKey(ConfigurationElement element) - { - return ((IndexSet)element).SetName; - } - public override ConfigurationElementCollectionType CollectionType => ConfigurationElementCollectionType.BasicMap; - protected override string ElementName => "IndexSet"; - - #endregion - - /// - /// Default property for accessing Image Sets - /// - /// - /// - public new IndexSet this[string setName] => (IndexSet)this.BaseGet(setName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Examine/Config/IndexSets.cs b/src/Umbraco.Examine/Config/IndexSets.cs deleted file mode 100644 index c6ad1476c3..0000000000 --- a/src/Umbraco.Examine/Config/IndexSets.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Configuration; - -namespace Umbraco.Examine.Config -{ - public sealed class IndexSets : ConfigurationSection - { - #region Singleton definition - - private IndexSets() { } - - public static IndexSets Instance { get; } = ConfigurationManager.GetSection(SectionName) as IndexSets; - - #endregion - - private const string SectionName = "ExamineLuceneIndexSets"; - - [ConfigurationCollection(typeof(IndexSetCollection))] - [ConfigurationProperty("", IsDefaultCollection = true, IsRequired = true)] - public IndexSetCollection Sets => (IndexSetCollection)base[""]; - } -} diff --git a/src/Umbraco.Examine/ContentIndexPopulator.cs b/src/Umbraco.Examine/ContentIndexPopulator.cs index 72615b4b26..51b9de4a0b 100644 --- a/src/Umbraco.Examine/ContentIndexPopulator.cs +++ b/src/Umbraco.Examine/ContentIndexPopulator.cs @@ -58,8 +58,10 @@ namespace Umbraco.Examine _parentId = parentId; } - protected override void PopulateIndexes(IEnumerable indexes) + protected override void PopulateIndexes(IReadOnlyList indexes) { + if (indexes.Count == 0) return; + const int pageSize = 10000; var pageIndex = 0; diff --git a/src/Umbraco.Examine/ContentValueSetBuilder.cs b/src/Umbraco.Examine/ContentValueSetBuilder.cs index 07e5e4565b..a6262c53fc 100644 --- a/src/Umbraco.Examine/ContentValueSetBuilder.cs +++ b/src/Umbraco.Examine/ContentValueSetBuilder.cs @@ -13,11 +13,11 @@ namespace Umbraco.Examine /// public class ContentValueSetBuilder : BaseValueSetBuilder, IContentValueSetBuilder, IPublishedContentValueSetBuilder { - private readonly IEnumerable _urlSegmentProviders; + private readonly UrlSegmentProviderCollection _urlSegmentProviders; private readonly IUserService _userService; public ContentValueSetBuilder(PropertyEditorCollection propertyEditors, - IEnumerable urlSegmentProviders, + UrlSegmentProviderCollection urlSegmentProviders, IUserService userService, bool publishedValuesOnly) : base(propertyEditors, publishedValuesOnly) @@ -42,9 +42,9 @@ namespace Umbraco.Examine var values = new Dictionary> { {"icon", c.ContentType.Icon.Yield()}, - {UmbracoExamineIndex.PublishedFieldName, new object[] {c.Published ? 1 : 0}}, //Always add invariant published value + {UmbracoExamineIndex.PublishedFieldName, new object[] {c.Published ? "y" : "n"}}, //Always add invariant published value {"id", new object[] {c.Id}}, - {"key", new object[] {c.Key}}, + {UmbracoExamineIndex.NodeKeyFieldName, new object[] {c.Key}}, {"parentID", new object[] {c.Level > 1 ? c.ParentId : -1}}, {"level", new object[] {c.Level}}, {"creatorID", new object[] {c.CreatorId}}, @@ -53,20 +53,20 @@ namespace Umbraco.Examine {"updateDate", new object[] {c.UpdateDate}}, //Always add invariant updateDate {"nodeName", PublishedValuesOnly //Always add invariant nodeName ? c.PublishName.Yield() - : c.Name.Yield()}, + : c.Name.Yield()}, {"urlName", urlValue.Yield()}, //Always add invariant urlName {"path", c.Path.Yield()}, {"nodeType", new object[] {c.ContentType.Id}}, {"creatorName", (c.GetCreatorProfile(_userService)?.Name ?? "??").Yield() }, {"writerName",(c.GetWriterProfile(_userService)?.Name ?? "??").Yield() }, {"writerID", new object[] {c.WriterId}}, - {"template", new object[] {c.Template?.Id ?? 0}}, - {UmbracoContentIndex.VariesByCultureFieldName, new object[] {0}}, + {"templateID", new object[] {c.TemplateId ?? 0}}, + {UmbracoContentIndex.VariesByCultureFieldName, new object[] {"n"}}, }; if (isVariant) { - values[UmbracoContentIndex.VariesByCultureFieldName] = new object[] { 1 }; + values[UmbracoContentIndex.VariesByCultureFieldName] = new object[] { "y" }; foreach (var culture in c.AvailableCultures) { @@ -76,7 +76,7 @@ namespace Umbraco.Examine values[$"nodeName_{lowerCulture}"] = PublishedValuesOnly ? c.GetPublishName(culture).Yield() : c.GetCultureName(culture).Yield(); - values[$"{UmbracoExamineIndex.PublishedFieldName}_{lowerCulture}"] = (c.IsCulturePublished(culture) ? 1 : 0).Yield(); + values[$"{UmbracoExamineIndex.PublishedFieldName}_{lowerCulture}"] = (c.IsCulturePublished(culture) ? "y" : "n").Yield(); values[$"updateDate_{lowerCulture}"] = PublishedValuesOnly ? c.GetPublishDate(culture).Yield() : c.GetUpdateDate(culture).Yield(); diff --git a/src/Umbraco.Examine/ContentValueSetValidator.cs b/src/Umbraco.Examine/ContentValueSetValidator.cs index d4f6ceb15f..9555566c53 100644 --- a/src/Umbraco.Examine/ContentValueSetValidator.cs +++ b/src/Umbraco.Examine/ContentValueSetValidator.cs @@ -95,17 +95,17 @@ namespace Umbraco.Examine if (!valueSet.Values.TryGetValue(UmbracoExamineIndex.PublishedFieldName, out var published)) return ValueSetValidationResult.Failed; - if (!published[0].Equals(1)) + if (!published[0].Equals("y")) return ValueSetValidationResult.Failed; //deal with variants, if there are unpublished variants than we need to remove them from the value set if (valueSet.Values.TryGetValue(UmbracoContentIndex.VariesByCultureFieldName, out var variesByCulture) - && variesByCulture.Count > 0 && variesByCulture[0].Equals(1)) + && variesByCulture.Count > 0 && variesByCulture[0].Equals("y")) { //so this valueset is for a content that varies by culture, now check for non-published cultures and remove those values foreach(var publishField in valueSet.Values.Where(x => x.Key.StartsWith($"{UmbracoExamineIndex.PublishedFieldName}_")).ToList()) { - if (publishField.Value.Count <= 0 || !publishField.Value[0].Equals(1)) + if (publishField.Value.Count <= 0 || !publishField.Value[0].Equals("y")) { //this culture is not published, so remove all of these culture values var cultureSuffix = publishField.Key.Substring(publishField.Key.LastIndexOf('_')); diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 525f0deaa1..4fe6c359d7 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Examine; using Examine.LuceneEngine.Providers; using Lucene.Net.Analysis; @@ -7,6 +9,7 @@ using Lucene.Net.Index; using Lucene.Net.QueryParsers; using Lucene.Net.Search; using Lucene.Net.Store; +using Umbraco.Core; using Version = Lucene.Net.Util.Version; using Umbraco.Core.Logging; @@ -15,9 +18,35 @@ namespace Umbraco.Examine /// /// Extension methods for the LuceneIndex /// - internal static class ExamineExtensions + public static class ExamineExtensions { - public static bool TryParseLuceneQuery(string query) + /// + /// Matches a culture iso name suffix + /// + /// + /// myFieldName_en-us will match the "en-us" + /// + internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled); + + /// + /// Returns all index fields that are culture specific (suffixed) + /// + /// + /// + /// + public static IEnumerable GetCultureFields(this IUmbracoIndex index, string culture) + { + var allFields = index.GetFields(); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var field in allFields) + { + var match = CultureIsoCodeFieldNameMatchExpression.Match(field); + if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value)) + yield return field; + } + } + + internal static bool TryParseLuceneQuery(string query) { //TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll // also do this rudimentary check diff --git a/src/Umbraco.Examine/IIndexPopulator.cs b/src/Umbraco.Examine/IIndexPopulator.cs index 153e88d46b..97a1216fae 100644 --- a/src/Umbraco.Examine/IIndexPopulator.cs +++ b/src/Umbraco.Examine/IIndexPopulator.cs @@ -1,22 +1,20 @@ -using System.Collections.Generic; -using Examine; +using Examine; namespace Umbraco.Examine { public interface IIndexPopulator { /// - /// If this index is registered with this populatr + /// If this index is registered with this populator /// /// /// bool IsRegistered(IIndex index); /// - /// Populate indexers + /// Populate indexers /// /// void Populate(params IIndex[] indexes); } - } diff --git a/src/Umbraco.Examine/IndexPopulator.cs b/src/Umbraco.Examine/IndexPopulator.cs index 9cd985df16..f9d4d85dc8 100644 --- a/src/Umbraco.Examine/IndexPopulator.cs +++ b/src/Umbraco.Examine/IndexPopulator.cs @@ -38,9 +38,9 @@ namespace Umbraco.Examine public void Populate(params IIndex[] indexes) { - PopulateIndexes(indexes.Where(IsRegistered)); + PopulateIndexes(indexes.Where(IsRegistered).ToList()); } - protected abstract void PopulateIndexes(IEnumerable indexes); + protected abstract void PopulateIndexes(IReadOnlyList indexes); } } diff --git a/src/Umbraco.Examine/IndexRebuilder.cs b/src/Umbraco.Examine/IndexRebuilder.cs index d946a8f783..43c309b9c5 100644 --- a/src/Umbraco.Examine/IndexRebuilder.cs +++ b/src/Umbraco.Examine/IndexRebuilder.cs @@ -42,6 +42,8 @@ namespace Umbraco.Examine ? ExamineManager.Indexes.Where(x => !x.IndexExists()) : ExamineManager.Indexes).ToArray(); + if (indexes.Length == 0) return; + foreach (var index in indexes) { index.CreateIndex(); // clear the index diff --git a/src/Umbraco.Examine/LuceneIndexCreator.cs b/src/Umbraco.Examine/LuceneIndexCreator.cs index 0e83b37dc5..806d0edc7a 100644 --- a/src/Umbraco.Examine/LuceneIndexCreator.cs +++ b/src/Umbraco.Examine/LuceneIndexCreator.cs @@ -29,7 +29,7 @@ namespace Umbraco.Examine public virtual Lucene.Net.Store.Directory CreateFileSystemLuceneDirectory(string folderName) { - var dirInfo = new DirectoryInfo(Path.Combine(IOHelper.MapPath(SystemDirectories.Data), "TEMP", "ExamineIndexes", folderName)); + var dirInfo = new DirectoryInfo(Path.Combine(IOHelper.MapPath(SystemDirectories.TempData), "ExamineIndexes", folderName)); if (!dirInfo.Exists) System.IO.Directory.CreateDirectory(dirInfo.FullName); diff --git a/src/Umbraco.Examine/MediaIndexPopulator.cs b/src/Umbraco.Examine/MediaIndexPopulator.cs index 2232d359e7..6dadcbe4b3 100644 --- a/src/Umbraco.Examine/MediaIndexPopulator.cs +++ b/src/Umbraco.Examine/MediaIndexPopulator.cs @@ -39,8 +39,10 @@ namespace Umbraco.Examine _mediaValueSetBuilder = mediaValueSetBuilder; } - protected override void PopulateIndexes(IEnumerable indexes) + protected override void PopulateIndexes(IReadOnlyList indexes) { + if (indexes.Count == 0) return; + const int pageSize = 10000; var pageIndex = 0; diff --git a/src/Umbraco.Examine/MediaValueSetBuilder.cs b/src/Umbraco.Examine/MediaValueSetBuilder.cs index 8df51570c1..23d0414d5d 100644 --- a/src/Umbraco.Examine/MediaValueSetBuilder.cs +++ b/src/Umbraco.Examine/MediaValueSetBuilder.cs @@ -10,11 +10,11 @@ namespace Umbraco.Examine { public class MediaValueSetBuilder : BaseValueSetBuilder { - private readonly IEnumerable _urlSegmentProviders; + private readonly UrlSegmentProviderCollection _urlSegmentProviders; private readonly IUserService _userService; public MediaValueSetBuilder(PropertyEditorCollection propertyEditors, - IEnumerable urlSegmentProviders, + UrlSegmentProviderCollection urlSegmentProviders, IUserService userService) : base(propertyEditors, false) { @@ -32,7 +32,7 @@ namespace Umbraco.Examine { {"icon", m.ContentType.Icon.Yield()}, {"id", new object[] {m.Id}}, - {"key", new object[] {m.Key}}, + {UmbracoExamineIndex.NodeKeyFieldName, new object[] {m.Key}}, {"parentID", new object[] {m.Level > 1 ? m.ParentId : -1}}, {"level", new object[] {m.Level}}, {"creatorID", new object[] {m.CreatorId}}, diff --git a/src/Umbraco.Examine/MemberIndexPopulator.cs b/src/Umbraco.Examine/MemberIndexPopulator.cs index 6e97ee9195..e20dab91ca 100644 --- a/src/Umbraco.Examine/MemberIndexPopulator.cs +++ b/src/Umbraco.Examine/MemberIndexPopulator.cs @@ -17,8 +17,10 @@ namespace Umbraco.Examine _memberService = memberService; _valueSetBuilder = valueSetBuilder; } - protected override void PopulateIndexes(IEnumerable indexes) + protected override void PopulateIndexes(IReadOnlyList indexes) { + if (indexes.Count == 0) return; + const int pageSize = 1000; var pageIndex = 0; diff --git a/src/Umbraco.Examine/MemberValueSetBuilder.cs b/src/Umbraco.Examine/MemberValueSetBuilder.cs index 9864aba18d..d9f0b7806d 100644 --- a/src/Umbraco.Examine/MemberValueSetBuilder.cs +++ b/src/Umbraco.Examine/MemberValueSetBuilder.cs @@ -23,7 +23,7 @@ namespace Umbraco.Examine { {"icon", m.ContentType.Icon.Yield()}, {"id", new object[] {m.Id}}, - {"key", new object[] {m.Key}}, + {UmbracoExamineIndex.NodeKeyFieldName, new object[] {m.Key}}, {"parentID", new object[] {m.Level > 1 ? m.ParentId : -1}}, {"level", new object[] {m.Level}}, {"creatorID", new object[] {m.CreatorId}}, diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 2bdf6f833d..1320f3b0d2 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -48,19 +48,12 @@ - + - - - - - - - diff --git a/src/Umbraco.Examine/UmbracoContentIndex.cs b/src/Umbraco.Examine/UmbracoContentIndex.cs index c584f5bf51..a9e2c72cb6 100644 --- a/src/Umbraco.Examine/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine/UmbracoContentIndex.cs @@ -6,14 +6,10 @@ using System.Linq; using Examine; using Umbraco.Core; using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Examine.LuceneEngine.Indexing; -using Examine.LuceneEngine.Providers; using Lucene.Net.Analysis; using Lucene.Net.Store; using Umbraco.Core.Composing; using Umbraco.Core.Logging; -using Umbraco.Examine.Config; using Examine.LuceneEngine; namespace Umbraco.Examine @@ -27,18 +23,7 @@ namespace Umbraco.Examine protected ILocalizationService LanguageService { get; } #region Constructors - - /// - /// Constructor for configuration providers - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public UmbracoContentIndex() - { - LanguageService = Current.Services.LocalizationService; - - //note: The validator for this config based indexer is set in the Initialize method - } - + /// /// Create an index at runtime /// @@ -52,14 +37,14 @@ namespace Umbraco.Examine /// public UmbracoContentIndex( string name, - FieldDefinitionCollection fieldDefinitions, Directory luceneDirectory, + FieldDefinitionCollection fieldDefinitions, Analyzer defaultAnalyzer, - ProfilingLogger profilingLogger, + IProfilingLogger profilingLogger, ILocalizationService languageService, IContentValueSetValidator validator, IReadOnlyDictionary indexValueTypes = null) - : base(name, fieldDefinitions, luceneDirectory, defaultAnalyzer, profilingLogger, validator, indexValueTypes) + : base(name, luceneDirectory, fieldDefinitions, defaultAnalyzer, profilingLogger, validator, indexValueTypes) { if (validator == null) throw new ArgumentNullException(nameof(validator)); LanguageService = languageService ?? throw new ArgumentNullException(nameof(languageService)); @@ -70,65 +55,6 @@ namespace Umbraco.Examine #endregion - #region Initialize - - /// - /// Set up all properties for the indexer based on configuration information specified. This will ensure that - /// all of the folders required by the indexer are created and exist. This will also create an instruction - /// file declaring the computer name that is part taking in the indexing. This file will then be used to - /// determine the master indexer machine in a load balanced environment (if one exists). - /// - /// The friendly name of the provider. - /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. - /// - /// The name of the provider is null. - /// - /// - /// The name of the provider has a length of zero. - /// - /// - /// An attempt is made to call on a provider after the provider has already been initialized. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override void Initialize(string name, NameValueCollection config) - { - base.Initialize(name, config); - - var supportUnpublished = false; - var supportProtected = false; - - //check if there's a flag specifying to support unpublished content, - //if not, set to false; - if (config["supportUnpublished"] != null) - bool.TryParse(config["supportUnpublished"], out supportUnpublished); - - //check if there's a flag specifying to support protected content, - //if not, set to false; - if (config["supportProtected"] != null) - bool.TryParse(config["supportProtected"], out supportProtected); - - - //now we need to build up the indexer options so we can create our validator - int? parentId = null; - if (IndexSetName.IsNullOrWhiteSpace() == false) - { - var indexSet = IndexSets.Instance.Sets[IndexSetName]; - parentId = indexSet.IndexParentId; - } - - ValueSetValidator = new ContentValueSetValidator( - supportUnpublished, supportProtected, - //Using a singleton here, we can't inject this when using config based providers and we don't use this - //anywhere else in this class - Current.Services.PublicAccessService, - parentId, - ConfigIndexCriteria?.IncludeItemTypes, ConfigIndexCriteria?.ExcludeItemTypes); - - PublishedValuesOnly = supportUnpublished; - } - - #endregion - /// /// Special check for invalid paths /// @@ -166,8 +92,8 @@ namespace Umbraco.Examine //these are the invalid items so we'll delete them //since the path is not valid we need to delete this item in case it exists in the index already and has now //been moved to an invalid parent. - foreach (var i in group) - base.PerformDeleteFromIndex(i.Id, args => { /*noop*/ }); + + base.PerformDeleteFromIndex(group.Select(x => x.Id), args => { /*noop*/ }); } else { @@ -192,24 +118,28 @@ namespace Umbraco.Examine /// When a content node is deleted, we also need to delete it's children from the index so we need to perform a /// custom Lucene search to find all decendents and create Delete item queues for them too. /// - /// ID of the node to delete + /// ID of the node to delete /// - protected override void PerformDeleteFromIndex(string nodeId, Action onComplete) + protected override void PerformDeleteFromIndex(IEnumerable itemIds, Action onComplete) { - //find all descendants based on path - var descendantPath = $@"\-1\,*{nodeId}\,*"; - var rawQuery = $"{IndexPathFieldName}:{descendantPath}"; - var searcher = GetSearcher(); - var c = searcher.CreateQuery(); - var filtered = c.NativeQuery(rawQuery); - var results = filtered.Execute(); + var idsAsList = itemIds.ToList(); + foreach (var nodeId in idsAsList) + { + //find all descendants based on path + var descendantPath = $@"\-1\,*{nodeId}\,*"; + var rawQuery = $"{IndexPathFieldName}:{descendantPath}"; + var searcher = GetSearcher(); + var c = searcher.CreateQuery(); + var filtered = c.NativeQuery(rawQuery); + var results = filtered.Execute(); - ProfilingLogger.Logger.Debug(GetType(), "DeleteFromIndex with query: {Query} (found {TotalItems} results)", rawQuery, results.TotalItemCount); + ProfilingLogger.Debug(GetType(), "DeleteFromIndex with query: {Query} (found {TotalItems} results)", rawQuery, results.TotalItemCount); - //need to queue a delete item for each one found - QueueIndexOperation(results.Select(r => new IndexOperation(new ValueSet(r.Id), IndexOperationType.Delete))); + //need to queue a delete item for each one found + QueueIndexOperation(results.Select(r => new IndexOperation(new ValueSet(r.Id), IndexOperationType.Delete))); + } - base.PerformDeleteFromIndex(nodeId, onComplete); + base.PerformDeleteFromIndex(idsAsList, onComplete); } } diff --git a/src/Umbraco.Examine/UmbracoExamineIndex.cs b/src/Umbraco.Examine/UmbracoExamineIndex.cs index fc7f834a29..24952050da 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndex.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using Examine.LuceneEngine.Providers; using Lucene.Net.Analysis; @@ -9,11 +8,9 @@ using Lucene.Net.Index; using Umbraco.Core; using Examine; using Examine.LuceneEngine; -using Examine.LuceneEngine.Indexing; using Lucene.Net.Store; using Umbraco.Core.Composing; using Umbraco.Core.Logging; -using Umbraco.Examine.Config; using Directory = Lucene.Net.Store.Directory; namespace Umbraco.Examine @@ -43,18 +40,6 @@ namespace Umbraco.Examine /// public const string RawFieldPrefix = SpecialFieldPrefix + "Raw_"; - /// - /// Constructor for config provider based indexes - /// - [EditorBrowsable(EditorBrowsableState.Never)] - protected UmbracoExamineIndex() - : base() - { - ProfilingLogger = Current.ProfilingLogger; - _configBased = true; - _diagnostics = new UmbracoExamineIndexDiagnostics(this, ProfilingLogger.Logger); - } - /// /// Create a new /// @@ -67,13 +52,13 @@ namespace Umbraco.Examine /// protected UmbracoExamineIndex( string name, - FieldDefinitionCollection fieldDefinitions, Directory luceneDirectory, + FieldDefinitionCollection fieldDefinitions, Analyzer defaultAnalyzer, - ProfilingLogger profilingLogger, + IProfilingLogger profilingLogger, IValueSetValidator validator = null, IReadOnlyDictionary indexValueTypes = null) - : base(name, fieldDefinitions, luceneDirectory, defaultAnalyzer, validator, indexValueTypes) + : base(name, luceneDirectory, fieldDefinitions, defaultAnalyzer, validator, indexValueTypes) { ProfilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger)); @@ -81,14 +66,12 @@ namespace Umbraco.Examine if (luceneDirectory is FSDirectory fsDir) LuceneIndexFolder = fsDir.Directory; - _diagnostics = new UmbracoExamineIndexDiagnostics(this, ProfilingLogger.Logger); + _diagnostics = new UmbracoExamineIndexDiagnostics(this, ProfilingLogger); } private readonly bool _configBased = false; - - - protected ProfilingLogger ProfilingLogger { get; } + protected IProfilingLogger ProfilingLogger { get; } /// /// When set to true Umbraco will keep the index in sync with Umbraco data automatically @@ -105,106 +88,19 @@ namespace Umbraco.Examine return searcher.GetAllIndexedFields(); } - protected ConfigIndexCriteria ConfigIndexCriteria { get; private set; } - - /// - /// The index set name which references an Examine - /// - public string IndexSetName { get; private set; } - - #region Initialize - - - /// - /// Setup the properties for the indexer from the provider settings - /// - /// - /// - /// - /// This is ONLY used for configuration based indexes - /// - public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) - { - ProfilingLogger.Logger.Debug(GetType(), "{IndexerName} indexer initializing", name); - - if (config["enableDefaultEventHandler"] != null && bool.TryParse(config["enableDefaultEventHandler"], out var enabled)) - { - EnableDefaultEventHandler = enabled; - } - - //this is config based, so add the default definitions - foreach (var field in UmbracoFieldDefinitionCollection.UmbracoIndexFieldDefinitions) - { - FieldDefinitionCollection.TryAdd(field); - } - - //Need to check if the index set is specified... - if (config["indexSet"] == null) - { - var possibleSuffixes = new[] {"Index", "Indexer"}; - foreach (var suffix in possibleSuffixes) - { - if (!name.EndsWith(suffix)) continue; - - var setNameByConvension = name.Remove(name.LastIndexOf(suffix, StringComparison.Ordinal)) + "IndexSet"; - //check if we can assign the index set by naming convention - var set = IndexSets.Instance.Sets.Cast().SingleOrDefault(x => x.SetName == setNameByConvension); - - if (set == null) continue; - - //we've found an index set by naming conventions :) - IndexSetName = set.SetName; - - var indexSet = IndexSets.Instance.Sets[IndexSetName]; - - //get the index criteria and ensure folder - ConfigIndexCriteria = CreateFieldDefinitionsFromConfig(indexSet); - foreach (var fieldDefinition in ConfigIndexCriteria.StandardFields.Union(ConfigIndexCriteria.UserFields)) - { - //replace any existing or add - FieldDefinitionCollection.AddOrUpdate(fieldDefinition); - } - break; - } - } - else - { - //if an index set is specified, ensure it exists and initialize the indexer based on the set - - if (IndexSets.Instance.Sets[config["indexSet"]] == null) - throw new ArgumentException("The indexSet specified for the LuceneExamineIndexer provider does not exist"); - - IndexSetName = config["indexSet"]; - - var indexSet = IndexSets.Instance.Sets[IndexSetName]; - - //get the index criteria and ensure folder - ConfigIndexCriteria = CreateFieldDefinitionsFromConfig(indexSet); - foreach (var fieldDefinition in ConfigIndexCriteria.StandardFields.Union(ConfigIndexCriteria.UserFields)) - { - //replace any existing or add - FieldDefinitionCollection.AddOrUpdate(fieldDefinition); - } - } - - base.Initialize(name, config); - } - - #endregion - /// /// override to check if we can actually initialize. /// /// /// This check is required since the base examine lib will try to rebuild on startup /// - protected override void PerformDeleteFromIndex(string nodeId, Action onComplete) + protected override void PerformDeleteFromIndex(IEnumerable itemIds, Action onComplete) { if (CanInitialize()) { using (new SafeCallContext()) { - base.PerformDeleteFromIndex(nodeId, onComplete); + base.PerformDeleteFromIndex(itemIds, onComplete); } } } @@ -226,7 +122,7 @@ namespace Umbraco.Examine /// protected override void OnIndexingError(IndexingErrorEventArgs ex) { - ProfilingLogger.Logger.Error(GetType(), ex.InnerException, ex.Message); + ProfilingLogger.Error(GetType(), ex.InnerException, ex.Message); base.OnIndexingError(ex); } @@ -262,7 +158,7 @@ namespace Umbraco.Examine /// protected override void AddDocument(Document doc, ValueSet valueSet, IndexWriter writer) { - ProfilingLogger.Logger.Debug(GetType(), + ProfilingLogger.Debug(GetType(), "Write lucene doc id:{DocumentId}, category:{DocumentCategory}, type:{DocumentItemType}", valueSet.Id, valueSet.Category, @@ -288,17 +184,7 @@ namespace Umbraco.Examine e.ValueSet.Values[IconFieldName] = icon; } } - - private ConfigIndexCriteria CreateFieldDefinitionsFromConfig(IndexSet indexSet) - { - return new ConfigIndexCriteria( - indexSet.IndexAttributeFields.Cast().Select(x => new FieldDefinition(x.Name, x.Type)).ToArray(), - indexSet.IndexUserFields.Cast().Select(x => new FieldDefinition(x.Name, x.Type)).ToArray(), - indexSet.IncludeNodeTypes.ToList().Select(x => x.Name).ToArray(), - indexSet.ExcludeNodeTypes.ToList().Select(x => x.Name).ToArray(), - indexSet.IndexParentId); - } - + #region IIndexDiagnostics private readonly UmbracoExamineIndexDiagnostics _diagnostics; diff --git a/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs b/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs index 227b52e085..fed5b9bae7 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs @@ -64,7 +64,7 @@ namespace Umbraco.Examine { [nameof(UmbracoExamineIndex.CommitCount)] = _index.CommitCount, [nameof(UmbracoExamineIndex.DefaultAnalyzer)] = _index.DefaultAnalyzer.GetType().Name, - [nameof(UmbracoExamineIndex.DirectoryFactory)] = _index.DirectoryFactory, + ["LuceneDirectory"] = _index.GetLuceneDirectory().GetType().Name, [nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler, [nameof(UmbracoExamineIndex.LuceneIndexFolder)] = _index.LuceneIndexFolder == null diff --git a/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs b/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs index 97d1f68727..1e7b51aa14 100644 --- a/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs +++ b/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs @@ -1,4 +1,7 @@ -using Examine; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Examine; +using Umbraco.Core; namespace Umbraco.Examine { @@ -30,7 +33,7 @@ namespace Umbraco.Examine new FieldDefinition("createDate", FieldDefinitionTypes.DateTime), new FieldDefinition("updateDate", FieldDefinitionTypes.DateTime), - new FieldDefinition("key", FieldDefinitionTypes.InvariantCultureIgnoreCase), + new FieldDefinition(UmbracoExamineIndex.NodeKeyFieldName, FieldDefinitionTypes.InvariantCultureIgnoreCase), new FieldDefinition("version", FieldDefinitionTypes.Raw), new FieldDefinition("nodeType", FieldDefinitionTypes.InvariantCultureIgnoreCase), new FieldDefinition("template", FieldDefinitionTypes.Raw), @@ -40,36 +43,54 @@ namespace Umbraco.Examine new FieldDefinition("email", FieldDefinitionTypes.EmailAddress), new FieldDefinition(UmbracoExamineIndex.PublishedFieldName, FieldDefinitionTypes.Raw), - new FieldDefinition(UmbracoExamineIndex.NodeKeyFieldName, FieldDefinitionTypes.Raw), new FieldDefinition(UmbracoExamineIndex.IndexPathFieldName, FieldDefinitionTypes.Raw), - new FieldDefinition(UmbracoExamineIndex.IconFieldName, FieldDefinitionTypes.Raw) + new FieldDefinition(UmbracoExamineIndex.IconFieldName, FieldDefinitionTypes.Raw), + new FieldDefinition(UmbracoContentIndex.VariesByCultureFieldName, FieldDefinitionTypes.Raw), }; - ///// - ///// Overridden to dynamically add field definitions for culture variations - ///// - ///// - ///// - ///// - //public override bool TryGetValue(string fieldName, out FieldDefinition fieldDefinition) - //{ - // var result = base.TryGetValue(fieldName, out fieldDefinition); - // if (result) return true; - // //if the fieldName is not suffixed with _iso-Code - // var underscoreIndex = fieldName.LastIndexOf('_'); - // if (underscoreIndex == -1) return false; + /// + /// Overridden to dynamically add field definitions for culture variations + /// + /// + /// + /// + /// + /// We need to do this so that we don't have to maintain a huge static list of all field names and their definitions + /// otherwise we'd have to dynamically add/remove definitions anytime languages are added/removed, etc... + /// For example, we have things like `nodeName` and `__Published` which are also used for culture fields like `nodeName_en-us` + /// and we don't want to have a full static list of all of these definitions when we can just define the one definition and then + /// dynamically apply that to culture specific fields. + /// + /// There is a caveat to this however, when a field definition is found for a non-culture field we will create and store a new field + /// definition for that culture so that the next time it needs to be looked up and used we are not allocating more objects. This does mean + /// however that if a language is deleted, the field definitions for that language will still exist in memory. This isn't going to cause any + /// problems and the mem will be cleared on next site restart but it's worth pointing out. + /// + public override bool TryGetValue(string fieldName, out FieldDefinition fieldDefinition) + { + if (base.TryGetValue(fieldName, out fieldDefinition)) + return true; + //before we use regex to match do some faster simple matching since this is going to execute quite a lot + if (!fieldName.Contains("_") || !fieldName.Contains("-")) + return false; + var match = ExamineExtensions.CultureIsoCodeFieldNameMatchExpression.Match(fieldName); + if (match.Success && match.Groups.Count == 3) + { + var nonCultureFieldName = match.Groups[1].Value; + //check if there's a definition for this and if so return the field definition for the culture field based on the non-culture field + if (base.TryGetValue(nonCultureFieldName, out var existingFieldDefinition)) + { + //now add a new field def + fieldDefinition = GetOrAdd(fieldName, s => new FieldDefinition(s, existingFieldDefinition.Type)); + return true; + } + } + return false; + } - // var isoCode = fieldName.Substring(underscoreIndex); - // if (isoCode.Length < 6) return false; //invalid isoCode - - // var hyphenIndex = isoCode.IndexOf('-'); - // if (hyphenIndex != 3) return false; //invalid isoCode - - // //we'll assume this is a valid isoCode - - //} + } } diff --git a/src/Umbraco.Examine/UmbracoMemberIndex.cs b/src/Umbraco.Examine/UmbracoMemberIndex.cs index 27490e0e18..fbf8a1cc0f 100644 --- a/src/Umbraco.Examine/UmbracoMemberIndex.cs +++ b/src/Umbraco.Examine/UmbracoMemberIndex.cs @@ -1,18 +1,7 @@ -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Services; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; +using System.Collections.Generic; using Examine; using Examine.LuceneEngine; -using Examine.LuceneEngine.Indexing; -using Examine.LuceneEngine.Providers; using Lucene.Net.Analysis; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Directory = Lucene.Net.Store.Directory; @@ -24,14 +13,6 @@ namespace Umbraco.Examine /// public class UmbracoMemberIndex : UmbracoExamineIndex { - /// - /// Constructor for config/provider based indexes - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public UmbracoMemberIndex() - { - } - /// /// Constructor to allow for creating an indexer at runtime /// @@ -46,47 +27,11 @@ namespace Umbraco.Examine FieldDefinitionCollection fieldDefinitions, Directory luceneDirectory, Analyzer analyzer, - ProfilingLogger profilingLogger, + IProfilingLogger profilingLogger, IValueSetValidator validator = null) : - base(name, fieldDefinitions, luceneDirectory, analyzer, profilingLogger, validator) + base(name, luceneDirectory, fieldDefinitions, analyzer, profilingLogger, validator) { } - - public override void Initialize(string name, NameValueCollection config) - { - base.Initialize(name, config); - - ValueSetValidator = new MemberValueSetValidator(ConfigIndexCriteria.IncludeItemTypes, ConfigIndexCriteria.ExcludeItemTypes); - } - - /// - /// Overridden to ensure that the umbraco system field definitions are in place - /// - /// - /// - protected override FieldValueTypeCollection CreateFieldValueTypes(IReadOnlyDictionary indexValueTypesFactory = null) - { - var keyDef = new FieldDefinition("__key", FieldDefinitionTypes.Raw); - FieldDefinitionCollection.TryAdd(keyDef); - - return base.CreateFieldValueTypes(indexValueTypesFactory); - } - - /// - /// Ensure some custom values are added to the index - /// - /// - protected override void OnTransformingIndexValues(IndexingItemEventArgs e) - { - base.OnTransformingIndexValues(e); - - if (e.ValueSet.Values.TryGetValue("key", out var key) && e.ValueSet.Values.ContainsKey("__key") == false) - { - //double __ prefix means it will be indexed as culture invariant - e.ValueSet.Values["__key"] = key; - } - - } - + } } diff --git a/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs index 0505974304..ee2e75cfad 100644 --- a/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs @@ -4,18 +4,12 @@ using System.Data.SqlServerCe; using System.IO; using System.Linq; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Horology; -using BenchmarkDotNet.Jobs; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; using Umbraco.Tests.Benchmarks.Config; using Umbraco.Tests.TestHelpers; @@ -34,13 +28,11 @@ namespace Umbraco.Tests.Benchmarks { IScopeProvider f = null; var l = new Lazy(() => f); - var p = new SqlServerSyntaxProvider(l); var factory = new UmbracoDatabaseFactory( "server=.\\SQLExpress;database=YOURDB;user id=YOURUSER;password=YOURPASS", Constants.DatabaseProviders.SqlServer, - new [] { p }, logger, - new MapperCollection(Enumerable.Empty())); + new Lazy(() => new MapperCollection(Enumerable.Empty()))); return factory.CreateDatabase(); } @@ -49,9 +41,8 @@ namespace Umbraco.Tests.Benchmarks var f = new UmbracoDatabaseFactory( cstr, Constants.DatabaseProviders.SqlCe, - new[] { new SqlCeSyntaxProvider() }, logger, - new MapperCollection(Enumerable.Empty())); + new Lazy(() => new MapperCollection(Enumerable.Empty()))); return f.CreateDatabase(); } diff --git a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs index ccd9b01969..8d15613791 100644 --- a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs @@ -150,7 +150,7 @@ namespace Umbraco.Tests.Benchmarks // however, unfortunately, the generated "compiled to delegate" code cannot access private stuff :( - _emittedCtor = ReflectionUtilities.EmitConstuctor>(); + _emittedCtor = ReflectionUtilities.EmitConstructor>(); } public IFoo IlCtor(IFoo foo) @@ -167,7 +167,7 @@ namespace Umbraco.Tests.Benchmarks [Benchmark] public void EmitCtor() { - var ctor = ReflectionUtilities.EmitConstuctor>(); + var ctor = ReflectionUtilities.EmitConstructor>(); var foo = ctor(_foo); } diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index f76f6b73b6..73f9656f56 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -67,7 +67,7 @@ - + diff --git a/src/Umbraco.Tests/Cache/CacheProviderTests.cs b/src/Umbraco.Tests/Cache/AppCacheTests.cs similarity index 64% rename from src/Umbraco.Tests/Cache/CacheProviderTests.cs rename to src/Umbraco.Tests/Cache/AppCacheTests.cs index d060df3c56..29d61cc14a 100644 --- a/src/Umbraco.Tests/Cache/CacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/AppCacheTests.cs @@ -7,9 +7,9 @@ using umbraco; namespace Umbraco.Tests.Cache { - public abstract class CacheProviderTests + public abstract class AppCacheTests { - internal abstract ICacheProvider Provider { get; } + internal abstract IAppCache AppCache { get; } protected abstract int GetTotalItemCount { get; } [SetUp] @@ -21,22 +21,22 @@ namespace Umbraco.Tests.Cache [TearDown] public virtual void TearDown() { - Provider.ClearAllCache(); + AppCache.Clear(); } [Test] public void Throws_On_Reentry() { - // don't run for StaticCacheProvider - not making sense - if (GetType() == typeof (StaticCacheProviderTests)) - Assert.Ignore("Do not run for StaticCacheProvider."); + // don't run for DictionaryAppCache - not making sense + if (GetType() == typeof (DictionaryAppCacheTests)) + Assert.Ignore("Do not run for DictionaryAppCache."); Exception exception = null; - var result = Provider.GetCacheItem("blah", () => + var result = AppCache.Get("blah", () => { try { - var result2 = Provider.GetCacheItem("blah"); + var result2 = AppCache.Get("blah"); } catch (Exception e) { @@ -56,7 +56,7 @@ namespace Umbraco.Tests.Cache object result; try { - result = Provider.GetCacheItem("Blah", () => + result = AppCache.Get("Blah", () => { counter++; throw new Exception("Do not cache this"); @@ -66,7 +66,7 @@ namespace Umbraco.Tests.Cache try { - result = Provider.GetCacheItem("Blah", () => + result = AppCache.Get("Blah", () => { counter++; throw new Exception("Do not cache this"); @@ -85,13 +85,13 @@ namespace Umbraco.Tests.Cache object result; - result = Provider.GetCacheItem("Blah", () => + result = AppCache.Get("Blah", () => { counter++; return ""; }); - result = Provider.GetCacheItem("Blah", () => + result = AppCache.Get("Blah", () => { counter++; return ""; @@ -108,14 +108,14 @@ namespace Umbraco.Tests.Cache var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); var cacheContent4 = new LiteralControl(); - Provider.GetCacheItem("Test1", () => cacheContent1); - Provider.GetCacheItem("Tester2", () => cacheContent2); - Provider.GetCacheItem("Tes3", () => cacheContent3); - Provider.GetCacheItem("different4", () => cacheContent4); + AppCache.Get("Test1", () => cacheContent1); + AppCache.Get("Tester2", () => cacheContent2); + AppCache.Get("Tes3", () => cacheContent3); + AppCache.Get("different4", () => cacheContent4); Assert.AreEqual(4, GetTotalItemCount); - var result = Provider.GetCacheItemsByKeySearch("Tes"); + var result = AppCache.SearchByKey("Tes"); Assert.AreEqual(3, result.Count()); } @@ -127,14 +127,14 @@ namespace Umbraco.Tests.Cache var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); var cacheContent4 = new LiteralControl(); - Provider.GetCacheItem("TTes1t", () => cacheContent1); - Provider.GetCacheItem("Tester2", () => cacheContent2); - Provider.GetCacheItem("Tes3", () => cacheContent3); - Provider.GetCacheItem("different4", () => cacheContent4); + AppCache.Get("TTes1t", () => cacheContent1); + AppCache.Get("Tester2", () => cacheContent2); + AppCache.Get("Tes3", () => cacheContent3); + AppCache.Get("different4", () => cacheContent4); Assert.AreEqual(4, GetTotalItemCount); - Provider.ClearCacheByKeyExpression("^\\w+es\\d.*"); + AppCache.ClearByRegex("^\\w+es\\d.*"); Assert.AreEqual(2, GetTotalItemCount); } @@ -146,14 +146,14 @@ namespace Umbraco.Tests.Cache var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); var cacheContent4 = new LiteralControl(); - Provider.GetCacheItem("Test1", () => cacheContent1); - Provider.GetCacheItem("Tester2", () => cacheContent2); - Provider.GetCacheItem("Tes3", () => cacheContent3); - Provider.GetCacheItem("different4", () => cacheContent4); + AppCache.Get("Test1", () => cacheContent1); + AppCache.Get("Tester2", () => cacheContent2); + AppCache.Get("Tes3", () => cacheContent3); + AppCache.Get("different4", () => cacheContent4); Assert.AreEqual(4, GetTotalItemCount); - Provider.ClearCacheByKeySearch("Test"); + AppCache.ClearByKey("Test"); Assert.AreEqual(2, GetTotalItemCount); } @@ -165,15 +165,15 @@ namespace Umbraco.Tests.Cache var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); var cacheContent4 = new LiteralControl(); - Provider.GetCacheItem("Test1", () => cacheContent1); - Provider.GetCacheItem("Test2", () => cacheContent2); - Provider.GetCacheItem("Test3", () => cacheContent3); - Provider.GetCacheItem("Test4", () => cacheContent4); + AppCache.Get("Test1", () => cacheContent1); + AppCache.Get("Test2", () => cacheContent2); + AppCache.Get("Test3", () => cacheContent3); + AppCache.Get("Test4", () => cacheContent4); Assert.AreEqual(4, GetTotalItemCount); - Provider.ClearCacheItem("Test1"); - Provider.ClearCacheItem("Test2"); + AppCache.Clear("Test1"); + AppCache.Clear("Test2"); Assert.AreEqual(2, GetTotalItemCount); } @@ -185,14 +185,14 @@ namespace Umbraco.Tests.Cache var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); var cacheContent4 = new LiteralControl(); - Provider.GetCacheItem("Test1", () => cacheContent1); - Provider.GetCacheItem("Test2", () => cacheContent2); - Provider.GetCacheItem("Test3", () => cacheContent3); - Provider.GetCacheItem("Test4", () => cacheContent4); + AppCache.Get("Test1", () => cacheContent1); + AppCache.Get("Test2", () => cacheContent2); + AppCache.Get("Test3", () => cacheContent3); + AppCache.Get("Test4", () => cacheContent4); Assert.AreEqual(4, GetTotalItemCount); - Provider.ClearAllCache(); + AppCache.Clear(); Assert.AreEqual(0, GetTotalItemCount); } @@ -201,7 +201,7 @@ namespace Umbraco.Tests.Cache public void Can_Add_When_Not_Available() { var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); - Provider.GetCacheItem("Test1", () => cacheContent1); + AppCache.Get("Test1", () => cacheContent1); Assert.AreEqual(1, GetTotalItemCount); } @@ -209,8 +209,8 @@ namespace Umbraco.Tests.Cache public void Can_Get_When_Available() { var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); - var result = Provider.GetCacheItem("Test1", () => cacheContent1); - var result2 = Provider.GetCacheItem("Test1", () => cacheContent1); + var result = AppCache.Get("Test1", () => cacheContent1); + var result2 = AppCache.Get("Test1", () => cacheContent1); Assert.AreEqual(1, GetTotalItemCount); Assert.AreEqual(result, result2); } @@ -222,15 +222,15 @@ namespace Umbraco.Tests.Cache var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); var cacheContent4 = new LiteralControl(); - Provider.GetCacheItem("Test1", () => cacheContent1); - Provider.GetCacheItem("Test2", () => cacheContent2); - Provider.GetCacheItem("Test3", () => cacheContent3); - Provider.GetCacheItem("Test4", () => cacheContent4); + AppCache.Get("Test1", () => cacheContent1); + AppCache.Get("Test2", () => cacheContent2); + AppCache.Get("Test3", () => cacheContent3); + AppCache.Get("Test4", () => cacheContent4); Assert.AreEqual(4, GetTotalItemCount); //Provider.ClearCacheObjectTypes("umbraco.MacroCacheContent"); - Provider.ClearCacheObjectTypes(typeof(MacroCacheContent).ToString()); + AppCache.ClearOfType(typeof(MacroCacheContent).ToString()); Assert.AreEqual(1, GetTotalItemCount); } @@ -242,14 +242,14 @@ namespace Umbraco.Tests.Cache var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); var cacheContent4 = new LiteralControl(); - Provider.GetCacheItem("Test1", () => cacheContent1); - Provider.GetCacheItem("Test2", () => cacheContent2); - Provider.GetCacheItem("Test3", () => cacheContent3); - Provider.GetCacheItem("Test4", () => cacheContent4); + AppCache.Get("Test1", () => cacheContent1); + AppCache.Get("Test2", () => cacheContent2); + AppCache.Get("Test3", () => cacheContent3); + AppCache.Get("Test4", () => cacheContent4); Assert.AreEqual(4, GetTotalItemCount); - Provider.ClearCacheObjectTypes(); + AppCache.ClearOfType(); Assert.AreEqual(1, GetTotalItemCount); } diff --git a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs similarity index 83% rename from src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs rename to src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index 169100153e..11e2c56873 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -8,15 +8,14 @@ using Umbraco.Core.Cache; using Umbraco.Core.Collections; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Tests.Collections; namespace Umbraco.Tests.Cache { [TestFixture] - public class DeepCloneRuntimeCacheProviderTests : RuntimeCacheProviderTests + public class DeepCloneAppCacheTests : RuntimeAppCacheTests { - private DeepCloneRuntimeCacheProvider _provider; + private DeepCloneAppCache _provider; protected override int GetTotalItemCount { @@ -26,15 +25,15 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - _provider = new DeepCloneRuntimeCacheProvider(new HttpRuntimeCacheProvider(HttpRuntime.Cache)); + _provider = new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache)); } - internal override ICacheProvider Provider + internal override IAppCache AppCache { get { return _provider; } } - internal override IRuntimeCacheProvider RuntimeProvider + internal override IAppPolicyCache AppPolicyCache { get { return _provider; } } @@ -75,15 +74,15 @@ namespace Umbraco.Tests.Cache public void DoesNotCacheExceptions() { string value; - Assert.Throws(() => { value = (string)_provider.GetCacheItem("key", () => GetValue(1)); }); - Assert.Throws(() => { value = (string)_provider.GetCacheItem("key", () => GetValue(2)); }); + Assert.Throws(() => { value = (string)_provider.Get("key", () => GetValue(1)); }); + Assert.Throws(() => { value = (string)_provider.Get("key", () => GetValue(2)); }); // does not throw - value = (string)_provider.GetCacheItem("key", () => GetValue(3)); + value = (string)_provider.Get("key", () => GetValue(3)); Assert.AreEqual("succ3", value); // cache - value = (string)_provider.GetCacheItem("key", () => GetValue(4)); + value = (string)_provider.Get("key", () => GetValue(4)); Assert.AreEqual("succ3", value); } diff --git a/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs index 37488600c7..4161f576c9 100644 --- a/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs @@ -28,8 +28,8 @@ namespace Umbraco.Tests.Cache public void Caches_Single() { var isCached = false; - var cache = new Mock(); - cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + var cache = new Mock(); + cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(() => { @@ -45,8 +45,8 @@ namespace Umbraco.Tests.Cache [Test] public void Get_Single_From_Cache() { - var cache = new Mock(); - cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem(1, AuditType.Copy, 123, "test", "blah")); + var cache = new Mock(); + cache.Setup(x => x.Get(It.IsAny())).Returns(new AuditItem(1, AuditType.Copy, 123, "test", "blah")); var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); @@ -58,14 +58,14 @@ namespace Umbraco.Tests.Cache public void Caches_Per_Id_For_Get_All() { var cached = new List(); - var cache = new Mock(); - cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + var cache = new Mock(); + cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((string cacheKey, Func o, TimeSpan? t, bool b, CacheItemPriority cip, CacheItemRemovedCallback circ, string[] s) => { cached.Add(cacheKey); }); - cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new AuditItem[] {}); + cache.Setup(x => x.SearchByKey(It.IsAny())).Returns(new AuditItem[] {}); var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); @@ -81,8 +81,8 @@ namespace Umbraco.Tests.Cache [Test] public void Get_All_Without_Ids_From_Cache() { - var cache = new Mock(); - cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new[] + var cache = new Mock(); + cache.Setup(x => x.SearchByKey(It.IsAny())).Returns(new[] { new AuditItem(1, AuditType.Copy, 123, "test", "blah"), new AuditItem(2, AuditType.Copy, 123, "test", "blah2") @@ -98,8 +98,8 @@ namespace Umbraco.Tests.Cache public void If_CreateOrUpdate_Throws_Cache_Is_Removed() { var cacheCleared = false; - var cache = new Mock(); - cache.Setup(x => x.ClearCacheItem(It.IsAny())) + var cache = new Mock(); + cache.Setup(x => x.Clear(It.IsAny())) .Callback(() => { cacheCleared = true; @@ -124,8 +124,8 @@ namespace Umbraco.Tests.Cache public void If_Removes_Throws_Cache_Is_Removed() { var cacheCleared = false; - var cache = new Mock(); - cache.Setup(x => x.ClearCacheItem(It.IsAny())) + var cache = new Mock(); + cache.Setup(x => x.Clear(It.IsAny())) .Callback(() => { cacheCleared = true; diff --git a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs index dc67bb532f..68b666632c 100644 --- a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs @@ -1,11 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; +using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Sync; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Cache.DistributedCache { @@ -20,15 +24,18 @@ namespace Umbraco.Tests.Cache.DistributedCache [SetUp] public void Setup() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var register = RegisterFactory.Create(); - container.Register(_ => new TestServerRegistrar()); - container.Register(_ => new TestServerMessenger(), new PerContainerLifetime()); + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.RegisterUnique(_ => new TestServerRegistrar()); + composition.RegisterUnique(_ => new TestServerMessenger()); + + composition.WithCollectionBuilder() .Add(); + Current.Factory = composition.CreateFactory(); + _distributedCache = new Umbraco.Web.Cache.DistributedCache(); } diff --git a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs index 3cc5e061a5..3532a11c63 100644 --- a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using Moq; using NUnit.Framework; +using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Models; @@ -19,6 +20,15 @@ namespace Umbraco.Tests.Cache [UmbracoTest(WithApplication = true)] public class DistributedCacheBinderTests : UmbracoTestBase { + protected override void Compose(Composition composition) + { + base.Compose(composition); + // refreshers.HandleEvents wants a UmbracoContext + // which wants these + composition.RegisterUnique(_ => Mock.Of()); + composition.WithCollectionBuilder(); + } + [Test] public void Can_Find_All_Event_Handlers() { @@ -37,13 +47,6 @@ namespace Umbraco.Tests.Cache //Permission.Deleted += PermissionDeleted; //PermissionRepository.AssignedPermissions += CacheRefresherEventHandler_AssignedPermissions; - new EventDefinition(null, serviceContext.ApplicationTreeService, new EventArgs(), "Deleted"), - new EventDefinition(null, serviceContext.ApplicationTreeService, new EventArgs(), "Updated"), - new EventDefinition(null, serviceContext.ApplicationTreeService, new EventArgs(), "New"), - - new EventDefinition(null, serviceContext.SectionService, new EventArgs(), "Deleted"), - new EventDefinition(null, serviceContext.SectionService, new EventArgs(), "New"), - new EventDefinition>(null, serviceContext.UserService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, serviceContext.UserService, new DeleteEventArgs(Enumerable.Empty())), new EventDefinition>(null, serviceContext.UserService, new SaveEventArgs(Enumerable.Empty())), @@ -138,11 +141,6 @@ namespace Umbraco.Tests.Cache if (domain.GetData(".appVPath") == null) domain.SetData(".appVPath", ""); - // refreshers.HandleEvents wants a UmbracoContext - // which wants these - Container.RegisterSingleton(_ => Mock.Of()); - Container.RegisterCollectionBuilder(); - // create some event definitions var definitions = new IEventDefinition[] { diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs index 404587bcfa..a4fbdf2224 100644 --- a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -37,8 +37,8 @@ namespace Umbraco.Tests.Cache }; var isCached = false; - var cache = new Mock(); - cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + var cache = new Mock(); + cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(() => { @@ -60,8 +60,8 @@ namespace Umbraco.Tests.Cache new AuditItem(2, AuditType.Copy, 123, "test", "blah2") }; - var cache = new Mock(); - cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem(1, AuditType.Copy, 123, "test", "blah")); + var cache = new Mock(); + cache.Setup(x => x.Get(It.IsAny())).Returns(new AuditItem(1, AuditType.Copy, 123, "test", "blah")); var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); @@ -78,8 +78,8 @@ namespace Umbraco.Tests.Cache IList list = null; - var cache = new Mock(); - cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + var cache = new Mock(); + cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((string cacheKey, Func o, TimeSpan? t, bool b, CacheItemPriority cip, CacheItemRemovedCallback circ, string[] s) => { @@ -87,7 +87,7 @@ namespace Umbraco.Tests.Cache list = o() as IList; }); - cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => + cache.Setup(x => x.Get(It.IsAny())).Returns(() => { //return null if this is the first pass return cached.Any() ? new DeepCloneableList(ListCloneBehavior.CloneOnce) : null; @@ -121,8 +121,8 @@ namespace Umbraco.Tests.Cache var cached = new List(); IList list = null; - var cache = new Mock(); - cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + var cache = new Mock(); + cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((string cacheKey, Func o, TimeSpan? t, bool b, CacheItemPriority cip, CacheItemRemovedCallback circ, string[] s) => { @@ -130,7 +130,7 @@ namespace Umbraco.Tests.Cache list = o() as IList; }); - cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem[] { }); + cache.Setup(x => x.Get(It.IsAny())).Returns(new AuditItem[] { }); var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); @@ -145,9 +145,9 @@ namespace Umbraco.Tests.Cache { var getAll = new[] { (AuditItem)null }; - var cache = new Mock(); + var cache = new Mock(); - cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => new DeepCloneableList(ListCloneBehavior.CloneOnce) + cache.Setup(x => x.Get(It.IsAny())).Returns(() => new DeepCloneableList(ListCloneBehavior.CloneOnce) { new AuditItem(1, AuditType.Copy, 123, "test", "blah"), new AuditItem(2, AuditType.Copy, 123, "test", "blah2") @@ -169,8 +169,8 @@ namespace Umbraco.Tests.Cache }; var cacheCleared = false; - var cache = new Mock(); - cache.Setup(x => x.ClearCacheItem(It.IsAny())) + var cache = new Mock(); + cache.Setup(x => x.Clear(It.IsAny())) .Callback(() => { cacheCleared = true; @@ -201,8 +201,8 @@ namespace Umbraco.Tests.Cache }; var cacheCleared = false; - var cache = new Mock(); - cache.Setup(x => x.ClearCacheItem(It.IsAny())) + var cache = new Mock(); + cache.Setup(x => x.Clear(It.IsAny())) .Callback(() => { cacheCleared = true; diff --git a/src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs similarity index 54% rename from src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs rename to src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs index cbb8d4e49d..0be38d2c55 100644 --- a/src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs @@ -5,21 +5,21 @@ using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Cache { [TestFixture] - public class HttpRequestCacheProviderTests : CacheProviderTests + public class HttpRequestAppCacheTests : AppCacheTests { - private HttpRequestCacheProvider _provider; + private HttpRequestAppCache _appCache; private FakeHttpContextFactory _ctx; public override void Setup() { base.Setup(); _ctx = new FakeHttpContextFactory("http://localhost/test"); - _provider = new HttpRequestCacheProvider(_ctx.HttpContext); + _appCache = new HttpRequestAppCache(_ctx.HttpContext); } - internal override ICacheProvider Provider + internal override IAppCache AppCache { - get { return _provider; } + get { return _appCache; } } protected override int GetTotalItemCount @@ -29,24 +29,24 @@ namespace Umbraco.Tests.Cache } [TestFixture] - public class StaticCacheProviderTests : CacheProviderTests + public class DictionaryAppCacheTests : AppCacheTests { - private StaticCacheProvider _provider; + private DictionaryAppCache _appCache; public override void Setup() { base.Setup(); - _provider = new StaticCacheProvider(); + _appCache = new DictionaryAppCache(); } - internal override ICacheProvider Provider + internal override IAppCache AppCache { - get { return _provider; } + get { return _appCache; } } protected override int GetTotalItemCount { - get { return _provider.StaticCache.Count; } + get { return _appCache.Items.Count; } } } } diff --git a/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs deleted file mode 100644 index 679b8c5125..0000000000 --- a/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Diagnostics; -using System.Web; -using NUnit.Framework; -using Umbraco.Core.Cache; - -namespace Umbraco.Tests.Cache -{ - [TestFixture] - public class HttpRuntimeCacheProviderTests : RuntimeCacheProviderTests - { - private HttpRuntimeCacheProvider _provider; - - protected override int GetTotalItemCount - { - get { return HttpRuntime.Cache.Count; } - } - - public override void Setup() - { - base.Setup(); - _provider = new HttpRuntimeCacheProvider(HttpRuntime.Cache); - } - - internal override ICacheProvider Provider - { - get { return _provider; } - } - - internal override IRuntimeCacheProvider RuntimeProvider - { - get { return _provider; } - } - - [Test] - public void DoesNotCacheExceptions() - { - string value; - Assert.Throws(() => { value = (string)_provider.GetCacheItem("key", () => GetValue(1)); }); - Assert.Throws(() => { value = (string)_provider.GetCacheItem("key", () => GetValue(2)); }); - - // does not throw - value = (string)_provider.GetCacheItem("key", () => GetValue(3)); - Assert.AreEqual("succ3", value); - - // cache - value = (string)_provider.GetCacheItem("key", () => GetValue(4)); - Assert.AreEqual("succ3", value); - } - - private static string GetValue(int i) - { - Debug.Print("get" + i); - if (i < 3) - throw new Exception("fail"); - return "succ" + i; - } - } -} diff --git a/src/Umbraco.Tests/Cache/ObjectCacheProviderTests.cs b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs similarity index 63% rename from src/Umbraco.Tests/Cache/ObjectCacheProviderTests.cs rename to src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs index e373fdda4d..b9c729f891 100644 --- a/src/Umbraco.Tests/Cache/ObjectCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs @@ -8,9 +8,9 @@ using Umbraco.Core.Cache; namespace Umbraco.Tests.Cache { [TestFixture] - public class ObjectCacheProviderTests : RuntimeCacheProviderTests + public class ObjectAppCacheTests : RuntimeAppCacheTests { - private ObjectCacheRuntimeCacheProvider _provider; + private ObjectCacheAppCache _provider; protected override int GetTotalItemCount { @@ -20,15 +20,15 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - _provider = new ObjectCacheRuntimeCacheProvider(); + _provider = new ObjectCacheAppCache(); } - internal override ICacheProvider Provider + internal override IAppCache AppCache { get { return _provider; } } - internal override IRuntimeCacheProvider RuntimeProvider + internal override IAppPolicyCache AppPolicyCache { get { return _provider; } } diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs index 7f2edac876..147a159d5f 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs @@ -4,6 +4,9 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using Umbraco.Tests.Testing.Objects.Accessors; @@ -48,26 +51,24 @@ namespace Umbraco.Tests.Cache.PublishedCache "; } - public override void SetUp() + protected override void Initialize() { - base.SetUp(); + base.Initialize(); _httpContextFactory = new FakeHttpContextFactory("~/Home"); - var umbracoSettings = SettingsForTests.GenerateMockUmbracoSettings(); - var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - SettingsForTests.ConfigureSettings(umbracoSettings); - SettingsForTests.ConfigureSettings(globalSettings); + var umbracoSettings = Factory.GetInstance(); + var globalSettings = Factory.GetInstance(); _xml = new XmlDocument(); _xml.LoadXml(GetXml()); var xmlStore = new XmlStore(() => _xml, null, null, null); - var cacheProvider = new StaticCacheProvider(); + var appCache = new DictionaryAppCache(); var domainCache = new DomainCache(ServiceContext.DomainService, DefaultCultureAccessor); var publishedShapshot = new Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedSnapshot( - new PublishedContentCache(xmlStore, domainCache, cacheProvider, globalSettings, new SiteDomainHelper(), ContentTypesCache, null, null), - new PublishedMediaCache(xmlStore, ServiceContext.MediaService, ServiceContext.UserService, cacheProvider, ContentTypesCache), - new PublishedMemberCache(null, cacheProvider, Current.Services.MemberService, ContentTypesCache), + new PublishedContentCache(xmlStore, domainCache, appCache, globalSettings, new SiteDomainHelper(), ContentTypesCache, null, null), + new PublishedMediaCache(xmlStore, ServiceContext.MediaService, ServiceContext.UserService, appCache, ContentTypesCache, Factory.GetInstance()), + new PublishedMemberCache(null, appCache, Current.Services.MemberService, ContentTypesCache), domainCache); var publishedSnapshotService = new Mock(); publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedShapshot); diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index aeda2eaca2..cfc45b8f53 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Xml; @@ -7,15 +6,16 @@ using Examine; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Tests.Testing; using Current = Umbraco.Web.Composing.Current; -using LightInject; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; using Umbraco.Tests.PublishedContent; namespace Umbraco.Tests.Cache.PublishedCache @@ -30,7 +30,7 @@ namespace Umbraco.Tests.Cache.PublishedCache { base.Compose(); - Container.GetInstance() + Composition.WithCollectionBuilder() .Clear() .Append(); } @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var mChild2 = MakeNewMedia("Child2", mType, user, mRoot2.Id); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(new XmlStore((XmlDocument) null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(new XmlStore((XmlDocument) null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); var roots = cache.GetAtRoot(); Assert.AreEqual(2, roots.Count()); Assert.IsTrue(roots.Select(x => x.Id).ContainsAll(new[] {mRoot1.Id, mRoot2.Id})); @@ -93,7 +93,7 @@ namespace Umbraco.Tests.Cache.PublishedCache //var publishedMedia = PublishedMediaTests.GetNode(mRoot.Id, GetUmbracoContext("/test", 1234)); var umbracoContext = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), Current.Services.MediaService, Current.Services.UserService, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), Current.Services.MediaService, Current.Services.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); var publishedMedia = cache.GetById(mRoot.Id); Assert.IsNotNull(publishedMedia); @@ -202,12 +202,12 @@ namespace Umbraco.Tests.Cache.PublishedCache {"creatorName", "Shannon"} }; - var result = new SearchResult("1234", 1, 1, () => fields.ToDictionary(x => x.Key, x => new List { x.Value })); + var result = new SearchResult("1234", 1, () => fields.ToDictionary(x => x.Key, x => new List { x.Value })); - var store = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new StaticCacheProvider(), ContentTypesCache); + var store = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result)); - DoAssert(doc, 1234, key, 0, 0, "/media/test.jpg", "Image", 23, "Shannon", "Shannon", 0, 0, "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2); + DoAssert(doc, 1234, key, templateIdVal: null, 0, "/media/test.jpg", "Image", 23, "Shannon", "Shannon", 0, 0, "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2); Assert.AreEqual(null, doc.Parent); } @@ -220,10 +220,10 @@ namespace Umbraco.Tests.Cache.PublishedCache var xmlDoc = GetMediaXml(); ((XmlElement)xmlDoc.DocumentElement.FirstChild).SetAttribute("key", key.ToString()); var navigator = xmlDoc.SelectSingleNode("/root/Image").CreateNavigator(); - var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); var doc = cache.CreateFromCacheValues(cache.ConvertFromXPathNavigator(navigator, true)); - DoAssert(doc, 2000, key, 0, 2, "image1", "Image", 23, "Shannon", "Shannon", 33, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); + DoAssert(doc, 2000, key, templateIdVal: null, 2, "image1", "Image", 23, "Shannon", "Shannon", 33, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); Assert.AreEqual(null, doc.Parent); Assert.AreEqual(2, doc.Children.Count()); Assert.AreEqual(2001, doc.Children.ElementAt(0).Id); @@ -337,7 +337,7 @@ namespace Umbraco.Tests.Cache.PublishedCache DictionaryPublishedContent dicDoc, int idVal = 1234, Guid keyVal = default(Guid), - int templateIdVal = 0, + int? templateIdVal = null, int sortOrderVal = 44, string urlNameVal = "testing", string nodeTypeAliasVal = "myType", @@ -368,7 +368,7 @@ namespace Umbraco.Tests.Cache.PublishedCache IPublishedContent doc, int idVal = 1234, Guid keyVal = default(Guid), - int templateIdVal = 0, + int? templateIdVal = null, int sortOrderVal = 44, string urlNameVal = "testing", string nodeTypeAliasVal = "myType", @@ -402,9 +402,6 @@ namespace Umbraco.Tests.Cache.PublishedCache Assert.AreEqual(createDateVal.Value, doc.CreateDate); Assert.AreEqual(updateDateVal.Value, doc.UpdateDate); Assert.AreEqual(levelVal, doc.Level); - } - - } } diff --git a/src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/RuntimeAppCacheTests.cs similarity index 50% rename from src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs rename to src/Umbraco.Tests/Cache/RuntimeAppCacheTests.cs index e45dfd4250..1beeae74db 100644 --- a/src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/RuntimeAppCacheTests.cs @@ -5,25 +5,23 @@ using Umbraco.Core.Cache; namespace Umbraco.Tests.Cache { - public abstract class RuntimeCacheProviderTests : CacheProviderTests + public abstract class RuntimeAppCacheTests : AppCacheTests { - - internal abstract IRuntimeCacheProvider RuntimeProvider { get; } - + internal abstract IAppPolicyCache AppPolicyCache { get; } [Test] [Explicit("Testing for timeouts cannot work on VSTS.")] public void Can_Add_And_Expire_Struct_Strongly_Typed_With_Null() { var now = DateTime.Now; - RuntimeProvider.InsertCacheItem("DateTimeTest", () => now, new TimeSpan(0, 0, 0, 0, 200)); - Assert.AreEqual(now, Provider.GetCacheItem("DateTimeTest")); - Assert.AreEqual(now, Provider.GetCacheItem("DateTimeTest")); + AppPolicyCache.Insert("DateTimeTest", () => now, new TimeSpan(0, 0, 0, 0, 200)); + Assert.AreEqual(now, AppCache.GetCacheItem("DateTimeTest")); + Assert.AreEqual(now, AppCache.GetCacheItem("DateTimeTest")); Thread.Sleep(300); //sleep longer than the cache expiration - Assert.AreEqual(default(DateTime), Provider.GetCacheItem("DateTimeTest")); - Assert.AreEqual(null, Provider.GetCacheItem("DateTimeTest")); + Assert.AreEqual(default(DateTime), AppCache.GetCacheItem("DateTimeTest")); + Assert.AreEqual(null, AppCache.GetCacheItem("DateTimeTest")); } } } diff --git a/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs b/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs index 1c2227f79b..2525eab45b 100644 --- a/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs @@ -28,14 +28,14 @@ namespace Umbraco.Tests.Cache public void Get_All_Doesnt_Cache() { var cached = new List(); - var cache = new Mock(); - cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + var cache = new Mock(); + cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((string cacheKey, Func o, TimeSpan? t, bool b, CacheItemPriority cip, CacheItemRemovedCallback circ, string[] s) => { cached.Add(cacheKey); }); - cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new AuditItem[] { }); + cache.Setup(x => x.SearchByKey(It.IsAny())).Returns(new AuditItem[] { }); var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); @@ -52,8 +52,8 @@ namespace Umbraco.Tests.Cache public void Caches_Single() { var isCached = false; - var cache = new Mock(); - cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + var cache = new Mock(); + cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(() => { diff --git a/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs b/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs new file mode 100644 index 0000000000..e732ae5766 --- /dev/null +++ b/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs @@ -0,0 +1,50 @@ +using System; +using System.Diagnostics; +using System.Web; +using NUnit.Framework; +using Umbraco.Core.Cache; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class WebCachingAppCacheTests : RuntimeAppCacheTests + { + private WebCachingAppCache _appCache; + + protected override int GetTotalItemCount => HttpRuntime.Cache.Count; + + public override void Setup() + { + base.Setup(); + _appCache = new WebCachingAppCache(HttpRuntime.Cache); + } + + internal override IAppCache AppCache => _appCache; + + internal override IAppPolicyCache AppPolicyCache => _appCache; + + [Test] + public void DoesNotCacheExceptions() + { + string value; + Assert.Throws(() => { value = (string)_appCache.Get("key", () => GetValue(1)); }); + Assert.Throws(() => { value = (string)_appCache.Get("key", () => GetValue(2)); }); + + // does not throw + value = (string)_appCache.Get("key", () => GetValue(3)); + Assert.AreEqual("succ3", value); + + // cache + value = (string)_appCache.Get("key", () => GetValue(4)); + Assert.AreEqual("succ3", value); + } + + private static string GetValue(int i) + { + Debug.Print("get" + i); + if (i < 3) + throw new Exception("fail"); + return "succ" + i; + } + } +} diff --git a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs index 46dae8bcfd..f40ca3f500 100644 --- a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs +++ b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using NUnit.Framework; using Umbraco.Core; @@ -13,16 +14,16 @@ namespace Umbraco.Tests.Clr [Test] public void EmitCtorEmits() { - var ctor1 = ReflectionUtilities.EmitConstuctor>(); + var ctor1 = ReflectionUtilities.EmitConstructor>(); Assert.IsInstanceOf(ctor1()); - var ctor2 = ReflectionUtilities.EmitConstuctor>(declaring: typeof(Class1)); + var ctor2 = ReflectionUtilities.EmitConstructor>(declaring: typeof(Class1)); Assert.IsInstanceOf(ctor2()); - var ctor3 = ReflectionUtilities.EmitConstuctor>(); + var ctor3 = ReflectionUtilities.EmitConstructor>(); Assert.IsInstanceOf(ctor3(42)); - var ctor4 = ReflectionUtilities.EmitConstuctor>(declaring: typeof(Class3)); + var ctor4 = ReflectionUtilities.EmitConstructor>(declaring: typeof(Class3)); Assert.IsInstanceOf(ctor4(42)); } @@ -43,14 +44,14 @@ namespace Umbraco.Tests.Clr [Test] public void EmitCtorEmitsPrivateCtor() { - var ctor = ReflectionUtilities.EmitConstuctor>(); + var ctor = ReflectionUtilities.EmitConstructor>(); Assert.IsInstanceOf(ctor("foo")); } [Test] public void EmitCtorThrowsIfNotFound() { - Assert.Throws(() => ReflectionUtilities.EmitConstuctor>()); + Assert.Throws(() => ReflectionUtilities.EmitConstructor>()); } [Test] @@ -63,7 +64,7 @@ namespace Umbraco.Tests.Clr [Test] public void EmitCtorReturnsNull() { - Assert.IsNull(ReflectionUtilities.EmitConstuctor>(false)); + Assert.IsNull(ReflectionUtilities.EmitConstructor>(false)); } [Test] @@ -553,6 +554,22 @@ namespace Umbraco.Tests.Clr // fixme - missing tests specifying 'returned' on method, property + [Test] + public void DeconstructAnonymousType() + { + var o = new { a = 1, b = "hello" }; + + var getters = new Dictionary>(); + foreach (var prop in o.GetType().GetProperties()) + getters[prop.Name] = ReflectionUtilities.EmitMethodUnsafe>(prop.GetMethod); + + Assert.AreEqual(2, getters.Count); + Assert.IsTrue(getters.ContainsKey("a")); + Assert.IsTrue(getters.ContainsKey("b")); + Assert.AreEqual(1, getters["a"](o)); + Assert.AreEqual("hello", getters["b"](o)); + } + #region IL Code // these functions can be examined in eg DotPeek to understand IL works diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 995350f80e..a04636f919 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -1,17 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Components; +using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Scoping; -using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Components { @@ -19,309 +18,426 @@ namespace Umbraco.Tests.Components public class ComponentTests { private static readonly List Composed = new List(); - private static readonly List Initialized = new List(); + private static readonly List Initialized = new List(); + private static readonly List Terminated = new List(); - private static IServiceContainer MockContainer(Action> setup = null) + private static IFactory MockFactory(Action> setup = null) { // fixme use IUmbracoDatabaseFactory vs UmbracoDatabaseFactory, clean it all up! - var testObjects = new TestObjects(null); + var mock = new Mock(); + var logger = Mock.Of(); - var s = testObjects.GetDefaultSqlSyntaxProviders(logger); - var f = new UmbracoDatabaseFactory(s, logger, new MapperCollection(Enumerable.Empty())); - var fs = new FileSystems(logger); + var f = new UmbracoDatabaseFactory(logger, new Lazy(() => new MapperCollection(Enumerable.Empty()))); + var fs = new FileSystems(mock.Object, logger); var p = new ScopeProvider(f, fs, logger); - var mock = new Mock(); mock.Setup(x => x.GetInstance(typeof (ILogger))).Returns(logger); - mock.Setup(x => x.GetInstance(typeof (ProfilingLogger))).Returns(new ProfilingLogger(Mock.Of(), Mock.Of())); + mock.Setup(x => x.GetInstance(typeof (IProfilingLogger))).Returns(new ProfilingLogger(Mock.Of(), Mock.Of())); mock.Setup(x => x.GetInstance(typeof (IUmbracoDatabaseFactory))).Returns(f); mock.Setup(x => x.GetInstance(typeof (IScopeProvider))).Returns(p); + setup?.Invoke(mock); return mock.Object; } + private static IRegister MockRegister() + { + return Mock.Of(); + } + + private static TypeLoader MockTypeLoader() + { + return new TypeLoader(); + } + + public static IRuntimeState MockRuntimeState(RuntimeLevel level) + { + var runtimeState = Mock.Of(); + Mock.Get(runtimeState).Setup(x => x.Level).Returns(level); + return runtimeState; + } + [Test] public void Boot1A() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var loader = new BootLoader(container); + var types = TypeArray(); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - goes away with RuntimeLevel.Unknown // => reorder components accordingly - loader.Boot(TypeArray(), RuntimeLevel.Unknown); - AssertTypeArray(TypeArray(), Composed); + composers.Compose(); + AssertTypeArray(TypeArray(), Composed); + + var factory = MockFactory(m => + { + m.Setup(x => x.TryGetInstance(It.Is(t => t == typeof(ISomeResource)))).Returns(() => new SomeResource()); + m.Setup(x => x.GetInstance(It.IsAny())).Returns((type) => + { + if (type == typeof(Composer1)) return new Composer1(); + if (type == typeof(Composer5)) return new Composer5(); + if (type == typeof(Component5)) return new Component5(new SomeResource()); + if (type == typeof(IProfilingLogger)) return new ProfilingLogger(Mock.Of(), Mock.Of()); + throw new NotSupportedException(type.FullName); + }); + }); + + var builder = composition.WithCollectionBuilder(); + builder.RegisterWith(register); + var components = builder.CreateCollection(factory); + + Assert.IsEmpty(components); + components.Initialize(); + Assert.IsEmpty(Initialized); + components.Terminate(); + Assert.IsEmpty(Terminated); } [Test] public void Boot1B() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); - var loader = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - stays with RuntimeLevel.Run // => reorder components accordingly - loader.Boot(TypeArray(), RuntimeLevel.Run); - AssertTypeArray(TypeArray(), Composed); + components.Compose(); + AssertTypeArray(TypeArray(), Composed); } [Test] public void Boot2() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var loader = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); // 21 is required by 20 // => reorder components accordingly - loader.Boot(TypeArray(), RuntimeLevel.Unknown); - AssertTypeArray(TypeArray(), Composed); + components.Compose(); + AssertTypeArray(TypeArray(), Composed); } [Test] public void Boot3() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var loader = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); // i23 requires 22 // 24, 25 implement i23 // 25 required by i23 // => reorder components accordingly - loader.Boot(TypeArray(), RuntimeLevel.Unknown); - AssertTypeArray(TypeArray(), Composed); + components.Compose(); + AssertTypeArray(TypeArray(), Composed); } [Test] public void BrokenRequire() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); try { // 2 is Core and requires 4 // 4 is missing // => throw - thing.Boot(TypeArray < Component1, Component2, Component3>(), RuntimeLevel.Unknown); + components.Compose(); Assert.Fail("Expected exception."); } catch (Exception e) { - Assert.AreEqual("Broken component dependency: Umbraco.Tests.Components.ComponentTests+Component2 -> Umbraco.Tests.Components.ComponentTests+Component4.", e.Message); + Assert.AreEqual("Broken composer dependency: Umbraco.Tests.Components.ComponentTests+Composer2 -> Umbraco.Tests.Components.ComponentTests+Composer4.", e.Message); } } [Test] public void BrokenRequired() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 13 is required by 1 // 1 is missing // => reorder components accordingly - thing.Boot(TypeArray(), RuntimeLevel.Unknown); - AssertTypeArray(TypeArray(), Composed); + components.Compose(); + AssertTypeArray(TypeArray(), Composed); } [Test] public void Initialize() { - var container = MockContainer(m => + Composed.Clear(); + Initialized.Clear(); + Terminated.Clear(); + + var register = MockRegister(); + var factory = MockFactory(m => { m.Setup(x => x.TryGetInstance(It.Is(t => t == typeof (ISomeResource)))).Returns(() => new SomeResource()); + m.Setup(x => x.GetInstance(It.IsAny())).Returns((type) => + { + if (type == typeof(Composer1)) return new Composer1(); + if (type == typeof(Composer5)) return new Composer5(); + if (type == typeof(Component5)) return new Component5(new SomeResource()); + if (type == typeof(IProfilingLogger)) return new ProfilingLogger(Mock.Of(), Mock.Of()); + throw new NotSupportedException(type.FullName); + }); }); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); - Composed.Clear(); - thing.Boot(new[] { typeof(Component1), typeof(Component5) }, RuntimeLevel.Unknown); - Assert.AreEqual(2, Composed.Count); - Assert.AreEqual(typeof(Component1), Composed[0]); - Assert.AreEqual(typeof(Component5), Composed[1]); - Assert.AreEqual(1, Initialized.Count); - Assert.AreEqual("Umbraco.Tests.Components.ComponentTests+SomeResource", Initialized[0]); + var types = new[] { typeof(Composer1), typeof(Composer5) }; + var composers = new Composers(composition, types, Mock.Of()); + + Assert.IsEmpty(Composed); + composers.Compose(); + AssertTypeArray(TypeArray(), Composed); + + var builder = composition.WithCollectionBuilder(); + builder.RegisterWith(register); + var components = builder.CreateCollection(factory); + + Assert.IsEmpty(Initialized); + components.Initialize(); + AssertTypeArray(TypeArray(), Initialized); + + Assert.IsEmpty(Terminated); + components.Terminate(); + AssertTypeArray(TypeArray(), Terminated); } [Test] public void Requires1() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer6), typeof(Composer7), typeof(Composer8) }; + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component6), typeof(Component7), typeof(Component8) }, RuntimeLevel.Unknown); + components.Compose(); Assert.AreEqual(2, Composed.Count); - Assert.AreEqual(typeof(Component6), Composed[0]); - Assert.AreEqual(typeof(Component8), Composed[1]); + Assert.AreEqual(typeof(Composer6), Composed[0]); + Assert.AreEqual(typeof(Composer8), Composed[1]); } [Test] public void Requires2A() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component9), typeof(Component2), typeof(Component4) }, RuntimeLevel.Unknown); + components.Compose(); Assert.AreEqual(2, Composed.Count); - Assert.AreEqual(typeof(Component4), Composed[0]); - Assert.AreEqual(typeof(Component2), Composed[1]); + Assert.AreEqual(typeof(Composer4), Composed[0]); + Assert.AreEqual(typeof(Composer2), Composed[1]); //Assert.AreEqual(typeof(Component9), Composed[2]); -- goes away with RuntimeLevel.Unknown } [Test] public void Requires2B() { - var container = MockContainer(); + var register = MockRegister(); + var factory = MockFactory(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component9), typeof(Component2), typeof(Component4) }, RuntimeLevel.Run); + composers.Compose(); + var builder = composition.WithCollectionBuilder(); + builder.RegisterWith(register); + var components = builder.CreateCollection(factory); Assert.AreEqual(3, Composed.Count); - Assert.AreEqual(typeof(Component4), Composed[0]); - Assert.AreEqual(typeof(Component2), Composed[1]); - Assert.AreEqual(typeof(Component9), Composed[2]); + Assert.AreEqual(typeof(Composer4), Composed[0]); + Assert.AreEqual(typeof(Composer2), Composed[1]); + Assert.AreEqual(typeof(Composer9), Composed[2]); } [Test] public void WeakDependencies() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer10) }; + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component10) }, RuntimeLevel.Unknown); + components.Compose(); Assert.AreEqual(1, Composed.Count); - Assert.AreEqual(typeof(Component10), Composed[0]); + Assert.AreEqual(typeof(Composer10), Composed[0]); - thing = new BootLoader(container); + types = new[] { typeof(Composer11) }; + components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - Assert.Throws(() => thing.Boot(new[] { typeof(Component11) }, RuntimeLevel.Unknown)); + Assert.Throws(() => components.Compose()); - thing = new BootLoader(container); + types = new[] { typeof(Composer2) }; + components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - Assert.Throws(() => thing.Boot(new[] { typeof(Component2) }, RuntimeLevel.Unknown)); + Assert.Throws(() => components.Compose()); - thing = new BootLoader(container); + types = new[] { typeof(Composer12) }; + components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component12) }, RuntimeLevel.Unknown); + components.Compose(); Assert.AreEqual(1, Composed.Count); - Assert.AreEqual(typeof(Component12), Composed[0]); + Assert.AreEqual(typeof(Composer12), Composed[0]); } [Test] public void DisableMissing() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer6), typeof(Composer8) }; // 8 disables 7 which is not in the list + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component6), typeof(Component8) }, RuntimeLevel.Unknown); // 8 disables 7 which is not in the list + components.Compose(); Assert.AreEqual(2, Composed.Count); - Assert.AreEqual(typeof(Component6), Composed[0]); - Assert.AreEqual(typeof(Component8), Composed[1]); + Assert.AreEqual(typeof(Composer6), Composed[0]); + Assert.AreEqual(typeof(Composer8), Composed[1]); } #region Components - public class TestComponentBase : UmbracoComponentBase + public class TestComposerBase : IComposer { - public override void Compose(Composition composition) + public virtual void Compose(Composition composition) { - base.Compose(composition); Composed.Add(GetType()); } } - public class Component1 : TestComponentBase + public class Composer1 : TestComposerBase { } - [RequireComponent(typeof(Component4))] - public class Component2 : TestComponentBase, IUmbracoCoreComponent + [ComposeAfter(typeof(Composer4))] + public class Composer2 : TestComposerBase, ICoreComposer { } - public class Component3 : TestComponentBase, IUmbracoUserComponent + public class Composer3 : TestComposerBase, IUserComposer { } - public class Component4 : TestComponentBase + public class Composer4 : TestComposerBase { } - public class Component5 : TestComponentBase + public class Composer5 : TestComposerBase { - public void Initialize(ISomeResource resource) + public override void Compose(Composition composition) { - Initialized.Add(resource.GetType().FullName); + base.Compose(composition); + composition.Components().Append(); } } - [DisableComponent] - public class Component6 : TestComponentBase + public class TestComponentBase : IComponent + { + public virtual void Initialize() + { + Initialized.Add(GetType()); + } + + public virtual void Terminate() + { + Terminated.Add(GetType()); + } + } + + public class Component5 : TestComponentBase + { + private readonly ISomeResource _resource; + + public Component5(ISomeResource resource) + { + _resource = resource; + } + } + + [Disable] + public class Composer6 : TestComposerBase { } - public class Component7 : TestComponentBase + public class Composer7 : TestComposerBase { } - [DisableComponent(typeof(Component7))] - [EnableComponent(typeof(Component6))] - public class Component8 : TestComponentBase + [Disable(typeof(Composer7))] + [Enable(typeof(Composer6))] + public class Composer8 : TestComposerBase { } - public interface ITestComponent : IUmbracoUserComponent + public interface ITestComposer : IUserComposer { } - public class Component9 : TestComponentBase, ITestComponent + public class Composer9 : TestComposerBase, ITestComposer { } - [RequireComponent(typeof(ITestComponent))] - public class Component10 : TestComponentBase + [ComposeAfter(typeof(ITestComposer))] + public class Composer10 : TestComposerBase { } - [RequireComponent(typeof(ITestComponent), false)] - public class Component11 : TestComponentBase + [ComposeAfter(typeof(ITestComposer), false)] + public class Composer11 : TestComposerBase { } - [RequireComponent(typeof(Component4), true)] - public class Component12 : TestComponentBase, IUmbracoCoreComponent + [ComposeAfter(typeof(Composer4), true)] + public class Composer12 : TestComposerBase, ICoreComposer { } - [RequiredComponent(typeof(Component1))] - public class Component13 : TestComponentBase + [ComposeBefore(typeof(Composer1))] + public class Composer13 : TestComposerBase { } public interface ISomeResource { } public class SomeResource : ISomeResource { } - public class Component20 : TestComponentBase + public class Composer20 : TestComposerBase { } - [RequiredComponent(typeof(Component20))] - public class Component21 : TestComponentBase + [ComposeBefore(typeof(Composer20))] + public class Composer21 : TestComposerBase { } - public class Component22 : TestComponentBase + public class Composer22 : TestComposerBase { } - [RequireComponent(typeof(Component22))] - public interface IComponent23 : IUmbracoComponent + [ComposeAfter(typeof(Composer22))] + public interface IComposer23 : IComposer { } - public class Component24 : TestComponentBase, IComponent23 + public class Composer24 : TestComposerBase, IComposer23 { } // should insert itself between 22 and anything i23 - [RequiredComponent(typeof(IComponent23))] + [ComposeBefore(typeof(IComposer23))] //[RequireComponent(typeof(Component22))] - not needed, implement i23 - public class Component25 : TestComponentBase, IComponent23 + public class Composer25 : TestComposerBase, IComposer23 { } #endregion diff --git a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs index 87b0cd5173..4c262fbf82 100644 --- a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs @@ -1,39 +1,40 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; +using Moq; using NUnit.Framework; using Umbraco.Core.Composing; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Logging; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Composing { [TestFixture] public class CollectionBuildersTests { - private ServiceContainer _container; + private Composition _composition; [SetUp] public void Setup() { Current.Reset(); - _container = new ServiceContainer(); - _container.ConfigureUmbracoCore(); + var register = RegisterFactory.Create(); + _composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); } [TearDown] public void TearDown() { Current.Reset(); - - _container.Dispose(); - _container = null; } [Test] public void ContainsTypes() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); @@ -42,14 +43,15 @@ namespace Umbraco.Tests.Composing Assert.IsFalse(builder.Has()); //Assert.IsFalse(col.ContainsType()); // does not compile - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CanClearBuilderBeforeCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); @@ -57,18 +59,20 @@ namespace Umbraco.Tests.Composing Assert.IsFalse(builder.Has()); Assert.IsFalse(builder.Has()); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col); } [Test] public void CannotClearBuilderOnceCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); Assert.Throws(() => builder.Clear()); } @@ -76,7 +80,7 @@ namespace Umbraco.Tests.Composing [Test] public void CanAppendToBuilder() { - var builder = _container.RegisterCollectionBuilder(); + var builder = _composition.WithCollectionBuilder(); builder.Append(); builder.Append(); @@ -84,16 +88,18 @@ namespace Umbraco.Tests.Composing Assert.IsTrue(builder.Has()); Assert.IsFalse(builder.Has()); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotAppendToBuilderOnceCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder(); + var builder = _composition.WithCollectionBuilder(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); Assert.Throws(() => builder.Append() @@ -103,18 +109,21 @@ namespace Umbraco.Tests.Composing [Test] public void CanAppendDuplicateToBuilderAndDeDuplicate() { - var builder = _container.RegisterCollectionBuilder(); + var builder = _composition.WithCollectionBuilder(); builder.Append(); builder.Append(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1)); } [Test] public void CannotAppendInvalidTypeToBUilder() { - var builder = _container.RegisterCollectionBuilder(); + var builder = _composition.WithCollectionBuilder(); + //builder.Append(); // does not compile Assert.Throws(() => builder.Append(new[] { typeof (Resolved4) }) // throws @@ -124,7 +133,7 @@ namespace Umbraco.Tests.Composing [Test] public void CanRemoveFromBuilder() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .Remove(); @@ -133,30 +142,33 @@ namespace Umbraco.Tests.Composing Assert.IsFalse(builder.Has()); Assert.IsFalse(builder.Has()); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1)); } [Test] public void CanRemoveMissingFromBuilder() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .Remove(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotRemoveFromBuilderOnceCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); Assert.Throws(() => builder.Remove() // throws ); @@ -165,7 +177,7 @@ namespace Umbraco.Tests.Composing [Test] public void CanInsertIntoBuilder() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .Insert(); @@ -174,18 +186,20 @@ namespace Umbraco.Tests.Composing Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved3), typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotInsertIntoBuilderOnceCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); Assert.Throws(() => builder.Insert() // throws ); @@ -194,29 +208,31 @@ namespace Umbraco.Tests.Composing [Test] public void CanInsertDuplicateIntoBuilderAndDeDuplicate() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .Insert(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } [Test] public void CanInsertIntoEmptyBuilder() { - var builder = _container.RegisterCollectionBuilder(); + var builder = _composition.WithCollectionBuilder(); builder.Insert(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2)); } [Test] public void CannotInsertIntoBuilderAtWrongIndex() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); @@ -232,7 +248,7 @@ namespace Umbraco.Tests.Composing [Test] public void CanInsertIntoBuilderBefore() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertBefore(); @@ -241,18 +257,20 @@ namespace Umbraco.Tests.Composing Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved3), typeof(Resolved2)); } [Test] public void CannotInsertIntoBuilderBeforeOnceCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); Assert.Throws(() => builder.InsertBefore() ); @@ -261,19 +279,20 @@ namespace Umbraco.Tests.Composing [Test] public void CanInsertDuplicateIntoBuilderBeforeAndDeDuplicate() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertBefore(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } [Test] public void CannotInsertIntoBuilderBeforeMissing() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append(); Assert.Throws(() => @@ -284,7 +303,7 @@ namespace Umbraco.Tests.Composing [Test] public void ScopeBuilderCreatesScopedCollection() { - _container.RegisterCollectionBuilder() + _composition.WithCollectionBuilder() .Append() .Append(); @@ -292,19 +311,25 @@ namespace Umbraco.Tests.Composing // but the container manages the scope, so to test the scope // the collection must come from the container - var col1 = _container.GetInstance(); - AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); + var factory = _composition.CreateFactory(); - var col2 = _container.GetInstance(); - AssertCollection(col2, typeof(Resolved1), typeof(Resolved2)); + using (factory.BeginScope()) + { + var col1 = factory.GetInstance(); + AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); + + var col2 = factory.GetInstance(); + AssertCollection(col2, typeof(Resolved1), typeof(Resolved2)); + + AssertSameCollection(col1, col2); + } - AssertSameCollection(col1, col2); } [Test] public void TransientBuilderCreatesTransientCollection() { - _container.RegisterCollectionBuilder() + _composition.WithCollectionBuilder() .Append() .Append(); @@ -312,10 +337,12 @@ namespace Umbraco.Tests.Composing // but the container manages the scope, so to test the scope // the collection must come from the container - var col1 = _container.GetInstance(); + var factory = _composition.CreateFactory(); + + var col1 = factory.GetInstance(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); - var col2 = _container.GetInstance(); + var col2 = factory.GetInstance(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); AssertNotSameCollection(col1, col2); @@ -324,19 +351,20 @@ namespace Umbraco.Tests.Composing [Test] public void BuilderRespectsTypesOrder() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Insert() .InsertBefore(); - var col1 = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col1 = builder.CreateCollection(factory); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2), typeof(Resolved3)); } [Test] public void ScopeBuilderRespectsContainerScope() { - _container.RegisterCollectionBuilder() + _composition.WithCollectionBuilder() .Append() .Append(); @@ -344,34 +372,40 @@ namespace Umbraco.Tests.Composing // but the container manages the scope, so to test the scope // the collection must come from the container - var scope1 = _container.BeginScope(); + TestCollection col1A, col1B; + + var factory = _composition.CreateFactory(); + + using (factory.BeginScope()) + { + col1A = factory.GetInstance(); + col1B = factory.GetInstance(); + } - var col1A = _container.GetInstance(); AssertCollection(col1A, typeof(Resolved1), typeof(Resolved2)); - var col1B = _container.GetInstance(); AssertCollection(col1B, typeof(Resolved1), typeof(Resolved2)); - AssertSameCollection(col1A, col1B); - _container.ScopeManagerProvider.GetScopeManager(_container).CurrentScope.Dispose(); - var scope2 = _container.BeginScope(); + TestCollection col2; + + using (factory.BeginScope()) + { + col2 = factory.GetInstance(); + } - var col2 = _container.GetInstance(); AssertCollection(col2, typeof(Resolved1), typeof(Resolved2)); - AssertNotSameCollection(col1A, col2); - - _container.ScopeManagerProvider.GetScopeManager(_container).CurrentScope.Dispose(); } [Test] public void WeightedBuilderCreatesWeightedCollection() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Add() .Add(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } @@ -432,44 +466,28 @@ namespace Umbraco.Tests.Composing // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilder : OrderedCollectionBuilderBase { - public TestCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override TestCollectionBuilder This => this; } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilderTransient : OrderedCollectionBuilderBase { - public TestCollectionBuilderTransient(IServiceContainer container) - : base(container) - { } - protected override TestCollectionBuilderTransient This => this; - protected override ILifetime CollectionLifetime => null; // transient + protected override Lifetime CollectionLifetime => Lifetime.Transient; // transient } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilderScope : OrderedCollectionBuilderBase { - public TestCollectionBuilderScope(IServiceContainer container) - : base(container) - { } - protected override TestCollectionBuilderScope This => this; - protected override ILifetime CollectionLifetime => new PerScopeLifetime(); + protected override Lifetime CollectionLifetime => Lifetime.Scope; } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilderWeighted : WeightedCollectionBuilderBase { - public TestCollectionBuilderWeighted(IServiceContainer container) - : base(container) - { } - protected override TestCollectionBuilderWeighted This => this; } diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index be595885e7..407d953509 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -4,6 +4,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Tests.TestHelpers; @@ -13,14 +14,14 @@ namespace Umbraco.Tests.Composing { protected TypeLoader TypeLoader { get; private set; } - protected ProfilingLogger ProfilingLogger { get; private set; } + protected IProfilingLogger ProfilingLogger { get; private set; } [SetUp] public void Initialize() { ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); - TypeLoader = new TypeLoader(NullCacheProvider.Instance, SettingsForTests.GenerateMockGlobalSettings(), ProfilingLogger, detectChanges: false) + TypeLoader = new TypeLoader(NoAppCache.Instance, LocalTempStorage.Default, ProfilingLogger, detectChanges: false) { AssembliesToScan = AssembliesToScan }; diff --git a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs new file mode 100644 index 0000000000..21ea961636 --- /dev/null +++ b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs @@ -0,0 +1,356 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Composing; + +namespace Umbraco.Tests.Composing +{ + [TestFixture] + public class ContainerConformingTests + { + // tests that a container conforms + + private IRegister GetRegister() => RegisterFactory.Create(); + + [Test] + public void CanRegisterAndGet() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var thing = factory.GetInstance(); + Assert.IsNotNull(thing); + Assert.IsInstanceOf(thing); + } + + [Test] + public void CanRegisterAndGetLazy() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var lazyThing = factory.GetInstance>(); + Assert.IsNotNull(lazyThing); + Assert.IsInstanceOf>(lazyThing); + var thing = lazyThing.Value; + Assert.IsNotNull(thing); + Assert.IsInstanceOf(thing); + } + + [Test] + public void CannotRegistedAndGetBase() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + Assert.IsNull(factory.TryGetInstance()); + } + + [Test] + public void CannotRegisterAndGetInterface() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + Assert.IsNull(factory.TryGetInstance()); + } + + [Test] + public void CanRegisterAndGetAllBase() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var things = factory.GetAllInstances(); + Assert.AreEqual(1, things.Count()); + + // lightInject: would be zero with option EnableVariance set to false + } + + [Test] + public void CanRegisterAndGetAllInterface() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var things = factory.GetAllInstances(); + Assert.AreEqual(1, things.Count()); + + // lightInject: would be zero with option EnableVariance set to false + } + + [Test] + public void CanRegisterBaseAndGet() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var thing = factory.GetInstance(); + Assert.IsNotNull(thing); + Assert.IsInstanceOf(thing); + } + + [Test] + public void CanRegisterInterfaceAndGet() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var thing = factory.GetInstance(); + Assert.IsNotNull(thing); + Assert.IsInstanceOf(thing); + } + + [Test] + public void NonSingletonServiceIsNotUnique() + { + var register = GetRegister(); + + register.Register(); + register.Register(); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + Assert.AreEqual(2, things.Count()); + + Assert.IsNull(factory.TryGetInstance()); + } + + [Test] + public void SingletonServiceIsUnique() // fixme - but what is LightInject actually doing + { + var register = GetRegister(); + + // fixme + // LightInject is 'unique' per serviceType+serviceName + // but that's not how all containers work + // and we should not rely on it + // if we need unique, use RegisterUnique + + // for Core services that ppl may want to redefine in components, + // it is important to be able to have a unique, singleton implementation, + // and to redefine it - how it's done at container's level depends + // on each container + + // redefine the service + register.Register(Lifetime.Singleton); + register.Register(Lifetime.Singleton); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + Assert.AreEqual(1, things.Count()); + + var thing = factory.GetInstance(); + Assert.IsInstanceOf(thing); + } + + [Test] + public void SingletonImplementationIsNotUnique() + { + var register = GetRegister(); + + // define two implementations + register.Register(Lifetime.Singleton); + register.Register(Lifetime.Singleton); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + Assert.AreEqual(2, things.Count()); + + Assert.IsNull(factory.TryGetInstance()); + } + + [Test] + public void ActualInstanceIsNotUnique() + { + var register = GetRegister(); + + // define two instances + register.Register(typeof(Thing1), new Thing1()); + register.Register(typeof(Thing1), new Thing2()); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + //Assert.AreEqual(2, things.Count()); + Assert.AreEqual(1, things.Count()); // well, yes they are unique? + + Assert.IsNull(factory.TryGetInstance()); + } + + [Test] + public void InterfaceInstanceIsNotUnique() + { + var register = GetRegister(); + + // define two instances + register.Register(typeof(IThing), new Thing1()); + register.Register(typeof(IThing), new Thing2()); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + //Assert.AreEqual(2, things.Count()); + Assert.AreEqual(1, things.Count()); // well, yes they are unique? + + //Assert.IsNull(factory.TryGetInstance()); + Assert.IsNotNull(factory.TryGetInstance()); // well, what? + } + + [Test] + public void CanInjectEnumerableOfBase() + { + var register = GetRegister(); + + register.Register(); + register.Register(); + register.Register(); + + var factory = register.CreateFactory(); + + var needThings = factory.GetInstance(); + Assert.AreEqual(2, needThings.Things.Count()); + } + + [Test] + public void CanGetEnumerableOfBase() + { + var register = GetRegister(); + + register.Register(); + register.Register(); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + Assert.AreEqual(2, things. Count()); + } + + [Test] + public void CanGetEmptyEnumerableOfBase() + { + var register = GetRegister(); + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + Assert.AreEqual(0, things.Count()); + } + + [Test] + public void CanGetEmptyAllInstancesOfBase() + { + var register = GetRegister(); + var factory = register.CreateFactory(); + + var things = factory.GetAllInstances(); + Assert.AreEqual(0, things.Count()); + } + + [Test] + public void CanTryGetEnumerableOfBase() + { + var register = GetRegister(); + + register.Register(); + register.Register(); + + var factory = register.CreateFactory(); + + var things = factory.TryGetInstance>(); + Assert.AreEqual(2, things.Count()); + } + + [Test] + public void CanRegisterSingletonInterface() + { + var register = GetRegister(); + register.Register(Lifetime.Singleton); + var factory = register.CreateFactory(); + var s1 = factory.GetInstance(); + var s2 = factory.GetInstance(); + Assert.AreSame(s1, s2); + } + + [Test] + public void CanRegisterSingletonClass() + { + var register = GetRegister(); + register.Register(Lifetime.Singleton); + var factory = register.CreateFactory(); + var s1 = factory.GetInstance(); + var s2 = factory.GetInstance(); + Assert.AreSame(s1, s2); + } + + [Test] + public void CanReRegisterSingletonInterface() + { + var register = GetRegister(); + register.Register(Lifetime.Singleton); + register.Register(Lifetime.Singleton); + var factory = register.CreateFactory(); + var s = factory.GetInstance(); + Assert.IsInstanceOf(s); + } + + [Test] + public void CanRegisterSingletonWithCreate() + { + var register = GetRegister(); + register.Register(c => c.CreateInstance(new Thing1()), Lifetime.Singleton); + var factory = register.CreateFactory(); + var s1 = factory.GetInstance(); + var s2 = factory.GetInstance(); + Assert.AreSame(s1, s2); + } + + public interface IThing { } + + public abstract class ThingBase : IThing { } + public class Thing1 : ThingBase { } + public class Thing2 : ThingBase { } + + public class Thing3 : ThingBase + { + public Thing3(Thing1 thing) { } + } + + public class NeedThings + { + public NeedThings(IEnumerable things) + { + Things = things; + } + + public IEnumerable Things { get; } + } + } +} diff --git a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs index 7a39186fea..cbabae1a83 100644 --- a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs +++ b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs @@ -1,10 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; +using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Composing { @@ -23,6 +28,11 @@ namespace Umbraco.Tests.Composing Current.Reset(); } + private IRegister CreateRegister() + { + return RegisterFactory.Create(); + } + // note // lazy collection builder does not throw on duplicate, just uses distinct types // so we don't have a test for duplicates as we had with resolvers in v7 @@ -30,22 +40,24 @@ namespace Umbraco.Tests.Composing [Test] public void LazyCollectionBuilderHandlesTypes() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = CreateRegister(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add() .Add() .Add() .Add(); - var values = container.GetInstance(); + var factory = composition.CreateFactory(); + + var values = factory.GetInstance(); Assert.AreEqual(3, values.Count()); Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); - var other = container.GetInstance(); + var other = factory.GetInstance(); Assert.AreNotSame(values, other); // transient var o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient @@ -54,21 +66,23 @@ namespace Umbraco.Tests.Composing [Test] public void LazyCollectionBuilderHandlesProducers() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = CreateRegister(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2) }) .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2) }) .Add(() => new[] { typeof(TransientObject1) }); - var values = container.GetInstance(); + var factory = composition.CreateFactory(); + + var values = factory.GetInstance(); Assert.AreEqual(3, values.Count()); Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); - var other = container.GetInstance(); + var other = factory.GetInstance(); Assert.AreNotSame(values, other); // transient var o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient @@ -77,22 +91,24 @@ namespace Umbraco.Tests.Composing [Test] public void LazyCollectionBuilderHandlesTypesAndProducers() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = CreateRegister(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add() .Add() .Add() .Add(() => new[] { typeof(TransientObject1) }); - var values = container.GetInstance(); + var factory = composition.CreateFactory(); + + var values = factory.GetInstance(); Assert.AreEqual(3, values.Count()); Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); - var other = container.GetInstance(); + var other = factory.GetInstance(); Assert.AreNotSame(values, other); // transient var o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient @@ -101,10 +117,10 @@ namespace Umbraco.Tests.Composing [Test] public void LazyCollectionBuilderThrowsOnIllegalTypes() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = CreateRegister(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add() // illegal, does not implement the interface! @@ -115,23 +131,25 @@ namespace Umbraco.Tests.Composing Assert.Throws(() => { - // but throws here when trying to register the types - var values = container.GetInstance(); + // but throws here when trying to register the types, right before creating the factory + var factory = composition.CreateFactory(); }); } [Test] public void LazyCollectionBuilderCanExcludeTypes() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = CreateRegister(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add() .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2), typeof(TransientObject1) }) .Exclude(); - var values = container.GetInstance(); + var factory = composition.CreateFactory(); + + var values = factory.GetInstance(); Assert.AreEqual(2, values.Count()); Assert.IsFalse(values.Select(x => x.GetType()) @@ -139,7 +157,7 @@ namespace Umbraco.Tests.Composing Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2) })); - var other = container.GetInstance(); + var other = factory.GetInstance(); Assert.AreNotSame(values, other); // transient var o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient @@ -165,13 +183,9 @@ namespace Umbraco.Tests.Composing // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilder : LazyCollectionBuilderBase { - public TestCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override TestCollectionBuilder This => this; - protected override ILifetime CollectionLifetime => null; // transient + protected override Lifetime CollectionLifetime => Lifetime.Transient; // transient } // ReSharper disable once ClassNeverInstantiated.Local diff --git a/src/Umbraco.Tests/Composing/LightInjectValidation.cs b/src/Umbraco.Tests/Composing/LightInjectValidation.cs new file mode 100644 index 0000000000..75062e613c --- /dev/null +++ b/src/Umbraco.Tests/Composing/LightInjectValidation.cs @@ -0,0 +1,349 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LightInject; +using System.Collections.Concurrent; +using System.Collections.ObjectModel; +using System.Reflection; +using ServiceMap = System.Collections.Generic.Dictionary>; + +/********************************************************************************* + The MIT License (MIT) + + Copyright (c) 2017 bernhard.richter@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +****************************************************************************** + LightInject.Validation version 1.0.1 + http://www.lightinject.net/ + http://twitter.com/bernhardrichter +******************************************************************************/ + +namespace Umbraco.Tests.Composing +{ + public static class LightInjectValidation + { + private static readonly ConcurrentDictionary LifeSpans = new ConcurrentDictionary(); + + private const string NotDisposeMessageServiceType = + @"The service {0} is being injected as a constructor argument into {1} implements IDisposable, " + + "but is registered without a lifetime (transient). LightInject will not be able to dispose the instance represented by {0}. " + + "If the intent was to manually control the instantiation and destruction, inject Func<{0}> instead. " + + "Otherwise register `{0}` with a lifetime (PerContainer, PerRequest or PerScope)."; + + private const string NotDisposeMessageImplementingType = + @"The service {0} represented by {1} is being injected as a constructor argument into {2} implements IDisposable, " + + "but is registered without a lifetime (transient). LightInject will not be able to dispose the instance represented by {0}. " + + "If the intent was to manually control the instantiation and destruction, inject Func<{0}> instead. " + + "Otherwise register `{0}` with a lifetime (PerContainer, PerRequest or PerScope)."; + + + private const string MissingDeferredDependency = + @"The injected '{0}' does not contain a registration for the underlying type '{1}'. " + + "Ensure that '{1}' is registered so that the service can be resolved by '{0}'"; + + /* + The service 'NameSpace.IBar' that is being injected into 'NameSpace.Foo' is registered with +with a 'Transient' lifetime while the 'NameSpace.Foo' is registered with the 'PerScope' lifetime. +Ensure that 'NameSpace.IBar' is registered with a lifetime that is equal to or has a longer lifetime than the 'PerScope' lifetime. + */ + private const string CaptiveDependency = + @"The service '{0}' that is being injected into {1} is registered with " + + "a '{2}' lifetime while the {1} is registered with the '{3}' lifetime. " + + "Ensure that '{0}' is registered with a lifetime that is equal to or has a longer lifetime than the '{3}' lifetime. " + + "Alternatively ensure that `{1}` is registered with a lifetime that is equal to or " + + "has a shorter lifetime than `{2}` lifetime."; + + private const string MissingDependency = + "Class: 'NameSpace.Foo', Parameter 'NameSpace.IBar bar' -> The injected service NameSpace IBar is not registered." + ; + + + static LightInjectValidation() + { + LifeSpans.TryAdd(typeof(PerRequestLifeTime), 10); + LifeSpans.TryAdd(typeof(PerScopeLifetime), 20); + LifeSpans.TryAdd(typeof(PerContainerLifetime), 30); + } + + public static IEnumerable Validate(this ServiceContainer container) + { + var serviceMap = container.AvailableServices.GroupBy(sr => sr.ServiceType).ToDictionary(gr => gr.Key, + gr => gr.ToDictionary(sr => sr.ServiceName, sr => sr, StringComparer.OrdinalIgnoreCase)); + + var verifyableServices = container.AvailableServices.Where(sr => sr.ImplementingType != null); + + return verifyableServices.SelectMany(sr => + ValidateConstructor(serviceMap, sr, container.ConstructorSelector.Execute(sr.ImplementingType))); + } + + private static IReadOnlyCollection ValidateConstructor(ServiceMap serviceMap, + ServiceRegistration serviceRegistration, ConstructorInfo constructorInfo) + { + var result = new Collection(); + + foreach (var parameter in constructorInfo.GetParameters()) + { + var validationTarget = new ValidationTarget(serviceRegistration, parameter); + Validate(validationTarget, serviceMap, result); + } + return result; + } + + private static void Validate(ValidationTarget validationTarget, ServiceMap serviceMap, ICollection result) + { + var registration = GetServiceRegistration(serviceMap, validationTarget); + if (registration == null) + { + if (validationTarget.ServiceType.IsFunc() || validationTarget.ServiceType.IsLazy()) + { + var serviceType = validationTarget.ServiceType.GenericTypeArguments[0]; + var underlyingvalidationTarget = validationTarget.WithServiceDescription(serviceType, string.Empty); + registration = GetServiceRegistration(serviceMap, underlyingvalidationTarget); + + if (registration != null) + { + return; + } + + if (serviceMap.ContainsAmbiguousRegistrationFor(serviceType)) + { + result.Add(new ValidationResult("", ValidationSeverity.Ambiguous, underlyingvalidationTarget)); + } + else + { + string message = string.Format(MissingDeferredDependency, validationTarget.ServiceType, underlyingvalidationTarget.ServiceType); + result.Add(new ValidationResult(message, ValidationSeverity.MissingDependency, underlyingvalidationTarget)); + } + } + else if (validationTarget.ServiceType.IsGenericType && validationTarget.ServiceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + var serviceType = validationTarget.ServiceType.GenericTypeArguments[0]; + var underlyingvalidationTarget = validationTarget.WithServiceDescription(serviceType, string.Empty); + var registrations = GetServiceRegistrations(serviceMap, underlyingvalidationTarget); + if (registrations.Any()) return; + + // strict: there has to be at least 1 + string message = string.Format(MissingDeferredDependency, validationTarget.ServiceType, underlyingvalidationTarget.ServiceType); + result.Add(new ValidationResult(message, ValidationSeverity.MissingDependency, underlyingvalidationTarget)); + } + else + { + if (serviceMap.ContainsAmbiguousRegistrationFor(validationTarget.ServiceType)) + { + result.Add(new ValidationResult("", ValidationSeverity.Ambiguous, validationTarget)); + } + else + { + result.Add(new ValidationResult("", ValidationSeverity.MissingDependency, validationTarget)); + } + } + } + else + { + ValidateDisposable(validationTarget, result, registration); + ValidateLifetime(validationTarget, registration, result); + } + } + + private static void ValidateDisposable(ValidationTarget validationTarget, ICollection result, + ServiceRegistration registration) + { + if (registration.ServiceType.Implements()) + { + var message = string.Format(NotDisposeMessageServiceType, registration.ServiceType, + validationTarget.DeclaringService.ImplementingType); + result.Add(new ValidationResult(message, ValidationSeverity.NotDisposed, validationTarget)); + } + + else if (registration.ImplementingType != null && registration.ImplementingType.Implements()) + { + var message = string.Format(NotDisposeMessageImplementingType, registration.ImplementingType, + registration.ServiceType, + validationTarget.DeclaringService.ImplementingType); + result.Add(new ValidationResult(message, ValidationSeverity.NotDisposed, validationTarget)); + } + } + + + private static void ValidateLifetime(ValidationTarget validationTarget, ServiceRegistration dependencyRegistration, ICollection result) + { + if (GetLifespan(validationTarget.DeclaringService.Lifetime) > GetLifespan(dependencyRegistration.Lifetime)) + { + var message = string.Format(CaptiveDependency, dependencyRegistration.ServiceType, + validationTarget.DeclaringService.ServiceType, GetLifetimeName(dependencyRegistration.Lifetime), + GetLifetimeName(validationTarget.DeclaringService.Lifetime)); + result.Add(new ValidationResult(message, ValidationSeverity.Captive, validationTarget)); + } + } + + public static void SetLifespan(int lifeSpan) where TLifetime : ILifetime + { + LifeSpans.TryAdd(typeof(TLifetime), lifeSpan); + } + + private static IEnumerable GetServiceRegistrations(ServiceMap serviceMap, ValidationTarget validationTarget) + { + return serviceMap.Where(x => validationTarget.ServiceType.IsAssignableFrom(x.Key)).SelectMany(x => x.Value.Values); + } + + private static ServiceRegistration GetServiceRegistration(ServiceMap serviceMap, ValidationTarget validationTarget) + { + if (!serviceMap.TryGetValue(validationTarget.ServiceType, out var registrations)) + { + return null; + } + + if (registrations.TryGetValue(string.Empty, out var registration)) + { + return registration; + } + + if (registrations.Count == 1) + { + return registrations.Values.First(); + } + + if (registrations.TryGetValue(validationTarget.ServiceName, out registration)) + { + return registration; + } + + return null; + } + + private static string GetLifetimeName(ILifetime lifetime) + { + if (lifetime == null) + { + return "Transient"; + } + return lifetime.GetType().Name; + } + + private static int GetLifespan(ILifetime lifetime) + { + if (lifetime == null) + { + return 0; + } + if (LifeSpans.TryGetValue(lifetime.GetType(), out var lifespan)) + { + return lifespan; + } + return 0; + } + } + + + public class ValidationTarget + { + public ServiceRegistration DeclaringService { get; } + public ParameterInfo Parameter { get; } + public Type ServiceType { get; } + public string ServiceName { get; } + + + public ValidationTarget(ServiceRegistration declaringRegistration, ParameterInfo parameter) : this(declaringRegistration, parameter, parameter.ParameterType, string.Empty) + { + } + + + public ValidationTarget(ServiceRegistration declaringService, ParameterInfo parameter, Type serviceType, string serviceName) + { + DeclaringService = declaringService; + Parameter = parameter; + ServiceType = serviceType; + ServiceName = serviceName; + + + if (serviceType.GetTypeInfo().IsGenericType && serviceType.GetTypeInfo().ContainsGenericParameters) + { + ServiceType = serviceType.GetGenericTypeDefinition(); + } + + } + + public ValidationTarget WithServiceDescription(Type serviceType, string serviceName) + { + return new ValidationTarget(DeclaringService, Parameter, serviceType, serviceName); + } + + } + + + + + + public class ValidationResult + { + public ValidationResult(string message, ValidationSeverity severity, ValidationTarget validationTarget) + { + Message = message; + Severity = severity; + ValidationTarget = validationTarget; + } + + public string Message { get; } + + public ValidationSeverity Severity { get; } + public ValidationTarget ValidationTarget { get; } + } + + public enum ValidationSeverity + { + NoIssues, + Captive, + NotDisposed, + MissingDependency, + Ambiguous + } + + internal static class TypeExtensions + { + public static bool Implements(this Type type) + { + return type.GetTypeInfo().ImplementedInterfaces.Contains(typeof(TBaseType)); + } + + public static bool IsFunc(this Type type) + { + var typeInfo = type.GetTypeInfo(); + return typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Func<>); + } + + public static bool IsLazy(this Type type) + { + var typeInfo = type.GetTypeInfo(); + return typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Lazy<>); + } + } + + internal static class ServiceMapExtensions + { + public static bool ContainsAmbiguousRegistrationFor(this ServiceMap serviceMap, Type serviceType) + { + if (!serviceMap.TryGetValue(serviceType, out var registrations)) + { + return false; + } + return registrations.Count > 1; + } + } +} diff --git a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs index e2145f557a..75030decbf 100644 --- a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs +++ b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs @@ -1,10 +1,15 @@ using System; using System.Linq; using System.Xml; -using LightInject; +using System.Xml.Linq; +using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core._Legacy.PackageActions; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Composing { @@ -14,18 +19,21 @@ namespace Umbraco.Tests.Composing [Test] public void PackageActionCollectionBuilderWorks() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = RegisterFactory.Create(); + + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add(() => TypeLoader.GetPackageActions()); + Current.Factory = composition.CreateFactory(); + var actions = Current.PackageActions; Assert.AreEqual(2, actions.Count()); // order is unspecified, but both must be there - bool hasAction1 = actions.ElementAt(0) is PackageAction1 || actions.ElementAt(1) is PackageAction1; - bool hasAction2 = actions.ElementAt(0) is PackageAction2 || actions.ElementAt(1) is PackageAction2; + var hasAction1 = actions.ElementAt(0) is PackageAction1 || actions.ElementAt(1) is PackageAction1; + var hasAction2 = actions.ElementAt(0) is PackageAction2 || actions.ElementAt(1) is PackageAction2; Assert.IsTrue(hasAction1); Assert.IsTrue(hasAction2); } @@ -34,7 +42,7 @@ namespace Umbraco.Tests.Composing public class PackageAction1 : IPackageAction { - public bool Execute(string packageName, XmlNode xmlData) + public bool Execute(string packageName, XElement xmlData) { throw new NotImplementedException(); } @@ -44,7 +52,7 @@ namespace Umbraco.Tests.Composing return "pa1"; } - public bool Undo(string packageName, XmlNode xmlData) + public bool Undo(string packageName, XElement xmlData) { throw new NotImplementedException(); } @@ -57,7 +65,7 @@ namespace Umbraco.Tests.Composing public class PackageAction2 : IPackageAction { - public bool Execute(string packageName, XmlNode xmlData) + public bool Execute(string packageName, XElement xmlData) { throw new NotImplementedException(); } @@ -67,7 +75,7 @@ namespace Umbraco.Tests.Composing return "pa2"; } - public bool Undo(string packageName, XmlNode xmlData) + public bool Undo(string packageName, XElement xmlData) { throw new NotImplementedException(); } diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index 9b52546dff..2b9474310b 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -93,7 +93,7 @@ namespace Umbraco.Tests.Composing Assert.AreEqual(21, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] } - private static ProfilingLogger GetTestProfilingLogger() + private static IProfilingLogger GetTestProfilingLogger() { var logger = new DebugDiagnosticsLogger(); var profiler = new TestProfiler(); diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 75edcd6404..add3424599 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -9,6 +9,7 @@ using umbraco; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; @@ -27,9 +28,9 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - _typeLoader = new TypeLoader(NullCacheProvider.Instance, SettingsForTests.GenerateMockGlobalSettings(), new ProfilingLogger(Mock.Of(), Mock.Of())); + _typeLoader = new TypeLoader(NoAppCache.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of())); - foreach (var file in Directory.GetFiles(IOHelper.MapPath("~/App_Data/TEMP/TypesCache"))) + foreach (var file in Directory.GetFiles(IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "TypesCache"))) File.Delete(file); // for testing, we'll specify which assemblies are scanned for the PluginTypeResolver diff --git a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs index 34fb2add8b..8587a7b194 100644 --- a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs +++ b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs @@ -2,6 +2,8 @@ using System.Web.Routing; using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Tests.TestHelpers; @@ -45,14 +47,13 @@ namespace Umbraco.Tests.Configurations [TestCase("~/some-wacky/nestedPath", "/MyVirtualDir/NestedVDir/", "some-wacky-nestedpath")] public void Umbraco_Mvc_Area(string path, string rootPath, string outcome) { - var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.Path).Returns(IOHelper.ResolveUrl(path)); - SettingsForTests.ConfigureSettings(globalSettingsMock.Object); SystemDirectories.Root = rootPath; - Assert.AreEqual(outcome, UmbracoConfig.For.GlobalSettings().GetUmbracoMvcArea()); + Assert.AreEqual(outcome, Current.Configs.Global().GetUmbracoMvcArea()); } - + [TestCase("/umbraco/editContent.aspx")] [TestCase("/install/default.aspx")] [TestCase("/install/")] @@ -92,10 +93,9 @@ namespace Umbraco.Tests.Configurations public void Is_Reserved_By_Route(string url, bool shouldMatch) { //reset the app config, we only want to test routes not the hard coded paths - var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.ReservedPaths).Returns(""); globalSettingsMock.Setup(x => x.ReservedUrls).Returns(""); - SettingsForTests.ConfigureSettings(globalSettingsMock.Object); var routes = new RouteCollection(); diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/TemplateElementDefaultTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/TemplateElementDefaultTests.cs deleted file mode 100644 index 4f7e8c1800..0000000000 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/TemplateElementDefaultTests.cs +++ /dev/null @@ -1,13 +0,0 @@ -using NUnit.Framework; - -namespace Umbraco.Tests.Configurations.UmbracoSettings -{ - [TestFixture] - public class TemplateElementDefaultTests : TemplateElementTests - { - protected override bool TestingDefaults - { - get { return true; } - } - } -} diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/TemplateElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/TemplateElementTests.cs deleted file mode 100644 index a2edf94ab5..0000000000 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/TemplateElementTests.cs +++ /dev/null @@ -1,16 +0,0 @@ -using NUnit.Framework; -using Umbraco.Core; - -namespace Umbraco.Tests.Configurations.UmbracoSettings -{ - [TestFixture] - public class TemplateElementTests : UmbracoSettingsTests - { - [Test] - public void DefaultRenderingEngine() - { - Assert.IsTrue(SettingsSection.Templates.DefaultRenderingEngine == RenderingEngine.Mvc); - } - - } -} diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config index 4c64485503..dd44e23328 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config @@ -145,10 +145,6 @@ - - Mvc - - false true diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.minimal.config b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.minimal.config index 21dfa1f19e..bee7133051 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.minimal.config +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.minimal.config @@ -37,11 +37,6 @@ - - - Mvc - - diff --git a/src/Umbraco.Tests/CoreThings/TryConvertToTests.cs b/src/Umbraco.Tests/CoreThings/TryConvertToTests.cs index 2e187d85a5..120a7f40c9 100644 --- a/src/Umbraco.Tests/CoreThings/TryConvertToTests.cs +++ b/src/Umbraco.Tests/CoreThings/TryConvertToTests.cs @@ -1,9 +1,9 @@ using System; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Strings; using Umbraco.Tests.Testing; namespace Umbraco.Tests.CoreThings @@ -11,14 +11,11 @@ namespace Umbraco.Tests.CoreThings [TestFixture] public class TryConvertToTests : UmbracoTestBase { - public override void SetUp() + protected void Compose() { - base.SetUp(); + base.Compose(); - var settings = SettingsForTests.GetDefaultUmbracoSettings(); - - // fixme - base should do it! - Container.RegisterSingleton(_ => new DefaultShortStringHelper(settings)); + Composition.RegisterUnique(f => new DefaultShortStringHelper(f.GetInstance())); } [Test] diff --git a/src/Umbraco.Tests/CoreThings/UdiTests.cs b/src/Umbraco.Tests/CoreThings/UdiTests.cs index 62aa56bd14..35080e8c24 100644 --- a/src/Umbraco.Tests/CoreThings/UdiTests.cs +++ b/src/Umbraco.Tests/CoreThings/UdiTests.cs @@ -2,13 +2,13 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using LightInject; using Moq; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Deploy; using Umbraco.Core.Logging; using Umbraco.Core.Serialization; @@ -23,11 +23,11 @@ namespace Umbraco.Tests.CoreThings public void SetUp() { // fixme - bad in a unit test - but Udi has a static ctor that wants it?! - var container = new Mock(); + var container = new Mock(); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - container.Setup(x => x.GetInstance(typeof (TypeLoader))).Returns( - new TypeLoader(NullCacheProvider.Instance, globalSettings, new ProfilingLogger(Mock.Of(), Mock.Of()))); - Current.Container = container.Object; + container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( + new TypeLoader(NoAppCache.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()))); + Current.Factory = container.Object; Udi.ResetUdiTypes(); } diff --git a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs index 182f1a98f5..088ef6b54b 100644 --- a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.Text; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Tests.TestHelpers; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Web; @@ -407,19 +407,19 @@ namespace Umbraco.Tests.FrontEnd private void SetUpDependencyContainer() { // fixme - bad in a unit test - but Udi has a static ctor that wants it?! - var container = new Mock(); + var container = new Mock(); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); container .Setup(x => x.GetInstance(typeof(TypeLoader))) .Returns(new TypeLoader( - NullCacheProvider.Instance, - globalSettings, + NoAppCache.Instance, + LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()) ) ); - Current.Container = container.Object; + Current.Factory = container.Object; } } } diff --git a/src/Umbraco.Tests/IO/FileSystemsTests.cs b/src/Umbraco.Tests/IO/FileSystemsTests.cs index 49b8af6d18..52de1bbcfa 100644 --- a/src/Umbraco.Tests/IO/FileSystemsTests.cs +++ b/src/Umbraco.Tests/IO/FileSystemsTests.cs @@ -1,16 +1,18 @@ using System; using System.IO; using System.Text; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Components; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Composing; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.Logging; using Umbraco.Core.Services; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.IO @@ -18,22 +20,32 @@ namespace Umbraco.Tests.IO [TestFixture] public class FileSystemsTests { - private ServiceContainer _container; + private IRegister _register; + private IFactory _factory; [SetUp] public void Setup() { - //init the config singleton - var config = SettingsForTests.GetDefaultUmbracoSettings(); - SettingsForTests.ConfigureSettings(config); + _register = RegisterFactory.Create(); - _container = new ServiceContainer(); - _container.ConfigureUmbracoCore(); - _container.Register(_ => Mock.Of()); - _container.Register(); - _container.Register(_ => Mock.Of()); - _container.Register(_ => Mock.Of()); - _container.RegisterSingleton(); + var composition = new Composition(_register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + composition.Register(_ => Mock.Of()); + composition.Register(_ => Mock.Of()); + composition.Register(_ => Mock.Of()); + composition.RegisterUnique(); + + composition.Configs.Add(SettingsForTests.GetDefaultGlobalSettings); + composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings); + + composition.ComposeFileSystems(); + + composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings); + + _factory = composition.CreateFactory(); + + Current.Reset(); + Current.Factory = _factory; // make sure we start clean // because some tests will create corrupt or weird filesystems @@ -47,31 +59,47 @@ namespace Umbraco.Tests.IO FileSystems.Reset(); Current.Reset(); - _container.Dispose(); + _register.DisposeIfDisposable(); } - private FileSystems FileSystems => _container.GetInstance(); + private FileSystems FileSystems => _factory.GetInstance(); [Test] - public void Can_Get_Base_File_System() + public void Can_Get_MediaFileSystem() { - var fileSystem = FileSystems.GetUnderlyingFileSystemProvider("media"); - + var fileSystem = _factory.GetInstance(); Assert.NotNull(fileSystem); } [Test] - public void Can_Get_Typed_File_System() + public void Can_Get_IMediaFileSystem() { - var fileSystem = FileSystems.GetFileSystemProvider(); - + var fileSystem = _factory.GetInstance(); Assert.NotNull(fileSystem); } [Test] - public void Media_Fs_Safe_Delete() + public void IMediaFileSystem_Is_Singleton() { - var fs = FileSystems.GetFileSystemProvider(); + var fileSystem1 = _factory.GetInstance(); + var fileSystem2 = _factory.GetInstance(); + Assert.AreSame(fileSystem1, fileSystem2); + } + + [Test] + public void Can_Unwrap_MediaFileSystem() + { + var fileSystem = _factory.GetInstance(); + var unwrapped = fileSystem.Unwrap(); + Assert.IsNotNull(unwrapped); + var physical = unwrapped as PhysicalFileSystem; + Assert.IsNotNull(physical); + } + + [Test] + public void Can_Delete_MediaFiles() + { + var fs = _factory.GetInstance(); var ms = new MemoryStream(Encoding.UTF8.GetBytes("test")); var virtPath = fs.GetMediaPath("file.txt", Guid.NewGuid(), Guid.NewGuid()); fs.AddFile(virtPath, ms); @@ -93,52 +121,42 @@ namespace Umbraco.Tests.IO Assert.IsTrue(Directory.Exists(physPath)); } - public void Singleton_Typed_File_System() - { - var fs1 = FileSystems.GetFileSystemProvider(); - var fs2 = FileSystems.GetFileSystemProvider(); - Assert.AreSame(fs1, fs2); + // fixme - don't make sense anymore + /* + [Test] + public void Cannot_Get_InvalidFileSystem() + { + // throws because InvalidTypedFileSystem does not have the proper attribute with an alias + Assert.Throws(() => FileSystems.GetFileSystem()); } [Test] - public void Exception_Thrown_On_Invalid_Typed_File_System() - { - Assert.Throws(() => FileSystems.GetFileSystemProvider()); - } - - [Test] - public void Exception_Thrown_On_NonConfigured_Typed_File_System() + public void Cannot_Get_NonConfiguredFileSystem() { // note: we need to reset the manager between tests else the Accept_Fallback test would corrupt that one - Assert.Throws(() => FileSystems.GetFileSystemProvider()); + // throws because NonConfiguredFileSystem has the proper attribute with an alias, + // but then the container cannot find an IFileSystem implementation for that alias + Assert.Throws(() => FileSystems.GetFileSystem()); + + // all we'd need to pass is to register something like: + //_container.Register("noconfig", factory => new PhysicalFileSystem("~/foo")); } - [Test] - public void Accept_Fallback_On_NonConfigured_Typed_File_System() + internal class InvalidFileSystem : FileSystemWrapper { - var fs = FileSystems.GetFileSystemProvider(() => new PhysicalFileSystem("~/App_Data/foo")); - - Assert.NotNull(fs); - } - - /// - /// Used in unit tests, for a typed file system we need to inherit from FileSystemWrapper and they MUST have a ctor - /// that only accepts a base IFileSystem object - /// - internal class InvalidTypedFileSystem : FileSystemWrapper - { - public InvalidTypedFileSystem(IFileSystem wrapped, string invalidParam) - : base(wrapped) + public InvalidFileSystem(IFileSystem innerFileSystem) + : base(innerFileSystem) { } } - [FileSystemProvider("noconfig")] - internal class NonConfiguredTypeFileSystem : FileSystemWrapper + [InnerFileSystem("noconfig")] + internal class NonConfiguredFileSystem : FileSystemWrapper { - public NonConfiguredTypeFileSystem(IFileSystem wrapped) - : base(wrapped) + public NonConfiguredFileSystem(IFileSystem innerFileSystem) + : base(innerFileSystem) { } } + */ } } diff --git a/src/Umbraco.Tests/IO/IoHelperTests.cs b/src/Umbraco.Tests/IO/IoHelperTests.cs index b2ef5e4d31..b0e59cbb55 100644 --- a/src/Umbraco.Tests/IO/IoHelperTests.cs +++ b/src/Umbraco.Tests/IO/IoHelperTests.cs @@ -37,7 +37,6 @@ namespace Umbraco.Tests.IO Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Css, true), IOHelper.MapPath(SystemDirectories.Css, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Data, true), IOHelper.MapPath(SystemDirectories.Data, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Install, true), IOHelper.MapPath(SystemDirectories.Install, false)); - Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Masterpages, true), IOHelper.MapPath(SystemDirectories.Masterpages, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Media, true), IOHelper.MapPath(SystemDirectories.Media, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Packages, true), IOHelper.MapPath(SystemDirectories.Packages, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Preview, true), IOHelper.MapPath(SystemDirectories.Preview, false)); diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 90fdf3810d..2244f9085d 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Scoping; @@ -25,7 +27,7 @@ namespace Umbraco.Tests.IO { SafeCallContext.Clear(); ClearFiles(); - ShadowFileSystems.ResetId(); + FileSystems.ResetShadowId(); } [TearDown] @@ -33,13 +35,13 @@ namespace Umbraco.Tests.IO { SafeCallContext.Clear(); ClearFiles(); - ShadowFileSystems.ResetId(); + FileSystems.ResetShadowId(); } private static void ClearFiles() { TestHelper.DeleteDirectory(IOHelper.MapPath("FileSysTests")); - TestHelper.DeleteDirectory(IOHelper.MapPath("App_Data")); + TestHelper.DeleteDirectory(IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs")); } private static string NormPath(string path) @@ -373,74 +375,92 @@ namespace Umbraco.Tests.IO Assert.IsFalse(File.Exists(path + "/ShadowTests/sub/sub/f2.txt")); } + class FS : FileSystemWrapper + { + public FS(IFileSystem innerFileSystem) + : base(innerFileSystem) + { } + } + [Test] public void ShadowScopeComplete() { var logger = Mock.Of(); var path = IOHelper.MapPath("FileSysTests"); - var appdata = IOHelper.MapPath("App_Data"); + var shadowfs = IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"); Directory.CreateDirectory(path); - Directory.CreateDirectory(appdata); + Directory.CreateDirectory(shadowfs); var scopedFileSystems = false; - var fs = new PhysicalFileSystem(path, "ignore"); - var sw = new ShadowWrapper(fs, "shadow", () => scopedFileSystems); - var swa = new[] { sw }; + var phy = new PhysicalFileSystem(path, "ignore"); + + var container = Mock.Of(); + var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems }; + var fs = fileSystems.GetFileSystem(phy); + var sw = (ShadowWrapper) fs.InnerFileSystem; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f1.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f1.txt")); + Assert.IsTrue(phy.FileExists("sub/f1.txt")); Guid id; // explicit shadow without scope does not work sw.Shadow(id = Guid.NewGuid()); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f2.txt")); + Assert.IsTrue(phy.FileExists("sub/f2.txt")); sw.UnShadow(true); - Assert.IsTrue(fs.FileExists("sub/f2.txt")); - Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + Assert.IsTrue(phy.FileExists("sub/f2.txt")); + Assert.IsFalse(Directory.Exists(shadowfs + "/" + id)); // shadow with scope but no complete does not complete scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f3.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f3.txt")); - Assert.AreEqual(1, Directory.GetDirectories(appdata + "/TEMP/ShadowFs").Length); + Assert.IsFalse(phy.FileExists("sub/f3.txt")); + var dirs = Directory.GetDirectories(shadowfs); + Assert.AreEqual(1, dirs.Length); + Assert.AreEqual((shadowfs + "/" + id).Replace('\\', '/'), dirs[0].Replace('\\', '/')); + dirs = Directory.GetDirectories(dirs[0]); + var typedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/typed")); + Assert.IsNotNull(typedDir); + dirs = Directory.GetDirectories(typedDir); + var scopedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/Umbraco.Tests.IO.ShadowFileSystemTests+FS")); // this is where files go + Assert.IsNotNull(scopedDir); scope.Dispose(); scopedFileSystems = false; - Assert.IsFalse(fs.FileExists("sub/f3.txt")); - TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id))); + Assert.IsFalse(phy.FileExists("sub/f3.txt")); + TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(shadowfs + "/" + id))); // shadow with scope and complete does complete scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f4.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f4.txt")); - Assert.AreEqual(1, Directory.GetDirectories(appdata + "/TEMP/ShadowFs").Length); + Assert.IsFalse(phy.FileExists("sub/f4.txt")); + Assert.AreEqual(1, Directory.GetDirectories(shadowfs).Length); scope.Complete(); scope.Dispose(); scopedFileSystems = false; - TestHelper.TryAssert(() => Assert.AreEqual(0, Directory.GetDirectories(appdata + "/TEMP/ShadowFs").Length)); - Assert.IsTrue(fs.FileExists("sub/f4.txt")); - Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + TestHelper.TryAssert(() => Assert.AreEqual(0, Directory.GetDirectories(shadowfs).Length)); + Assert.IsTrue(phy.FileExists("sub/f4.txt")); + Assert.IsFalse(Directory.Exists(shadowfs + "/" + id)); // test scope for "another thread" scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f5.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f5.txt")); + Assert.IsFalse(phy.FileExists("sub/f5.txt")); // pretend we're another thread w/out scope scopedFileSystems = false; @@ -448,12 +468,12 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f6.txt", ms); scopedFileSystems = true; // pretend we have a scope - Assert.IsTrue(fs.FileExists("sub/f6.txt")); // other thread has written out to fs + Assert.IsTrue(phy.FileExists("sub/f6.txt")); // other thread has written out to fs scope.Complete(); scope.Dispose(); scopedFileSystems = false; - Assert.IsTrue(fs.FileExists("sub/f5.txt")); - Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + Assert.IsTrue(phy.FileExists("sub/f5.txt")); + Assert.IsFalse(Directory.Exists(shadowfs + "/" + id)); } [Test] @@ -462,27 +482,30 @@ namespace Umbraco.Tests.IO var logger = Mock.Of(); var path = IOHelper.MapPath("FileSysTests"); - var appdata = IOHelper.MapPath("App_Data"); + var shadowfs = IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"); Directory.CreateDirectory(path); var scopedFileSystems = false; - var fs = new PhysicalFileSystem(path, "ignore"); - var sw = new ShadowWrapper(fs, "shadow", () => scopedFileSystems); - var swa = new[] { sw }; + var phy = new PhysicalFileSystem(path, "ignore"); + + var container = Mock.Of(); + var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems }; + var fs = fileSystems.GetFileSystem( phy); + var sw = (ShadowWrapper) fs.InnerFileSystem; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f1.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f1.txt")); + Assert.IsTrue(phy.FileExists("sub/f1.txt")); Guid id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f2.txt")); + Assert.IsFalse(phy.FileExists("sub/f2.txt")); // pretend we're another thread w/out scope scopedFileSystems = false; @@ -490,15 +513,15 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f2.txt", ms); scopedFileSystems = true; // pretend we have a scope - Assert.IsTrue(fs.FileExists("sub/f2.txt")); // other thread has written out to fs + Assert.IsTrue(phy.FileExists("sub/f2.txt")); // other thread has written out to fs scope.Complete(); scope.Dispose(); scopedFileSystems = false; - Assert.IsTrue(fs.FileExists("sub/f2.txt")); - TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id))); + Assert.IsTrue(phy.FileExists("sub/f2.txt")); + TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(shadowfs + "/" + id))); string text; - using (var s = fs.OpenFile("sub/f2.txt")) + using (var s = phy.OpenFile("sub/f2.txt")) using (var r = new StreamReader(s)) text = r.ReadToEnd(); @@ -512,27 +535,30 @@ namespace Umbraco.Tests.IO var logger = Mock.Of(); var path = IOHelper.MapPath("FileSysTests"); - var appdata = IOHelper.MapPath("App_Data"); + var shadowfs = IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"); Directory.CreateDirectory(path); var scopedFileSystems = false; - var fs = new PhysicalFileSystem(path, "ignore"); - var sw = new ShadowWrapper(fs, "shadow", () => scopedFileSystems); - var swa = new[] { sw }; + var phy = new PhysicalFileSystem(path, "ignore"); + + var container = Mock.Of(); + var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems }; + var fs = fileSystems.GetFileSystem( phy); + var sw = (ShadowWrapper)fs.InnerFileSystem; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f1.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f1.txt")); + Assert.IsTrue(phy.FileExists("sub/f1.txt")); Guid id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f2.txt")); + Assert.IsFalse(phy.FileExists("sub/f2.txt")); // pretend we're another thread w/out scope scopedFileSystems = false; @@ -540,11 +566,11 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f2.txt/f2.txt", ms); scopedFileSystems = true; // pretend we have a scope - Assert.IsTrue(fs.FileExists("sub/f2.txt/f2.txt")); // other thread has written out to fs + Assert.IsTrue(phy.FileExists("sub/f2.txt/f2.txt")); // other thread has written out to fs using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f3.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f3.txt")); + Assert.IsFalse(phy.FileExists("sub/f3.txt")); scope.Complete(); @@ -570,7 +596,7 @@ namespace Umbraco.Tests.IO } // still, the rest of the changes has been applied ok - Assert.IsTrue(fs.FileExists("sub/f3.txt")); + Assert.IsTrue(phy.FileExists("sub/f3.txt")); } [Test] diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 2aee582acc..7b22d282f0 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -20,6 +19,7 @@ using static Umbraco.Tests.Cache.DistributedCache.DistributedCacheTests; namespace Umbraco.Tests.Integration { [TestFixture] + [Category("Slow")] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class ContentEventsTests : TestWithSomeContentBase { @@ -50,10 +50,10 @@ namespace Umbraco.Tests.Integration { base.Compose(); - Container.Register(_ => new TestServerRegistrar()); // localhost-only - Container.Register(new PerContainerLifetime()); + Composition.Register(_ => new TestServerRegistrar()); // localhost-only + Composition.RegisterUnique(); - Container.RegisterCollectionBuilder() + Composition.WithCollectionBuilder() .Add() .Add() .Add(); diff --git a/src/Umbraco.Tests/Issues/U9560.cs b/src/Umbraco.Tests/Issues/U9560.cs index c5748e21e0..e422cbc86b 100644 --- a/src/Umbraco.Tests/Issues/U9560.cs +++ b/src/Umbraco.Tests/Issues/U9560.cs @@ -3,7 +3,8 @@ using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Tests.Testing; -using LightInject; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Issues @@ -26,7 +27,7 @@ namespace Umbraco.Tests.Issues var aliasName = string.Empty; // read fields, same as what we do with PetaPoco Fetch - using (var db = Container.GetInstance().CreateDatabase()) + using (var db = Factory.GetInstance().CreateDatabase()) { db.OpenSharedConnection(); try @@ -54,7 +55,7 @@ namespace Umbraco.Tests.Issues Assert.AreEqual("Alias", aliasName); // try differently - using (var db = Container.GetInstance().CreateDatabase()) + using (var db = Factory.GetInstance().CreateDatabase()) { db.OpenSharedConnection(); try diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index 2cb7b1aa4e..0d60181c79 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -4,7 +4,9 @@ using System.Web.UI.WebControls; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Macros; @@ -19,14 +21,15 @@ namespace Umbraco.Tests.Macros public void Setup() { //we DO want cache enabled for these tests - var cacheHelper = new CacheHelper( - new ObjectCacheRuntimeCacheProvider(), - new StaticCacheProvider(), - NullCacheProvider.Instance, - new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); + var cacheHelper = new AppCaches( + new ObjectCacheAppCache(), + NoAppCache.Instance, + new IsolatedCaches(type => new ObjectCacheAppCache())); //Current.ApplicationContext = new ApplicationContext(cacheHelper, new ProfilingLogger(Mock.Of(), Mock.Of())); - UmbracoConfig.For.SetUmbracoSettings(SettingsForTests.GetDefaultUmbracoSettings()); + Current.Reset(); + Current.UnlockConfigs(); + Current.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings); } [TestCase("123", "IntProp", typeof(int))] diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 19aaf79581..4e791c0169 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -2,7 +2,6 @@ using System.Linq; using Moq; using System.Text; -using LightInject; using NUnit.Framework; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -14,27 +13,25 @@ using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.Services; -using Umbraco.Web.ContentApps; namespace Umbraco.Tests.Manifest { [TestFixture] public class ManifestParserTests { - private ManifestParser _parser; [SetUp] public void Setup() { Current.Reset(); - var container = Mock.Of(); - Current.Container = container; + var factory = Mock.Of(); + Current.Factory = factory; - var serviceContext = new ServiceContext( + var serviceContext = ServiceContext.CreatePartial( localizedTextService: Mock.Of()); - Mock.Get(container) + Mock.Get(factory) .Setup(x => x.GetInstance(It.IsAny())) .Returns(x => { @@ -47,7 +44,7 @@ namespace Umbraco.Tests.Manifest new RequiredValidator(Mock.Of()), new RegexValidator(Mock.Of(), null) }; - _parser = new ManifestParser(NullCacheProvider.Instance, new ManifestValueValidatorCollection(validators), Mock.Of()); + _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), Mock.Of()); } [Test] @@ -432,5 +429,21 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.AreEqual(1, db1.Sections.Length); Assert.AreEqual("forms", db1.Sections[0]); } + + [Test] + public void CanParseManifest_Sections() + { + const string json = @"{'sections': [ + { ""alias"": ""content"", ""name"": ""Content"" }, + { ""alias"": ""hello"", ""name"": ""World"" } +]}"; + + var manifest = _parser.ParseManifest(json); + Assert.AreEqual(2, manifest.Sections.Length); + Assert.AreEqual("content", manifest.Sections[0].Alias); + Assert.AreEqual("hello", manifest.Sections[1].Alias); + Assert.AreEqual("Content", manifest.Sections[0].Name); + Assert.AreEqual("World", manifest.Sections[1].Name); + } } } diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index 2a5b76e1bd..f1f46133c3 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -3,17 +3,9 @@ using System.Collections.Specialized; using System.Configuration.Provider; using System.Security.Cryptography; using System.Web.Security; -using LightInject; using Moq; using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Core.Scoping; using Umbraco.Core.Security; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Membership diff --git a/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs b/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs index 9c0560351b..47c854333a 100644 --- a/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs +++ b/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs @@ -1,7 +1,4 @@ using System; -using System.Configuration; -using System.Linq; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -11,8 +8,6 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Sync; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Cache.DistributedCache; -using Umbraco.Tests.TestHelpers.Stubs; namespace Umbraco.Tests.Misc @@ -32,7 +27,7 @@ namespace Umbraco.Tests.Misc [Test] public void NoApplicationUrlByDefault() { - var state = new RuntimeState(Mock.Of(), new Lazy(Mock.Of), new Lazy(Mock.Of), Mock.Of(), Mock.Of()); + var state = new RuntimeState(Mock.Of(), Mock.Of(), Mock.Of(), new Lazy(), new Lazy()); Assert.IsNull(state.ApplicationUrl); } @@ -51,10 +46,7 @@ namespace Umbraco.Tests.Misc var registrar = new Mock(); registrar.Setup(x => x.GetCurrentServerUmbracoApplicationUrl()).Returns("http://server1.com/umbraco"); - var state = new RuntimeState( - Mock.Of(), - new Lazy(() => registrar.Object), - new Lazy(Mock.Of), settings, globalConfig.Object); + var state = new RuntimeState(Mock.Of(), settings, globalConfig.Object, new Lazy(), new Lazy(() => registrar.Object)); state.EnsureApplicationUrl(); @@ -75,9 +67,9 @@ namespace Umbraco.Tests.Misc ApplicationUrlHelper.ApplicationUrlProvider = request => "http://server1.com/umbraco"; - - var state = new RuntimeState(Mock.Of(), new Lazy(Mock.Of), new Lazy(Mock.Of), settings, globalConfig.Object); + + var state = new RuntimeState(Mock.Of(), settings, globalConfig.Object, new Lazy(), new Lazy(() => Mock.Of())); state.EnsureApplicationUrl(); @@ -101,7 +93,7 @@ namespace Umbraco.Tests.Misc // still NOT set Assert.IsNull(url); } - + [Test] public void SetApplicationUrlFromStSettingsNoSsl() { @@ -112,8 +104,8 @@ namespace Umbraco.Tests.Misc var globalConfig = Mock.Get(SettingsForTests.GenerateMockGlobalSettings()); globalConfig.Setup(x => x.UseHttps).Returns(false); - - + + var url = ApplicationUrlHelper.TryGetApplicationUrl(settings, Mock.Of(), globalConfig.Object, Mock.Of()); Assert.AreEqual("http://mycoolhost.com/umbraco", url); @@ -129,8 +121,8 @@ namespace Umbraco.Tests.Misc var globalConfig = Mock.Get(SettingsForTests.GenerateMockGlobalSettings()); globalConfig.Setup(x => x.UseHttps).Returns(true); - - + + var url = ApplicationUrlHelper.TryGetApplicationUrl(settings, Mock.Of(), globalConfig.Object, Mock.Of()); Assert.AreEqual("https://mycoolhost.com/umbraco", url); @@ -146,13 +138,11 @@ namespace Umbraco.Tests.Misc var globalConfig = Mock.Get(SettingsForTests.GenerateMockGlobalSettings()); globalConfig.Setup(x => x.UseHttps).Returns(true); - + var url = ApplicationUrlHelper.TryGetApplicationUrl(settings, Mock.Of(), globalConfig.Object, Mock.Of()); Assert.AreEqual("httpx://whatever.com/umbraco", url); } - - } } diff --git a/src/Umbraco.Tests/Misc/UriUtilityTests.cs b/src/Umbraco.Tests/Misc/UriUtilityTests.cs index 0bd9156f20..42c69d3967 100644 --- a/src/Umbraco.Tests/Misc/UriUtilityTests.cs +++ b/src/Umbraco.Tests/Misc/UriUtilityTests.cs @@ -90,7 +90,6 @@ namespace Umbraco.Tests.Misc var settings = SettingsForTests.GenerateMockUmbracoSettings(); var requestMock = Mock.Get(settings.RequestHandler); requestMock.Setup(x => x.AddTrailingSlash).Returns(trailingSlash); - SettingsForTests.ConfigureSettings(settings); UriUtility.SetAppDomainAppVirtualPath("/"); diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index d31fcbf744..14f766fba1 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -5,17 +5,16 @@ using System.Globalization; using System.Linq; using System.Threading; using Moq; -using NUnit.Framework; using Umbraco.Core; +using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Serialization; using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; @@ -26,22 +25,15 @@ namespace Umbraco.Tests.Models [TestFixture] public class ContentTests : UmbracoTestBase { - public override void SetUp() - { - base.SetUp(); - - var config = SettingsForTests.GetDefaultUmbracoSettings(); - SettingsForTests.ConfigureSettings(config); - } - protected override void Compose() { base.Compose(); - Container.Register(_ => Mock.Of()); - Container.Register(); - Container.Register(_ => Mock.Of()); - Container.Register(_ => Mock.Of()); + Composition.Register(_ => Mock.Of()); + Composition.ComposeFileSystems(); + + Composition.Register(_ => Mock.Of()); + Composition.Register(_ => Mock.Of()); } [Test] @@ -51,7 +43,7 @@ namespace Umbraco.Tests.Models var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; const string langFr = "fr-FR"; - + contentType.Variations = ContentVariation.Culture; Assert.IsFalse(content.IsPropertyDirty("CultureInfos")); //hasn't been changed @@ -69,7 +61,7 @@ namespace Umbraco.Tests.Models Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); Assert.IsTrue(content.IsPropertyDirty("CultureInfos")); //it's true now since we've updated a name } @@ -100,7 +92,7 @@ namespace Umbraco.Tests.Models Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); content.PublishCulture(langFr); //we've set the name, now we're publishing it - Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); //it's true now since we've updated a name } @@ -206,7 +198,7 @@ namespace Umbraco.Tests.Models Assert.AreNotSame(content.Properties, clone.Properties); } - private static ProfilingLogger GetTestProfilingLogger() + private static IProfilingLogger GetTestProfilingLogger() { var logger = new DebugDiagnosticsLogger(); var profiler = new TestProfiler(); @@ -235,16 +227,13 @@ namespace Umbraco.Tests.Models content.ContentSchedule.Add(DateTime.Now, DateTime.Now.AddDays(1)); //content.ChangePublishedState(PublishedState.Published); content.SortOrder = 5; - content.Template = new Template((string) "Test Template", (string) "testTemplate") - { - Id = 88 - }; + content.TemplateId = 88; content.Trashed = false; content.UpdateDate = DateTime.Now; content.WriterId = 23; - var runtimeCache = new ObjectCacheRuntimeCacheProvider(); - runtimeCache.InsertCacheItem(content.Id.ToString(CultureInfo.InvariantCulture), () => content); + var runtimeCache = new ObjectCacheAppCache(); + runtimeCache.Insert(content.Id.ToString(CultureInfo.InvariantCulture), () => content); var proflog = GetTestProfilingLogger(); @@ -252,7 +241,7 @@ namespace Umbraco.Tests.Models { for (int j = 0; j < 1000; j++) { - var clone = runtimeCache.GetCacheItem(content.Id.ToString(CultureInfo.InvariantCulture)); + var clone = runtimeCache.Get(content.Id.ToString(CultureInfo.InvariantCulture)); } } @@ -297,15 +286,12 @@ namespace Umbraco.Tests.Models content.Path = "-1,4,10"; content.ContentSchedule.Add(DateTime.Now, DateTime.Now.AddDays(1)); content.SortOrder = 5; - content.Template = new Template((string) "Test Template", (string) "testTemplate") - { - Id = 88 - }; + content.TemplateId = 88; content.Trashed = false; content.UpdateDate = DateTime.Now; content.WriterId = 23; - + // Act var clone = (Content)content.DeepClone(); @@ -340,8 +326,8 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.PublishedState, content.PublishedState); Assert.AreEqual(clone.SortOrder, content.SortOrder); Assert.AreEqual(clone.PublishedState, content.PublishedState); - Assert.AreNotSame(clone.Template, content.Template); - Assert.AreEqual(clone.Template, content.Template); + Assert.AreNotSame(clone.TemplateId, content.TemplateId); + Assert.AreEqual(clone.TemplateId, content.TemplateId); Assert.AreEqual(clone.Trashed, content.Trashed); Assert.AreEqual(clone.UpdateDate, content.UpdateDate); Assert.AreEqual(clone.VersionId, content.VersionId); @@ -402,7 +388,7 @@ namespace Umbraco.Tests.Models content.SetCultureName("Hello", "en-US"); content.SetCultureName("World", "es-ES"); content.PublishCulture("en-US"); - + var i = 200; foreach (var property in content.Properties) { @@ -416,16 +402,12 @@ namespace Umbraco.Tests.Models content.Level = 3; content.Path = "-1,4,10"; content.SortOrder = 5; - content.Template = new Template((string)"Test Template", (string)"testTemplate") - { - Id = 88 - }; - + content.TemplateId = 88; + content.Trashed = true; content.UpdateDate = DateTime.Now; content.WriterId = 23; - - content.Template.UpdateDate = DateTime.Now; //update a child object + content.ContentType.UpdateDate = DateTime.Now; //update a child object // Act @@ -433,18 +415,18 @@ namespace Umbraco.Tests.Models // Assert Assert.IsTrue(content.WasDirty()); - Assert.IsTrue(content.WasPropertyDirty("Id")); - Assert.IsTrue(content.WasPropertyDirty("CreateDate")); - Assert.IsTrue(content.WasPropertyDirty("CreatorId")); - Assert.IsTrue(content.WasPropertyDirty("Key")); - Assert.IsTrue(content.WasPropertyDirty("Level")); - Assert.IsTrue(content.WasPropertyDirty("Path")); - Assert.IsTrue(content.WasPropertyDirty("ContentSchedule")); - Assert.IsTrue(content.WasPropertyDirty("SortOrder")); - Assert.IsTrue(content.WasPropertyDirty("Template")); - Assert.IsTrue(content.WasPropertyDirty("Trashed")); - Assert.IsTrue(content.WasPropertyDirty("UpdateDate")); - Assert.IsTrue(content.WasPropertyDirty("WriterId")); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.Id))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.CreateDate))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.CreatorId))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.Key))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.Level))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.Path))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.ContentSchedule))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.SortOrder))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.TemplateId))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.Trashed))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.UpdateDate))); + Assert.IsTrue(content.WasPropertyDirty(nameof(Content.WriterId))); foreach (var prop in content.Properties) { Assert.IsTrue(prop.WasDirty()); @@ -465,7 +447,6 @@ namespace Umbraco.Tests.Models Assert.IsTrue(culture.Value.WasPropertyDirty("Date")); } //verify child objects were reset too - Assert.IsTrue(content.Template.WasPropertyDirty("UpdateDate")); Assert.IsTrue(content.ContentType.WasPropertyDirty("UpdateDate")); } @@ -492,10 +473,7 @@ namespace Umbraco.Tests.Models content.ContentSchedule.Add(DateTime.Now, DateTime.Now.AddDays(1)); //content.ChangePublishedState(PublishedState.Publishing); content.SortOrder = 5; - content.Template = new Template((string) "Test Template", (string) "testTemplate") - { - Id = 88 - }; + content.TemplateId = 88; content.Trashed = false; content.UpdateDate = DateTime.Now; content.WriterId = 23; diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index 6ef28d8290..d9e65ba6c6 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -93,7 +93,7 @@ namespace Umbraco.Tests.Models } } - private static ProfilingLogger GetTestProfilingLogger() + private static IProfilingLogger GetTestProfilingLogger() { var logger = new DebugDiagnosticsLogger(); var profiler = new TestProfiler(); diff --git a/src/Umbraco.Tests/Models/ContentXmlTest.cs b/src/Umbraco.Tests/Models/ContentXmlTest.cs index ab318ec1cb..5cc86a4322 100644 --- a/src/Umbraco.Tests/Models/ContentXmlTest.cs +++ b/src/Umbraco.Tests/Models/ContentXmlTest.cs @@ -2,7 +2,9 @@ using System.Xml.Linq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -29,7 +31,7 @@ namespace Umbraco.Tests.Models var urlName = content.GetUrlSegment(new[]{new DefaultUrlSegmentProvider() }); // Act - XElement element = content.ToXml(); + XElement element = content.ToXml(Factory.GetInstance()); // Assert Assert.That(element, Is.Not.Null); @@ -49,7 +51,7 @@ namespace Umbraco.Tests.Models Assert.AreEqual(content.GetCreatorProfile(ServiceContext.UserService).Name, (string)element.Attribute("creatorName")); Assert.AreEqual(content.GetWriterProfile(ServiceContext.UserService).Name, (string)element.Attribute("writerName")); Assert.AreEqual(content.WriterId.ToString(), (string)element.Attribute("writerID")); - Assert.AreEqual(content.Template == null ? "0" : content.Template.Id.ToString(), (string)element.Attribute("template")); + Assert.AreEqual(content.TemplateId.ToString(), (string)element.Attribute("template")); Assert.AreEqual(content.Properties["title"].GetValue().ToString(), element.Elements("title").Single().Value); Assert.AreEqual(content.Properties["bodyText"].GetValue().ToString(), element.Elements("bodyText").Single().Value); diff --git a/src/Umbraco.Tests/Models/MacroTests.cs b/src/Umbraco.Tests/Models/MacroTests.cs index b4060134bd..d980cd1d58 100644 --- a/src/Umbraco.Tests/Models/MacroTests.cs +++ b/src/Umbraco.Tests/Models/MacroTests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using NUnit.Framework; +using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Tests.TestHelpers; @@ -13,8 +14,9 @@ namespace Umbraco.Tests.Models [SetUp] public void Init() { - var config = SettingsForTests.GetDefaultUmbracoSettings(); - SettingsForTests.ConfigureSettings(config); + Current.Reset(); + Current.UnlockConfigs(); + Current.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings); } [Test] diff --git a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs index 23a9eafe76..57d38e342e 100644 --- a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using AutoMapper; using NUnit.Framework; @@ -9,7 +8,6 @@ using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; -using LightInject; namespace Umbraco.Tests.Models.Mapping { @@ -22,16 +20,16 @@ namespace Umbraco.Tests.Models.Mapping base.Compose(); var manifestBuilder = new ManifestParser( - CacheHelper.CreateDisabledCacheHelper().RuntimeCache, + AppCaches.Disabled, new ManifestValueValidatorCollection(Enumerable.Empty()), - Logger) + Composition.Logger) { Path = TestHelper.CurrentAssemblyDirectory }; - Container.Register(_ => manifestBuilder); + Composition.RegisterUnique(_ => manifestBuilder); Func> typeListProducerList = Enumerable.Empty; - Container.GetInstance() + Composition.WithCollectionBuilder() .Clear() .Add(typeListProducerList); } @@ -39,7 +37,7 @@ namespace Umbraco.Tests.Models.Mapping [Test] public void AssertConfigurationIsValid() { - var profiles = Container.GetAllInstances().ToArray(); + var profiles = Factory.GetAllInstances().ToArray(); var config = new MapperConfiguration(cfg => { diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 76e618ea26..b2d1440010 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; using System.Linq; using AutoMapper; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -55,13 +53,13 @@ namespace Umbraco.Tests.Models.Mapping var dataEditors = new DataEditorCollection(editors); _editorsMock = new Mock(dataEditors); _editorsMock.Setup(x => x[It.IsAny()]).Returns(editors[0]); - Container.RegisterSingleton(f => _editorsMock.Object); + Composition.RegisterUnique(f => _editorsMock.Object); - Container.RegisterSingleton(_ => _contentTypeService.Object); - Container.RegisterSingleton(_ => _contentService.Object); - Container.RegisterSingleton(_ => _dataTypeService.Object); - Container.RegisterSingleton(_ => _entityService.Object); - Container.RegisterSingleton(_ => _fileService.Object); + Composition.RegisterUnique(_ => _contentTypeService.Object); + Composition.RegisterUnique(_ => _contentService.Object); + Composition.RegisterUnique(_ => _dataTypeService.Object); + Composition.RegisterUnique(_ => _entityService.Object); + Composition.RegisterUnique(_ => _fileService.Object); } [Test] diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index 03a59584a3..cab9de9204 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -12,7 +12,6 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Core.Composing; using Umbraco.Tests.Testing; using Current = Umbraco.Web.Composing.Current; @@ -26,7 +25,7 @@ namespace Umbraco.Tests.Models.Mapping { base.Compose(); - Container.RegisterSingleton(f => Mock.Of()); + Composition.RegisterUnique(f => Mock.Of()); } [DataEditor("Test.Test", "Test", "~/Test.html")] @@ -158,7 +157,7 @@ namespace Umbraco.Tests.Models.Mapping AssertBasicProperty(invariantContent, p); AssertDisplayProperty(invariantContent, p); } - + Assert.AreEqual(content.PropertyGroups.Count(), invariantContent.Tabs.Count()); } diff --git a/src/Umbraco.Tests/Models/MediaXmlTest.cs b/src/Umbraco.Tests/Models/MediaXmlTest.cs index 492803ce17..d0d00c64a6 100644 --- a/src/Umbraco.Tests/Models/MediaXmlTest.cs +++ b/src/Umbraco.Tests/Models/MediaXmlTest.cs @@ -3,6 +3,7 @@ using System.Xml.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -29,7 +30,12 @@ namespace Umbraco.Tests.Models // reference, so static ctor runs, so event handlers register // and then, this will reset the width, height... because the file does not exist, of course ;-( - var ignored = new FileUploadPropertyEditor(Mock.Of(), new MediaFileSystem(Mock.Of()), Mock.Of()); + var logger = Mock.Of(); + var scheme = Mock.Of(); + var config = Mock.Of(); + + var mediaFileSystem = new MediaFileSystem(Mock.Of(), config, scheme, logger); + var ignored = new FileUploadPropertyEditor(Mock.Of(), mediaFileSystem, config); var media = MockedMedia.CreateMediaImage(mediaType, -1); media.WriterId = -1; // else it's zero and that's not a user and it breaks the tests @@ -45,7 +51,7 @@ namespace Umbraco.Tests.Models var urlName = media.GetUrlSegment(new[] { new DefaultUrlSegmentProvider() }); // Act - XElement element = media.ToXml(); + XElement element = media.ToXml(Factory.GetInstance()); // Assert Assert.That(element, Is.Not.Null); diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs index 76ea804c57..c09f2e9460 100644 --- a/src/Umbraco.Tests/Models/MemberTests.cs +++ b/src/Umbraco.Tests/Models/MemberTests.cs @@ -2,9 +2,10 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; +using Umbraco.Core.Composing; using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; using Umbraco.Core.Serialization; +using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Models @@ -12,6 +13,15 @@ namespace Umbraco.Tests.Models [TestFixture] public class MemberTests { + [SetUp] + public void Setup() + { + Current.Reset(); + Current.UnlockConfigs(); + Current.Configs.Add(SettingsForTests.GetDefaultGlobalSettings); + Current.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings); + } + [Test] public void Can_Deep_Clone() { diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs index 5e982633d2..797b79381a 100644 --- a/src/Umbraco.Tests/Models/UserTests.cs +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -2,14 +2,24 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; +using Umbraco.Core.Composing; using Umbraco.Core.Models.Membership; using Umbraco.Core.Serialization; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Models { [TestFixture] public class UserTests { + [SetUp] + public void Setup() + { + Current.Reset(); + Current.UnlockConfigs(); + Current.Configs.Add(SettingsForTests.GetDefaultGlobalSettings); + } + [Test] public void Can_Deep_Clone() { diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index e6f4e53d26..103fd35074 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -1,9 +1,9 @@ using System; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -27,8 +27,13 @@ namespace Umbraco.Tests.Models // need to be able to retrieve them all... Current.Reset(); - var container = Mock.Of(); - Current.Container = container; + + var configs = new Configs(); + configs.Add(SettingsForTests.GetDefaultGlobalSettings); + configs.Add(SettingsForTests.GetDefaultUmbracoSettings); + + var factory = Mock.Of(); + Current.Factory = factory; var dataEditors = new DataEditorCollection(new IDataEditor[] { @@ -46,20 +51,20 @@ namespace Umbraco.Tests.Models .Setup(x => x.GetDataType(It.IsAny())) .Returns(x => dataType); - var serviceContext = new ServiceContext( + var serviceContext = ServiceContext.CreatePartial( dataTypeService: dataTypeService, localizedTextService: Mock.Of()); - Mock.Get(container) + Mock.Get(factory) .Setup(x => x.GetInstance(It.IsAny())) .Returns(x => { + if (x == typeof(Configs)) return configs; if (x == typeof(PropertyEditorCollection)) return propertyEditors; if (x == typeof(ServiceContext)) return serviceContext; if (x == typeof(ILocalizedTextService)) return serviceContext.LocalizationService; - throw new Exception("oops"); + throw new NotSupportedException(x.FullName); }); - } [Test] @@ -430,7 +435,7 @@ namespace Umbraco.Tests.Models Assert.IsTrue(content.IsCultureAvailable(langUk)); Assert.IsFalse(content.IsCulturePublished(langUk)); Assert.IsNull(content.GetPublishName(langUk)); - Assert.IsNull(content.GetPublishDate(langUk)); // not published + Assert.IsNull(content.GetPublishDate(langUk)); // not published Assert.IsFalse(content.IsCultureAvailable(langEs)); Assert.IsFalse(content.IsCultureEdited(langEs)); // not avail, so... not edited @@ -438,7 +443,7 @@ namespace Umbraco.Tests.Models // not published! Assert.IsNull(content.GetPublishName(langEs)); - Assert.IsNull(content.GetPublishDate(langEs)); + Assert.IsNull(content.GetPublishDate(langEs)); // cannot test IsCultureEdited here - as that requires the content service and repository // see: ContentServiceTests.Can_SaveRead_Variations diff --git a/src/Umbraco.Tests/Packaging/CreatedPackagesRepositoryTests.cs b/src/Umbraco.Tests/Packaging/CreatedPackagesRepositoryTests.cs new file mode 100644 index 0000000000..010572abec --- /dev/null +++ b/src/Umbraco.Tests/Packaging/CreatedPackagesRepositoryTests.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; +using Umbraco.Core.Models.Packaging; +using Umbraco.Core.Packaging; +using Umbraco.Core.Services; +using Umbraco.Tests.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Packaging +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] + public class CreatedPackagesRepositoryTests : TestWithDatabaseBase + { + private Guid _testBaseFolder; + + public override void SetUp() + { + base.SetUp(); + _testBaseFolder = Guid.NewGuid(); + } + + public override void TearDown() + { + base.TearDown(); + + //clear out files/folders + Directory.Delete(IOHelper.MapPath("~/" + _testBaseFolder), true); + } + + public ICreatedPackagesRepository PackageBuilder => new PackagesRepository( + ServiceContext.ContentService, ServiceContext.ContentTypeService, ServiceContext.DataTypeService, + ServiceContext.FileService, ServiceContext.MacroService, ServiceContext.LocalizationService, + Factory.GetInstance(), Logger, + "createdPackages.config", + //temp paths + tempFolderPath: "~/" + _testBaseFolder + "/temp", + packagesFolderPath: "~/" + _testBaseFolder + "/packages", + mediaFolderPath: "~/" + _testBaseFolder + "/media"); + + [Test] + public void Delete() + { + var def1 = new PackageDefinition + { + Name = "test", + Url = "http://test.com", + Author = "Someone", + AuthorUrl = "http://test.com" + }; + + var result = PackageBuilder.SavePackage(def1); + Assert.IsTrue(result); + + PackageBuilder.Delete(def1.Id); + + def1 = PackageBuilder.GetById(def1.Id); + Assert.IsNull(def1); + } + + [Test] + public void Create_New() + { + var def1 = new PackageDefinition + { + Name = "test", + Url = "http://test.com", + Author = "Someone", + AuthorUrl = "http://test.com" + }; + + var result = PackageBuilder.SavePackage(def1); + + Assert.IsTrue(result); + Assert.AreEqual(1, def1.Id); + Assert.AreNotEqual(default(Guid).ToString(), def1.PackageId); + + var def2 = new PackageDefinition + { + Name = "test2", + Url = "http://test2.com", + Author = "Someone2", + AuthorUrl = "http://test2.com" + }; + + result = PackageBuilder.SavePackage(def2); + + Assert.IsTrue(result); + Assert.AreEqual(2, def2.Id); + Assert.AreNotEqual(default(Guid).ToString(), def2.PackageId); + } + + [Test] + public void Update_Not_Found() + { + var def = new PackageDefinition + { + Id = 3, //doesn't exist + Name = "test", + Url = "http://test.com", + Author = "Someone", + AuthorUrl = "http://test.com" + }; + + var result = PackageBuilder.SavePackage(def); + + Assert.IsFalse(result); + } + + [Test] + public void Update() + { + var def = new PackageDefinition + { + Name = "test", + Url = "http://test.com", + Author = "Someone", + AuthorUrl = "http://test.com" + }; + var result = PackageBuilder.SavePackage(def); + + def.Name = "updated"; + def.Files = new List {"hello.txt", "world.png"}; + result = PackageBuilder.SavePackage(def); + Assert.IsTrue(result); + + //re-get + def = PackageBuilder.GetById(def.Id); + Assert.AreEqual("updated", def.Name); + Assert.AreEqual(2, def.Files.Count); + //TODO: There's a whole lot more assertions to be done + + } + + [Test] + public void Export() + { + var file1 = $"~/{_testBaseFolder}/App_Plugins/MyPlugin/package.manifest"; + var file2 = $"~/{_testBaseFolder}/App_Plugins/MyPlugin/styles.css"; + var mappedFile1 = IOHelper.MapPath(file1); + var mappedFile2 = IOHelper.MapPath(file2); + Directory.CreateDirectory(Path.GetDirectoryName(mappedFile1)); + Directory.CreateDirectory(Path.GetDirectoryName(mappedFile2)); + File.WriteAllText(mappedFile1, "hello world"); + File.WriteAllText(mappedFile2, "hello world"); + + var def = new PackageDefinition + { + Name = "test", + Url = "http://test.com", + Author = "Someone", + AuthorUrl = "http://test.com", + Files = new List { file1, file2 }, + Actions = "" + }; + var result = PackageBuilder.SavePackage(def); + Assert.IsTrue(result); + Assert.IsTrue(def.PackagePath.IsNullOrWhiteSpace()); + + var zip = PackageBuilder.ExportPackage(def); + + def = PackageBuilder.GetById(def.Id); //re-get + Assert.IsNotNull(def.PackagePath); + + using (var archive = ZipFile.OpenRead(IOHelper.MapPath(zip))) + { + Assert.AreEqual(3, archive.Entries.Count); + + //the 2 files we manually added + Assert.IsNotNull(archive.Entries.Where(x => x.Name == "package.manifest")); + Assert.IsNotNull(archive.Entries.Where(x => x.Name == "styles.css")); + + //this is the actual package definition/manifest (not the developer manifest!) + var packageXml = archive.Entries.FirstOrDefault(x => x.Name == "package.xml"); + Assert.IsNotNull(packageXml); + + using (var stream = packageXml.Open()) + { + var xml = XDocument.Load(stream); + Assert.AreEqual("umbPackage", xml.Root.Name.ToString()); + Assert.AreEqual(2, xml.Root.Element("files").Elements("file").Count()); + + Assert.AreEqual("", xml.Element("umbPackage").Element("Actions").ToString(SaveOptions.DisableFormatting)); + + //TODO: There's a whole lot more assertions to be done + } + } + } + } +} diff --git a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs b/src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs similarity index 77% rename from src/Umbraco.Tests/Services/Importing/PackageImportTests.cs rename to src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs index b33ff83c4a..ba71caad95 100644 --- a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs +++ b/src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs @@ -1,24 +1,29 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Xml.Linq; using NUnit.Framework; -using Umbraco.Core.Models; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Packaging; +using Umbraco.Core.Packaging; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Tests.Services; +using Umbraco.Tests.Services.Importing; using Umbraco.Tests.Testing; -using LightInject; -namespace Umbraco.Tests.Services.Importing +namespace Umbraco.Tests.Packaging { [TestFixture] + [Category("Slow")] [Apartment(ApartmentState.STA)] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class PackageImportTests : TestWithSomeContentBase + public class PackageDataInstallationTests : TestWithSomeContentBase { [HideFromTypeFinder] public class Editor1 : DataEditor @@ -49,13 +54,25 @@ namespace Umbraco.Tests.Services.Importing // pollute everything, they are ignored by the type finder and explicitely // added to the editors collection - Container.GetInstance() + Composition.WithCollectionBuilder() .Add() .Add(); } + protected override void ComposeApplication(bool withApplication) + { + base.ComposeApplication(withApplication); + + if (!withApplication) return; + + // re-register with actual media fs + Composition.ComposeFileSystems(); + } + + private PackageDataInstallation PackageDataInstallation => Factory.GetInstance(); + [Test] - public void PackagingService_Can_Import_uBlogsy_ContentTypes_And_Verify_Structure() + public void Can_Import_uBlogsy_ContentTypes_And_Verify_Structure() { // Arrange string strXml = ImportResources.uBlogsy_Package; @@ -63,12 +80,11 @@ namespace Umbraco.Tests.Services.Importing var dataTypeElement = xml.Descendants("DataTypes").First(); var templateElement = xml.Descendants("Templates").First(); var docTypeElement = xml.Descendants("DocumentTypes").First(); - var packagingService = ServiceContext.PackagingService; // Act - var dataTypes = packagingService.ImportDataTypeDefinitions(dataTypeElement); - var templates = packagingService.ImportTemplates(templateElement); - var contentTypes = packagingService.ImportContentTypes(docTypeElement); + var dataTypes = PackageDataInstallation.ImportDataTypes(dataTypeElement.Elements("DataType").ToList(), 0); + var templates = PackageDataInstallation.ImportTemplates(templateElement.Elements("Template").ToList(), 0); + var contentTypes = PackageDataInstallation.ImportDocumentTypes(docTypeElement.Elements("DocumentType"), 0); var numberOfTemplates = (from doc in templateElement.Elements("Template") select doc).Count(); var numberOfDocTypes = (from doc in docTypeElement.Elements("DocumentType") select doc).Count(); @@ -103,7 +119,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Inherited_ContentTypes_And_Verify_PropertyTypes_UniqueIds() + public void Can_Import_Inherited_ContentTypes_And_Verify_PropertyTypes_UniqueIds() { // Arrange var strXml = ImportResources.InheritedDocTypes_Package; @@ -111,12 +127,11 @@ namespace Umbraco.Tests.Services.Importing var dataTypeElement = xml.Descendants("DataTypes").First(); var templateElement = xml.Descendants("Templates").First(); var docTypeElement = xml.Descendants("DocumentTypes").First(); - var packagingService = ServiceContext.PackagingService; // Act - var dataTypes = packagingService.ImportDataTypeDefinitions(dataTypeElement); - var templates = packagingService.ImportTemplates(templateElement); - var contentTypes = packagingService.ImportContentTypes(docTypeElement); + var dataTypes = PackageDataInstallation.ImportDataTypes(dataTypeElement.Elements("DataType").ToList(), 0); + var templates = PackageDataInstallation.ImportTemplates(templateElement.Elements("Template").ToList(), 0); + var contentTypes = PackageDataInstallation.ImportDocumentTypes(docTypeElement.Elements("DocumentType"), 0); // Assert var mRBasePage = contentTypes.First(x => x.Alias == "MRBasePage"); @@ -129,7 +144,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Inherited_ContentTypes_And_Verify_PropertyGroups_And_PropertyTypes() + public void Can_Import_Inherited_ContentTypes_And_Verify_PropertyGroups_And_PropertyTypes() { // Arrange string strXml = ImportResources.InheritedDocTypes_Package; @@ -137,12 +152,11 @@ namespace Umbraco.Tests.Services.Importing var dataTypeElement = xml.Descendants("DataTypes").First(); var templateElement = xml.Descendants("Templates").First(); var docTypeElement = xml.Descendants("DocumentTypes").First(); - var packagingService = ServiceContext.PackagingService; // Act - var dataTypes = packagingService.ImportDataTypeDefinitions(dataTypeElement); - var templates = packagingService.ImportTemplates(templateElement); - var contentTypes = packagingService.ImportContentTypes(docTypeElement); + var dataTypes = PackageDataInstallation.ImportDataTypes(dataTypeElement.Elements("DataType").ToList(), 0); + var templates = PackageDataInstallation.ImportTemplates(templateElement.Elements("Template").ToList(), 0); + var contentTypes = PackageDataInstallation.ImportDocumentTypes(docTypeElement.Elements("DocumentType"), 0); var numberOfDocTypes = (from doc in docTypeElement.Elements("DocumentType") select doc).Count(); @@ -169,17 +183,17 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Template_Package_Xml() + public void Can_Import_Template_Package_Xml() { // Arrange string strXml = ImportResources.StandardMvc_Package; var xml = XElement.Parse(strXml); var element = xml.Descendants("Templates").First(); - var packagingService = ServiceContext.PackagingService; + var init = ServiceContext.FileService.GetTemplates().Count(); // Act - var templates = packagingService.ImportTemplates(element); + var templates = PackageDataInstallation.ImportTemplates(element.Elements("Template").ToList(), 0); var numberOfTemplates = (from doc in element.Elements("Template") select doc).Count(); var allTemplates = ServiceContext.FileService.GetTemplates(); @@ -193,16 +207,16 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Single_Template() + public void Can_Import_Single_Template() { // Arrange string strXml = ImportResources.StandardMvc_Package; var xml = XElement.Parse(strXml); - var element = xml.Descendants("Templates").First().Element("Template"); - var packagingService = ServiceContext.PackagingService; + var element = xml.Descendants("Templates").First(); + // Act - var templates = packagingService.ImportTemplates(element); + var templates = PackageDataInstallation.ImportTemplate(element.Elements("Template").First(), 0); // Assert Assert.That(templates, Is.Not.Null); @@ -211,7 +225,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_StandardMvc_ContentTypes_Package_Xml() + public void Can_Import_StandardMvc_ContentTypes_Package_Xml() { // Arrange string strXml = ImportResources.StandardMvc_Package; @@ -219,12 +233,12 @@ namespace Umbraco.Tests.Services.Importing var dataTypeElement = xml.Descendants("DataTypes").First(); var templateElement = xml.Descendants("Templates").First(); var docTypeElement = xml.Descendants("DocumentTypes").First(); - var packagingService = ServiceContext.PackagingService; + // Act - var dataTypeDefinitions = packagingService.ImportDataTypeDefinitions(dataTypeElement); - var templates = packagingService.ImportTemplates(templateElement); - var contentTypes = packagingService.ImportContentTypes(docTypeElement); + var dataTypeDefinitions = PackageDataInstallation.ImportDataTypes(dataTypeElement.Elements("DataType").ToList(), 0); + var templates = PackageDataInstallation.ImportTemplates(templateElement.Elements("Template").ToList(), 0); + var contentTypes = PackageDataInstallation.ImportDocumentTypes(docTypeElement.Elements("DocumentType"), 0); var numberOfDocTypes = (from doc in docTypeElement.Elements("DocumentType") select doc).Count(); // Assert @@ -247,7 +261,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_StandardMvc_ContentTypes_And_Templates_Xml() + public void Can_Import_StandardMvc_ContentTypes_And_Templates_Xml() { // Arrange string strXml = ImportResources.StandardMvc_Package; @@ -257,13 +271,13 @@ namespace Umbraco.Tests.Services.Importing var docTypeElement = xml.Descendants("DocumentTypes").First(); // Act - var dataTypeDefinitions = ServiceContext.PackagingService.ImportDataTypeDefinitions(dataTypeElement); - var templates = ServiceContext.PackagingService.ImportTemplates(templateElement); - var contentTypes = ServiceContext.PackagingService.ImportContentTypes(docTypeElement); + var dataTypeDefinitions = PackageDataInstallation.ImportDataTypes(dataTypeElement.Elements("DataType").ToList(), 0); + var templates = PackageDataInstallation.ImportTemplates(templateElement.Elements("Template").ToList(), 0); + var contentTypes = PackageDataInstallation.ImportDocumentTypes(docTypeElement.Elements("DocumentType"), 0); var numberOfDocTypes = (from doc in docTypeElement.Elements("DocumentType") select doc).Count(); //Assert - Re-Import contenttypes doesn't throw - Assert.DoesNotThrow(() => ServiceContext.PackagingService.ImportContentTypes(docTypeElement)); + Assert.DoesNotThrow(() => PackageDataInstallation.ImportDocumentTypes(docTypeElement.Elements("DocumentType"), 0)); Assert.That(contentTypes.Count(), Is.EqualTo(numberOfDocTypes)); Assert.That(dataTypeDefinitions, Is.Not.Null); Assert.That(dataTypeDefinitions.Any(), Is.True); @@ -271,7 +285,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Fanoe_Starterkit_ContentTypes_And_Templates_Xml() + public void Can_Import_Fanoe_Starterkit_ContentTypes_And_Templates_Xml() { // Arrange string strXml = ImportResources.Fanoe_Package; @@ -281,13 +295,13 @@ namespace Umbraco.Tests.Services.Importing var docTypeElement = xml.Descendants("DocumentTypes").First(); // Act - var dataTypeDefinitions = ServiceContext.PackagingService.ImportDataTypeDefinitions(dataTypeElement); - var templates = ServiceContext.PackagingService.ImportTemplates(templateElement); - var contentTypes = ServiceContext.PackagingService.ImportContentTypes(docTypeElement); + var dataTypeDefinitions = PackageDataInstallation.ImportDataTypes(dataTypeElement.Elements("DataType").ToList(), 0); + var templates = PackageDataInstallation.ImportTemplates(templateElement.Elements("Template").ToList(), 0); + var contentTypes = PackageDataInstallation.ImportDocumentTypes(docTypeElement.Elements("DocumentType"), 0); var numberOfDocTypes = (from doc in docTypeElement.Elements("DocumentType") select doc).Count(); //Assert - Re-Import contenttypes doesn't throw - Assert.DoesNotThrow(() => ServiceContext.PackagingService.ImportContentTypes(docTypeElement)); + Assert.DoesNotThrow(() => PackageDataInstallation.ImportDocumentTypes(docTypeElement.Elements("DocumentType"), 0)); Assert.That(contentTypes.Count(), Is.EqualTo(numberOfDocTypes)); Assert.That(dataTypeDefinitions, Is.Not.Null); Assert.That(dataTypeDefinitions.Any(), Is.True); @@ -295,7 +309,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Content_Package_Xml() + public void Can_Import_Content_Package_Xml() { // Arrange string strXml = ImportResources.StandardMvc_Package; @@ -303,12 +317,13 @@ namespace Umbraco.Tests.Services.Importing var dataTypeElement = xml.Descendants("DataTypes").First(); var docTypesElement = xml.Descendants("DocumentTypes").First(); var element = xml.Descendants("DocumentSet").First(); - var packagingService = ServiceContext.PackagingService; + var packageDocument = CompiledPackageDocument.Create(element); // Act - var dataTypeDefinitions = packagingService.ImportDataTypeDefinitions(dataTypeElement); - var contentTypes = packagingService.ImportContentTypes(docTypesElement); - var contents = packagingService.ImportContent(element); + var dataTypeDefinitions = PackageDataInstallation.ImportDataTypes(dataTypeElement.Elements("DataType").ToList(), 0); + var contentTypes = PackageDataInstallation.ImportDocumentTypes(docTypesElement.Elements("DocumentType"), 0); + var importedContentTypes = contentTypes.ToDictionary(x => x.Alias, x => x); + var contents = PackageDataInstallation.ImportContent(packageDocument, -1, importedContentTypes, 0); var numberOfDocs = (from doc in element.Descendants() where (string) doc.Attribute("isDoc") == "" select doc).Count(); @@ -322,7 +337,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_CheckboxList_Content_Package_Xml_With_Property_Editor_Aliases() + public void Can_Import_CheckboxList_Content_Package_Xml_With_Property_Editor_Aliases() { AssertCheckBoxListTests(ImportResources.CheckboxList_Content_Package); } @@ -336,12 +351,13 @@ namespace Umbraco.Tests.Services.Importing var dataTypeElement = xml.Descendants("DataTypes").First(); var docTypesElement = xml.Descendants("DocumentTypes").First(); var element = xml.Descendants("DocumentSet").First(); - var packagingService = ServiceContext.PackagingService; + var packageDocument = CompiledPackageDocument.Create(element); // Act - var dataTypeDefinitions = packagingService.ImportDataTypeDefinitions(dataTypeElement); - var contentTypes = packagingService.ImportContentTypes(docTypesElement); - var contents = packagingService.ImportContent(element); + var dataTypeDefinitions = PackageDataInstallation.ImportDataTypes(dataTypeElement.Elements("DataType").ToList(), 0); + var contentTypes = PackageDataInstallation.ImportDocumentTypes(docTypesElement.Elements("DocumentType"), 0); + var importedContentTypes = contentTypes.ToDictionary(x => x.Alias, x => x); + var contents = PackageDataInstallation.ImportContent(packageDocument, -1, importedContentTypes, 0); var numberOfDocs = (from doc in element.Descendants() where (string)doc.Attribute("isDoc") == "" select doc).Count(); @@ -365,16 +381,16 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Templates_Package_Xml_With_Invalid_Master() + public void Can_Import_Templates_Package_Xml_With_Invalid_Master() { // Arrange string strXml = ImportResources.XsltSearch_Package; var xml = XElement.Parse(strXml); var templateElement = xml.Descendants("Templates").First(); - var packagingService = ServiceContext.PackagingService; + // Act - var templates = packagingService.ImportTemplates(templateElement); + var templates = PackageDataInstallation.ImportTemplates(templateElement.Elements("Template").ToList(), 0); var numberOfTemplates = (from doc in templateElement.Elements("Template") select doc).Count(); // Assert @@ -383,15 +399,15 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Single_DocType() + public void Can_Import_Single_DocType() { // Arrange string strXml = ImportResources.SingleDocType; var docTypeElement = XElement.Parse(strXml); - var packagingService = ServiceContext.PackagingService; + // Act - var contentTypes = packagingService.ImportContentTypes(docTypeElement); + var contentTypes = PackageDataInstallation.ImportDocumentType(docTypeElement, 0); // Assert Assert.That(contentTypes.Any(), Is.True); @@ -400,17 +416,18 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Export_Single_DocType() + public void Can_Export_Single_DocType() { // Arrange string strXml = ImportResources.SingleDocType; var docTypeElement = XElement.Parse(strXml); - var packagingService = ServiceContext.PackagingService; + + var serializer = Factory.GetInstance(); // Act - var contentTypes = packagingService.ImportContentTypes(docTypeElement); + var contentTypes = PackageDataInstallation.ImportDocumentType(docTypeElement, 0); var contentType = contentTypes.FirstOrDefault(); - var element = packagingService.Export(contentType); + var element = serializer.Serialize(contentType); // Assert Assert.That(element, Is.Not.Null); @@ -423,15 +440,15 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_ReImport_Single_DocType() + public void Can_ReImport_Single_DocType() { // Arrange string strXml = ImportResources.SingleDocType; var docTypeElement = XElement.Parse(strXml); // Act - var contentTypes = ServiceContext.PackagingService.ImportContentTypes(docTypeElement); - var contentTypesUpdated = ServiceContext.PackagingService.ImportContentTypes(docTypeElement); + var contentTypes = PackageDataInstallation.ImportDocumentType(docTypeElement, 0); + var contentTypesUpdated = PackageDataInstallation.ImportDocumentType(docTypeElement, 0); // Assert Assert.That(contentTypes.Any(), Is.True); @@ -446,14 +463,14 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_ReImport_Templates_To_Update() + public void Can_ReImport_Templates_To_Update() { var newPackageXml = XElement.Parse(ImportResources.TemplateOnly_Package); var updatedPackageXml = XElement.Parse(ImportResources.TemplateOnly_Updated_Package); var templateElement = newPackageXml.Descendants("Templates").First(); var templateElementUpdated = updatedPackageXml.Descendants("Templates").First(); - var packagingService = ServiceContext.PackagingService; + var fileService = ServiceContext.FileService; // kill default test data @@ -461,8 +478,8 @@ namespace Umbraco.Tests.Services.Importing // Act var numberOfTemplates = (from doc in templateElement.Elements("Template") select doc).Count(); - var templates = packagingService.ImportTemplates(templateElement); - var templatesAfterUpdate = packagingService.ImportTemplates(templateElementUpdated); + var templates = PackageDataInstallation.ImportTemplates(templateElement.Elements("Template").ToList(), 0); + var templatesAfterUpdate = PackageDataInstallation.ImportTemplates(templateElementUpdated.Elements("Template").ToList(), 0); var allTemplates = fileService.GetTemplates(); // Assert @@ -474,7 +491,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_DictionaryItems() + public void Can_Import_DictionaryItems() { // Arrange const string expectedEnglishParentValue = "ParentValue"; @@ -488,7 +505,7 @@ namespace Umbraco.Tests.Services.Importing AddLanguages(); // Act - ServiceContext.PackagingService.ImportDictionaryItems(dictionaryItemsElement); + PackageDataInstallation.ImportDictionaryItems(dictionaryItemsElement.Elements("DictionaryItem"), 0); // Assert AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); @@ -498,7 +515,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Nested_DictionaryItems() + public void Can_Import_Nested_DictionaryItems() { // Arrange const string parentKey = "Parent"; @@ -510,7 +527,7 @@ namespace Umbraco.Tests.Services.Importing AddLanguages(); // Act - var dictionaryItems = ServiceContext.PackagingService.ImportDictionaryItems(dictionaryItemsElement); + var dictionaryItems = PackageDataInstallation.ImportDictionaryItems(dictionaryItemsElement.Elements("DictionaryItem"), 0); // Assert Assert.That(ServiceContext.LocalizationService.DictionaryItemExists(parentKey), "DictionaryItem parentKey does not exist"); @@ -524,7 +541,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_WhenExistingDictionaryKey_ImportsNewChildren() + public void WhenExistingDictionaryKey_ImportsNewChildren() { // Arrange const string expectedEnglishParentValue = "ExistingParentValue"; @@ -539,7 +556,7 @@ namespace Umbraco.Tests.Services.Importing AddExistingEnglishAndNorwegianParentDictionaryItem(expectedEnglishParentValue, expectedNorwegianParentValue); // Act - ServiceContext.PackagingService.ImportDictionaryItems(dictionaryItemsElement); + PackageDataInstallation.ImportDictionaryItems(dictionaryItemsElement.Elements("DictionaryItem"), 0); // Assert AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); @@ -549,7 +566,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_WhenExistingDictionaryKey_OnlyAddsNewLanguages() + public void WhenExistingDictionaryKey_OnlyAddsNewLanguages() { // Arrange const string expectedEnglishParentValue = "ExistingParentValue"; @@ -564,7 +581,7 @@ namespace Umbraco.Tests.Services.Importing AddExistingEnglishParentDictionaryItem(expectedEnglishParentValue); // Act - ServiceContext.PackagingService.ImportDictionaryItems(dictionaryItemsElement); + PackageDataInstallation.ImportDictionaryItems(dictionaryItemsElement.Elements("DictionaryItem"), 0); // Assert AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); @@ -574,14 +591,14 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Languages() + public void Can_Import_Languages() { // Arrange var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); var LanguageItemsElement = newPackageXml.Elements("Languages").First(); // Act - var languages = ServiceContext.PackagingService.ImportLanguages(LanguageItemsElement); + var languages = PackageDataInstallation.ImportLanguages(LanguageItemsElement.Elements("Language"), 0); var allLanguages = ServiceContext.LocalizationService.GetAllLanguages(); // Assert @@ -593,16 +610,16 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Macros() + public void Can_Import_Macros() { // Arrange string strXml = ImportResources.uBlogsy_Package; var xml = XElement.Parse(strXml); var macrosElement = xml.Descendants("Macros").First(); - var packagingService = ServiceContext.PackagingService; + // Act - var macros = packagingService.ImportMacros(macrosElement).ToList(); + var macros = PackageDataInstallation.ImportMacros(macrosElement.Elements("macro"), 0).ToList(); // Assert Assert.That(macros.Any(), Is.True); @@ -615,16 +632,16 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Macros_With_Properties() + public void Can_Import_Macros_With_Properties() { // Arrange string strXml = ImportResources.XsltSearch_Package; var xml = XElement.Parse(strXml); var macrosElement = xml.Descendants("Macros").First(); - var packagingService = ServiceContext.PackagingService; + // Act - var macros = packagingService.ImportMacros(macrosElement).ToList(); + var macros = PackageDataInstallation.ImportMacros(macrosElement.Elements("macro"), 0).ToList(); // Assert Assert.That(macros.Any(), Is.True); @@ -638,18 +655,18 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Package_With_Compositions() + public void Can_Import_Package_With_Compositions() { // Arrange string strXml = ImportResources.CompositionsTestPackage; var xml = XElement.Parse(strXml); var templateElement = xml.Descendants("Templates").First(); var docTypeElement = xml.Descendants("DocumentTypes").First(); - var packagingService = ServiceContext.PackagingService; + // Act - var templates = packagingService.ImportTemplates(templateElement); - var contentTypes = packagingService.ImportContentTypes(docTypeElement); + var templates = PackageDataInstallation.ImportTemplates(templateElement.Elements("Template").ToList(), 0); + var contentTypes = PackageDataInstallation.ImportDocumentTypes(docTypeElement.Elements("DocumentType"), 0); var numberOfDocTypes = (from doc in docTypeElement.Elements("DocumentType") select doc).Count(); // Assert @@ -667,16 +684,16 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void PackagingService_Can_Import_Package_With_Compositions_Ordered() + public void Can_Import_Package_With_Compositions_Ordered() { // Arrange string strXml = ImportResources.CompositionsTestPackage_Random; var xml = XElement.Parse(strXml); var docTypeElement = xml.Descendants("DocumentTypes").First(); - var packagingService = ServiceContext.PackagingService; + // Act - var contentTypes = packagingService.ImportContentTypes(docTypeElement); + var contentTypes = PackageDataInstallation.ImportDocumentTypes(docTypeElement.Elements("DocumentType"), 0); var numberOfDocTypes = (from doc in docTypeElement.Elements("DocumentType") select doc).Count(); // Assert diff --git a/src/Umbraco.Tests/Packaging/PackageExtractionTests.cs b/src/Umbraco.Tests/Packaging/PackageExtractionTests.cs index 5416162ed2..61494166ff 100644 --- a/src/Umbraco.Tests/Packaging/PackageExtractionTests.cs +++ b/src/Umbraco.Tests/Packaging/PackageExtractionTests.cs @@ -12,11 +12,11 @@ namespace Umbraco.Tests.Packaging { private const string PackageFileName = "Document_Type_Picker_1.1.umb"; - private static string GetTestPackagePath(string packageName) + private static FileInfo GetTestPackagePath(string packageName) { const string testPackagesDirName = "Packaging\\Packages"; string path = Path.Combine(IOHelper.GetRootDirectorySafe(), testPackagesDirName, packageName); - return path; + return new FileInfo(path); } [Test] diff --git a/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs b/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs index 0855d81548..4256a66a2d 100644 --- a/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs +++ b/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs @@ -1,74 +1,175 @@ -using Moq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Moq; using NUnit.Framework; -using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; +using Umbraco.Core.Models; using Umbraco.Core.Models.Packaging; using Umbraco.Core.Packaging; -using Umbraco.Core.Services; +using Umbraco.Core.PropertyEditors; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing; +using File = System.IO.File; namespace Umbraco.Tests.Packaging { [TestFixture] - public class PackageInstallationTest + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] + public class PackageInstallationTest : TestWithDatabaseBase { - private const string Xml = @" - - - 095e064b-ba4d-442d-9006-3050983c13d8.dll/binAuros.DocumentTypePicker.dll - - - Document Type Picker - 1.1 - MIT - http://www.auros.co.uk - - 3 - 0 - 0 - - - - @tentonipete - auros.co.uk - - - - - - - - - - - - - - -"; + private Guid _testBaseFolder; + + public override void SetUp() + { + base.SetUp(); + _testBaseFolder = Guid.NewGuid(); + } + + public override void TearDown() + { + base.TearDown(); + + //clear out files/folders + var path = IOHelper.MapPath("~/" + _testBaseFolder); + if (Directory.Exists(path)) + Directory.Delete(path, true); + } + + private CompiledPackageXmlParser Parser => new CompiledPackageXmlParser(new ConflictingPackageData(ServiceContext.MacroService, ServiceContext.FileService)); + + private PackageDataInstallation PackageDataInstallation => new PackageDataInstallation( + Logger, ServiceContext.FileService, ServiceContext.MacroService, ServiceContext.LocalizationService, + ServiceContext.DataTypeService, ServiceContext.EntityService, + ServiceContext.ContentTypeService, ServiceContext.ContentService, + Factory.GetInstance()); + + private IPackageInstallation PackageInstallation => new PackageInstallation( + PackageDataInstallation, + new PackageFileInstallation(Parser, ProfilingLogger), + Parser, Mock.Of(), + applicationRootFolder: new DirectoryInfo(IOHelper.MapPath("~/" + _testBaseFolder))); //we don't want to extract package files to the real root, so extract to a test folder + + private const string DocumentTypePickerPackage = "Document_Type_Picker_1.1.umb"; + private const string HelloPackage = "Hello_1.0.0.zip"; [Test] - public void Test() + public void Can_Read_Compiled_Package_1() { - // Arrange - const string pagePath = "Test.umb"; - - var packageExtraction = new Mock(); - - string test; - packageExtraction.Setup(a => a.ReadTextFileFromArchive(pagePath, Constants.Packaging.PackageXmlFileName, out test)).Returns(Xml); - - var fileService = new Mock(); - var macroService = new Mock(); - var packagingService = new Mock(); - - var sut = new PackageInstallation(packagingService.Object, macroService.Object, fileService.Object, packageExtraction.Object); - - // Act - InstallationSummary installationSummary = sut.InstallPackage(pagePath, -1); - - // Assert - Assert.IsNotNull(installationSummary); - //Assert.Inconclusive("Lots of more tests can be written"); + var package = PackageInstallation.ReadPackage( + //this is where our test zip file is + new FileInfo(Path.Combine(IOHelper.MapPath("~/Packaging/packages"), DocumentTypePickerPackage))); + Assert.IsNotNull(package); + Assert.AreEqual(1, package.Files.Count); + Assert.AreEqual("095e064b-ba4d-442d-9006-3050983c13d8.dll", package.Files[0].UniqueFileName); + Assert.AreEqual("/bin", package.Files[0].OriginalPath); + Assert.AreEqual("Auros.DocumentTypePicker.dll", package.Files[0].OriginalName); + Assert.AreEqual("Document Type Picker", package.Name); + Assert.AreEqual("1.1", package.Version); + Assert.AreEqual("http://www.opensource.org/licenses/mit-license.php", package.LicenseUrl); + Assert.AreEqual("MIT", package.License); + Assert.AreEqual(3, package.UmbracoVersion.Major); + Assert.AreEqual(RequirementsType.Legacy, package.UmbracoVersionRequirementsType); + Assert.AreEqual("@tentonipete", package.Author); + Assert.AreEqual("auros.co.uk", package.AuthorUrl); + Assert.AreEqual("Document Type Picker datatype that enables back office user to select one or many document types.", package.Readme); + Assert.AreEqual(1, package.DataTypes.Count()); } + [Test] + public void Can_Read_Compiled_Package_2() + { + var package = PackageInstallation.ReadPackage( + //this is where our test zip file is + new FileInfo(Path.Combine(IOHelper.MapPath("~/Packaging/packages"), HelloPackage))); + Assert.IsNotNull(package); + Assert.AreEqual(0, package.Files.Count); + Assert.AreEqual("Hello", package.Name); + Assert.AreEqual("1.0.0", package.Version); + Assert.AreEqual("http://opensource.org/licenses/MIT", package.LicenseUrl); + Assert.AreEqual("MIT License", package.License); + Assert.AreEqual(8, package.UmbracoVersion.Major); + Assert.AreEqual(0, package.UmbracoVersion.Minor); + Assert.AreEqual(0, package.UmbracoVersion.Build); + Assert.AreEqual(RequirementsType.Strict, package.UmbracoVersionRequirementsType); + Assert.AreEqual("asdf", package.Author); + Assert.AreEqual("http://hello.com", package.AuthorUrl); + Assert.AreEqual("asdf", package.Readme); + Assert.AreEqual(1, package.Documents.Count()); + Assert.AreEqual("root", package.Documents.First().ImportMode); + Assert.AreEqual(1, package.DocumentTypes.Count()); + Assert.AreEqual(1, package.Templates.Count()); + Assert.AreEqual(1, package.DataTypes.Count()); + } + + [Test] + public void Can_Read_Compiled_Package_Warnings() + { + //copy a file to the same path that the package will install so we can detect file conflicts + var path = IOHelper.MapPath("~/" + _testBaseFolder); + Console.WriteLine(path); + + var filePath = Path.Combine(path, "bin", "Auros.DocumentTypePicker.dll"); + Directory.CreateDirectory(Path.GetDirectoryName(filePath)); + File.WriteAllText(filePath, "test"); + + //this is where our test zip file is + var packageFile = Path.Combine(IOHelper.MapPath("~/Packaging/packages"), DocumentTypePickerPackage); + Console.WriteLine(packageFile); + + var package = PackageInstallation.ReadPackage(new FileInfo(packageFile)); + var preInstallWarnings = package.Warnings; + Assert.IsNotNull(preInstallWarnings); + + Assert.AreEqual(1, preInstallWarnings.FilesReplaced.Count()); + Assert.AreEqual("bin\\Auros.DocumentTypePicker.dll", preInstallWarnings.FilesReplaced.First()); + + //TODO: More Asserts + } + + [Test] + public void Install_Files() + { + var package = PackageInstallation.ReadPackage( + //this is where our test zip file is + new FileInfo(Path.Combine(IOHelper.MapPath("~/Packaging/packages"), DocumentTypePickerPackage))); + + var def = PackageDefinition.FromCompiledPackage(package); + def.Id = 1; + def.PackageId = Guid.NewGuid(); + def.Files = new List(); //clear out the files of the def for testing, this should be populated by the install + + var result = PackageInstallation.InstallPackageFiles(def, package, -1).ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("bin\\Auros.DocumentTypePicker.dll", result[0]); + Assert.IsTrue(File.Exists(Path.Combine(IOHelper.MapPath("~/" + _testBaseFolder), result[0]))); + + //make sure the def is updated too + Assert.AreEqual(result.Count, def.Files.Count); + } + + [Test] + public void Install_Data() + { + var package = PackageInstallation.ReadPackage( + //this is where our test zip file is + new FileInfo(Path.Combine(IOHelper.MapPath("~/Packaging/packages"), DocumentTypePickerPackage))); + var def = PackageDefinition.FromCompiledPackage(package); + def.Id = 1; + def.PackageId = Guid.NewGuid(); + + var summary = PackageInstallation.InstallPackageData(def, package, -1); + + Assert.AreEqual(1, summary.DataTypesInstalled.Count()); + + + //make sure the def is updated too + Assert.AreEqual(summary.DataTypesInstalled.Count(), def.DataTypes.Count); + } + + } } diff --git a/src/Umbraco.Tests/Packaging/Packages/Document_Type_Picker_1.1.zip b/src/Umbraco.Tests/Packaging/Packages/Document_Type_Picker_1.1.zip new file mode 100644 index 0000000000..18449bd373 Binary files /dev/null and b/src/Umbraco.Tests/Packaging/Packages/Document_Type_Picker_1.1.zip differ diff --git a/src/Umbraco.Tests/Packaging/Packages/Hello_1.0.0.zip b/src/Umbraco.Tests/Packaging/Packages/Hello_1.0.0.zip new file mode 100644 index 0000000000..c95cb282d1 Binary files /dev/null and b/src/Umbraco.Tests/Packaging/Packages/Hello_1.0.0.zip differ diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 788ee597dd..32689fe192 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Security; namespace Umbraco.Tests.Persistence { @@ -33,7 +34,7 @@ namespace Umbraco.Tests.Persistence _sqlCeSyntaxProvider = new SqlCeSyntaxProvider(); _sqlSyntaxProviders = new[] { (ISqlSyntaxProvider) _sqlCeSyntaxProvider }; _logger = Mock.Of(); - _databaseFactory = new UmbracoDatabaseFactory(_sqlSyntaxProviders, _logger, Mock.Of()); + _databaseFactory = new UmbracoDatabaseFactory(_logger, new Lazy(() => Mock.Of())); } [TearDown] @@ -76,7 +77,7 @@ namespace Umbraco.Tests.Persistence } // re-create the database factory and database context with proper connection string - _databaseFactory = new UmbracoDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _sqlSyntaxProviders, _logger, Mock.Of()); + _databaseFactory = new UmbracoDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _logger, new Lazy(() => Mock.Of())); // create application context //var appCtx = new ApplicationContext( @@ -88,9 +89,11 @@ namespace Umbraco.Tests.Persistence // create the umbraco database DatabaseSchemaCreator schemaHelper; using (var database = _databaseFactory.CreateDatabase()) + using (var transaction = database.GetTransaction()) { schemaHelper = new DatabaseSchemaCreator(database, _logger); schemaHelper.InitializeDatabaseSchema(); + transaction.Complete(); } var umbracoNodeTable = schemaHelper.TableExists("umbracoNode"); diff --git a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs index bff43d5ea8..6aba8187f2 100644 --- a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs +++ b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs @@ -19,8 +19,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling { const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=x;password=umbraco"; const string providerName = Constants.DbProviderNames.SqlServer; - var sqlSyntax = new[] { new SqlServerSyntaxProvider(new Lazy(() => null)) }; - var factory = new UmbracoDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of(), Mock.Of()); + var factory = new UmbracoDatabaseFactory(connectionString, providerName, Mock.Of(), new Lazy(() => Mock.Of())); using (var database = factory.CreateDatabase()) { @@ -34,8 +33,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling { const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco"; const string providerName = Constants.DbProviderNames.SqlServer; - var sqlSyntax = new[] { new SqlServerSyntaxProvider(new Lazy(() => null)) }; - var factory = new UmbracoDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of(), Mock.Of()); + var factory = new UmbracoDatabaseFactory(connectionString, providerName, Mock.Of(), new Lazy(() => Mock.Of())); using (var database = factory.CreateDatabase()) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs index eb85656ee4..de970c900d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs @@ -1,6 +1,5 @@ using System.Linq; using NUnit.Framework; -using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -26,7 +25,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sp = TestObjects.GetScopeProvider(Logger); using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); repo.Save(new AuditItem(-1, AuditType.System, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "This is a System audit trail")); var dtos = scope.Database.Fetch("WHERE id > -1"); @@ -42,7 +41,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sp = TestObjects.GetScopeProvider(Logger); using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); for (var i = 0; i < 100; i++) { @@ -55,7 +54,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query(), 0, 10, out var total, Direction.Descending, null, null); @@ -70,7 +69,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sp = TestObjects.GetScopeProvider(Logger); using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); for (var i = 0; i < 100; i++) { @@ -83,7 +82,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); var query = sp.SqlContext.Query().Where(x => x.UserId == -1); @@ -113,7 +112,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sp = TestObjects.GetScopeProvider(Logger); using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); for (var i = 0; i < 100; i++) { @@ -126,7 +125,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query(), 0, 9, out var total, Direction.Descending, new[] {AuditType.Publish}, null) @@ -144,7 +143,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sp = TestObjects.GetScopeProvider(Logger); using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); for (var i = 0; i < 100; i++) { @@ -157,7 +156,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query(), 0, 8, out var total, Direction.Descending, null, sp.SqlContext.Query().Where(item => item.Comment == "Content created")) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 6a282169fb..03701dd3b5 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -7,12 +7,9 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; @@ -35,8 +32,8 @@ namespace Umbraco.Tests.Persistence.Repositories private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository) { - var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); - var templateRepository = new TemplateRepository(scopeAccessor, cacheHelper, Logger, Mock.Of(), Mock.Of(), Mock.Of()); + var cacheHelper = AppCaches.Disabled; + var templateRepository = new TemplateRepository(scopeAccessor, cacheHelper, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(scopeAccessor, cacheHelper, Logger); contentTypeRepository = new ContentTypeRepository(scopeAccessor, cacheHelper, Logger, templateRepository); var languageRepository = new LanguageRepository(scopeAccessor, cacheHelper, Logger); @@ -46,20 +43,20 @@ namespace Umbraco.Tests.Persistence.Repositories private ContentTypeRepository CreateRepository(IScopeAccessor scopeAccessor) { - var templateRepository = new TemplateRepository(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var contentTypeRepository = new ContentTypeRepository(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), Logger, templateRepository); + var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, templateRepository); return contentTypeRepository; } private MediaTypeRepository CreateMediaTypeRepository(IScopeAccessor scopeAccessor) { - var contentTypeRepository = new MediaTypeRepository(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), Logger); + var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); return contentTypeRepository; } private EntityContainerRepository CreateContainerRepository(IScopeAccessor scopeAccessor, Guid containerEntityType) { - return new EntityContainerRepository(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), Logger, containerEntityType); + return new EntityContainerRepository(scopeAccessor, AppCaches.Disabled, Logger, containerEntityType); } //TODO Add test to verify SetDefaultTemplates updates both AllowedTemplates and DefaultTemplate(id). @@ -71,7 +68,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var templateRepo = new TemplateRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Logger, Mock.Of(), Mock.Of(), Mock.Of()); + var templateRepo = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var repository = CreateRepository((IScopeAccessor) provider); var templates = new[] { diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 41daa05022..b9724b0770 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; -using LightInject; +using Umbraco.Core.Composing; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; @@ -20,12 +20,12 @@ namespace Umbraco.Tests.Persistence.Repositories { private IDataTypeRepository CreateRepository() { - return Container.GetInstance(); + return Factory.GetInstance(); } private EntityContainerRepository CreateContainerRepository(IScopeAccessor scopeAccessor) { - return new EntityContainerRepository(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), Logger, Constants.ObjectTypes.DataTypeContainer); + return new EntityContainerRepository(scopeAccessor, AppCaches.Disabled, Logger, Constants.ObjectTypes.DataTypeContainer); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs index 8e8306095f..155ea1905c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs @@ -6,8 +6,8 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; -using LightInject; -using Umbraco.Core.Scoping; +using Umbraco.Core; +using Umbraco.Core.Composing; namespace Umbraco.Tests.Persistence.Repositories { @@ -24,7 +24,7 @@ namespace Umbraco.Tests.Persistence.Repositories private IDictionaryRepository CreateRepository() { - return Container.GetInstance(); + return Factory.GetInstance(); } [Test] @@ -230,7 +230,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var languageRepository = Container.GetInstance(); + var languageRepository = Factory.GetInstance(); var repository = CreateRepository(); var language = languageRepository.Get(1); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 9f84d9faf5..bb2d981b07 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -43,48 +43,47 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); } - private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository, out DataTypeRepository dtdRepository, CacheHelper cacheHelper = null) + private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository, out DataTypeRepository dtdRepository, AppCaches appCaches = null) { - cacheHelper = cacheHelper ?? CacheHelper; + appCaches = appCaches ?? AppCaches; TemplateRepository tr; var ctRepository = CreateRepository(scopeAccessor, out contentTypeRepository, out tr); var editors = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); - dtdRepository = new DataTypeRepository(scopeAccessor, cacheHelper, new Lazy(() => editors), Logger); + dtdRepository = new DataTypeRepository(scopeAccessor, appCaches, new Lazy(() => editors), Logger); return ctRepository; } - private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository, CacheHelper cacheHelper = null) + private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository, AppCaches appCaches = null) { TemplateRepository tr; - return CreateRepository(scopeAccessor, out contentTypeRepository, out tr, cacheHelper); + return CreateRepository(scopeAccessor, out contentTypeRepository, out tr, appCaches); } - private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository, CacheHelper cacheHelper = null) + private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository, AppCaches appCaches = null) { - cacheHelper = cacheHelper ?? CacheHelper; + appCaches = appCaches ?? AppCaches; - templateRepository = new TemplateRepository(scopeAccessor, cacheHelper, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(scopeAccessor, cacheHelper, Logger); - contentTypeRepository = new ContentTypeRepository(scopeAccessor, cacheHelper, Logger, templateRepository); - var languageRepository = new LanguageRepository(scopeAccessor, cacheHelper, Logger); - var repository = new DocumentRepository(scopeAccessor, cacheHelper, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock()); + var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger); + contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, templateRepository); + var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger); + var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); return repository; } [Test] public void CacheActiveForIntsAndGuids() { - var realCache = new CacheHelper( - new ObjectCacheRuntimeCacheProvider(), - new StaticCacheProvider(), - new StaticCacheProvider(), - new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())); + var realCache = new AppCaches( + new ObjectCacheAppCache(), + new DictionaryAppCache(), + new IsolatedCaches(t => new ObjectCacheAppCache())); var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = CreateRepository((IScopeAccessor)provider, out var contentTypeRepository, cacheHelper: realCache); + var repository = CreateRepository((IScopeAccessor)provider, out var contentTypeRepository, appCaches: realCache); var udb = (UmbracoDatabase)scope.Database; @@ -432,8 +431,9 @@ namespace Umbraco.Tests.Persistence.Repositories var fetched = repository.Get(textpage.Id); - Assert.NotNull(textpage.Template); - Assert.AreEqual(textpage.Template, contentType.DefaultTemplate); + Assert.True(textpage.TemplateId.HasValue); + Assert.NotZero(textpage.TemplateId.Value); + Assert.AreEqual(textpage.TemplateId, contentType.DefaultTemplate.Id); scope.Complete(); @@ -557,12 +557,12 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor)provider, out _); var content = repository.Get(NodeDto.NodeIdSeed + 2); - content.Template = null; + content.TemplateId = null; repository.Save(content); var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); - Assert.IsNull(updatedContent.Template); + Assert.False(updatedContent.TemplateId.HasValue); } } @@ -670,7 +670,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void AliasRegexTest() { - var regex = new SqlServerSyntaxProvider(new Lazy(() => null)).AliasRegex; + var regex = new SqlServerSyntaxProvider().AliasRegex; Assert.AreEqual(@"(\[\w+]\.\[\w+])\s+AS\s+(\[\w+])", regex.ToString()); const string sql = "SELECT [table].[column1] AS [alias1], [table].[column2] AS [alias2] FROM [table];"; var matches = regex.Matches(sql); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index e88b6e3f44..28df4b366a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -4,9 +4,7 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; @@ -22,12 +20,12 @@ namespace Umbraco.Tests.Persistence.Repositories private DomainRepository CreateRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository, out DocumentRepository documentRepository, out LanguageRepository languageRepository) { var accessor = (IScopeAccessor) provider; - var templateRepository = new TemplateRepository(accessor, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(accessor, DisabledCache, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, DisabledCache, Logger, templateRepository); - languageRepository = new LanguageRepository(accessor, DisabledCache, Logger); - documentRepository = new DocumentRepository(accessor, DisabledCache, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); - var domainRepository = new DomainRepository(accessor, DisabledCache, Logger); + var templateRepository = new TemplateRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var tagRepository = new TagRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, templateRepository); + languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); + documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); return domainRepository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs index a063d2e387..5823537f7a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs @@ -26,7 +26,7 @@ namespace Umbraco.Tests.Persistence.Repositories private LanguageRepository CreateRepository(IScopeProvider provider) { - return new LanguageRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + return new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs index 5ae25d629f..6f215f4a35 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs @@ -35,7 +35,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); var macro = new Macro("test1", "Test", "~/views/macropartials/test.cshtml", MacroTypes.PartialView); ; @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); var macro = repository.Get(1); macro.Alias = "test2"; @@ -69,7 +69,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); // Assert Assert.That(repository, Is.Not.Null); @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); // Act var macro = repository.Get(1); @@ -111,7 +111,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); // Act var macros = repository.GetMany(); @@ -129,7 +129,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); // Act var query = scope.SqlContext.Query().Where(x => x.Alias.ToUpper() == "TEST1"); @@ -147,7 +147,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); // Act var query = scope.SqlContext.Query().Where(x => x.Name.StartsWith("Test")); @@ -165,7 +165,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); // Act var macro = new Macro("test", "Test", "~/views/macropartials/test.cshtml", MacroTypes.PartialView); @@ -186,7 +186,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); // Act var macro = repository.Get(2); @@ -221,7 +221,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); // Act var macro = repository.Get(3); @@ -242,7 +242,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); // Act var exists = repository.Exists(3); @@ -261,7 +261,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); var macro = repository.Get(1); macro.Properties.Add(new MacroProperty("new1", "New1", 3, "test")); @@ -287,7 +287,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); var macro = new Macro("newmacro", "A new macro", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView); macro.Properties.Add(new MacroProperty("blah1", "New1", 4, "test.editor")); @@ -312,7 +312,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); var macro = new Macro("newmacro", "A new macro", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView); macro.Properties.Add(new MacroProperty("blah1", "New1", 4, "test.editor")); @@ -336,7 +336,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); var macro = new Macro("newmacro", "A new macro", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView); var prop1 = new MacroProperty("blah1", "New1", 4, "test.editor"); @@ -367,7 +367,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); var macro = repository.Get(1); macro.Properties.Add(new MacroProperty("new1", "New1", 3, "test")); @@ -394,7 +394,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); var macro = repository.Get(1); macro.Properties.Add(new MacroProperty("new1", "New1", 3, "test")); @@ -422,7 +422,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); repository.Save(new Macro("test1", "Test1", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView)); repository.Save(new Macro("test2", "Test2", "~/views/macropartials/test2.cshtml", MacroTypes.PartialView)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index d1e7f96ff3..eaf0425edf 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -30,14 +30,14 @@ namespace Umbraco.Tests.Persistence.Repositories CreateTestData(); } - private MediaRepository CreateRepository(IScopeProvider provider, out MediaTypeRepository mediaTypeRepository, CacheHelper cacheHelper = null) + private MediaRepository CreateRepository(IScopeProvider provider, out MediaTypeRepository mediaTypeRepository, AppCaches appCaches = null) { - cacheHelper = cacheHelper ?? CacheHelper; + appCaches = appCaches ?? AppCaches; var scopeAccessor = (IScopeAccessor) provider; - mediaTypeRepository = new MediaTypeRepository(scopeAccessor, cacheHelper, Logger); - var tagRepository = new TagRepository(scopeAccessor, cacheHelper, Logger); - var repository = new MediaRepository(scopeAccessor, cacheHelper, Logger, mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); + mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger); + var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger); + var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); return repository; } @@ -46,16 +46,15 @@ namespace Umbraco.Tests.Persistence.Repositories { MediaTypeRepository mediaTypeRepository; - var realCache = new CacheHelper( - new ObjectCacheRuntimeCacheProvider(), - new StaticCacheProvider(), - new StaticCacheProvider(), - new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())); + var realCache = new AppCaches( + new ObjectCacheAppCache(), + new DictionaryAppCache(), + new IsolatedCaches(t => new ObjectCacheAppCache())); var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = CreateRepository(provider, out mediaTypeRepository, cacheHelper: realCache); + var repository = CreateRepository(provider, out mediaTypeRepository, appCaches: realCache); var udb = (UmbracoDatabase)scope.Database; diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index 85f49306d2..49bb93f2a7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -2,6 +2,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -20,12 +21,12 @@ namespace Umbraco.Tests.Persistence.Repositories { private MediaTypeRepository CreateRepository(IScopeProvider provider) { - return new MediaTypeRepository((IScopeAccessor) provider, DisabledCache, Logger); + return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); } private EntityContainerRepository CreateContainerRepository(IScopeProvider provider) { - return new EntityContainerRepository((IScopeAccessor) provider, DisabledCache, Logger, Constants.ObjectTypes.MediaTypeContainer); + return new EntityContainerRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Constants.ObjectTypes.MediaTypeContainer); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 38ecc6c8ae..dea15cd4ad 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -6,6 +6,7 @@ using Moq; using NPoco; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -28,10 +29,10 @@ namespace Umbraco.Tests.Persistence.Repositories private MemberRepository CreateRepository(IScopeProvider provider, out MemberTypeRepository memberTypeRepository, out MemberGroupRepository memberGroupRepository) { var accessor = (IScopeAccessor) provider; - memberTypeRepository = new MemberTypeRepository(accessor, DisabledCache, Logger); - memberGroupRepository = new MemberGroupRepository(accessor, DisabledCache, Logger); - var tagRepo = new TagRepository(accessor, DisabledCache, Logger); - var repository = new MemberRepository(accessor, DisabledCache, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); + memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger); + memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger); + var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger); + var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index e3d15613c2..0c2314fd47 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.Persistence.Repositories { private MemberTypeRepository CreateRepository(IScopeProvider provider) { - return new MemberTypeRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs index cf307d2ea9..9c326b3ddc 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs @@ -1,12 +1,10 @@ using System.Linq; +using Moq; using NUnit.Framework; using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Composing; using Umbraco.Core.Persistence.Repositories.Implement; -using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -29,7 +27,7 @@ namespace Umbraco.Tests.Persistence.Repositories { base.Compose(); - Container.RegisterSingleton(f => new DataEditorCollection(Enumerable.Empty())); + Composition.RegisterUnique(f => new DataEditorCollection(Enumerable.Empty())); } [Test] @@ -37,10 +35,13 @@ namespace Umbraco.Tests.Persistence.Repositories { // unless noted otherwise, no changes / 7.2.8 + var fileSystems = Mock.Of(); + Mock.Get(fileSystems).Setup(x => x.PartialViewsFileSystem).Returns(_fileSystem); + var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new PartialViewRepository(_fileSystem); + var repository = new PartialViewRepository(fileSystems); var partialView = new PartialView(PartialViewType.PartialView, "test-path-1.cshtml") { Content = "// partialView" }; repository.Save(partialView); diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 39b3b0f797..b7f354a2fb 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -4,10 +4,8 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; @@ -29,7 +27,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repo = new PublicAccessRepository((IScopeAccessor) provider, CacheHelper, Logger); + var repo = new PublicAccessRepository((IScopeAccessor) provider, AppCaches, Logger); var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] { @@ -59,7 +57,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = provider.CreateScope()) { scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - var repo = new PublicAccessRepository((IScopeAccessor) provider, CacheHelper, Logger); + var repo = new PublicAccessRepository((IScopeAccessor) provider, AppCaches, Logger); var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] { @@ -99,7 +97,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = provider.CreateScope()) { scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - var repo = new PublicAccessRepository((IScopeAccessor) provider, CacheHelper, Logger); + var repo = new PublicAccessRepository((IScopeAccessor) provider, AppCaches, Logger); var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] { @@ -144,7 +142,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repo = new PublicAccessRepository((IScopeAccessor) provider, CacheHelper, Logger); + var repo = new PublicAccessRepository((IScopeAccessor) provider, AppCaches, Logger); var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] { @@ -182,7 +180,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repo = new PublicAccessRepository((IScopeAccessor) provider, CacheHelper, Logger); + var repo = new PublicAccessRepository((IScopeAccessor) provider, AppCaches, Logger); var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] { @@ -210,7 +208,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repo = new PublicAccessRepository((IScopeAccessor) provider, CacheHelper, Logger); + var repo = new PublicAccessRepository((IScopeAccessor) provider, AppCaches, Logger); var allEntries = new List(); for (int i = 0; i < 10; i++) @@ -274,7 +272,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repo = new PublicAccessRepository((IScopeAccessor) provider, CacheHelper, Logger); + var repo = new PublicAccessRepository((IScopeAccessor) provider, AppCaches, Logger); var entry1 = new PublicAccessEntry(content[0], content[1], content[2], new[] { @@ -307,11 +305,11 @@ namespace Umbraco.Tests.Persistence.Repositories private DocumentRepository CreateRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository) { var accessor = (IScopeAccessor) provider; - var templateRepository = new TemplateRepository(accessor, CacheHelper, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(accessor, CacheHelper, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, CacheHelper, Logger, templateRepository); - var languageRepository = new LanguageRepository(accessor, CacheHelper, Logger); - var repository = new DocumentRepository(accessor, CacheHelper, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + var templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock()); + var tagRepository = new TagRepository(accessor, AppCaches, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, templateRepository); + var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); + var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs index ca2c713a27..66f6655c77 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs @@ -188,7 +188,7 @@ namespace Umbraco.Tests.Persistence.Repositories private IRedirectUrlRepository CreateRepository(IScopeProvider provider) { - return new RedirectUrlRepository((IScopeAccessor) provider, CacheHelper, Logger); + return new RedirectUrlRepository((IScopeAccessor) provider, AppCaches, Logger); } private IContent _textpage, _subpage, _otherpage, _trashed; diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index 9ffcfa4442..cea7f44b71 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -30,8 +30,8 @@ namespace Umbraco.Tests.Persistence.Repositories private RelationRepository CreateRepository(IScopeProvider provider, out RelationTypeRepository relationTypeRepository) { var accessor = (IScopeAccessor) provider; - relationTypeRepository = new RelationTypeRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); - var repository = new RelationRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), relationTypeRepository); + relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Mock.Of()); + var repository = new RelationRepository(accessor, Mock.Of(), relationTypeRepository); return repository; } @@ -266,12 +266,11 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var relationTypeRepository = new RelationTypeRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); - var relationRepository = new RelationRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), relationTypeRepository); + var relationTypeRepository = new RelationTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); + var relationRepository = new RelationRepository((IScopeAccessor) provider, Mock.Of(), relationTypeRepository); relationTypeRepository.Save(relateContent); - relationTypeRepository.Save(relateContentType); - + relationTypeRepository.Save(relateContentType); //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs index ce59f17ea1..e52e2dfcdf 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Persistence.Repositories private RelationTypeRepository CreateRepository(IScopeProvider provider) { - return new RelationTypeRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + return new RelationTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); } @@ -232,7 +232,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new RelationTypeRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new RelationTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); repository.Save(relateContent);//Id 2 repository.Save(relateContentType);//Id 3 diff --git a/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs index 755682a913..36c1bbdfb4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs @@ -4,13 +4,10 @@ using System.Text; using Moq; using NUnit.Framework; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -20,13 +17,16 @@ namespace Umbraco.Tests.Persistence.Repositories [UmbracoTest(WithApplication = true, Database = UmbracoTestOptions.Database.NewEmptyPerFixture)] public class ScriptRepositoryTest : TestWithDatabaseBase { + private IFileSystems _fileSystems; private IFileSystem _fileSystem; public override void SetUp() { base.SetUp(); + _fileSystems = Mock.Of(); _fileSystem = new PhysicalFileSystem(SystemDirectories.Scripts); + Mock.Get(_fileSystems).Setup(x => x.ScriptsFileSystem).Returns(_fileSystem); using (var stream = CreateStream("Umbraco.Sys.registerNamespace(\"Umbraco.Utils\");")) { _fileSystem.AddFile("test-script.js", stream); @@ -37,7 +37,7 @@ namespace Umbraco.Tests.Persistence.Repositories { base.Compose(); - Container.RegisterSingleton(f => new DataEditorCollection(Enumerable.Empty())); + Composition.RegisterUnique(f => new DataEditorCollection(Enumerable.Empty())); } [Test] @@ -47,9 +47,8 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - // Act - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Assert Assert.That(repository, Is.Not.Null); @@ -63,12 +62,12 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Act var script = new Script("test-add-script.js") { Content = "/// " }; repository.Save(script); - + //Assert Assert.That(_fileSystem.FileExists("test-add-script.js"), Is.True); @@ -82,16 +81,16 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Act var script = new Script("test-updated-script.js") { Content = "/// " }; repository.Save(script); - + script.Content = "/// "; repository.Save(script); - + var scriptUpdated = repository.Get("test-updated-script.js"); @@ -108,12 +107,12 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Act var script = repository.Get("test-script.js"); repository.Delete(script); - + // Assert @@ -128,7 +127,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Act var exists = repository.Get("test-script.js"); @@ -147,7 +146,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); var script = new Script("test-script1.js") { Content = "/// " }; repository.Save(script); @@ -155,7 +154,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(script2); var script3 = new Script("test-script3.js") { Content = "/// " }; repository.Save(script3); - + // Act var scripts = repository.GetMany(); @@ -175,7 +174,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); var script = new Script("test-script1.js") { Content = "/// " }; repository.Save(script); @@ -183,7 +182,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(script2); var script3 = new Script("test-script3.js") { Content = "/// " }; repository.Save(script3); - + // Act var scripts = repository.GetMany("test-script1.js", "test-script2.js"); @@ -203,7 +202,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Act var exists = repository.Exists("test-script.js"); @@ -222,17 +221,17 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); var script = new Script("test-move-script.js") { Content = content }; repository.Save(script); - + // Act script = repository.Get("test-move-script.js"); script.Path = "moved/test-move-script.js"; repository.Save(script); - + var existsOld = repository.Exists("test-move-script.js"); var existsNew = repository.Exists("moved/test-move-script.js"); @@ -255,11 +254,11 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); var script = new Script("test-path-1.js") { Content = "// script" }; repository.Save(script); - + Assert.IsTrue(_fileSystem.FileExists("test-path-1.js")); Assert.AreEqual("test-path-1.js", script.Path); Assert.AreEqual("/scripts/test-path-1.js", script.VirtualPath); @@ -267,14 +266,14 @@ namespace Umbraco.Tests.Persistence.Repositories //ensure you can prefix the same path as the root path name script = new Script("scripts/path-2/test-path-2.js") { Content = "// script" }; repository.Save(script); - + Assert.IsTrue(_fileSystem.FileExists("scripts/path-2/test-path-2.js")); Assert.AreEqual("scripts\\path-2\\test-path-2.js", script.Path); Assert.AreEqual("/scripts/scripts/path-2/test-path-2.js", script.VirtualPath); script = new Script("path-2/test-path-2.js") { Content = "// script" }; repository.Save(script); - + Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.js")); Assert.AreEqual("path-2\\test-path-2.js", script.Path); // fixed in 7.3 - 7.2.8 does not update the path Assert.AreEqual("/scripts/path-2/test-path-2.js", script.VirtualPath); @@ -286,7 +285,7 @@ namespace Umbraco.Tests.Persistence.Repositories script = new Script("path-2\\test-path-3.js") { Content = "// script" }; repository.Save(script); - + Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-3.js")); Assert.AreEqual("path-2\\test-path-3.js", script.Path); Assert.AreEqual("/scripts/path-2/test-path-3.js", script.VirtualPath); @@ -328,11 +327,11 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); //Delete all files - Purge((PhysicalFileSystem) _fileSystem, ""); - _fileSystem = null; + Purge(_fileSystems.ScriptsFileSystem, ""); + _fileSystems = null; } - private void Purge(PhysicalFileSystem fs, string path) + private void Purge(IFileSystem fs, string path) { var files = fs.GetFiles(path, "*.js"); foreach (var file in files) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs index 08c34f453c..e2fc4b4705 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs @@ -16,13 +16,13 @@ namespace Umbraco.Tests.Persistence.Repositories [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class ServerRegistrationRepositoryTest : TestWithDatabaseBase { - private CacheHelper _cacheHelper; + private AppCaches _appCaches; public override void SetUp() { base.SetUp(); - _cacheHelper = new CacheHelper(); + _appCaches = new AppCaches(); CreateTestData(); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index dd0bc36ff3..6fae1d4749 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Text; +using Moq; using NUnit.Framework; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -15,13 +16,16 @@ namespace Umbraco.Tests.Persistence.Repositories [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] public class StylesheetRepositoryTest : TestWithDatabaseBase { + private IFileSystems _fileSystems; private IFileSystem _fileSystem; public override void SetUp() { base.SetUp(); + _fileSystems = Mock.Of(); _fileSystem = new PhysicalFileSystem(SystemDirectories.Css); + Mock.Get(_fileSystems).Setup(x => x.StylesheetsFileSystem).Returns(_fileSystem); var stream = CreateStream("body {background:#EE7600; color:#FFF;}"); _fileSystem.AddFile("styles.css", stream); } @@ -30,11 +34,10 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Instantiate_Repository() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { // Act - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Assert @@ -46,15 +49,14 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = new Stylesheet("test-add.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + //Assert Assert.That(_fileSystem.FileExists("test-add.css"), Is.True); @@ -65,20 +67,19 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + var stylesheetUpdate = repository.Get("test-update.css"); stylesheetUpdate.Content = "body { color:#000; }"; repository.Save(stylesheetUpdate); - + var stylesheetUpdated = repository.Get("test-update.css"); @@ -93,20 +94,19 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_With_Property() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + stylesheet.AddProperty(new StylesheetProperty("Test", "p", "font-size:2em;")); repository.Save(stylesheet); - + //re-get stylesheet = repository.Get(stylesheet.Name); @@ -121,15 +121,14 @@ namespace Umbraco.Tests.Persistence.Repositories public void Throws_When_Adding_Duplicate_Properties() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + stylesheet.AddProperty(new StylesheetProperty("Test", "p", "font-size:2em;")); @@ -141,18 +140,17 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = new Stylesheet("test-delete.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + repository.Delete(stylesheet); - + //Assert Assert.That(_fileSystem.FileExists("test-delete.css"), Is.False); @@ -163,10 +161,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = repository.Get("styles.css"); @@ -183,14 +180,13 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); var stylesheet = new Stylesheet("styles-v2.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + // Act var stylesheets = repository.GetMany(); @@ -207,14 +203,13 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_With_Params() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); var stylesheet = new Stylesheet("styles-v2.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + // Act var stylesheets = repository.GetMany("styles-v2.css", "styles.css"); @@ -231,10 +226,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var exists = repository.Exists("styles.css"); @@ -249,21 +243,20 @@ namespace Umbraco.Tests.Persistence.Repositories { // unless noted otherwise, no changes / 7.2.8 - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); var stylesheet = new Stylesheet("test-path-1.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + Assert.IsTrue(_fileSystem.FileExists("test-path-1.css")); Assert.AreEqual("test-path-1.css", stylesheet.Path); Assert.AreEqual("/css/test-path-1.css", stylesheet.VirtualPath); stylesheet = new Stylesheet("path-2/test-path-2.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.css")); Assert.AreEqual("path-2\\test-path-2.css", stylesheet.Path); // fixed in 7.3 - 7.2.8 does not update the path Assert.AreEqual("/css/path-2/test-path-2.css", stylesheet.VirtualPath); @@ -275,7 +268,7 @@ namespace Umbraco.Tests.Persistence.Repositories stylesheet = new Stylesheet("path-2\\test-path-3.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-3.css")); Assert.AreEqual("path-2\\test-path-3.css", stylesheet.Path); Assert.AreEqual("/css/path-2/test-path-3.css", stylesheet.VirtualPath); diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index d53d680e94..b927d23740 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -1,6 +1,7 @@ using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -946,26 +947,26 @@ namespace Umbraco.Tests.Persistence.Repositories private TagRepository CreateRepository(IScopeProvider provider) { - return new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); + return new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); } private DocumentRepository CreateContentRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository) { var accessor = (IScopeAccessor) provider; - var templateRepository = new TemplateRepository(accessor, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(accessor, DisabledCache, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, DisabledCache, Logger, templateRepository); - var languageRepository = new LanguageRepository(accessor, DisabledCache, Logger); - var repository = new DocumentRepository(accessor, DisabledCache, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, templateRepository); + var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); return repository; } private MediaRepository CreateMediaRepository(IScopeProvider provider, out MediaTypeRepository mediaTypeRepository) { var accessor = (IScopeAccessor) provider; - var tagRepository = new TagRepository(accessor, DisabledCache, Logger); - mediaTypeRepository = new MediaTypeRepository(accessor, DisabledCache, Logger); - var repository = new MediaRepository(accessor, DisabledCache, Logger, mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); + var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); + mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger); + var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); return repository; } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 3ef769adbc..a9fbfdf322 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -6,6 +6,7 @@ using System.Text; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -22,128 +23,42 @@ namespace Umbraco.Tests.Persistence.Repositories [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class TemplateRepositoryTest : TestWithDatabaseBase { - private IFileSystem _masterPageFileSystem; - private IFileSystem _viewsFileSystem; + private IFileSystems _fileSystems; - private ITemplateRepository CreateRepository(IScopeProvider provider, ITemplatesSection templatesSection = null) + private ITemplateRepository CreateRepository(IScopeProvider provider) { - return new TemplateRepository((IScopeAccessor) provider, DisabledCache, Logger, - templatesSection ?? Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc), - _masterPageFileSystem, _viewsFileSystem); + return new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, _fileSystems); } public override void SetUp() { base.SetUp(); - _masterPageFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); - _viewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); + _fileSystems = Mock.Of(); + var viewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); + Mock.Get(_fileSystems).Setup(x => x.MvcViewsFileSystem).Returns(viewsFileSystem); } [Test] public void Can_Instantiate_Repository() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Assert Assert.That(repository, Is.Not.Null); } - - } - - [Test] - public void Can_Perform_Add_MasterPage_Detect_Content() - { - // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repository = CreateRepository(provider); - - // Act - var template = new Template("test", "test") - { - Content = @"<%@ Master Language=""C#"" %>" - }; - repository.Save(template); - - - //Assert - Assert.That(repository.Get("test"), Is.Not.Null); - Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.True); - } - - } - - [Test] - public void Can_Perform_Add_MasterPage_With_Default_Content() - { - // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repository = CreateRepository(provider, Mock.Of(x => x.DefaultRenderingEngine == RenderingEngine.WebForms)); - - // Act - var template = new Template("test", "test"); - repository.Save(template); - - - //Assert - Assert.That(repository.Get("test"), Is.Not.Null); - Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.True); - Assert.AreEqual(@"<%@ Master Language=""C#"" MasterPageFile=""~/umbraco/masterpages/default.master"" AutoEventWireup=""true"" %> - - - - -".StripWhitespace(), template.Content.StripWhitespace()); - } - - } - - [Test] - public void Can_Perform_Add_MasterPage_With_Default_Content_With_Parent() - { - // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repository = CreateRepository(provider, Mock.Of(x => x.DefaultRenderingEngine == RenderingEngine.WebForms)); - - //NOTE: This has to be persisted first - var template = new Template("test", "test"); - repository.Save(template); - - - // Act - var template2 = new Template("test2", "test2"); - template2.SetMasterTemplate(template); - repository.Save(template2); - - - //Assert - Assert.That(repository.Get("test2"), Is.Not.Null); - Assert.That(_masterPageFileSystem.FileExists("test2.master"), Is.True); - Assert.AreEqual(@"<%@ Master Language=""C#"" MasterPageFile=""~/masterpages/test.master"" AutoEventWireup=""true"" %> - -".StripWhitespace(), template2.Content.StripWhitespace()); - } - } [Test] public void Can_Perform_Add_View() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test"); @@ -152,19 +67,17 @@ namespace Umbraco.Tests.Persistence.Repositories //Assert Assert.That(repository.Get("test"), Is.Not.Null); - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.True); } - } [Test] public void Can_Perform_Add_View_With_Default_Content() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test") @@ -172,41 +85,36 @@ namespace Umbraco.Tests.Persistence.Repositories Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template); - //Assert Assert.That(repository.Get("test"), Is.Not.Null); - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.True); Assert.AreEqual( @"@inherits Umbraco.Web.Mvc.UmbracoViewPage @{ Layout = null;}".StripWhitespace(), template.Content.StripWhitespace()); } - } [Test] public void Can_Perform_Add_View_With_Default_Content_With_Parent() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); //NOTE: This has to be persisted first var template = new Template("test", "test"); repository.Save(template); - // Act var template2 = new Template("test2", "test2"); template2.SetMasterTemplate(template); repository.Save(template2); - //Assert Assert.That(repository.Get("test2"), Is.Not.Null); - Assert.That(_viewsFileSystem.FileExists("test2.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test2.cshtml"), Is.True); Assert.AreEqual( "@inherits Umbraco.Web.Mvc.UmbracoViewPage @{ Layout = \"test.cshtml\";}".StripWhitespace(), template2.Content.StripWhitespace()); @@ -217,10 +125,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_Unique_Alias() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test") @@ -228,29 +135,25 @@ namespace Umbraco.Tests.Persistence.Repositories Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template); - var template2 = new Template("test", "test") { Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template2); - //Assert Assert.AreEqual("test1", template2.Alias); } - } [Test] public void Can_Perform_Update_Unique_Alias() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test") @@ -258,66 +161,30 @@ namespace Umbraco.Tests.Persistence.Repositories Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template); - var template2 = new Template("test1", "test1") { Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template2); - template.Alias = "test1"; repository.Save(template); - //Assert Assert.AreEqual("test11", template.Alias); - Assert.That(_viewsFileSystem.FileExists("test11.cshtml"), Is.True); - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.False); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test11.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.False); } - - } - - [Test] - public void Can_Perform_Update_MasterPage() - { - // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repository = CreateRepository(provider); - - // Act - var template = new Template("test", "test") - { - Content = @"<%@ Master Language=""C#"" %>" - }; - repository.Save(template); - - - template.Content = @"<%@ Master Language=""VB"" %>"; - repository.Save(template); - - - var updated = repository.Get("test"); - - // Assert - Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.True); - Assert.That(updated.Content, Is.EqualTo(@"<%@ Master Language=""VB"" %>")); - } - - } [Test] public void Can_Perform_Update_View() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test") @@ -325,73 +192,40 @@ namespace Umbraco.Tests.Persistence.Repositories Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template); - template.Content += ""; repository.Save(template); - var updated = repository.Get("test"); // Assert - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.True); Assert.That(updated.Content, Is.EqualTo(ViewHelper.GetDefaultFileContent() + "")); } } - [Test] - public void Can_Perform_Delete_MasterPage() - { - // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repository = CreateRepository(provider); - - var template = new Template("test", "test") - { - Content = @"<%@ Master Language=""C#"" %>" - }; - repository.Save(template); - - - // Act - var templates = repository.Get("test"); - Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.True); - repository.Delete(templates); - - - // Assert - Assert.IsNull(repository.Get("test")); - Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.False); - } - } - [Test] public void Can_Perform_Delete_View() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var template = new Template("test", "test") { Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template); - // Act var templates = repository.Get("test"); - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.True); repository.Delete(templates); - // Assert Assert.IsNull(repository.Get("test")); - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.False); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.False); } } @@ -399,39 +233,33 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_When_Assigned_To_Doc() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var templateRepository = CreateRepository(provider); + var templateRepository = CreateRepository(ScopeProvider); - var tagRepository = new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); - var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) provider, DisabledCache, Logger, templateRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, DisabledCache, Logger); - var contentRepo = new DocumentRepository((IScopeAccessor) provider, DisabledCache, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger); + var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, templateRepository); + var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger); + var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! contentTypeRepository.Save(contentType); var textpage = MockedContent.CreateSimpleContent(contentType); contentRepo.Save(textpage); - - var template = new Template("test", "test") { Content = @"<%@ Master Language=""C#"" %>" }; templateRepository.Save(template); - - textpage.Template = template; + textpage.TemplateId = template.Id; contentRepo.Save(textpage); - // Act var templates = templateRepository.Get("test"); templateRepository.Delete(templates); - // Assert Assert.IsNull(templateRepository.Get("test")); @@ -442,10 +270,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_Nested_Templates() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var parent = new Template("parent", "parent") { @@ -466,12 +293,10 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(parent); repository.Save(child); repository.Save(baby); - // Act var templates = repository.Get("parent"); repository.Delete(templates); - // Assert Assert.IsNull(repository.Get("test")); @@ -482,10 +307,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_All() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var created = CreateHierarchy(repository).ToArray(); @@ -510,10 +334,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Children() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var created = CreateHierarchy(repository).ToArray(); @@ -521,7 +344,6 @@ namespace Umbraco.Tests.Persistence.Repositories var childrenById = repository.GetChildren(created[1].Id); var childrenByAlias = repository.GetChildren(created[1].Alias); - // Assert Assert.AreEqual(2, childrenById.Count()); Assert.AreEqual(2, childrenById.DistinctBy(x => x.Id).Count()); @@ -534,17 +356,15 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Children_At_Root() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); CreateHierarchy(repository).ToArray(); // Act var children = repository.GetChildren(-1); - // Assert Assert.AreEqual(1, children.Count()); Assert.AreEqual(1, children.DistinctBy(x => x.Id).Count()); @@ -555,18 +375,15 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Descendants() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); - + var repository = CreateRepository(ScopeProvider); var created = CreateHierarchy(repository).ToArray(); // Act var descendantsById = repository.GetDescendants(created[1].Id); var descendantsByAlias = repository.GetDescendants(created[1].Alias); - // Assert Assert.AreEqual(3, descendantsById.Count()); Assert.AreEqual(3, descendantsById.DistinctBy(x => x.Id).Count()); @@ -580,10 +397,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Path_Is_Set_Correctly_On_Creation() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var parent = new Template("parent", "parent"); var child1 = new Template("child1", "child1"); @@ -612,7 +428,6 @@ namespace Umbraco.Tests.Persistence.Repositories baby2.MasterTemplateAlias = toddler4.Alias; baby2.MasterTemplateId = new Lazy(() => toddler4.Id); - // Act repository.Save(parent); repository.Save(child1); @@ -623,7 +438,6 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(toddler4); repository.Save(baby1); repository.Save(baby2); - // Assert Assert.AreEqual(string.Format("-1,{0}", parent.Id), parent.Path); @@ -643,10 +457,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Path_Is_Set_Correctly_On_Update() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var parent = new Template("parent", "parent"); var child1 = new Template("child1", "child1"); @@ -668,12 +481,10 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(child2); repository.Save(toddler1); repository.Save(toddler2); - //Act toddler2.SetMasterTemplate(child2); repository.Save(toddler2); - //Assert Assert.AreEqual($"-1,{parent.Id},{child2.Id},{toddler2.Id}", toddler2.Path); @@ -684,10 +495,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Path_Is_Set_Correctly_On_Update_With_Master_Template_Removal() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var parent = new Template("parent", "parent"); var child1 = new Template("child1", "child1"); @@ -697,12 +507,10 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(parent); repository.Save(child1); - //Act child1.SetMasterTemplate(null); repository.Save(child1); - //Assert Assert.AreEqual($"-1,{child1.Id}", child1.Path); @@ -714,21 +522,13 @@ namespace Umbraco.Tests.Persistence.Repositories { base.TearDown(); - _masterPageFileSystem = null; - _viewsFileSystem = null; + _fileSystems = null; + //Delete all files - var fsMaster = new PhysicalFileSystem(SystemDirectories.Masterpages); - var masterPages = fsMaster.GetFiles("", "*.master"); - foreach (var file in masterPages) - { - fsMaster.DeleteFile(file); - } var fsViews = new PhysicalFileSystem(SystemDirectories.MvcViews); - var views = fsMaster.GetFiles("", "*.cshtml"); + var views = fsViews.GetFiles("", "*.cshtml"); foreach (var file in views) - { - fsMaster.DeleteFile(file); - } + fsViews.DeleteFile(file); } protected Stream CreateStream(string contents = null) diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs index aeec6065df..311372ef10 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.Persistence.Repositories { private UserGroupRepository CreateRepository(IScopeProvider provider) { - return new UserGroupRepository((IScopeAccessor) provider, Core.Cache.CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + return new UserGroupRepository((IScopeAccessor) provider, Core.Cache.AppCaches.Disabled, Mock.Of()); } [Test] @@ -131,7 +131,7 @@ namespace Umbraco.Tests.Persistence.Repositories var id = userGroup.Id; - var repository2 = new UserGroupRepository((IScopeAccessor) provider, Core.Cache.CacheHelper.CreateDisabledCacheHelper(), Logger); + var repository2 = new UserGroupRepository((IScopeAccessor) provider, Core.Cache.AppCaches.Disabled, Logger); repository2.Delete(userGroup); scope.Complete(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index e778f987eb..4ee6ecf9e3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -26,9 +26,9 @@ namespace Umbraco.Tests.Persistence.Repositories private MediaRepository CreateMediaRepository(IScopeProvider provider, out IMediaTypeRepository mediaTypeRepository) { var accessor = (IScopeAccessor) provider; - mediaTypeRepository = new MediaTypeRepository(accessor, CacheHelper, Mock.Of()); - var tagRepository = new TagRepository(accessor, CacheHelper, Mock.Of()); - var repository = new MediaRepository(accessor, CacheHelper, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); + mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of()); + var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of()); + var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); return repository; } @@ -41,25 +41,25 @@ namespace Umbraco.Tests.Persistence.Repositories private DocumentRepository CreateContentRepository(IScopeProvider provider, out IContentTypeRepository contentTypeRepository, out ITemplateRepository templateRepository) { var accessor = (IScopeAccessor) provider; - templateRepository = new TemplateRepository(accessor, CacheHelper, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(accessor, CacheHelper, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, CacheHelper, Logger, templateRepository); - var languageRepository = new LanguageRepository(accessor, CacheHelper, Logger); - var repository = new DocumentRepository(accessor, CacheHelper, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock()); + var tagRepository = new TagRepository(accessor, AppCaches, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, templateRepository); + var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); + var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); return repository; } private UserRepository CreateRepository(IScopeProvider provider) { var accessor = (IScopeAccessor) provider; - var repository = new UserRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Logger, Mappers, TestObjects.GetGlobalSettings()); + var repository = new UserRepository(accessor, AppCaches.Disabled, Logger, Mappers, TestObjects.GetGlobalSettings()); return repository; } private UserGroupRepository CreateUserGroupRepository(IScopeProvider provider) { var accessor = (IScopeAccessor) provider; - return new UserGroupRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Logger); + return new UserGroupRepository(accessor, AppCaches.Disabled, Logger); } [Test] @@ -207,7 +207,7 @@ namespace Umbraco.Tests.Persistence.Repositories var id = user.Id; - var repository2 = new UserRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Logger, Mock.Of(),TestObjects.GetGlobalSettings()); + var repository2 = new UserRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Mock.Of(),TestObjects.GetGlobalSettings()); repository2.Delete(user); diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index df842dc43c..557de9eb11 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs @@ -74,7 +74,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, [Test] public void Format_SqlServer_NonClusteredIndexDefinition_AddsNonClusteredDirective() { - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var indexDefinition = CreateIndexDefinition(); indexDefinition.IndexType = IndexTypes.NonClustered; @@ -86,7 +86,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, [Test] public void Format_SqlServer_NonClusteredIndexDefinition_UsingIsClusteredFalse_AddsClusteredDirective() { - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var indexDefinition = CreateIndexDefinition(); indexDefinition.IndexType = IndexTypes.Clustered; @@ -99,7 +99,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, public void CreateIndexBuilder_SqlServer_NonClustered_CreatesNonClusteredIndex() { var logger = Mock.Of(); - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax); var context = new MigrationContext(db, logger); @@ -120,7 +120,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex() { var logger = Mock.Of(); - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax); var context = new MigrationContext(db, logger); @@ -141,7 +141,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex_Multi_Columnn() { var logger = Mock.Of(); - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax); var context = new MigrationContext(db, logger); @@ -162,7 +162,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, public void CreateIndexBuilder_SqlServer_Clustered_CreatesClusteredIndex() { var logger = Mock.Of(); - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax); var context = new MigrationContext(db, logger); diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 1f6b569a0e..c55da764e2 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -1,21 +1,23 @@ using System; using System.Globalization; -using LightInject; using Moq; using Newtonsoft.Json; using NUnit.Framework; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; -using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Services; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Models; using Umbraco.Web; @@ -68,14 +70,18 @@ namespace Umbraco.Tests.PropertyEditors { try { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); - container.RegisterCollectionBuilder(); + var container = RegisterFactory.Create(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.Register(f => Mock.Of()); - container.Register(f => Mock.Of()); - container.RegisterSingleton(); - var mediaFileSystem = new MediaFileSystem(Mock.Of()); + composition.WithCollectionBuilder(); + + Current.Factory = composition.CreateFactory(); + + var logger = Mock.Of(); + var scheme = Mock.Of(); + var config = Mock.Of(); + + var mediaFileSystem = new MediaFileSystem(Mock.Of(), config, scheme, logger); var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new ImageCropperPropertyEditor(Mock.Of(), mediaFileSystem, Mock.Of(), Mock.Of())) { Id = 1 }); diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index f7d09bd2c0..ea5fbcaa06 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -1,14 +1,16 @@ using System; using System.Threading; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Strings; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.PropertyEditors @@ -22,10 +24,13 @@ namespace Umbraco.Tests.PropertyEditors //normalize culture Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); - container.Register(_ + var register = RegisterFactory.Create(); + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + register.Register(_ => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefaultUmbracoSettings()))); + + Current.Factory = composition.CreateFactory(); } [TearDown] diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index edc4face17..eb0f1a9368 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -1,15 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; @@ -172,10 +175,11 @@ namespace Umbraco.Tests.Published public void SimpleConverter3Test() { Current.Reset(); - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var register = RegisterFactory.Create(); - Current.Container.RegisterCollectionBuilder() + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + composition.WithCollectionBuilder() .Append() .Append(); @@ -184,7 +188,9 @@ namespace Umbraco.Tests.Published typeof (PublishedSnapshotTestObjects.TestElementModel1), typeof (PublishedSnapshotTestObjects.TestElementModel2), typeof (PublishedSnapshotTestObjects.TestContentModel1), typeof (PublishedSnapshotTestObjects.TestContentModel2), }); - Current.Container.Register(f => factory); + register.Register(f => factory); + + Current.Factory = composition.CreateFactory(); var cacheMock = new Mock(); var cacheContent = new Dictionary(); @@ -193,9 +199,9 @@ namespace Umbraco.Tests.Published publishedSnapshotMock.Setup(x => x.Content).Returns(cacheMock.Object); var publishedSnapshotAccessorMock = new Mock(); publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot).Returns(publishedSnapshotMock.Object); - Current.Container.Register(f => publishedSnapshotAccessorMock.Object); + register.Register(f => publishedSnapshotAccessorMock.Object); - var converters = Current.Container.GetInstance(); + var converters = Current.Factory.GetInstance(); var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new VoidEditor(Mock.Of())) { Id = 1 }, diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index cf00345b65..fc1b4d8f3c 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -270,7 +270,7 @@ namespace Umbraco.Tests.Published // ReSharper disable UnassignedGetOnlyAutoProperty public override int Id { get; } - public override int TemplateId { get; } + public override int? TemplateId { get; } public override int SortOrder { get; } public override string Name { get; } public override PublishedCultureInfo GetCulture(string culture = ".") => throw new NotSupportedException(); diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 33a595626e..76fdd81ec2 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -118,8 +118,8 @@ namespace Umbraco.Tests.Published publishedContentTypeFactory.CreatePropertyType("prop1", 1), }); - var elementsCache = new DictionaryCacheProvider(); - var snapshotCache = new DictionaryCacheProvider(); + var elementsCache = new FastDictionaryAppCache(); + var snapshotCache = new FastDictionaryAppCache(); var publishedSnapshot = new Mock(); publishedSnapshot.Setup(x => x.SnapshotCache).Returns(snapshotCache); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 492f1f7dc0..ef37a822c1 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -4,6 +4,7 @@ using System.Data; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Logging; @@ -34,8 +35,11 @@ namespace Umbraco.Tests.PublishedContent // this test implements a full standalone NuCache (based upon a test IDataSource, does not // use any local db files, does not rely on any database) - and tests variations - SettingsForTests.ConfigureSettings(SettingsForTests.GenerateMockUmbracoSettings()); - var globalSettings = UmbracoConfig.For.GlobalSettings(); + Current.Reset(); + Current.UnlockConfigs(); + Current.Configs.Add(SettingsForTests.GenerateMockUmbracoSettings); + Current.Configs.Add(() => new GlobalSettings()); + var globalSettings = Current.Configs.Global(); // create a content node kit var kit = new ContentNodeKit @@ -101,7 +105,7 @@ namespace Umbraco.Tests.PublishedContent Mock.Get(dataTypeService).Setup(x => x.GetAll()).Returns(dataTypes); // create a service context - var serviceContext = new ServiceContext( + var serviceContext = ServiceContext.CreatePartial( dataTypeService : dataTypeService, memberTypeService: Mock.Of(), memberService: Mock.Of(), @@ -148,7 +152,8 @@ namespace Umbraco.Tests.PublishedContent new TestDefaultCultureAccessor(), dataSource, globalSettings, - new SiteDomainHelper()); + new SiteDomainHelper(), + Mock.Of()); // get a snapshot, get a published content var snapshot = snapshotService.CreatePublishedSnapshot(previewToken: null); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index aa9e7e4918..73f3cd1537 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -97,7 +97,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetContent(true, 1); //change a doc type alias - var c = (TestPublishedContent) doc.Children.ElementAt(0); + var c = (TestPublishedContent)doc.Children.ElementAt(0); c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var dt = doc.ChildrenAsTable(Current.Services, "Child"); @@ -201,7 +201,7 @@ namespace Umbraco.Tests.PublishedContent public IPublishedContent Parent { get; set; } public int Id { get; set; } public Guid Key { get; set; } - public int TemplateId { get; set; } + public int? TemplateId { get; set; } public int SortOrder { get; set; } public string Name { get; set; } public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 4b431d18e6..7a96f670e6 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -2,13 +2,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Reflection; using Moq; using NUnit.Framework; -using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Tests.Testing; using Umbraco.Web; @@ -23,12 +20,12 @@ namespace Umbraco.Tests.PublishedContent { base.Compose(); - Container.RegisterSingleton(_ => GetServiceContext()); + Composition.RegisterUnique(_ => GetServiceContext()); } protected ServiceContext GetServiceContext() { - var serviceContext = TestObjects.GetServiceContextMock(Container); + var serviceContext = TestObjects.GetServiceContextMock(Factory); MockLocalizationService(serviceContext); return serviceContext; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 2732fb465a..5f3a51f4f6 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; +using Umbraco.Core; using Umbraco.Tests.Testing; namespace Umbraco.Tests.PublishedContent @@ -194,7 +195,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void PublishedContentQueryTypedContentList() { - var query = new PublishedContentQuery(UmbracoContext.Current.ContentCache, UmbracoContext.Current.MediaCache); + var query = new PublishedContentQuery(UmbracoContext.Current.ContentCache, UmbracoContext.Current.MediaCache, UmbracoContext.Current.VariationContextAccessor); var result = query.Content(new[] { 1, 2, 4 }).ToArray(); Assert.AreEqual(2, result.Length); Assert.AreEqual(1, result[0].Id); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs index 85432768f0..e293653c37 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Collections.ObjectModel; using System.Web.Routing; using Moq; using Umbraco.Core.Models.PublishedContent; @@ -10,7 +9,8 @@ using Umbraco.Web.Routing; using Umbraco.Web.Security; using Umbraco.Core.Composing; using Current = Umbraco.Core.Composing.Current; -using LightInject; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -37,12 +37,12 @@ namespace Umbraco.Tests.PublishedContent { base.Compose(); - Container.RegisterSingleton(f => new PublishedModelFactory(f.GetInstance().GetTypes())); + Composition.RegisterUnique(f => new PublishedModelFactory(f.GetInstance().GetTypes())); } - protected override TypeLoader CreateTypeLoader(IServiceFactory f) + protected override TypeLoader CreateTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - var pluginManager = base.CreateTypeLoader(f); + var pluginManager = base.CreateTypeLoader(runtimeCache, globalSettings, logger); // this is so the model factory looks into the test assembly pluginManager.AssembliesToScan = pluginManager.AssembliesToScan diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index b1fbc43f08..d18c6b6668 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -1,14 +1,12 @@ using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Tests.TestHelpers; -using LightInject; using Moq; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Services; using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.PublishedContent @@ -25,10 +23,8 @@ namespace Umbraco.Tests.PublishedContent // fixme - what about the if (PropertyValueConvertersResolver.HasCurrent == false) ?? // can we risk double - registering and then, what happens? - var builder = Container.TryGetInstance() - ?? Container.RegisterCollectionBuilder(); - - builder.Clear() + Composition.WithCollectionBuilder() + .Clear() .Append() .Append() .Append(); @@ -38,7 +34,7 @@ namespace Umbraco.Tests.PublishedContent { base.Initialize(); - var converters = Container.GetInstance(); + var converters = Factory.GetInstance(); var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new RichTextPropertyEditor(Mock.Of())) { Id = 1 }); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index ed7affa79e..705b2fd826 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -9,8 +9,9 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Core.Composing; -using LightInject; using Moq; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -32,9 +33,9 @@ namespace Umbraco.Tests.PublishedContent { base.Compose(); - Container.RegisterSingleton(f => new PublishedModelFactory(f.GetInstance().GetTypes())); - Container.RegisterSingleton(); - Container.RegisterSingleton(); + Composition.RegisterUnique(f => new PublishedModelFactory(f.GetInstance().GetTypes())); + Composition.RegisterUnique(); + Composition.RegisterUnique(); var logger = Mock.Of(); var dataTypeService = new TestObjects.TestDataTypeService( @@ -44,14 +45,14 @@ namespace Umbraco.Tests.PublishedContent new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 }, new DataType(new TextboxPropertyEditor(logger)) { Id = 1004 }, new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 }); - Container.RegisterSingleton(f => dataTypeService); + Composition.RegisterUnique(f => dataTypeService); } protected override void Initialize() { base.Initialize(); - var factory = Container.GetInstance() as PublishedContentTypeFactory; + var factory = Factory.GetInstance() as PublishedContentTypeFactory; // need to specify a custom callback for unit tests // AutoPublishedContentTypes generates properties automatically @@ -68,13 +69,14 @@ namespace Umbraco.Tests.PublishedContent factory.CreatePropertyType("testRecursive", 1), }; var compositionAliases = new[] { "MyCompositionAlias" }; - var type = new AutoPublishedContentType(0, "anything", compositionAliases, propertyTypes); - ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; + var anythingType = new AutoPublishedContentType(0, "anything", compositionAliases, propertyTypes); + var homeType = new AutoPublishedContentType(0, "home", compositionAliases, propertyTypes); + ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType; } - protected override TypeLoader CreateTypeLoader(IServiceFactory f) + protected override TypeLoader CreateTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - var pluginManager = base.CreateTypeLoader(f); + var pluginManager = base.CreateTypeLoader(runtimeCache, globalSettings, logger); // this is so the model factory looks into the test assembly pluginManager.AssembliesToScan = pluginManager.AssembliesToScan @@ -232,10 +234,10 @@ namespace Umbraco.Tests.PublishedContent } [Test] - [Ignore("Fails as long as PublishedContentModel is internal.")] // fixme public void Is_Last_From_Where_Filter2() { var doc = GetNode(1173); + var ct = doc.ContentType; var items = doc.Children .Select(x => x.CreateModel()) // linq, returns IEnumerable @@ -453,12 +455,12 @@ namespace Umbraco.Tests.PublishedContent public void FirstChildAsT() { var doc = GetNode(1046); // has child nodes - - var model = doc.FirstChild(x => true); // predicate + + var model = doc.FirstChild(x => true); // predicate Assert.IsNotNull(model); Assert.IsTrue(model.Id == 1173); - Assert.IsInstanceOf(model); + Assert.IsInstanceOf(model); Assert.IsInstanceOf(model); doc = GetNode(1175); // does not have child nodes @@ -817,7 +819,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void FragmentProperty() { - var factory = Container.GetInstance() as PublishedContentTypeFactory; + var factory = Factory.GetInstance() as PublishedContentTypeFactory; var pt = factory.CreatePropertyType("detached", 1003); var ct = factory.CreateContentType(0, "alias", new[] { pt }); @@ -836,7 +838,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Fragment2() { - var factory = Container.GetInstance() as PublishedContentTypeFactory; + var factory = Factory.GetInstance() as PublishedContentTypeFactory; var pt1 = factory.CreatePropertyType("legend", 1004); var pt2 = factory.CreatePropertyType("image", 1005); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index 309ce1c0fb..dfb51e83fb 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -1,7 +1,6 @@ using System.Web; using System.Xml.Linq; using System.Xml.XPath; -using Lucene.Net.Store; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; @@ -20,10 +19,10 @@ using Umbraco.Core.Strings; using Umbraco.Examine; using Current = Umbraco.Web.Composing.Current; using Umbraco.Tests.Testing; -using LightInject; +using Umbraco.Core.Composing; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Tests.PublishedContent { @@ -42,7 +41,7 @@ namespace Umbraco.Tests.PublishedContent { base.Compose(); - Container.GetInstance() + Composition.WithCollectionBuilder() .Clear() .Append(); } @@ -68,7 +67,9 @@ namespace Umbraco.Tests.PublishedContent /// internal IPublishedContent GetNode(int id, UmbracoContext umbracoContext) { - var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), Current.Services.MediaService, Current.Services.UserService, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), + ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, + Factory.GetInstance()); var doc = cache.GetById(id); Assert.IsNotNull(doc); return doc; @@ -106,14 +107,14 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual("
This is some content
", propVal2.ToString()); var propVal3 = publishedMedia.Value("Content"); - Assert.IsInstanceOf(propVal3); + Assert.IsInstanceOf(propVal3); Assert.AreEqual("
This is some content
", propVal3.ToString()); } [Test] public void Ensure_Children_Sorted_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -125,7 +126,7 @@ namespace Umbraco.Tests.PublishedContent var searcher = indexer.GetSearcher(); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, indexer, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -142,7 +143,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Find_In_Recycle_Bin() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -155,7 +156,7 @@ namespace Umbraco.Tests.PublishedContent var searcher = indexer.GetSearcher(); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, indexer, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); //ensure it is found var publishedMedia = cache.GetById(3113); @@ -190,7 +191,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -202,7 +203,7 @@ namespace Umbraco.Tests.PublishedContent var searcher = indexer.GetSearcher(); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, indexer, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -218,7 +219,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Descendants_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -230,7 +231,7 @@ namespace Umbraco.Tests.PublishedContent var searcher = indexer.GetSearcher(); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, indexer, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -246,7 +247,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void DescendantsOrSelf_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -258,7 +259,7 @@ namespace Umbraco.Tests.PublishedContent var searcher = indexer.GetSearcher(); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, indexer, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -274,7 +275,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ancestors_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) @@ -287,7 +288,7 @@ namespace Umbraco.Tests.PublishedContent var ctx = GetUmbracoContext("/test"); var searcher = indexer.GetSearcher(); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, indexer, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(3113); @@ -300,7 +301,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void AncestorsOrSelf_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -313,7 +314,7 @@ namespace Umbraco.Tests.PublishedContent var ctx = GetUmbracoContext("/test"); var searcher = indexer.GetSearcher(); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, indexer, new StaticCacheProvider(), ContentTypesCache); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(3113); @@ -463,10 +464,6 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Convert_From_Standard_Xml() { - var config = SettingsForTests.GenerateMockUmbracoSettings(); - - SettingsForTests.ConfigureSettings(config); - var nodeId = 2112; var xml = XElement.Parse(@" @@ -485,7 +482,7 @@ namespace Umbraco.Tests.PublishedContent "); var node = xml.DescendantsAndSelf("Image").Single(x => (int)x.Attribute("id") == nodeId); - var publishedMedia = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new StaticCacheProvider(), ContentTypesCache); + var publishedMedia = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); var nav = node.CreateNavigator(); @@ -505,7 +502,7 @@ namespace Umbraco.Tests.PublishedContent var errorXml = new XElement("error", string.Format("No media is maching '{0}'", 1234)); var nav = errorXml.CreateNavigator(); - var publishedMedia = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new StaticCacheProvider(), ContentTypesCache); + var publishedMedia = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); var converted = publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/"), 1234); Assert.IsNull(converted); diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 0c4059ca7c..f7077ecb3a 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -36,9 +36,9 @@ namespace Umbraco.Tests.PublishedContent public void Resync() { } - public ICacheProvider SnapshotCache => null; + public IAppCache SnapshotCache => null; - public ICacheProvider ElementsCache => null; + public IAppCache ElementsCache => null; } class SolidPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache @@ -172,7 +172,7 @@ namespace Umbraco.Tests.PublishedContent public int Id { get; set; } public Guid Key { get; set; } - public int TemplateId { get; set; } + public int? TemplateId { get; set; } public int SortOrder { get; set; } public string Name { get; set; } public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs index 224b4a7934..72848753e5 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs @@ -1,8 +1,9 @@ using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; -using LightInject; namespace Umbraco.Tests.Routing { @@ -17,7 +18,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext(urlAsString); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); - var lookup = new ContentFinderByIdPath(Container.GetInstance().WebRouting, Logger); + var lookup = new ContentFinderByIdPath(Factory.GetInstance().WebRouting, Logger); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs index 5080ab339d..c40b2e5876 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs @@ -1,12 +1,12 @@ using Moq; using NUnit.Framework; -using LightInject; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; using Umbraco.Core.Models; using Umbraco.Tests.Testing; using Current = Umbraco.Web.Composing.Current; -using Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Tests.Routing { @@ -29,10 +29,8 @@ namespace Umbraco.Tests.Routing [TestCase("/home/Sub1.aspx/blah")] public void Match_Document_By_Url_With_Template(string urlAsString) { - - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); var template1 = CreateTemplate("test"); var template2 = CreateTemplate("blah"); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs index 8b591dfb84..fa8beea2c2 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs @@ -2,6 +2,8 @@ using System.Globalization; using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; @@ -27,16 +29,15 @@ namespace Umbraco.Tests.Routing [TestCase("/test-page", 1172)] public void Match_Document_By_Url_Hide_Top_Level(string urlString, int expectedId) { - var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.HideTopLevelNodeFromPath).Returns(true); - SettingsForTests.ConfigureSettings(globalSettingsMock.Object); var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettingsMock.Object); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); var lookup = new ContentFinderByUrl(Logger); - Assert.IsTrue(UmbracoConfig.For.GlobalSettings().HideTopLevelNodeFromPath); + Assert.IsTrue(Current.Configs.Global().HideTopLevelNodeFromPath); // fixme debugging - going further down, the routes cache is NOT empty?! if (urlString == "/home/sub1") @@ -63,16 +64,15 @@ namespace Umbraco.Tests.Routing [TestCase("/home/Sub1.aspx", 1173)] public void Match_Document_By_Url(string urlString, int expectedId) { - var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettingsMock.Object); - + var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettingsMock.Object); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); var lookup = new ContentFinderByUrl(Logger); - Assert.IsFalse(UmbracoConfig.For.GlobalSettings().HideTopLevelNodeFromPath); + Assert.IsFalse(Current.Configs.Global().HideTopLevelNodeFromPath); var result = lookup.TryFindContent(frequest); @@ -89,15 +89,14 @@ namespace Umbraco.Tests.Routing [TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)] public void Match_Document_By_Url_With_Special_Characters(string urlString, int expectedId) { - var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettingsMock.Object); var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettingsMock.Object); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); var lookup = new ContentFinderByUrl(Logger); - + var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); @@ -117,9 +116,8 @@ namespace Umbraco.Tests.Routing [TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)] public void Match_Document_By_Url_With_Special_Characters_Using_Hostname(string urlString, int expectedId) { - var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettingsMock.Object); var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettingsMock.Object); var publishedRouter = CreatePublishedRouter(); @@ -147,16 +145,15 @@ namespace Umbraco.Tests.Routing [TestCase("/æøå/home/sub1/custom-sub-4-with-æøå", 1180)] public void Match_Document_By_Url_With_Special_Characters_In_Hostname(string urlString, int expectedId) { - var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettingsMock.Object); var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettingsMock.Object); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); frequest.Domain = new DomainAndUri(new Domain(1, "mysite/æøå", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/æøå")); var lookup = new ContentFinderByUrl(Logger); - + var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs index 5787d3e613..dfbe9b0cda 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs @@ -1,5 +1,7 @@ using Moq; using NUnit.Framework; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; @@ -124,12 +126,11 @@ namespace Umbraco.Tests.Routing { SetDomains3(); - var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.HideTopLevelNodeFromPath).Returns(true); - SettingsForTests.ConfigureSettings(globalSettingsMock.Object); - + var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettingsMock.Object); - var publishedRouter = CreatePublishedRouter(Container); + var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); // must lookup domain else lookup by url fails @@ -167,12 +168,11 @@ namespace Umbraco.Tests.Routing // defaults depend on test environment expectedCulture = expectedCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture.Name; - var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.HideTopLevelNodeFromPath).Returns(true); - SettingsForTests.ConfigureSettings(globalSettingsMock.Object); var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettingsMock.Object); - var publishedRouter = CreatePublishedRouter(Container); + var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); // must lookup domain else lookup by url fails diff --git a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs index 987fa0a869..a81f345e38 100644 --- a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs +++ b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs @@ -1,9 +1,11 @@ -using System; -using Moq; +using Moq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; namespace Umbraco.Tests.Routing { @@ -14,7 +16,7 @@ namespace Umbraco.Tests.Routing { base.Compose(); - Container.Register(); + Composition.Register(); } private void SetDomains1() @@ -264,12 +266,11 @@ namespace Umbraco.Tests.Routing { SetDomains1(); - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings.Object); - var publishedRouter = CreatePublishedRouter(Container); + var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); // lookup domain @@ -314,12 +315,11 @@ namespace Umbraco.Tests.Routing // defaults depend on test environment expectedCulture = expectedCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture.Name; - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings.Object); - var publishedRouter = CreatePublishedRouter(Container); + var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); // lookup domain @@ -370,11 +370,10 @@ namespace Umbraco.Tests.Routing { SetDomains3(); - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings.Object); - var publishedRouter = CreatePublishedRouter(Container); + var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); // lookup domain diff --git a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs index a2a627227e..8e9e52258c 100644 --- a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs +++ b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs @@ -3,10 +3,11 @@ using System.Globalization; using System.Linq; using Moq; using NUnit.Framework; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Web.Routing; @@ -15,17 +16,6 @@ namespace Umbraco.Tests.Routing [TestFixture] public class GetContentUrlsTests : UrlRoutingTestBase { - private IUmbracoSettingsSection _umbracoSettings; - - public override void SetUp() - { - base.SetUp(); - - //generate new mock settings and assign so we can configure in individual tests - _umbracoSettings = SettingsForTests.GenerateMockUmbracoSettings(); - SettingsForTests.ConfigureSettings(_umbracoSettings); - } - private ILocalizedTextService GetTextService() { var textService = Mock.Of( @@ -59,7 +49,7 @@ namespace Umbraco.Tests.Routing content.Path = "-1,1046"; var umbContext = GetUmbracoContext("http://localhost:8000"); - var publishedRouter = CreatePublishedRouter(Container, + var publishedRouter = CreatePublishedRouter(Factory, contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(Logger) })); var urls = content.GetContentUrls(publishedRouter, umbContext, @@ -79,10 +69,12 @@ namespace Umbraco.Tests.Routing content.Id = 1046; //fixme: we are using this ID only because it's built into the test XML published cache content.Path = "-1,1046"; content.Published = true; - + + var umbracoSettings = Current.Configs.Settings(); + var umbContext = GetUmbracoContext("http://localhost:8000", - urlProviders: new []{ new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper()) }); - var publishedRouter = CreatePublishedRouter(Container, + urlProviders: new []{ new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper()) }); + var publishedRouter = CreatePublishedRouter(Factory, contentFinders:new ContentFinderCollection(new[]{new ContentFinderByUrl(Logger) })); var urls = content.GetContentUrls(publishedRouter, umbContext, @@ -110,9 +102,11 @@ namespace Umbraco.Tests.Routing child.Path = "-1,1046,1173"; child.Published = true; + var umbracoSettings = Current.Configs.Settings(); + var umbContext = GetUmbracoContext("http://localhost:8000", - urlProviders: new[] { new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper()) }); - var publishedRouter = CreatePublishedRouter(Container, + urlProviders: new[] { new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper()) }); + var publishedRouter = CreatePublishedRouter(Factory, contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(Logger) })); var urls = child.GetContentUrls(publishedRouter, umbContext, diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index b277194063..e681c8556d 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -2,10 +2,10 @@ using System.Linq; using System.Web.Mvc; using System.Web.Routing; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; @@ -13,11 +13,12 @@ using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.Models; using Umbraco.Web.Mvc; -using Umbraco.Web.Routing; using Umbraco.Web.WebApi; using Umbraco.Core.Strings; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.Testing; using Umbraco.Tests.Testing.Objects.Accessors; @@ -47,17 +48,8 @@ namespace Umbraco.Tests.Routing : base(umbracoApplication) { } - public override void Boot(ServiceContainer container) - { - // do it before anything else - this is the only place where it's possible - var logger = Mock.Of(); - container.RegisterInstance(logger); - var profiler = Mock.Of(); - container.RegisterInstance(profiler); - container.RegisterInstance(new ProfilingLogger(logger, profiler)); - - base.Boot(container); - } + protected override ILogger GetLogger() => Mock.Of(); + protected override IProfiler GetProfiler() => Mock.Of(); } protected override void Compose() @@ -67,13 +59,13 @@ namespace Umbraco.Tests.Routing // set the default RenderMvcController Current.DefaultRenderMvcControllerType = typeof(RenderMvcController); // fixme WRONG! - var surfaceControllerTypes = new SurfaceControllerTypeCollection(Current.TypeLoader.GetSurfaceControllers()); - Container.RegisterInstance(surfaceControllerTypes); + var surfaceControllerTypes = new SurfaceControllerTypeCollection(Composition.TypeLoader.GetSurfaceControllers()); + Composition.RegisterUnique(surfaceControllerTypes); - var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(Current.TypeLoader.GetUmbracoApiControllers()); - Container.RegisterInstance(umbracoApiControllerTypes); + var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(Composition.TypeLoader.GetUmbracoApiControllers()); + Composition.RegisterUnique(umbracoApiControllerTypes); - Container.RegisterSingleton(_ => new DefaultShortStringHelper(SettingsForTests.GetDefaultUmbracoSettings())); + Composition.RegisterUnique(_ => new DefaultShortStringHelper(SettingsForTests.GetDefaultUmbracoSettings())); } public override void TearDown() @@ -181,6 +173,11 @@ namespace Umbraco.Tests.Routing /// public class CustomDocumentController : RenderMvcController { + public CustomDocumentController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger) + : base(globalSettings, umbracoContext, services, appCaches, logger, profilingLogger) + { + } + public ActionResult HomePage(ContentModel model) { return View(); diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index 69533c3c77..c87a02f43a 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -4,13 +4,17 @@ using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Configuration; +using Umbraco.Core.Composing; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Sync; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; namespace Umbraco.Tests.Routing { @@ -18,23 +22,34 @@ namespace Umbraco.Tests.Routing [Apartment(ApartmentState.STA)] public class UmbracoModuleTests : BaseWebTest { - private UmbracoModule _module; + private UmbracoInjectedModule _module; public override void SetUp() { base.SetUp(); + // fixme - be able to get the UmbracoModule from the container. any reason settings were from testobjects? //create the module - _module = new UmbracoModule - { - GlobalSettings = TestObjects.GetGlobalSettings(), - Logger = Mock.Of() - }; - var runtime = new RuntimeState(_module.Logger, new Lazy(), new Lazy(), Mock.Of(), _module.GlobalSettings); + var logger = Mock.Of(); + var globalSettings = TestObjects.GetGlobalSettings(); + var runtime = new RuntimeState(logger, Mock.Of(), globalSettings, + new Lazy(), new Lazy()); + + _module = new UmbracoInjectedModule + ( + globalSettings, + Mock.Of(), + Factory.GetInstance(), + Factory.GetInstance(), + new UrlProviderCollection(new IUrlProvider[0]), + runtime, + logger, + null, // fixme - PublishedRouter complexities... + Mock.Of() + ); - _module.Runtime = runtime; runtime.Level = RuntimeLevel.Run; - + //SettingsForTests.ReservedPaths = "~/umbraco,~/install/"; //SettingsForTests.ReservedUrls = "~/config/splashes/booting.aspx,~/install/default.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd"; diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index 2f1e4e3476..819ea0b01c 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -4,7 +4,9 @@ using System.Globalization; using System.Linq; using Moq; using NUnit.Framework; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.TestHelpers; @@ -23,18 +25,13 @@ namespace Umbraco.Tests.Routing protected override void Compose() { base.Compose(); - Container.Register(); + Composition.Register(); } - private IUmbracoSettingsSection _umbracoSettings; - - public override void SetUp() + protected override void ComposeSettings() { - base.SetUp(); - - //generate new mock settings and assign so we can configure in individual tests - _umbracoSettings = SettingsForTests.GenerateMockUmbracoSettings(); - SettingsForTests.ConfigureSettings(_umbracoSettings); + Composition.Configs.Add(SettingsForTests.GenerateMockUmbracoSettings); + Composition.Configs.Add(SettingsForTests.GenerateMockGlobalSettings); } /// @@ -44,17 +41,18 @@ namespace Umbraco.Tests.Routing [Test] public void Ensure_Cache_Is_Correct() { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); + + var umbracoSettings = Current.Configs.Settings(); var umbracoContext = GetUmbracoContext("/test", 1111, urlProviders: new[] { - new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) + new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings: globalSettings.Object); - var requestHandlerMock = Mock.Get(_umbracoSettings.RequestHandler); + var requestHandlerMock = Mock.Get(umbracoSettings.RequestHandler); requestHandlerMock.Setup(x => x.AddTrailingSlash).Returns(false);// (cached routes have none) var samples = new Dictionary { @@ -107,17 +105,18 @@ namespace Umbraco.Tests.Routing [TestCase(1172, "/test-page/")] public void Get_Url_Not_Hiding_Top_Level(int nodeId, string niceUrlMatch) { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); + + var umbracoSettings = Current.Configs.Settings(); var umbracoContext = GetUmbracoContext("/test", 1111, urlProviders: new[] { - new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) + new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings: globalSettings.Object); - var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + var requestMock = Mock.Get(umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); var result = umbracoContext.UrlProvider.GetUrl(nodeId); @@ -137,17 +136,18 @@ namespace Umbraco.Tests.Routing [TestCase(1172, "/test-page/")] // not hidden because not first root public void Get_Url_Hiding_Top_Level(int nodeId, string niceUrlMatch) { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(true); - SettingsForTests.ConfigureSettings(globalSettings.Object); + + var umbracoSettings = Current.Configs.Settings(); var umbracoContext = GetUmbracoContext("/test", 1111, urlProviders: new[] { - new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) + new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings: globalSettings.Object); - var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + var requestMock = Mock.Get(umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); var result = umbracoContext.UrlProvider.GetUrl(nodeId); @@ -159,12 +159,13 @@ namespace Umbraco.Tests.Routing { const string currentUri = "http://example.us/test"; - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); - var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + var umbracoSettings = Current.Configs.Settings(); + + var requestMock = Mock.Get(umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); @@ -186,9 +187,9 @@ namespace Umbraco.Tests.Routing snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())) .Returns(snapshot); - var umbracoContext = GetUmbracoContext(currentUri, umbracoSettings: _umbracoSettings, + var umbracoContext = GetUmbracoContext(currentUri, umbracoSettings: umbracoSettings, urlProviders: new[] { - new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) + new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings: globalSettings.Object, snapshotService: snapshotService.Object); @@ -207,12 +208,13 @@ namespace Umbraco.Tests.Routing { const string currentUri = "http://example.fr/test"; - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); - var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + var umbracoSettings = Current.Configs.Settings(); + + var requestMock = Mock.Get(umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); @@ -243,9 +245,9 @@ namespace Umbraco.Tests.Routing snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())) .Returns(snapshot); - var umbracoContext = GetUmbracoContext(currentUri, umbracoSettings: _umbracoSettings, + var umbracoContext = GetUmbracoContext(currentUri, umbracoSettings: umbracoSettings, urlProviders: new[] { - new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) + new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings: globalSettings.Object, snapshotService: snapshotService.Object); @@ -264,12 +266,13 @@ namespace Umbraco.Tests.Routing { const string currentUri = "http://example.us/test"; - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); - var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + var umbracoSettings = Current.Configs.Settings(); + + var requestMock = Mock.Get(umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); @@ -300,9 +303,9 @@ namespace Umbraco.Tests.Routing snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())) .Returns(snapshot); - var umbracoContext = GetUmbracoContext(currentUri, umbracoSettings: _umbracoSettings, + var umbracoContext = GetUmbracoContext(currentUri, umbracoSettings: umbracoSettings, urlProviders: new[] { - new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) + new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings: globalSettings.Object, snapshotService: snapshotService.Object); @@ -317,17 +320,18 @@ namespace Umbraco.Tests.Routing [Test] public void Get_Url_Relative_Or_Absolute() { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); - var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + var umbracoSettings = Current.Configs.Settings(); + + var requestMock = Mock.Get(umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); - var umbracoContext = GetUmbracoContext("http://example.com/test", 1111, umbracoSettings: _umbracoSettings, urlProviders: new[] + var umbracoContext = GetUmbracoContext("http://example.com/test", 1111, umbracoSettings: umbracoSettings, urlProviders: new[] { - new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) + new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings: globalSettings.Object); Assert.AreEqual("/home/sub1/custom-sub-1/", umbracoContext.UrlProvider.GetUrl(1177)); @@ -343,18 +347,19 @@ namespace Umbraco.Tests.Routing [Test] public void Get_Url_Unpublished() { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); + + var umbracoSettings = Current.Configs.Settings(); var umbracoContext = GetUmbracoContext("http://example.com/test", 1111, urlProviders: new[] { - new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) + new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings: globalSettings.Object); //mock the Umbraco settings that we need - var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + var requestMock = Mock.Get(umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); Assert.AreEqual("#", umbracoContext.UrlProvider.GetUrl(999999)); diff --git a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs index 9b291249c9..4928cd01dc 100644 --- a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs +++ b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs @@ -1,6 +1,8 @@ using System; using Moq; using NUnit.Framework; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -193,9 +195,8 @@ DetermineRouteById(id): [TestCase(2006, false, "/x/b/e")] public void GetRouteByIdNoHide(int id, bool hide, string expected) { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings: globalSettings.Object); var cache = umbracoContext.ContentCache as PublishedContentCache; @@ -218,9 +219,8 @@ DetermineRouteById(id): [TestCase(2006, true, "/b/e")] // risky! public void GetRouteByIdHide(int id, bool hide, string expected) { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings: globalSettings.Object); var cache = umbracoContext.ContentCache as PublishedContentCache; @@ -233,14 +233,13 @@ DetermineRouteById(id): [Test] public void GetRouteByIdCache() { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); var cache = umbracoContext.ContentCache as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); - + var route = cache.GetRouteById(false, 1000); Assert.AreEqual("/a", route); @@ -265,9 +264,8 @@ DetermineRouteById(id): [TestCase("/x", false, 2000)] public void GetByRouteNoHide(string route, bool hide, int expected) { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); var cache = umbracoContext.ContentCache as PublishedContentCache; @@ -297,9 +295,8 @@ DetermineRouteById(id): [TestCase("/b/c", true, 1002)] // (hence the 2005 collision) public void GetByRouteHide(string route, bool hide, int expected) { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); var cache = umbracoContext.ContentCache as PublishedContentCache; @@ -321,14 +318,13 @@ DetermineRouteById(id): [Test] public void GetByRouteCache() { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); var cache = umbracoContext.ContentCache as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); - + var content = cache.GetByRoute(false, "/a/b/c"); Assert.IsNotNull(content); Assert.AreEqual(1002, content.Id); diff --git a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs index 6965c4dce9..7566c5372d 100644 --- a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs +++ b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs @@ -2,6 +2,7 @@ using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -33,13 +34,13 @@ namespace Umbraco.Tests.Routing { base.Compose(); - Container.RegisterSingleton(_ => GetServiceContext()); + Composition.RegisterUnique(_ => GetServiceContext()); } protected ServiceContext GetServiceContext() { // get the mocked service context to get the mocked domain service - var serviceContext = TestObjects.GetServiceContextMock(Container); + var serviceContext = TestObjects.GetServiceContextMock(Factory); //setup mock domain service var domainService = Mock.Get(serviceContext.DomainService); diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index c7ce42e4bc..ba646f6d94 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Routing; -using Umbraco.Core.Composing; namespace Umbraco.Tests.Routing { @@ -19,8 +21,8 @@ namespace Umbraco.Tests.Routing { base.Compose(); - Container.RegisterSingleton(_ => Mock.Of()); - Container.Register(); + Composition.RegisterUnique(_ => Mock.Of()); + Composition.Register(); } void SetDomains1() @@ -177,10 +179,9 @@ namespace Umbraco.Tests.Routing var request = Mock.Get(settings.RequestHandler); request.Setup(x => x.UseDomainPrefixes).Returns(false); - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); // ignored w/domains - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 1111, umbracoSettings: settings, urlProviders: new[] { @@ -213,10 +214,9 @@ namespace Umbraco.Tests.Routing var request = Mock.Get(settings.RequestHandler); request.Setup(x => x.UseDomainPrefixes).Returns(false); - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); // ignored w/domains - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 1111, umbracoSettings: settings, urlProviders: new[] { @@ -241,10 +241,9 @@ namespace Umbraco.Tests.Routing var request = Mock.Get(settings.RequestHandler); request.Setup(x => x.UseDomainPrefixes).Returns(false); - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); // ignored w/domains - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 1111, umbracoSettings: settings, urlProviders: new[] { @@ -275,10 +274,9 @@ namespace Umbraco.Tests.Routing var request = Mock.Get(settings.RequestHandler); request.Setup(x => x.UseDomainPrefixes).Returns(false); - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); // ignored w/domains - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 1111, umbracoSettings: settings, urlProviders: new[] { @@ -299,10 +297,9 @@ namespace Umbraco.Tests.Routing var request = Mock.Get(settings.RequestHandler); request.Setup(x => x.UseDomainPrefixes).Returns(false); - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); // ignored w/domains - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("/test", 1111, umbracoSettings: settings, urlProviders: new[] { @@ -366,10 +363,9 @@ namespace Umbraco.Tests.Routing var requestMock = Mock.Get(settings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); // ignored w/domains - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("http://domain1.com/test", 1111, umbracoSettings: settings, urlProviders: new[] { @@ -398,10 +394,9 @@ namespace Umbraco.Tests.Routing { var settings = SettingsForTests.GenerateMockUmbracoSettings(); - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); // ignored w/domains - SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext("http://domain1.com/en/test", 1111, umbracoSettings: settings, urlProviders: new[] { diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index 6b2737a3d1..e28a6485d6 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -1,12 +1,14 @@ using System; using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Routing; using Umbraco.Core.Services; -using Umbraco.Core.Composing; namespace Umbraco.Tests.Routing { @@ -21,17 +23,16 @@ namespace Umbraco.Tests.Routing protected override void Compose() { base.Compose(); - Container.RegisterSingleton(_ => Mock.Of()); - Container.Register(); + Composition.RegisterUnique(_ => Mock.Of()); + Composition.Register(); } [Test] public void DoNotPolluteCache() { - var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettings = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); var settings = SettingsForTests.GenerateMockUmbracoSettings(); var request = Mock.Get(settings.RequestHandler); diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs old mode 100755 new mode 100644 index 9a8164356a..c894659865 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -1,16 +1,20 @@ using System; using System.Collections.Generic; +using System.Data; using System.Web.Hosting; using Examine; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Runtime; +using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; @@ -18,13 +22,13 @@ using Umbraco.Web; namespace Umbraco.Tests.Runtimes { [TestFixture] - [Ignore("cannot work until we refactor IUmbracoDatabaseFactory vs UmbracoDatabaseFactory")] public class CoreRuntimeTests { [SetUp] public void SetUp() { TestComponent.Reset(); + Current.Reset(); } public void TearDown() @@ -39,12 +43,28 @@ namespace Umbraco.Tests.Runtimes { app.HandleApplicationStart(app, new EventArgs()); + var e = app.Runtime.State.BootFailedException; + var m = ""; + switch (e) + { + case null: + m = ""; + break; + case BootFailedException bfe when bfe.InnerException != null: + m = "BootFailed: " + bfe.InnerException.GetType() + " " + bfe.InnerException.Message + " " + bfe.InnerException.StackTrace; + break; + default: + m = e.GetType() + " " + e.Message + " " + e.StackTrace; + break; + } + + Assert.AreNotEqual(RuntimeLevel.BootFailed, app.Runtime.State.Level, m); + Assert.IsTrue(TestComposer.Ctored); + Assert.IsTrue(TestComposer.Composed); Assert.IsTrue(TestComponent.Ctored); - Assert.IsTrue(TestComponent.Composed); - Assert.IsTrue(TestComponent.Initialized1); - Assert.IsTrue(TestComponent.Initialized2); Assert.IsNotNull(TestComponent.ProfilingLogger); - Assert.IsInstanceOf(TestComponent.ProfilingLogger.Logger); + Assert.IsInstanceOf(TestComponent.ProfilingLogger); + Assert.IsInstanceOf(((ProfilingLogger) TestComponent.ProfilingLogger).Logger); // note: components are NOT disposed after boot @@ -58,40 +78,23 @@ namespace Umbraco.Tests.Runtimes // test application public class TestUmbracoApplication : UmbracoApplicationBase { + public IRuntime Runtime { get; private set; } + protected override IRuntime GetRuntime() { - return new TestRuntime(); + return Runtime = new TestRuntime(); } } // test runtime public class TestRuntime : CoreRuntime { - // the application's logger is created by the application - // through GetLogger, that custom application can override - protected override ILogger GetLogger() - { - //return Mock.Of(); - return new DebugDiagnosticsLogger(); - } - - public override void Compose(ServiceContainer container) - { - base.Compose(container); - - // the application's profiler and profiling logger are - // registered by CoreRuntime.Compose() but can be - // overriden afterwards - they haven't been resolved yet - container.RegisterSingleton(_ => new TestProfiler()); - container.RegisterSingleton(factory => new ProfilingLogger(factory.GetInstance(), factory.GetInstance())); - - // must override the database factory - container.RegisterSingleton(_ => GetDatabaseFactory()); - } + protected override ILogger GetLogger() => new DebugDiagnosticsLogger(); + protected override IProfiler GetProfiler() => new TestProfiler(); // must override the database factory // else BootFailedException because U cannot connect to the configured db - private static IUmbracoDatabaseFactory GetDatabaseFactory() + protected internal override IUmbracoDatabaseFactory GetDatabaseFactory() { var mock = new Mock(); mock.Setup(x => x.Configured).Returns(true); @@ -99,19 +102,51 @@ namespace Umbraco.Tests.Runtimes return mock.Object; } + protected override Configs GetConfigs() + { + var configs = new Configs(); + configs.Add(SettingsForTests.GetDefaultGlobalSettings); + configs.Add(SettingsForTests.GetDefaultUmbracoSettings); + return configs; + } + + // fixme so how the f* should we do it now? + /* // pretend we have the proper migration // else BootFailedException because our mock IUmbracoDatabaseFactory does not provide databases - protected override bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + protected override bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory) { return true; } + */ - private MainDom _mainDom; - - public override void Boot(ServiceContainer container) + // because we don't even have the core runtime component, + // there are a few required stuff that we need to compose + public override void Compose(Composition composition) { - base.Boot(container); - _mainDom = container.GetInstance(); + base.Compose(composition); + + var scopeProvider = Mock.Of(); + Mock.Get(scopeProvider) + .Setup(x => x.CreateScope( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(Mock.Of()); + + composition.RegisterUnique(scopeProvider); + } + + private IMainDom _mainDom; + + public override IFactory Boot(IRegister container) + { + var factory = base.Boot(container); + _mainDom = factory.GetInstance(); + return factory; } public override void Terminate() @@ -123,64 +158,75 @@ namespace Umbraco.Tests.Runtimes // runs with only one single component // UmbracoCoreComponent will be force-added too // and that's it - protected override IEnumerable GetComponentTypes() + protected override IEnumerable GetComposerTypes(TypeLoader typeLoader) { - return new[] { typeof(TestComponent) }; + return new[] { typeof(TestComposer) }; } } - public class TestComponent : UmbracoComponentBase + public class TestComposer : IComposer { // test flags public static bool Ctored; public static bool Composed; - public static bool Initialized1; - public static bool Initialized2; - public static bool Terminated; - public static ProfilingLogger ProfilingLogger; public static void Reset() { - Ctored = Composed = Initialized1 = Initialized2 = Terminated = false; - ProfilingLogger = null; + Ctored = Composed = false; } - public TestComponent() + public TestComposer() { Ctored = true; } - public override void Compose(Composition composition) + public void Compose(Composition composition) { - base.Compose(composition); - - composition.Container.Register(factory => SettingsForTests.GetDefaultUmbracoSettings()); - composition.Container.RegisterSingleton(); + composition.Register(factory => SettingsForTests.GetDefaultUmbracoSettings()); + composition.RegisterUnique(); + composition.Components().Append(); Composed = true; } + } + + public class TestComponent : IComponent, IDisposable + { + // test flags + public static bool Ctored; + public static bool Initialized; + public static bool Terminated; + public static IProfilingLogger ProfilingLogger; + + public bool Disposed; + + public static void Reset() + { + Ctored = Initialized = Terminated = false; + ProfilingLogger = null; + } + + public TestComponent(IProfilingLogger proflog) + { + Ctored = true; + ProfilingLogger = proflog; + } public void Initialize() { - Initialized1 = true; + Initialized = true; } - public void Initialize(ILogger logger) + public void Terminate() { - Initialized2 = true; - } - - public void Initialize(ProfilingLogger proflog) - { - ProfilingLogger = proflog; - } - - public override void Terminate() - { - base.Terminate(); Terminated = true; } + + public void Dispose() + { + Disposed = true; + } } } } diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs new file mode 100644 index 0000000000..3a52eea17c --- /dev/null +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -0,0 +1,398 @@ +using System; +using System.Configuration; +using System.Data.SqlServerCe; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Web; +using Examine; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Runtime; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Sync; +using Umbraco.Tests.Composing; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; +using Umbraco.Web.Cache; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Runtime; +using File = System.IO.File; + +namespace Umbraco.Tests.Runtimes +{ + [TestFixture] + public class StandaloneTests + { + [Test] + [Explicit("This test must be run manually")] + public void StandaloneTest() + { + IFactory factory = null; + + // clear + foreach (var file in Directory.GetFiles(Path.Combine(IOHelper.MapPath("~/App_Data")), "NuCache.*")) + File.Delete(file); + + // settings + // reset the current version to 0.0.0, clear connection strings + ConfigurationManager.AppSettings["umbracoConfigurationStatus"] = ""; + // fixme we need a better management of settings here (and, true config files?) + + // create the very basic and essential things we need + var logger = new ConsoleLogger(); + var profiler = new LogProfiler(logger); + var profilingLogger = new ProfilingLogger(logger, profiler); + var appCaches = new AppCaches(); // fixme has HttpRuntime stuff? + var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance())); + var typeLoader = new TypeLoader(appCaches.RuntimeCache, LocalTempStorage.Default, profilingLogger); + var mainDom = new SimpleMainDom(); + var runtimeState = new RuntimeState(logger, null, null, new Lazy(() => mainDom), new Lazy(() => factory.GetInstance())); + + // create the register and the composition + var register = RegisterFactory.Create(); + var composition = new Composition(register, typeLoader, profilingLogger, runtimeState); + composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState); + + // create the core runtime and have it compose itself + var coreRuntime = new CoreRuntime(); + coreRuntime.Compose(composition); + + // determine actual runtime level + runtimeState.DetermineRuntimeLevel(databaseFactory, logger); + Console.WriteLine(runtimeState.Level); + // going to be Install BUT we want to force components to be there (nucache etc) + runtimeState.Level = RuntimeLevel.Run; + + var composerTypes = typeLoader.GetTypes() // all of them + .Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components + .Where(x => x != typeof(WebRuntimeComposer)); // exclude web runtime + var composers = new Composers(composition, composerTypes, profilingLogger); + composers.Compose(); + + // must registers stuff that WebRuntimeComponent would register otherwise + // fixme UmbracoContext creates a snapshot that it does not register with the accessor + // and so, we have to use the UmbracoContextPublishedSnapshotAccessor + // the UmbracoContext does not know about the accessor + // else that would be a catch-22 where they both know about each other? + //composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(_ => Mock.Of(), Lifetime.Singleton); + composition.RegisterUnique(f => new DistributedCache()); + composition.WithCollectionBuilder().Append(); + composition.RegisterUnique(); + composition.RegisterUnique(f => ExamineManager.Instance); + + // initialize some components only/individually + composition.WithCollectionBuilder() + .Clear() + .Append(); + + // configure + composition.Configs.Add(SettingsForTests.GetDefaultGlobalSettings); + composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings); + + // create and register the factory + Current.Factory = factory = composition.CreateFactory(); + + // instantiate and initialize components + var components = factory.GetInstance(); + + // do stuff + Console.WriteLine(runtimeState.Level); + + // install + if (true || runtimeState.Level == RuntimeLevel.Install) + { + var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var file = Path.Combine(path, "Umbraco.sdf"); + if (File.Exists(file)) + File.Delete(file); + + // create the database file + // databaseBuilder.ConfigureEmbeddedDatabaseConnection() can do it too, + // but then it wants to write the connection string to web.config = bad + using (var engine = new SqlCeEngine("Data Source=|DataDirectory|\\Umbraco.sdf;Flush Interval=1;")) + { + engine.CreateDatabase(); + } + + //var databaseBuilder = factory.GetInstance(); + //databaseFactory.Configure(DatabaseBuilder.EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe); + //databaseBuilder.CreateDatabaseSchemaAndData(); + + databaseFactory.Configure(DatabaseBuilder.EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe); + + var scopeProvider = factory.GetInstance(); + using (var scope = scopeProvider.CreateScope()) + { + var creator = new DatabaseSchemaCreator(scope.Database, logger); + creator.InitializeDatabaseSchema(); + scope.Complete(); + } + } + + // done installing + runtimeState.Level = RuntimeLevel.Run; + + // instantiate to register events + // should be done by Initialize? + // should we invoke Initialize? + _ = factory.GetInstance(); + + // at that point, Umbraco can run! + // though, we probably still need to figure out what depends on HttpContext... + var contentService = factory.GetInstance(); + var content = contentService.GetById(1234); + Assert.IsNull(content); + + // create a document type and a document + var contentType = new ContentType(-1) { Alias = "ctype", Name = "ctype" }; + factory.GetInstance().Save(contentType); + content = new Content("test", -1, contentType); + contentService.Save(content); + + // assert that it is possible to get the document back + content = contentService.GetById(content.Id); + Assert.IsNotNull(content); + Assert.AreEqual("test", content.Name); + + // need an UmbracoCOntext to access the cache + // fixme - not exactly pretty, should not depend on HttpContext + var httpContext = Mock.Of(); + var withUmbracoContext = UmbracoContext.EnsureContext(httpContext); + var umbracoContext = Umbraco.Web.Composing.Current.UmbracoContext; + + // assert that there is no published document + var pcontent = umbracoContext.ContentCache.GetById(content.Id); + Assert.IsNull(pcontent); + + // but a draft document + pcontent = umbracoContext.ContentCache.GetById(true, content.Id); + Assert.IsNotNull(pcontent); + Assert.AreEqual("test", pcontent.Name); + Assert.IsTrue(pcontent.IsDraft()); + + // no published url + Assert.AreEqual("#", pcontent.GetUrl()); + + // now publish the document + make some unpublished changes + contentService.SaveAndPublish(content); + content.Name = "testx"; + contentService.Save(content); + + // assert that snapshot has been updated and there is now a published document + pcontent = umbracoContext.ContentCache.GetById(content.Id); + Assert.IsNotNull(pcontent); + Assert.AreEqual("test", pcontent.Name); + Assert.IsFalse(pcontent.IsDraft()); + + // but the url is the published one - no draft url + Assert.AreEqual("/test/", pcontent.GetUrl()); + + // and also an updated draft document + pcontent = umbracoContext.ContentCache.GetById(true, content.Id); + Assert.IsNotNull(pcontent); + Assert.AreEqual("testx", pcontent.Name); + Assert.IsTrue(pcontent.IsDraft()); + + // and the published document has a url + Assert.AreEqual("/test/", pcontent.GetUrl()); + + withUmbracoContext.Dispose(); + mainDom.Stop(); + components.Terminate(); + + // exit! + } + + [Test] + [Explicit("This test must be run manually")] + public void ValidateComposition() + { + // this is almost what CoreRuntime does, without + // - managing MainDom + // - configuring for unhandled exceptions, assembly resolution, application root path + // - testing for database, and for upgrades (runtime level) + // - assigning the factory to Current.Factory + + // create the very basic and essential things we need + var logger = new ConsoleLogger(); + var profiler = Mock.Of(); + var profilingLogger = new ProfilingLogger(logger, profiler); + var appCaches = AppCaches.Disabled; + var databaseFactory = Mock.Of(); + var typeLoader = new TypeLoader(appCaches.RuntimeCache, LocalTempStorage.Default, profilingLogger); + var runtimeState = Mock.Of(); + Mock.Get(runtimeState).Setup(x => x.Level).Returns(RuntimeLevel.Run); + var mainDom = Mock.Of(); + Mock.Get(mainDom).Setup(x => x.IsMainDom).Returns(true); + + // create the register and the composition + var register = RegisterFactory.Create(); + var composition = new Composition(register, typeLoader, profilingLogger, runtimeState); + composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState); + + // create the core runtime and have it compose itself + var coreRuntime = new CoreRuntime(); + coreRuntime.Compose(composition); + + // get the components + // all of them? + var composerTypes = typeLoader.GetTypes(); + // filtered + composerTypes = composerTypes + .Where(x => !x.FullName.StartsWith("Umbraco.Tests")); + // single? + //var componentTypes = new[] { typeof(CoreRuntimeComponent) }; + var composers = new Composers(composition, composerTypes, profilingLogger); + + // get components to compose themselves + composers.Compose(); + + // create the factory + var factory = composition.CreateFactory(); + + // at that point Umbraco is fully composed + // but nothing is initialized (no maindom, nothing - beware!) + // to actually *run* Umbraco standalone, better use a StandaloneRuntime + // that would inherit from CoreRuntime and ensure everything starts + + // get components to initialize themselves + //components.Initialize(factory); + + // and then, validate + var lightInjectContainer = (LightInject.ServiceContainer) factory.Concrete; + var results = lightInjectContainer.Validate().ToList(); + foreach (var resultGroup in results.GroupBy(x => x.Severity).OrderBy(x => x.Key)) + { + Console.WriteLine($"{resultGroup.Key}: {resultGroup.Count()}"); + } + + foreach (var resultGroup in results.GroupBy(x => x.Severity).OrderBy(x => x.Key)) + foreach (var result in resultGroup) + { + Console.WriteLine(); + Console.Write(ToText(result)); + } + + Assert.AreEqual(0, results.Count); + } + + private static string ToText(ValidationResult result) + { + var text = new StringBuilder(); + + text.AppendLine($"{result.Severity}: {WordWrap(result.Message, 120)}"); + var target = result.ValidationTarget; + text.Append("\tsvce: "); + text.Append(target.ServiceName); + text.Append(target.DeclaringService.ServiceType); + if (!target.DeclaringService.ServiceName.IsNullOrWhiteSpace()) + { + text.Append(" '"); + text.Append(target.DeclaringService.ServiceName); + text.Append("'"); + } + + text.Append(" ("); + if (target.DeclaringService.Lifetime == null) + text.Append("Transient"); + else + text.Append(target.DeclaringService.Lifetime.ToString().TrimStart("LightInject.").TrimEnd("Lifetime")); + text.AppendLine(")"); + text.Append("\timpl: "); + text.Append(target.DeclaringService.ImplementingType); + text.AppendLine(); + text.Append("\tparm: "); + text.Append(target.Parameter); + text.AppendLine(); + + return text.ToString(); + } + + private static string WordWrap(string text, int width) + { + int pos, next; + var sb = new StringBuilder(); + var nl = Environment.NewLine; + + // Lucidity check + if (width < 1) + return text; + + // Parse each line of text + for (pos = 0; pos < text.Length; pos = next) + { + // Find end of line + var eol = text.IndexOf(nl, pos, StringComparison.Ordinal); + + if (eol == -1) + next = eol = text.Length; + else + next = eol + nl.Length; + + // Copy this line of text, breaking into smaller lines as needed + if (eol > pos) + { + do + { + var len = eol - pos; + + if (len > width) + len = BreakLine(text, pos, width); + + if (pos > 0) + sb.Append("\t\t"); + sb.Append(text, pos, len); + sb.Append(nl); + + // Trim whitespace following break + pos += len; + + while (pos < eol && char.IsWhiteSpace(text[pos])) + pos++; + + } while (eol > pos); + } + else sb.Append(nl); // Empty line + } + + return sb.ToString(); + } + + private static int BreakLine(string text, int pos, int max) + { + // Find last whitespace in line + var i = max - 1; + while (i >= 0 && !char.IsWhiteSpace(text[pos + i])) + i--; + if (i < 0) + return max; // No whitespace found; break at maximum length + // Find start of whitespace + while (i >= 0 && char.IsWhiteSpace(text[pos + i])) + i--; + // Return length of text before whitespace + return i + 1; + } + } +} diff --git a/src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs similarity index 92% rename from src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs rename to src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs index b9f8f6576b..97688a7113 100644 --- a/src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs @@ -2,14 +2,13 @@ using System.Web.Mvc; using NUnit.Framework; using Umbraco.Core.Profiling; -using Umbraco.Web; using Umbraco.Web.Mvc; using Umbraco.Web.Runtime; namespace Umbraco.Tests.Runtimes { [TestFixture] - public class WebRuntimeTests + public class WebRuntimeComponentTests { [Test] public void WrapViewEngines_HasEngines_WrapsAll() @@ -43,7 +42,6 @@ namespace Umbraco.Tests.Runtimes Assert.That(((ProfilingViewEngine)engines[1]).Inner, Is.InstanceOf()); } - [Test] public void WrapViewEngines_HasProfiledEngine_AddsSameInstance() { @@ -61,10 +59,7 @@ namespace Umbraco.Tests.Runtimes [Test] public void WrapViewEngines_CollectionIsNull_DoesNotThrow() { - IList engines = null; - Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(engines)); - Assert.That(engines, Is.Null); + Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(null)); } - } } diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs index 59cf30c0fb..3664717af7 100644 --- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs +++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs @@ -13,6 +13,7 @@ namespace Umbraco.Tests.Scheduling { [TestFixture] [Timeout(30000)] + [Category("Slow")] public class BackgroundTaskRunnerTests { private ILogger _logger; diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 4d49747004..4aa28b5975 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -1,10 +1,9 @@ using System; -using System.Collections.Generic; using System.Linq; -using LightInject; using Moq; using NUnit.Framework; -using Umbraco.Core.Collections; +using Umbraco.Core; +using Umbraco.Core.Components; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.IO; @@ -15,6 +14,7 @@ using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Composing; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Services; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Scoping { @@ -31,12 +31,20 @@ namespace Umbraco.Tests.Scoping DoThing2 = null; DoThing3 = null; - Current.Container = new ServiceContainer(); + var register = RegisterFactory.Create(); - _testObjects = new TestObjects(Current.Container); - Current.Container.RegisterSingleton(f => Current.Container); - Current.Container.RegisterSingleton(factory => new FileSystems(factory.TryGetInstance())); - Current.Container.RegisterCollectionBuilder(); + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + _testObjects = new TestObjects(register); + + composition.RegisterUnique(factory => new FileSystems(factory, factory.TryGetInstance())); + composition.WithCollectionBuilder(); + + composition.Configs.Add(SettingsForTests.GetDefaultGlobalSettings); + composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings); + + Current.Reset(); + Current.Factory = composition.CreateFactory(); SettingsForTests.Reset(); // ensure we have configuration } diff --git a/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs b/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs index ae16ba484f..0a23e4a1b9 100644 --- a/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs @@ -4,8 +4,8 @@ using System.Text; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.IO; -using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -23,11 +23,21 @@ namespace Umbraco.Tests.Scoping ClearFiles(); } + protected override void ComposeApplication(bool withApplication) + { + base.ComposeApplication(withApplication); + + if (!withApplication) return; + + // re-register with actual media fs + Composition.ComposeFileSystems(); + } + public override void TearDown() { base.TearDown(); SafeCallContext.Clear(); - ShadowFileSystems.ResetId(); + FileSystems.ResetShadowId(); ClearFiles(); } @@ -35,7 +45,7 @@ namespace Umbraco.Tests.Scoping { TestHelper.DeleteDirectory(IOHelper.MapPath("media")); TestHelper.DeleteDirectory(IOHelper.MapPath("FileSysTests")); - TestHelper.DeleteDirectory(IOHelper.MapPath("App_Data")); + TestHelper.DeleteDirectory(IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs")); } [TestCase(true)] @@ -43,7 +53,7 @@ namespace Umbraco.Tests.Scoping public void CreateMediaTest(bool complete) { var physMediaFileSystem = new PhysicalFileSystem(IOHelper.MapPath("media"), "ignore"); - var mediaFileSystem = Current.FileSystems.MediaFileSystem; + var mediaFileSystem = Current.MediaFileSystem; Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt")); @@ -62,12 +72,12 @@ namespace Umbraco.Tests.Scoping if (complete) { - Assert.IsTrue(Current.FileSystems.MediaFileSystem.FileExists("f1.txt")); + Assert.IsTrue(Current.MediaFileSystem.FileExists("f1.txt")); Assert.IsTrue(physMediaFileSystem.FileExists("f1.txt")); } else { - Assert.IsFalse(Current.FileSystems.MediaFileSystem.FileExists("f1.txt")); + Assert.IsFalse(Current.MediaFileSystem.FileExists("f1.txt")); Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt")); } } @@ -76,7 +86,7 @@ namespace Umbraco.Tests.Scoping public void MultiThread() { var physMediaFileSystem = new PhysicalFileSystem(IOHelper.MapPath("media"), "ignore"); - var mediaFileSystem = Current.FileSystems.MediaFileSystem; + var mediaFileSystem = Current.MediaFileSystem; var scopeProvider = ScopeProvider; using (var scope = scopeProvider.CreateScope(scopeFileSystems: true)) diff --git a/src/Umbraco.Tests/Scoping/ScopeTests.cs b/src/Umbraco.Tests/Scoping/ScopeTests.cs index 3f2740291b..6c5e9a74b5 100644 --- a/src/Umbraco.Tests/Scoping/ScopeTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeTests.cs @@ -540,7 +540,7 @@ namespace Umbraco.Tests.Scoping var scopeProvider = ScopeProvider; bool? completed = null; - Exception exception = null; + bool? completed2 = null; Assert.IsNull(scopeProvider.AmbientScope); using (var scope = scopeProvider.CreateScope()) @@ -551,15 +551,7 @@ namespace Umbraco.Tests.Scoping // at that point the scope is gone, but the context is still there var ambientContext = scopeProvider.AmbientContext; - - try - { - ambientContext.Enlist("another", c2 => { }); - } - catch (Exception e) - { - exception = e; - } + ambientContext.Enlist("another", c2 => { completed2 = c2; }); }); if (complete) scope.Complete(); @@ -567,8 +559,8 @@ namespace Umbraco.Tests.Scoping Assert.IsNull(scopeProvider.AmbientScope); Assert.IsNull(scopeProvider.AmbientContext); Assert.IsNotNull(completed); - Assert.IsNotNull(exception); - Assert.IsInstanceOf(exception); + Assert.AreEqual(complete,completed.Value); + Assert.AreEqual(complete, completed2.Value); } [Test] diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 211bdc3cdb..e8f3463ca7 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Web.Routing; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -15,7 +14,6 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; @@ -47,10 +45,10 @@ namespace Umbraco.Tests.Scoping // but then, it requires a lot of plumbing ;( // fixme - and we cannot inject a DistributedCache yet // so doing all this mess - Container.RegisterSingleton(); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterCollectionBuilder() - .Add(f => f.TryGetInstance().GetCacheRefreshers()); + Composition.RegisterUnique(); + Composition.RegisterUnique(f => Mock.Of()); + Composition.WithCollectionBuilder() + .Add(() => Composition.TypeLoader.GetCacheRefreshers()); } public override void TearDown() @@ -97,7 +95,8 @@ namespace Umbraco.Tests.Scoping documentRepository, mediaRepository, memberRepository, DefaultCultureAccessor, new DatabaseDataSource(), - Container.GetInstance(), new SiteDomainHelper()); + Factory.GetInstance(), new SiteDomainHelper(), + Factory.GetInstance()); } protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null) diff --git a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs index 9ace5860e1..dee7a99ad0 100644 --- a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs @@ -10,7 +10,6 @@ using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using Umbraco.Web.Cache; -using LightInject; using Moq; using Umbraco.Core.Events; using Umbraco.Core.Logging; @@ -32,22 +31,19 @@ namespace Umbraco.Tests.Scoping // but then, it requires a lot of plumbing ;( // fixme - and we cannot inject a DistributedCache yet // so doing all this mess - Container.RegisterSingleton(); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterCollectionBuilder() - .Add(f => f.TryGetInstance().GetCacheRefreshers()); + Composition.RegisterUnique(); + Composition.RegisterUnique(f => Mock.Of()); + Composition.WithCollectionBuilder() + .Add(() => Composition.TypeLoader.GetCacheRefreshers()); } - protected override void ComposeCacheHelper() + protected override AppCaches GetAppCaches() { // this is what's created core web runtime - var cacheHelper = new CacheHelper( - new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), - new StaticCacheProvider(), - NullCacheProvider.Instance, - new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); - Container.RegisterSingleton(f => cacheHelper); - Container.RegisterSingleton(f => f.GetInstance().RuntimeCache); + return new AppCaches( + new DeepCloneAppCache(new ObjectCacheAppCache()), + NoAppCache.Instance, + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); } [TearDown] @@ -63,13 +59,13 @@ namespace Umbraco.Tests.Scoping { var scopeProvider = ScopeProvider; var service = Current.Services.UserService; - var globalCache = Current.ApplicationCache.IsolatedRuntimeCache.GetOrCreateCache(typeof(IUser)); + var globalCache = Current.AppCaches.IsolatedCaches.GetOrCreate(typeof(IUser)); var user = (IUser)new User("name", "email", "username", "rawPassword"); service.Save(user); // global cache contains the entity - var globalCached = (IUser) globalCache.GetCacheItem(GetCacheIdKey(user.Id), () => null); + var globalCached = (IUser) globalCache.Get(GetCacheIdKey(user.Id), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(user.Id, globalCached.Id); Assert.AreEqual("name", globalCached.Name); @@ -88,20 +84,20 @@ namespace Umbraco.Tests.Scoping Assert.AreSame(scope, scopeProvider.AmbientScope); // scope has its own isolated cache - var scopedCache = scope.IsolatedRuntimeCache.GetOrCreateCache(typeof (IUser)); + var scopedCache = scope.IsolatedCaches.GetOrCreate(typeof (IUser)); Assert.AreNotSame(globalCache, scopedCache); user.Name = "changed"; service.Save(user); // scoped cache contains the "new" entity - var scopeCached = (IUser) scopedCache.GetCacheItem(GetCacheIdKey(user.Id), () => null); + var scopeCached = (IUser) scopedCache.Get(GetCacheIdKey(user.Id), () => null); Assert.IsNotNull(scopeCached); Assert.AreEqual(user.Id, scopeCached.Id); Assert.AreEqual("changed", scopeCached.Name); // global cache is unchanged - globalCached = (IUser) globalCache.GetCacheItem(GetCacheIdKey(user.Id), () => null); + globalCached = (IUser) globalCache.Get(GetCacheIdKey(user.Id), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(user.Id, globalCached.Id); Assert.AreEqual("name", globalCached.Name); @@ -111,7 +107,7 @@ namespace Umbraco.Tests.Scoping } Assert.IsNull(scopeProvider.AmbientScope); - globalCached = (IUser) globalCache.GetCacheItem(GetCacheIdKey(user.Id), () => null); + globalCached = (IUser) globalCache.Get(GetCacheIdKey(user.Id), () => null); if (complete) { // global cache has been cleared @@ -128,7 +124,7 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(complete ? "changed" : "name", user.Name); // global cache contains the entity again - globalCached = (IUser) globalCache.GetCacheItem(GetCacheIdKey(user.Id), () => null); + globalCached = (IUser) globalCache.Get(GetCacheIdKey(user.Id), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(user.Id, globalCached.Id); Assert.AreEqual(complete ? "changed" : "name", globalCached.Name); @@ -140,18 +136,18 @@ namespace Umbraco.Tests.Scoping { var scopeProvider = ScopeProvider; var service = Current.Services.LocalizationService; - var globalCache = Current.ApplicationCache.IsolatedRuntimeCache.GetOrCreateCache(typeof (ILanguage)); + var globalCache = Current.AppCaches.IsolatedCaches.GetOrCreate(typeof (ILanguage)); var lang = (ILanguage) new Language("fr-FR"); service.Save(lang); // global cache has been flushed, reload - var globalFullCached = (IEnumerable) globalCache.GetCacheItem(GetCacheTypeKey(), () => null); + var globalFullCached = (IEnumerable) globalCache.Get(GetCacheTypeKey(), () => null); Assert.IsNull(globalFullCached); var reload = service.GetLanguageById(lang.Id); // global cache contains the entity - globalFullCached = (IEnumerable) globalCache.GetCacheItem(GetCacheTypeKey(), () => null); + globalFullCached = (IEnumerable) globalCache.Get(GetCacheTypeKey(), () => null); Assert.IsNotNull(globalFullCached); var globalCached = globalFullCached.First(x => x.Id == lang.Id); Assert.IsNotNull(globalCached); @@ -169,19 +165,19 @@ namespace Umbraco.Tests.Scoping Assert.AreSame(scope, scopeProvider.AmbientScope); // scope has its own isolated cache - var scopedCache = scope.IsolatedRuntimeCache.GetOrCreateCache(typeof (ILanguage)); + var scopedCache = scope.IsolatedCaches.GetOrCreate(typeof (ILanguage)); Assert.AreNotSame(globalCache, scopedCache); lang.IsoCode = "de-DE"; service.Save(lang); // scoped cache has been flushed, reload - var scopeFullCached = (IEnumerable) scopedCache.GetCacheItem(GetCacheTypeKey(), () => null); + var scopeFullCached = (IEnumerable) scopedCache.Get(GetCacheTypeKey(), () => null); Assert.IsNull(scopeFullCached); reload = service.GetLanguageById(lang.Id); // scoped cache contains the "new" entity - scopeFullCached = (IEnumerable) scopedCache.GetCacheItem(GetCacheTypeKey(), () => null); + scopeFullCached = (IEnumerable) scopedCache.Get(GetCacheTypeKey(), () => null); Assert.IsNotNull(scopeFullCached); var scopeCached = scopeFullCached.First(x => x.Id == lang.Id); Assert.IsNotNull(scopeCached); @@ -189,7 +185,7 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual("de-DE", scopeCached.IsoCode); // global cache is unchanged - globalFullCached = (IEnumerable) globalCache.GetCacheItem(GetCacheTypeKey(), () => null); + globalFullCached = (IEnumerable) globalCache.Get(GetCacheTypeKey(), () => null); Assert.IsNotNull(globalFullCached); globalCached = globalFullCached.First(x => x.Id == lang.Id); Assert.IsNotNull(globalCached); @@ -201,7 +197,7 @@ namespace Umbraco.Tests.Scoping } Assert.IsNull(scopeProvider.AmbientScope); - globalFullCached = (IEnumerable) globalCache.GetCacheItem(GetCacheTypeKey(), () => null); + globalFullCached = (IEnumerable) globalCache.Get(GetCacheTypeKey(), () => null); if (complete) { // global cache has been cleared @@ -218,7 +214,7 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(complete ? "de-DE" : "fr-FR", lang.IsoCode); // global cache contains the entity again - globalFullCached = (IEnumerable) globalCache.GetCacheItem(GetCacheTypeKey(), () => null); + globalFullCached = (IEnumerable) globalCache.Get(GetCacheTypeKey(), () => null); Assert.IsNotNull(globalFullCached); globalCached = globalFullCached.First(x => x.Id == lang.Id); Assert.IsNotNull(globalCached); @@ -232,7 +228,7 @@ namespace Umbraco.Tests.Scoping { var scopeProvider = ScopeProvider; var service = Current.Services.LocalizationService; - var globalCache = Current.ApplicationCache.IsolatedRuntimeCache.GetOrCreateCache(typeof (IDictionaryItem)); + var globalCache = Current.AppCaches.IsolatedCaches.GetOrCreate(typeof (IDictionaryItem)); var lang = (ILanguage)new Language("fr-FR"); service.Save(lang); @@ -245,7 +241,7 @@ namespace Umbraco.Tests.Scoping service.Save(item); // global cache contains the entity - var globalCached = (IDictionaryItem) globalCache.GetCacheItem(GetCacheIdKey(item.Id), () => null); + var globalCached = (IDictionaryItem) globalCache.Get(GetCacheIdKey(item.Id), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(item.Id, globalCached.Id); Assert.AreEqual("item-key", globalCached.ItemKey); @@ -261,20 +257,20 @@ namespace Umbraco.Tests.Scoping Assert.AreSame(scope, scopeProvider.AmbientScope); // scope has its own isolated cache - var scopedCache = scope.IsolatedRuntimeCache.GetOrCreateCache(typeof (IDictionaryItem)); + var scopedCache = scope.IsolatedCaches.GetOrCreate(typeof (IDictionaryItem)); Assert.AreNotSame(globalCache, scopedCache); item.ItemKey = "item-changed"; service.Save(item); // scoped cache contains the "new" entity - var scopeCached = (IDictionaryItem) scopedCache.GetCacheItem(GetCacheIdKey(item.Id), () => null); + var scopeCached = (IDictionaryItem) scopedCache.Get(GetCacheIdKey(item.Id), () => null); Assert.IsNotNull(scopeCached); Assert.AreEqual(item.Id, scopeCached.Id); Assert.AreEqual("item-changed", scopeCached.ItemKey); // global cache is unchanged - globalCached = (IDictionaryItem) globalCache.GetCacheItem(GetCacheIdKey(item.Id), () => null); + globalCached = (IDictionaryItem) globalCache.Get(GetCacheIdKey(item.Id), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(item.Id, globalCached.Id); Assert.AreEqual("item-key", globalCached.ItemKey); @@ -284,7 +280,7 @@ namespace Umbraco.Tests.Scoping } Assert.IsNull(scopeProvider.AmbientScope); - globalCached = (IDictionaryItem) globalCache.GetCacheItem(GetCacheIdKey(item.Id), () => null); + globalCached = (IDictionaryItem) globalCache.Get(GetCacheIdKey(item.Id), () => null); if (complete) { // global cache has been cleared @@ -301,7 +297,7 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(complete ? "item-changed" : "item-key", item.ItemKey); // global cache contains the entity again - globalCached = (IDictionaryItem) globalCache.GetCacheItem(GetCacheIdKey(item.Id), () => null); + globalCached = (IDictionaryItem) globalCache.Get(GetCacheIdKey(item.Id), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(item.Id, globalCached.Id); Assert.AreEqual(complete ? "item-changed" : "item-key", globalCached.ItemKey); diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 1afbf5cb12..568bafa4e6 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Xml; using Moq; using NUnit.Framework; -using LightInject; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -34,10 +34,16 @@ namespace Umbraco.Tests.Scoping // but then, it requires a lot of plumbing ;( // fixme - and we cannot inject a DistributedCache yet // so doing all this mess - Container.RegisterSingleton(); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterCollectionBuilder() - .Add(f => f.TryGetInstance().GetCacheRefreshers()); + Composition.RegisterUnique(); + Composition.RegisterUnique(f => Mock.Of()); + Composition.WithCollectionBuilder() + .Add(() => Composition.TypeLoader.GetCacheRefreshers()); + } + + protected override void ComposeSettings() + { + Composition.Configs.Add(SettingsForTests.GenerateMockUmbracoSettings); + Composition.Configs.Add(SettingsForTests.GenerateMockGlobalSettings); } [TearDown] @@ -65,7 +71,7 @@ namespace Umbraco.Tests.Scoping // xmlStore.Xml - the actual main xml document // publishedContentCache.GetXml() - the captured xml - private static XmlStore XmlStore => (Current.Container.GetInstance() as PublishedSnapshotService).XmlStore; + private static XmlStore XmlStore => (Current.Factory.GetInstance() as PublishedSnapshotService).XmlStore; private static XmlDocument XmlMaster => XmlStore.Xml; private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.ContentCache).GetXml(false); @@ -80,10 +86,8 @@ namespace Umbraco.Tests.Scoping Assert.AreSame(XmlStore, ((PublishedContentCache) umbracoContext.ContentCache).XmlStore); // settings - var settings = SettingsForTests.GenerateMockUmbracoSettings(); - var contentMock = Mock.Get(settings.Content); + var contentMock = Mock.Get(Factory.GetInstance().Content); contentMock.Setup(x => x.XmlCacheEnabled).Returns(false); - SettingsForTests.ConfigureSettings(settings); // create document type, document var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" }; @@ -201,9 +205,8 @@ namespace Umbraco.Tests.Scoping // settings var settings = SettingsForTests.GenerateMockUmbracoSettings(); - var contentMock = Mock.Get(settings.Content); + var contentMock = Mock.Get(Factory.GetInstance().Content); contentMock.Setup(x => x.XmlCacheEnabled).Returns(false); - SettingsForTests.ConfigureSettings(settings); // create document type var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" }; diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index 322b79429e..bf74f6fd59 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -5,6 +5,8 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -34,7 +36,7 @@ namespace Umbraco.Tests.Services protected override void Compose() { base.Compose(); - Container.Register(); + Composition.Register(); } [Test] @@ -43,7 +45,7 @@ namespace Umbraco.Tests.Services Assert.IsInstanceOf(Current.Profiler); } - private static ProfilingLogger GetTestProfilingLogger() + private static IProfilingLogger GetTestProfilingLogger() { var logger = new DebugDiagnosticsLogger(); var profiler = new TestProfiler(); @@ -161,11 +163,11 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepo = new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, DisabledCache, Logger, tRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, DisabledCache, Logger); - var repository = new DocumentRepository((IScopeAccessor) provider, DisabledCache, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); + var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, tRepository); + var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); // Act Stopwatch watch = Stopwatch.StartNew(); @@ -194,11 +196,11 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepo = new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, DisabledCache, Logger, tRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, DisabledCache, Logger); - var repository = new DocumentRepository((IScopeAccessor) provider, DisabledCache, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); + var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, tRepository); + var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); // Act Stopwatch watch = Stopwatch.StartNew(); @@ -225,11 +227,11 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepo = new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, DisabledCache, Logger, tRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, DisabledCache, Logger); - var repository = new DocumentRepository((IScopeAccessor) provider, DisabledCache, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); + var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, tRepository); + var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); // Act var contents = repository.GetMany(); @@ -259,11 +261,11 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepo = new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, DisabledCache, Logger, tRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, DisabledCache, Logger); - var repository = new DocumentRepository((IScopeAccessor) provider, DisabledCache, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); + var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, tRepository); + var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); // Act var contents = repository.GetMany(); diff --git a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs index 8f66e98b76..62a3526af5 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using LightInject; +using Umbraco.Core.Composing; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Services base.Compose(); // fixme - do it differently - Container.Register(factory => factory.GetInstance().TextService); + Composition.Register(factory => factory.GetInstance().TextService); } [Test] diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index d3ebf703a5..039bcaed24 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -7,10 +7,8 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using LightInject; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Events; @@ -22,6 +20,8 @@ using Umbraco.Core.Services.Implement; using Umbraco.Tests.Testing; using System.Reflection; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; namespace Umbraco.Tests.Services { @@ -31,6 +31,7 @@ namespace Umbraco.Tests.Services /// as well as configuration. /// [TestFixture] + [Category("Slow")] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true, WithApplication = true, @@ -56,8 +57,7 @@ namespace Umbraco.Tests.Services { base.Compose(); - // fixme - do it differently - Container.Register(factory => factory.GetInstance().TextService); + Composition.RegisterUnique(factory => Mock.Of()); } /// @@ -222,7 +222,7 @@ namespace Umbraco.Tests.Services c.ContentSchedule.Add(now.AddSeconds(5), null); //release in 5 seconds var r = ServiceContext.ContentService.Save(c); Assert.IsTrue(r.Success, r.Result.ToString()); - } + } else { c.ContentSchedule.Add(null, now.AddSeconds(5)); //expire in 5 seconds @@ -258,7 +258,7 @@ namespace Umbraco.Tests.Services variant.Add(c); } - + var runSched = ServiceContext.ContentService.PerformScheduledPublish( now.AddMinutes(1)).ToList(); //process anything scheduled before a minute from now @@ -742,7 +742,7 @@ namespace Umbraco.Tests.Services public void Can_Unpublish_Content_Variation() { // Arrange - + var langUk = new Language("en-GB") { IsDefault = true }; var langFr = new Language("fr-FR"); @@ -792,6 +792,54 @@ namespace Umbraco.Tests.Services } + [Test] + public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State() + { + // Arrange + + var langGB = new Language("en-GB") { IsDefault = true }; + var langFr = new Language("fr-FR"); + + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langGB); + + var contentType = MockedContentTypes.CreateMetaContentType(); + contentType.Variations = ContentVariation.Culture; + foreach(var prop in contentType.PropertyTypes) + prop.Variations = ContentVariation.Culture; + var keywordsProp = contentType.PropertyTypes.Single(x => x.Alias == "metakeywords"); + keywordsProp.Variations = ContentVariation.Nothing; // this one is invariant + + ServiceContext.ContentTypeService.Save(contentType); + + IContent content = new Content("content", -1, contentType); + content.SetCultureName("content-en", langGB.IsoCode); + content.SetCultureName("content-fr", langFr.IsoCode); + content.PublishCulture(langGB.IsoCode); + content.PublishCulture(langFr.IsoCode); + Assert.IsTrue(ServiceContext.ContentService.SavePublishing(content).Success); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.AreEqual(PublishedState.Published, content.PublishedState); + Assert.IsTrue(content.IsCulturePublished(langGB.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsFalse(content.IsCultureEdited(langGB.IsoCode)); + Assert.IsFalse(content.IsCultureEdited(langFr.IsoCode)); + + //update the invariant property and save a pending version + content.SetValue("metakeywords", "hello"); + ServiceContext.ContentService.Save(content); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.AreEqual(PublishedState.Published, content.PublishedState); + Assert.IsTrue(content.IsCulturePublished(langGB.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCultureEdited(langGB.IsoCode)); + Assert.IsFalse(content.IsCultureEdited(langFr.IsoCode)); + } + [Test] public void Can_Publish_Content_Variation_And_Detect_Changed_Cultures() { @@ -1066,7 +1114,7 @@ namespace Umbraco.Tests.Services foreach (var x in descendants) Console.WriteLine(" ".Substring(0, x.Level) + x.Id); } - + Console.WriteLine(); // publish parent & its branch @@ -1420,7 +1468,7 @@ namespace Umbraco.Tests.Services var descendants = new List(); while(page * pageSize < total) descendants.AddRange(contentService.GetPagedDescendants(content.Id, page++, pageSize, out total)); - + Assert.AreNotEqual(-20, content.ParentId); Assert.IsFalse(content.Trashed); Assert.AreEqual(3, descendants.Count); @@ -2989,11 +3037,11 @@ namespace Umbraco.Tests.Services private DocumentRepository CreateRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository) { var accessor = (IScopeAccessor) provider; - var templateRepository = new TemplateRepository(accessor, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(accessor, DisabledCache, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, DisabledCache, Logger, templateRepository); - var languageRepository = new LanguageRepository(accessor, DisabledCache, Logger); - var repository = new DocumentRepository(accessor, DisabledCache, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, templateRepository); + var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); return repository; } } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 8ea4856861..8dc8a2b45c 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -17,10 +17,41 @@ using Umbraco.Tests.Scoping; namespace Umbraco.Tests.Services { [TestFixture] + [Category("Slow")] [Apartment(ApartmentState.STA)] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] public class ContentTypeServiceTests : TestWithSomeContentBase { + [Test] + public void CanSaveAndGetIsElement() + { + //create content type with a property type that varies by culture + IContentType contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Nothing; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Nothing + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + contentType = ServiceContext.ContentTypeService.Get(contentType.Id); + Assert.IsFalse(contentType.IsElement); + + contentType.IsElement = true; + ServiceContext.ContentTypeService.Save(contentType); + + contentType = ServiceContext.ContentTypeService.Get(contentType.Id); + Assert.IsTrue(contentType.IsElement); + } + [Test] public void Change_Content_Type_Variation_Clears_Redirects() { diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index c28d4f7955..d0c0b93b48 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -35,10 +34,10 @@ namespace Umbraco.Tests.Services base.Compose(); // pfew - see note in ScopedNuCacheTests? - Container.RegisterSingleton(); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterCollectionBuilder() - .Add(f => f.TryGetInstance().GetCacheRefreshers()); + Composition.RegisterUnique(); + Composition.RegisterUnique(f => Mock.Of()); + Composition.WithCollectionBuilder() + .Add(() => Composition.TypeLoader.GetCacheRefreshers()); } protected override IPublishedSnapshotService CreatePublishedSnapshotService() @@ -50,7 +49,7 @@ namespace Umbraco.Tests.Services var contentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), Mock.Of()); //var documentRepository = Mock.Of(); - var documentRepository = Container.GetInstance(); + var documentRepository = Factory.GetInstance(); var mediaRepository = Mock.Of(); var memberRepository = Mock.Of(); @@ -68,7 +67,8 @@ namespace Umbraco.Tests.Services documentRepository, mediaRepository, memberRepository, DefaultCultureAccessor, new DatabaseDataSource(), - Container.GetInstance(), new SiteDomainHelper()); + Factory.GetInstance(), new SiteDomainHelper(), + Factory.GetInstance()); } public class LocalServerMessenger : ServerMessengerBase @@ -770,4 +770,4 @@ namespace Umbraco.Tests.Services "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/Services/EntityXmlSerializerTests.cs b/src/Umbraco.Tests/Services/EntityXmlSerializerTests.cs new file mode 100644 index 0000000000..28c69344b7 --- /dev/null +++ b/src/Umbraco.Tests/Services/EntityXmlSerializerTests.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Tests.Services.Importing; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class EntityXmlSerializerTests : TestWithSomeContentBase + { + private IEntityXmlSerializer Serializer => Factory.GetInstance(); + + [Test] + public void Can_Export_Macro() + { + // Arrange + var macro = new Macro("test1", "Test", "~/views/macropartials/test.cshtml", MacroTypes.PartialView); + ServiceContext.MacroService.Save(macro); + + // Act + var element = Serializer.Serialize(macro); + + // Assert + Assert.That(element, Is.Not.Null); + Assert.That(element.Element("name").Value, Is.EqualTo("Test")); + Assert.That(element.Element("alias").Value, Is.EqualTo("test1")); + Debug.Print(element.ToString()); + } + + [Test] + public void Can_Export_DictionaryItems() + { + // Arrange + CreateDictionaryData(); + var dictionaryItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Parent"); + + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); + + // Act + var xml = Serializer.Serialize(new[] { dictionaryItem }); + + // Assert + Assert.That(xml.ToString(), Is.EqualTo(dictionaryItemsElement.ToString())); + } + + [Test] + public void Can_Export_Languages() + { + // Arrange + var languageNbNo = new Language("nb-NO") { CultureName = "Norwegian" }; + ServiceContext.LocalizationService.Save(languageNbNo); + + var languageEnGb = new Language("en-GB") { CultureName = "English (United Kingdom)" }; + ServiceContext.LocalizationService.Save(languageEnGb); + + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var languageItemsElement = newPackageXml.Elements("Languages").First(); + + // Act + var xml = Serializer.Serialize(new[] { languageNbNo, languageEnGb }); + + // Assert + Assert.That(xml.ToString(), Is.EqualTo(languageItemsElement.ToString())); + } + + private void CreateDictionaryData() + { + var languageNbNo = new Language("nb-NO") { CultureName = "nb-NO" }; + ServiceContext.LocalizationService.Save(languageNbNo); + + var languageEnGb = new Language("en-GB") { CultureName = "en-GB" }; + ServiceContext.LocalizationService.Save(languageEnGb); + + var parentItem = new DictionaryItem("Parent"); + var parentTranslations = new List + { + new DictionaryTranslation(languageNbNo, "ForelderVerdi"), + new DictionaryTranslation(languageEnGb, "ParentValue") + }; + parentItem.Translations = parentTranslations; + ServiceContext.LocalizationService.Save(parentItem); + + var childItem = new DictionaryItem(parentItem.Key, "Child"); + var childTranslations = new List + { + new DictionaryTranslation(languageNbNo, "BarnVerdi"), + new DictionaryTranslation(languageEnGb, "ChildValue") + }; + childItem.Translations = childTranslations; + ServiceContext.LocalizationService.Save(childItem); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml b/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml index 5f6371bba1..baadf0340f 100644 --- a/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml +++ b/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml @@ -4163,7 +4163,7 @@ html { text-align: center; } -.text--center .seperator { +.text--center .separator { margin-right: auto; margin-left: auto; } @@ -4998,4 +4998,4 @@ nav > ul li.active ul li a { - \ No newline at end of file + diff --git a/src/Umbraco.Tests/Services/MacroServiceTests.cs b/src/Umbraco.Tests/Services/MacroServiceTests.cs index 6539a37114..69e816585e 100644 --- a/src/Umbraco.Tests/Services/MacroServiceTests.cs +++ b/src/Umbraco.Tests/Services/MacroServiceTests.cs @@ -26,7 +26,7 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); repository.Save(new Macro("test1", "Test1", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView)); repository.Save(new Macro("test2", "Test2", "~/views/macropartials/test2.cshtml", MacroTypes.PartialView)); diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 078336262f..13cde1c659 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -24,6 +24,7 @@ using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Services { [TestFixture] + [Category("Slow")] [Apartment(ApartmentState.STA)] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true, WithApplication = true)] public class MemberServiceTests : TestWithSomeContentBase diff --git a/src/Umbraco.Tests/Services/PackagingServiceTests.cs b/src/Umbraco.Tests/Services/PackagingServiceTests.cs index 87225c1288..f6878e9407 100644 --- a/src/Umbraco.Tests/Services/PackagingServiceTests.cs +++ b/src/Umbraco.Tests/Services/PackagingServiceTests.cs @@ -1,16 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Xml.Linq; +using System.IO; using NUnit.Framework; using Umbraco.Core.IO; -using Umbraco.Core.Models; using Umbraco.Core.Models.Packaging; -using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -using Umbraco.Tests.Services.Importing; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -20,134 +12,10 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class PackagingServiceTests : TestWithSomeContentBase { - [Test] - public void PackagingService_Can_Export_Macro() - { - // Arrange - var macro = new Macro("test1", "Test", "~/views/macropartials/test.cshtml", MacroTypes.PartialView); - ServiceContext.MacroService.Save(macro); + - // Act - var element = ServiceContext.PackagingService.Export(macro); + - // Assert - Assert.That(element, Is.Not.Null); - Assert.That(element.Element("name").Value, Is.EqualTo("Test")); - Assert.That(element.Element("alias").Value, Is.EqualTo("test1")); - Debug.Print(element.ToString()); - } - - [Test] - public void PackagingService_Can_Export_DictionaryItems() - { - // Arrange - CreateDictionaryData(); - var dictionaryItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Parent"); - - var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); - var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); - - // Act - var xml = ServiceContext.PackagingService.Export(new []{dictionaryItem}); - - // Assert - Assert.That(xml.ToString(), Is.EqualTo(dictionaryItemsElement.ToString())); - } - - [Test] - public void PackagingService_Can_Export_Languages() - { - // Arrange - var languageNbNo = new Language("nb-NO") { CultureName = "Norwegian" }; - ServiceContext.LocalizationService.Save(languageNbNo); - - var languageEnGb = new Language("en-GB") { CultureName = "English (United Kingdom)" }; - ServiceContext.LocalizationService.Save(languageEnGb); - - var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); - var languageItemsElement = newPackageXml.Elements("Languages").First(); - - // Act - var xml = ServiceContext.PackagingService.Export(new[] { languageNbNo, languageEnGb }); - - // Assert - Assert.That(xml.ToString(), Is.EqualTo(languageItemsElement.ToString())); - } - - private static string GetTestPackagePath(string packageName) - { - const string testPackagesDirName = "Packaging\\Packages"; - string path = Path.Combine(IOHelper.GetRootDirectorySafe(), testPackagesDirName, packageName); - return path; - } - - - [Test] - public void PackagingService_Can_ImportPackage() - { - var packagingService = (PackagingService)ServiceContext.PackagingService; - - const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; - - string testPackagePath = GetTestPackagePath(documentTypePickerUmb); - - InstallationSummary installationSummary = packagingService.InstallPackage(testPackagePath); - - Assert.IsNotNull(installationSummary); - } - - - [Test] - public void PackagingService_Can_GetPackageMetaData() - { - var packagingService = (PackagingService)ServiceContext.PackagingService; - - const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; - - string testPackagePath = GetTestPackagePath(documentTypePickerUmb); - - MetaData packageMetaData = packagingService.GetPackageMetaData(testPackagePath); - Assert.IsNotNull(packageMetaData); - } - - [Test] - public void PackagingService_Can_GetPackageWarnings() - { - var packagingService = (PackagingService)ServiceContext.PackagingService; - - const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; - - string testPackagePath = GetTestPackagePath(documentTypePickerUmb); - - PreInstallWarnings preInstallWarnings = packagingService.GetPackageWarnings(testPackagePath); - Assert.IsNotNull(preInstallWarnings); - } - - private void CreateDictionaryData() - { - var languageNbNo = new Language("nb-NO") { CultureName = "nb-NO" }; - ServiceContext.LocalizationService.Save(languageNbNo); - - var languageEnGb = new Language("en-GB") { CultureName = "en-GB" }; - ServiceContext.LocalizationService.Save(languageEnGb); - - var parentItem = new DictionaryItem("Parent"); - var parentTranslations = new List - { - new DictionaryTranslation(languageNbNo, "ForelderVerdi"), - new DictionaryTranslation(languageEnGb, "ParentValue") - }; - parentItem.Translations = parentTranslations; - ServiceContext.LocalizationService.Save(parentItem); - - var childItem = new DictionaryItem(parentItem.Key, "Child"); - var childTranslations = new List - { - new DictionaryTranslation(languageNbNo, "BarnVerdi"), - new DictionaryTranslation(languageEnGb, "ChildValue") - }; - childItem.Translations = childTranslations; - ServiceContext.LocalizationService.Save(childItem); - } + } } diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index 900a466a1d..09743b350f 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -57,7 +57,7 @@ namespace Umbraco.Tests.Services base.TearDown(); } - private static ProfilingLogger GetTestProfilingLogger() + private static IProfilingLogger GetTestProfilingLogger() { var logger = new DebugDiagnosticsLogger(); var profiler = new TestProfiler(); diff --git a/src/Umbraco.Tests/Services/SectionServiceTests.cs b/src/Umbraco.Tests/Services/SectionServiceTests.cs index ca5d755220..84eb0d1cbc 100644 --- a/src/Umbraco.Tests/Services/SectionServiceTests.cs +++ b/src/Umbraco.Tests/Services/SectionServiceTests.cs @@ -1,8 +1,10 @@ using NUnit.Framework; using System.Linq; using System.Threading; +using Umbraco.Core.Composing; using Umbraco.Core.Models.Membership; using Umbraco.Tests.Testing; +using Umbraco.Web.Services; namespace Umbraco.Tests.Services { @@ -14,15 +16,7 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)] public class SectionServiceTests : TestWithSomeContentBase { - public override void CreateTestData() - { - base.CreateTestData(); - - ServiceContext.SectionService.MakeNew("Content", "content", "icon-content"); - ServiceContext.SectionService.MakeNew("Media", "media", "icon-media"); - ServiceContext.SectionService.MakeNew("Settings", "settings", "icon-settings"); - ServiceContext.SectionService.MakeNew("Developer", "developer", "icon-developer"); - } + private ISectionService SectionService => Factory.GetInstance(); [Test] public void SectionService_Can_Get_Allowed_Sections_For_User() @@ -31,7 +25,7 @@ namespace Umbraco.Tests.Services var user = CreateTestUser(); // Act - var result = ServiceContext.SectionService.GetAllowedSections(user.Id).ToList(); + var result = SectionService.GetAllowedSections(user.Id).ToList(); // Assert Assert.AreEqual(3, result.Count); @@ -63,7 +57,7 @@ namespace Umbraco.Tests.Services Name = "Group B" }; userGroupB.AddAllowedSection("settings"); - userGroupB.AddAllowedSection("developer"); + userGroupB.AddAllowedSection("member"); ServiceContext.UserService.Save(userGroupB, new[] { user.Id }, false); return ServiceContext.UserService.GetUserById(user.Id); diff --git a/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs b/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs index 0fb0dee68c..a4006409be 100644 --- a/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs +++ b/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs @@ -9,15 +9,6 @@ namespace Umbraco.Tests.Strings [TestFixture] public class CmsHelperCasingTests : UmbracoTestBase { - [SetUp] - public void Setup() - { - //set default config - var config = SettingsForTests.GetDefaultUmbracoSettings(); - SettingsForTests.ConfigureSettings(config); - - } - [TestCase("thisIsTheEnd", "This Is The End")] [TestCase("th", "Th")] [TestCase("t", "t")] diff --git a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs index 36e5874e14..0d39fcc9e5 100644 --- a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs +++ b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs @@ -1,15 +1,13 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -71,7 +69,7 @@ namespace Umbraco.Tests.Strings })); // fixme - move to a "compose" thing? - Container.RegisterSingleton(f => _helper); + Composition.RegisterUnique(f => _helper); } private static readonly Regex FrenchElisionsRegex = new Regex("\\b(c|d|j|l|m|n|qu|s|t)('|\u8217)", RegexOptions.Compiled | RegexOptions.IgnoreCase); @@ -125,7 +123,6 @@ namespace Umbraco.Tests.Strings var contentMock = Mock.Get(settings.RequestHandler); contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty()); contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false); - SettingsForTests.ConfigureSettings(settings); const string input1 = "ÆØÅ and æøå and 中文测试 and אודות האתר and größer БбДдЖж page"; const string input2 = "ÆØÅ and æøå and größer БбДдЖж page"; diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 81dec809c8..f0db3991b8 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -1,15 +1,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Linq; -using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Strings @@ -17,12 +13,10 @@ namespace Umbraco.Tests.Strings [TestFixture] public class StringExtensionsTests : UmbracoTestBase { - public override void SetUp() + protected override void Compose() { - base.SetUp(); - - // fixme - in "compose"? - Container.RegisterSingleton(_ => new MockShortStringHelper()); + base.Compose(); + Composition.RegisterUnique(_ => new MockShortStringHelper()); } [Test] @@ -75,6 +69,17 @@ namespace Umbraco.Tests.Strings Assert.AreEqual(stripped, result); } + [TestCase("'+alert(1234)+'", "+alert1234+")] + [TestCase("'+alert(56+78)+'", "+alert56+78+")] + [TestCase("{{file}}", "file")] + [TestCase("'+alert('hello')+'", "+alerthello+")] + [TestCase("Test", "Test")] + public void Clean_From_XSS(string input, string result) + { + var cleaned = input.CleanForXss(); + Assert.AreEqual(cleaned, result); + } + [TestCase("This is a string to encrypt")] [TestCase("This is a string to encrypt\nThis is a second line")] [TestCase(" White space is preserved ")] diff --git a/src/Umbraco.Tests/Templates/MasterPageHelperTests.cs b/src/Umbraco.Tests/Templates/MasterPageHelperTests.cs deleted file mode 100644 index a8344893c0..0000000000 --- a/src/Umbraco.Tests/Templates/MasterPageHelperTests.cs +++ /dev/null @@ -1,21 +0,0 @@ -using NUnit.Framework; -using Umbraco.Core.IO; - -namespace Umbraco.Tests.Templates -{ - [TestFixture] - public class MasterPageHelperTests - { - - [TestCase(@"<%@ master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] - [TestCase(@"<%@ Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] - [TestCase(@"<%@Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] - [TestCase(@"<%@ Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] - [TestCase(@"<%@master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] - public void IsMasterPageSyntax(string design) - { - Assert.IsTrue(MasterPageHelper.IsMasterPageSyntax(design)); - } - - } -} diff --git a/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs b/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs deleted file mode 100644 index 340f0ea79f..0000000000 --- a/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -using Moq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; -using Umbraco.Core.Scoping; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Templates -{ - [TestFixture] - public class TemplateRepositoryTests - { - private readonly Mock _cacheMock = new Mock(); - private readonly Mock _viewFileSystemMock = new Mock(); - private readonly Mock _masterpageFileSystemMock = new Mock(); - private readonly Mock _templateConfigMock = new Mock(); - private TemplateRepository _templateRepository; - - private readonly TestObjects TestObjects = new TestObjects(null); - - [SetUp] - public void Setup() - { - var logger = Mock.Of(); - - var accessorMock = new Mock(); - var scopeMock = new Mock(); - var database = TestObjects.GetUmbracoSqlCeDatabase(logger); - scopeMock.Setup(x => x.Database).Returns(database); - accessorMock.Setup(x => x.AmbientScope).Returns(scopeMock.Object); - - _templateRepository = new TemplateRepository(accessorMock.Object, _cacheMock.Object, logger, _templateConfigMock.Object, _masterpageFileSystemMock.Object, _viewFileSystemMock.Object); - - } - - [Test] - public void DetermineTemplateRenderingEngine_Returns_MVC_When_ViewFile_Exists_And_Content_Has_Webform_Markup() - { - // Project in MVC mode - _templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.Mvc); - - // Template has masterpage content - var templateMock = new Mock(); - templateMock.Setup(x => x.Alias).Returns("Something"); - templateMock.Setup(x => x.Content).Returns(""); - - // but MVC View already exists - _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(true); - - var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); - Assert.AreEqual(RenderingEngine.Mvc, res); - } - - [Test] - public void DetermineTemplateRenderingEngine_Returns_WebForms_When_ViewFile_Doesnt_Exist_And_Content_Has_Webform_Markup() - { - // Project in MVC mode - _templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.Mvc); - - // Template has masterpage content - var templateMock = new Mock(); - templateMock.Setup(x => x.Alias).Returns("Something"); - templateMock.Setup(x => x.Content).Returns(""); - - // MVC View doesn't exist - _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(false); - - var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); - Assert.AreEqual(RenderingEngine.WebForms, res); - } - - [Test] - public void DetermineTemplateRenderingEngine_Returns_WebForms_When_MasterPage_Exists_And_In_Mvc_Mode() - { - // Project in MVC mode - _templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.Mvc); - - var templateMock = new Mock(); - templateMock.Setup(x => x.Alias).Returns("Something"); - - // but masterpage already exists - _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(false); - _masterpageFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(true); - - var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); - Assert.AreEqual(RenderingEngine.WebForms, res); - } - - [Test] - public void DetermineTemplateRenderingEngine_Returns_Mvc_When_ViewPage_Exists_And_In_Webforms_Mode() - { - // Project in WebForms mode - _templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.WebForms); - - var templateMock = new Mock(); - templateMock.Setup(x => x.Alias).Returns("Something"); - - // but MVC View already exists - _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(true); - _masterpageFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(false); - - var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); - Assert.AreEqual(RenderingEngine.Mvc, res); - } - - } -} diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index bc29139918..35fcc853d4 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -1,15 +1,16 @@ -using LightInject; -using Moq; +using Moq; using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Profiling; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Persistence; +using Umbraco.Tests.Components; namespace Umbraco.Tests.TestHelpers { @@ -34,22 +35,27 @@ namespace Umbraco.Tests.TestHelpers var sqlSyntax = new SqlCeSyntaxProvider(); - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); - - container.RegisterSingleton(factory => Mock.Of()); - container.RegisterSingleton(factory => Mock.Of()); + var container = RegisterFactory.Create(); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var pluginManager = new TypeLoader(NullCacheProvider.Instance, - SettingsForTests.GenerateMockGlobalSettings(), + var typeLoader = new TypeLoader(NoAppCache.Instance, + LocalTempStorage.Default, logger, false); - container.RegisterInstance(pluginManager); - container.RegisterCollectionBuilder() - .Add(() => Current.TypeLoader.GetAssignedMapperTypes()); - Mappers = container.GetInstance(); + var composition = new Composition(container, typeLoader, Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + composition.RegisterUnique(_ => Mock.Of()); + composition.RegisterUnique(_ => Mock.Of()); + + composition.RegisterUnique(typeLoader); + + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetAssignedMapperTypes()); + + var factory = Current.Factory = composition.CreateFactory(); + + Mappers = factory.GetInstance(); var pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init()); diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index 5eea6bcf72..f7e3744600 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -1,10 +1,9 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading; -using LightInject; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -28,8 +27,8 @@ namespace Umbraco.Tests.TestHelpers { base.Compose(); - Container.RegisterSingleton(); - Container.RegisterSingleton(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); } protected override void Initialize() @@ -86,19 +85,19 @@ namespace Umbraco.Tests.TestHelpers "; } - internal PublishedRouter CreatePublishedRouter(IServiceContainer container = null, ContentFinderCollection contentFinders = null) + internal PublishedRouter CreatePublishedRouter(IFactory container = null, ContentFinderCollection contentFinders = null) { return CreatePublishedRouter(TestObjects.GetUmbracoSettings().WebRouting, container, contentFinders); } - internal static PublishedRouter CreatePublishedRouter(IWebRoutingSection webRoutingSection, IServiceContainer container = null, ContentFinderCollection contentFinders = null) + internal static PublishedRouter CreatePublishedRouter(IWebRoutingSection webRoutingSection, IFactory container = null, ContentFinderCollection contentFinders = null) { return new PublishedRouter( webRoutingSection, contentFinders ?? new ContentFinderCollection(Enumerable.Empty()), new TestLastChanceFinder(), new TestVariationContextAccessor(), - container?.TryGetInstance() ?? new ServiceContext(), + container?.TryGetInstance() ?? ServiceContext.CreatePartial(), new ProfilingLogger(Mock.Of(), Mock.Of())); } } diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 7152622b07..5978820601 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -9,26 +8,20 @@ using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using System.Web.Security; using Moq; -using Semver; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dictionary; -using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; using Umbraco.Web.WebApi; -using LightInject; -using System.Globalization; +using Umbraco.Core.Logging; using Umbraco.Tests.Testing.Objects.Accessors; namespace Umbraco.Tests.TestHelpers.ControllerTesting @@ -58,7 +51,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var mockedDataTypeService = Mock.Of(); var mockedContentTypeService = Mock.Of(); - var serviceContext = new ServiceContext( + var serviceContext = ServiceContext.CreatePartial( userService: mockedUserService, contentService: mockedContentService, mediaService: mockedMediaService, @@ -67,8 +60,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting memberTypeService: mockedMemberTypeService, dataTypeService: mockedDataTypeService, contentTypeService: mockedContentTypeService, - localizedTextService:Mock.Of(), - sectionService:Mock.Of()); + localizedTextService:Mock.Of()); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); @@ -141,10 +133,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var publishedSnapshotService = new Mock(); publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedSnapshot.Object); - //var umbracoContextAccessor = new TestUmbracoContextAccessor(); - //Umbraco.Web.Composing.Current.UmbracoContextAccessor = umbracoContextAccessor; var umbracoContextAccessor = Umbraco.Web.Composing.Current.UmbracoContextAccessor; - Current.Container.Register(factory => umbracoContextAccessor.UmbracoContext); // but really, should we inject this?! var umbCtx = UmbracoContext.EnsureContext( umbracoContextAccessor, @@ -161,7 +150,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/hello/world/1234")); - var membershipHelper = new MembershipHelper(umbCtx, Mock.Of(), Mock.Of()); + var membershipHelper = new MembershipHelper(new TestUmbracoContextAccessor(umbCtx), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), null, Mock.Of(), Mock.Of()); var umbHelper = new UmbracoHelper(umbCtx, Mock.Of(), diff --git a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs index d38cd8af6f..cfd935cd82 100644 --- a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs +++ b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.IO; +using System.IO; using System.Configuration; using Moq; using Umbraco.Core; @@ -11,22 +10,6 @@ namespace Umbraco.Tests.TestHelpers { public class SettingsForTests { - public static void ConfigureSettings(IGlobalSettings settings) - { - UmbracoConfig.For.SetGlobalConfig(settings); - } - - // umbracoSettings - - /// - /// Sets the umbraco settings singleton to the object specified - /// - /// - public static void ConfigureSettings(IUmbracoSettingsSection settings) - { - UmbracoConfig.For.SetUmbracoSettings(settings); - } - public static IGlobalSettings GenerateMockGlobalSettings() { var config = Mock.Of( @@ -55,7 +38,6 @@ namespace Umbraco.Tests.TestHelpers var content = new Mock(); var security = new Mock(); var requestHandler = new Mock(); - var templates = new Mock(); var logging = new Mock(); var tasks = new Mock(); var providers = new Mock(); @@ -64,7 +46,6 @@ namespace Umbraco.Tests.TestHelpers settings.Setup(x => x.Content).Returns(content.Object); settings.Setup(x => x.Security).Returns(security.Object); settings.Setup(x => x.RequestHandler).Returns(requestHandler.Object); - settings.Setup(x => x.Templates).Returns(templates.Object); settings.Setup(x => x.Logging).Returns(logging.Object); settings.Setup(x => x.ScheduledTasks).Returns(tasks.Object); settings.Setup(x => x.Providers).Returns(providers.Object); @@ -78,7 +59,6 @@ namespace Umbraco.Tests.TestHelpers settings.Setup(x => x.RequestHandler.UseDomainPrefixes).Returns(false); settings.Setup(x => x.RequestHandler.CharCollection).Returns(RequestHandlerElement.GetDefaultCharReplacements()); settings.Setup(x => x.WebRouting.UrlProviderMode).Returns("AutoLegacy"); - settings.Setup(x => x.Templates.DefaultRenderingEngine).Returns(RenderingEngine.Mvc); settings.Setup(x => x.Providers.DefaultBackOfficeUserProvider).Returns("UsersMembershipProvider"); return settings.Object; @@ -103,7 +83,7 @@ namespace Umbraco.Tests.TestHelpers // SaveSetting("umbracoConfigurationStatus"); //} - + // reset & defaults @@ -131,8 +111,6 @@ namespace Umbraco.Tests.TestHelpers private static void ResetSettings() { _defaultGlobalSettings = null; - ConfigureSettings(GetDefaultUmbracoSettings()); - ConfigureSettings(GetDefaultGlobalSettings()); } private static IUmbracoSettingsSection _defaultUmbracoSettings; diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index 00e1a363b8..36c5961b9f 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -47,7 +47,8 @@ namespace Umbraco.Tests.TestHelpers.Stubs var allParams = ctor.GetParameters().ToArray(); foreach (var parameter in allParams) { - var found = possibleParams.SingleOrDefault(x => x.GetType() == parameter.ParameterType); + var found = possibleParams.SingleOrDefault(x => x.GetType() == parameter.ParameterType) + ?? Current.Factory.GetInstance(parameter.ParameterType); if (found != null) args.Add(found); } if (args.Count == allParams.Length) diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestExamineManager.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestExamineManager.cs index f8d48c5703..7bb3b90d81 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestExamineManager.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestExamineManager.cs @@ -9,14 +9,16 @@ namespace Umbraco.Tests.TestHelpers.Stubs private readonly ConcurrentDictionary _indexers = new ConcurrentDictionary(); private readonly ConcurrentDictionary _searchers = new ConcurrentDictionary(); - public void AddIndex(IIndex indexer) + public IIndex AddIndex(IIndex indexer) { _indexers.TryAdd(indexer.Name, indexer); + return indexer; } - public void AddSearcher(ISearcher searcher) + public ISearcher AddSearcher(ISearcher searcher) { _searchers.TryAdd(searcher.Name, searcher); + return searcher; } public void Dispose() diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 9c0bb61cb3..58c8e37cbf 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -15,7 +15,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs } public int Id { get; } - public int TemplateId { get; set; } + public int? TemplateId { get; set; } public int SortOrder { get; set; } public string Name { get; set; } public IVariationContextAccessor VariationContextAccessor { get; set; } diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 1fe814e4c6..b6f597bf46 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -55,12 +55,12 @@ namespace Umbraco.Tests.TestHelpers public static void InitializeContentDirectories() { - CreateDirectories(new[] { SystemDirectories.Masterpages, SystemDirectories.MvcViews, SystemDirectories.Media, SystemDirectories.AppPlugins }); + CreateDirectories(new[] { SystemDirectories.MvcViews, SystemDirectories.Media, SystemDirectories.AppPlugins }); } public static void CleanContentDirectories() { - CleanDirectories(new[] { SystemDirectories.Masterpages, SystemDirectories.MvcViews, SystemDirectories.Media }); + CleanDirectories(new[] { SystemDirectories.MvcViews, SystemDirectories.Media }); } public static void CreateDirectories(string[] directories) @@ -77,7 +77,6 @@ namespace Umbraco.Tests.TestHelpers { var preserves = new Dictionary { - { SystemDirectories.Masterpages, new[] {"dummy.txt"} }, { SystemDirectories.MvcViews, new[] {"dummy.txt"} } }; foreach (var directory in directories) diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 167d6730ed..c56eae9cd8 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -3,18 +3,18 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; +using System.Linq.Expressions; using System.Web; -using LightInject; using Moq; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Events; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; using Umbraco.Web.PublishedCache; @@ -53,34 +53,35 @@ namespace Umbraco.Tests.TestHelpers /// Gets a mocked service context built with mocked services. /// /// A ServiceContext. - public ServiceContext GetServiceContextMock(IServiceFactory container = null) + public ServiceContext GetServiceContextMock(IFactory container = null) { - return new ServiceContext( - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService()); + // fixme - else some tests break - figure it out + container = null; + + return ServiceContext.CreatePartial( + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container)); } - private T MockService(IServiceFactory container = null) + private T MockService(IFactory container = null) where T : class { return container?.TryGetInstance() ?? new Mock().Object; @@ -139,6 +140,25 @@ namespace Umbraco.Tests.TestHelpers return SettingsForTests.GetDefaultGlobalSettings(); } + public IFileSystems GetFileSystemsMock() + { + var fileSystems = Mock.Of(); + + MockFs(fileSystems, x => x.MacroPartialsFileSystem); + MockFs(fileSystems, x => x.MvcViewsFileSystem); + MockFs(fileSystems, x => x.PartialViewsFileSystem); + MockFs(fileSystems, x => x.ScriptsFileSystem); + MockFs(fileSystems, x => x.StylesheetsFileSystem); + + return fileSystems; + } + + private void MockFs(IFileSystems fileSystems, Expression> fileSystem) + { + var fs = Mock.Of(); + Mock.Get(fileSystems).Setup(fileSystem).Returns(fs); + } + #region Inner classes private class MockDbConnection : DbConnection diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 4529c4f1ef..109860146c 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -1,9 +1,7 @@ using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; -using LightInject; using Moq; using NPoco; using Umbraco.Core; @@ -14,6 +12,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Packaging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Repositories; @@ -23,6 +22,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web.Services; namespace Umbraco.Tests.TestHelpers @@ -32,27 +32,11 @@ namespace Umbraco.Tests.TestHelpers /// internal partial class TestObjects { - private readonly IServiceContainer _container; + private readonly IRegister _register; - public TestObjects(IServiceContainer container) + public TestObjects(IRegister register) { - _container = container; - } - - /// - /// Gets the default ISqlSyntaxProvider objects. - /// - /// A logger. - /// A (lazy) scope provider. - /// The default ISqlSyntaxProvider objects. - public IEnumerable GetDefaultSqlSyntaxProviders(ILogger logger, Lazy lazyScopeProvider = null) - { - return new ISqlSyntaxProvider[] - { - new MySqlSyntaxProvider(logger), - new SqlCeSyntaxProvider(), - new SqlServerSyntaxProvider(lazyScopeProvider ?? new Lazy(() => null)) - }; + _register = register; } /// @@ -79,13 +63,13 @@ namespace Umbraco.Tests.TestHelpers /// that can begin a transaction. public UmbracoDatabase GetUmbracoSqlServerDatabase(ILogger logger) { - var syntax = new SqlServerSyntaxProvider(new Lazy(() => null)); // do NOT try to get the server's version! + var syntax = new SqlServerSyntaxProvider(); // do NOT try to get the server's version! var connection = GetDbConnection(); var sqlContext = new SqlContext(syntax, DatabaseType.SqlServer2008, Mock.Of()); return new UmbracoDatabase(connection, sqlContext, logger); } - public void RegisterServices(IServiceContainer container) + public void RegisterServices(IRegister register) { } /// @@ -95,23 +79,25 @@ namespace Umbraco.Tests.TestHelpers /// A cache. /// A logger. /// + /// /// An event messages factory. /// Some url segment providers. - /// A container. + /// + /// A container. /// /// A ServiceContext. /// Should be used sparingly for integration tests only - for unit tests /// just mock the services to be passed to the ctor of the ServiceContext. public ServiceContext GetServiceContext( IScopeProvider scopeProvider, IScopeAccessor scopeAccessor, - CacheHelper cache, + AppCaches cache, ILogger logger, IGlobalSettings globalSettings, IUmbracoSettingsSection umbracoSettings, IEventMessagesFactory eventMessagesFactory, - IEnumerable urlSegmentProviders, + UrlSegmentProviderCollection urlSegmentProviders, TypeLoader typeLoader, - IServiceFactory container = null) + IFactory factory = null) { if (scopeProvider == null) throw new ArgumentNullException(nameof(scopeProvider)); if (scopeAccessor == null) throw new ArgumentNullException(nameof(scopeAccessor)); @@ -119,14 +105,17 @@ namespace Umbraco.Tests.TestHelpers if (logger == null) throw new ArgumentNullException(nameof(logger)); if (eventMessagesFactory == null) throw new ArgumentNullException(nameof(eventMessagesFactory)); - var mediaFileSystem = new MediaFileSystem(Mock.Of()); + var scheme = Mock.Of(); + var config = Mock.Of(); - var externalLoginService = GetLazyService(container, c => new ExternalLoginService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var publicAccessService = GetLazyService(container, c => new PublicAccessService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var domainService = GetLazyService(container, c => new DomainService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var auditService = GetLazyService(container, c => new AuditService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); + var mediaFileSystem = new MediaFileSystem(Mock.Of(), config, scheme, logger); - var localizedTextService = GetLazyService(container, c => new LocalizedTextService( + var externalLoginService = GetLazyService(factory, c => new ExternalLoginService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var publicAccessService = GetLazyService(factory, c => new PublicAccessService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var domainService = GetLazyService(factory, c => new DomainService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var auditService = GetLazyService(factory, c => new AuditService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); + + var localizedTextService = GetLazyService(factory, c => new LocalizedTextService( new Lazy(() => { var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/")); @@ -151,7 +140,7 @@ namespace Umbraco.Tests.TestHelpers return new LocalizedTextServiceFileSources( logger, - cache.RuntimeCache, + cache, mainLangFolder, pluginLangFolders.Concat(userLangFolders)); @@ -161,34 +150,47 @@ namespace Umbraco.Tests.TestHelpers var runtimeState = Mock.Of(); var idkMap = new IdkMap(scopeProvider); - var localizationService = GetLazyService(container, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); - var userService = GetLazyService(container, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); - var dataTypeService = GetLazyService(container, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var contentService = GetLazyService(container, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var notificationService = GetLazyService(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, GetRepo(c), globalSettings, umbracoSettings.Content)); - var serverRegistrationService = GetLazyService(container, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var memberGroupService = GetLazyService(container, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var memberService = GetLazyService(container, c => new MemberService(scopeProvider, logger, eventMessagesFactory, memberGroupService.Value, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var mediaService = GetLazyService(container, c => new MediaService(scopeProvider, mediaFileSystem, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var contentTypeService = GetLazyService(container, c => new ContentTypeService(scopeProvider, logger, eventMessagesFactory, contentService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var mediaTypeService = GetLazyService(container, c => new MediaTypeService(scopeProvider, logger, eventMessagesFactory, mediaService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var fileService = GetLazyService(container, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var localizationService = GetLazyService(factory, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); + var userService = GetLazyService(factory, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); + var dataTypeService = GetLazyService(factory, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var notificationService = GetLazyService(factory, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, GetRepo(c), globalSettings, umbracoSettings.Content)); + var serverRegistrationService = GetLazyService(factory, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var memberGroupService = GetLazyService(factory, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var memberService = GetLazyService(factory, c => new MemberService(scopeProvider, logger, eventMessagesFactory, memberGroupService.Value, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var mediaService = GetLazyService(factory, c => new MediaService(scopeProvider, mediaFileSystem, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var contentTypeService = GetLazyService(factory, c => new ContentTypeService(scopeProvider, logger, eventMessagesFactory, contentService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var mediaTypeService = GetLazyService(factory, c => new MediaTypeService(scopeProvider, logger, eventMessagesFactory, mediaService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var fileService = GetLazyService(factory, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var memberTypeService = GetLazyService(container, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); - var entityService = GetLazyService(container, c => new EntityService( + var memberTypeService = GetLazyService(factory, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); + var entityService = GetLazyService(factory, c => new EntityService( scopeProvider, logger, eventMessagesFactory, contentService.Value, contentTypeService.Value, mediaService.Value, mediaTypeService.Value, dataTypeService.Value, memberService.Value, memberTypeService.Value, idkMap, GetRepo(c))); - var macroService = GetLazyService(container, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); - var packagingService = GetLazyService(container, c => new PackagingService(logger, contentService.Value, contentTypeService.Value, mediaService.Value, macroService.Value, dataTypeService.Value, fileService.Value, localizationService.Value, entityService.Value, userService.Value, scopeProvider, urlSegmentProviders, GetRepo(c), GetRepo(c), new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())))); - var relationService = GetLazyService(container, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo(c), GetRepo(c))); - var treeService = GetLazyService(container, c => new ApplicationTreeService(logger, cache, typeLoader)); - var tagService = GetLazyService(container, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var sectionService = GetLazyService(container, c => new SectionService(userService.Value, treeService.Value, scopeProvider, cache)); - var redirectUrlService = GetLazyService(container, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var consentService = GetLazyService(container, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var macroService = GetLazyService(factory, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); + var packagingService = GetLazyService(factory, c => + { + var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); + var compiledPackageXmlParser = new CompiledPackageXmlParser(new ConflictingPackageData(macroService.Value, fileService.Value)); + return new PackagingService( + auditService.Value, + new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value, + new EntityXmlSerializer(contentService.Value, mediaService.Value, dataTypeService.Value, userService.Value, localizationService.Value, contentTypeService.Value, urlSegmentProviders), logger, "createdPackages.config"), + new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value, + new EntityXmlSerializer(contentService.Value, mediaService.Value, dataTypeService.Value, userService.Value, localizationService.Value, contentTypeService.Value, urlSegmentProviders), logger, "installedPackages.config"), + new PackageInstallation( + new PackageDataInstallation(logger, fileService.Value, macroService.Value, localizationService.Value, dataTypeService.Value, entityService.Value, contentTypeService.Value, contentService.Value, propertyEditorCollection), + new PackageFileInstallation(compiledPackageXmlParser, new ProfilingLogger(logger, new TestProfiler())), + compiledPackageXmlParser, Mock.Of(), + new DirectoryInfo(IOHelper.GetRootDirectorySafe()))); + }); + var relationService = GetLazyService(factory, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo(c), GetRepo(c))); + var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var redirectUrlService = GetLazyService(factory, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var consentService = GetLazyService(factory, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); return new ServiceContext( publicAccessService, @@ -209,8 +211,6 @@ namespace Umbraco.Tests.TestHelpers serverRegistrationService, entityService, relationService, - treeService, - sectionService, macroService, memberTypeService, memberGroupService, @@ -220,13 +220,13 @@ namespace Umbraco.Tests.TestHelpers consentService); } - private Lazy GetLazyService(IServiceFactory container, Func ctor) + private Lazy GetLazyService(IFactory container, Func ctor) where T : class { return new Lazy(() => container?.TryGetInstance() ?? ctor(container)); } - private T GetRepo(IServiceFactory container) + private T GetRepo(IFactory container) where T : class, IRepository { return container?.TryGetInstance() ?? Mock.Of(); @@ -239,11 +239,11 @@ namespace Umbraco.Tests.TestHelpers //var mappersBuilder = new MapperCollectionBuilder(Current.Container); // fixme //mappersBuilder.AddCore(); //var mappers = mappersBuilder.CreateCollection(); - var mappers = Current.Container.GetInstance(); - databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, GetDefaultSqlSyntaxProviders(logger), logger, mappers); + var mappers = Current.Factory.GetInstance(); + databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, logger, new Lazy(() => mappers)); } - fileSystems = fileSystems ?? new FileSystems(logger); + fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger); var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger); return scopeProvider; } diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 6b52137542..2deb30bbdd 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -26,9 +26,7 @@ using File = System.IO.File; using Umbraco.Core.Composing; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Scoping; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; -using LightInject; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; @@ -49,13 +47,9 @@ namespace Umbraco.Tests.TestHelpers [UmbracoTest(WithApplication = true)] public abstract class TestWithDatabaseBase : UmbracoTestBase { - private CacheHelper _disabledCacheHelper; - private string _databasePath; private static byte[] _databaseBytes; - protected CacheHelper DisabledCache => _disabledCacheHelper ?? (_disabledCacheHelper = CacheHelper.CreateDisabledCacheHelper()); - protected PublishedContentTypeCache ContentTypesCache { get; private set; } protected override ISqlSyntaxProvider SqlSyntax => GetSyntaxProvider(); @@ -64,7 +58,7 @@ namespace Umbraco.Tests.TestHelpers internal ScopeProvider ScopeProvider => Current.ScopeProvider as ScopeProvider; - protected ISqlContext SqlContext => Container.GetInstance(); + protected ISqlContext SqlContext => Factory.GetInstance(); public override void SetUp() { @@ -78,23 +72,22 @@ namespace Umbraco.Tests.TestHelpers { base.Compose(); - Container.Register(); - Container.Register(factory => PublishedSnapshotService); - Container.Register(factory => DefaultCultureAccessor); + Composition.Register(); + Composition.Register(factory => PublishedSnapshotService); + Composition.Register(factory => DefaultCultureAccessor); - Container.GetInstance() + Composition.WithCollectionBuilder() .Clear() - .Add(f => f.GetInstance().GetDataEditors()); + .Add(() => Composition.TypeLoader.GetDataEditors()); - Container.RegisterSingleton(f => + Composition.RegisterUnique(f => { if (Options.Database == UmbracoTestOptions.Database.None) return TestObjects.GetDatabaseFactoryMock(); - var sqlSyntaxProviders = new[] { new SqlCeSyntaxProvider() }; var logger = f.GetInstance(); var mappers = f.GetInstance(); - var factory = new UmbracoDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, logger, mappers); + var factory = new UmbracoDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), logger, new Lazy(() => mappers)); factory.ResetForTests(); return factory; }); @@ -108,7 +101,7 @@ namespace Umbraco.Tests.TestHelpers public override void TearDown() { - var profilingLogger = Container.TryGetInstance(); + var profilingLogger = Factory.TryGetInstance(); var timer = profilingLogger?.TraceDuration("teardown"); // fixme move that one up try { @@ -138,9 +131,8 @@ namespace Umbraco.Tests.TestHelpers } // ensure the configuration matches the current version for tests - var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.ConfigurationStatus).Returns(UmbracoVersion.Current.ToString(3)); - SettingsForTests.ConfigureSettings(globalSettingsMock.Object); using (ProfilingLogger.TraceDuration("Initialize database.")) { @@ -254,13 +246,13 @@ namespace Umbraco.Tests.TestHelpers protected virtual IPublishedSnapshotService CreatePublishedSnapshotService() { - var cache = NullCacheProvider.Instance; + var cache = NoAppCache.Instance; ContentTypesCache = new PublishedContentTypeCache( - Container.GetInstance(), - Container.GetInstance(), - Container.GetInstance(), - Container.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), Logger); // testing=true so XmlStore will not use the file nor the database @@ -269,13 +261,14 @@ namespace Umbraco.Tests.TestHelpers var variationContextAccessor = new TestVariationContextAccessor(); var service = new PublishedSnapshotService( ServiceContext, - Container.GetInstance(), + Factory.GetInstance(), ScopeProvider, cache, publishedSnapshotAccessor, variationContextAccessor, - Container.GetInstance(), Container.GetInstance(), Container.GetInstance(), + Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), DefaultCultureAccessor, Logger, - Container.GetInstance(), new SiteDomainHelper(), + Factory.GetInstance(), new SiteDomainHelper(), + Factory.GetInstance(), ContentTypesCache, null, true, Options.PublishedRepositoryEvents); @@ -376,11 +369,11 @@ namespace Umbraco.Tests.TestHelpers var umbracoContext = new UmbracoContext( httpContext, service, - new WebSecurity(httpContext, Container.GetInstance(), - Container.GetInstance()), - umbracoSettings ?? Container.GetInstance(), + new WebSecurity(httpContext, Factory.GetInstance(), + Factory.GetInstance()), + umbracoSettings ?? Factory.GetInstance(), urlProviders ?? Enumerable.Empty(), - globalSettings ?? Container.GetInstance(), + globalSettings ?? Factory.GetInstance(), new TestVariationContextAccessor()); if (setSingleton) diff --git a/src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs b/src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs index da93218907..4f3b801af9 100644 --- a/src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs +++ b/src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs @@ -5,5 +5,14 @@ namespace Umbraco.Tests.Testing.Objects.Accessors public class TestUmbracoContextAccessor : IUmbracoContextAccessor { public UmbracoContext UmbracoContext { get; set; } + + public TestUmbracoContextAccessor() + { + } + + public TestUmbracoContextAccessor(UmbracoContext umbracoContext) + { + UmbracoContext = umbracoContext; + } } } diff --git a/src/Umbraco.Tests/Testing/Objects/Accessors/TestVariationContextAccessor.cs b/src/Umbraco.Tests/Testing/Objects/Accessors/TestVariationContextAccessor.cs index 3c7377f2cc..134b709447 100644 --- a/src/Umbraco.Tests/Testing/Objects/Accessors/TestVariationContextAccessor.cs +++ b/src/Umbraco.Tests/Testing/Objects/Accessors/TestVariationContextAccessor.cs @@ -8,10 +8,6 @@ namespace Umbraco.Tests.Testing.Objects.Accessors public class TestVariationContextAccessor : IVariationContextAccessor { /// - public VariationContext VariationContext - { - get; - set; - } + public VariationContext VariationContext { get; set; } } } diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 5b93de0c09..575f14e818 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -6,7 +6,9 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Dictionary; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -51,11 +53,11 @@ namespace Umbraco.Tests.Testing.TestingTests var umbracoContext = TestObjects.GetUmbracoContextMock(); // unless we can inject them in MembershipHelper, we need need this - Container.Register(_ => Mock.Of()); - Container.Register(_ => Mock.Of()); - Container.Register(_ => Mock.Of()); - Container.Register(_ => CacheHelper.CreateDisabledCacheHelper()); - Container.Register(); + Composition.Register(_ => Mock.Of()); + Composition.Register(_ => Mock.Of()); + Composition.Register(_ => Mock.Of()); + Composition.Register(_ => AppCaches.Disabled); + Composition.Register(); // ReSharper disable once UnusedVariable var helper = new UmbracoHelper(umbracoContext, @@ -63,8 +65,8 @@ namespace Umbraco.Tests.Testing.TestingTests Mock.Of(), Mock.Of(), Mock.Of(), - new MembershipHelper(umbracoContext, Mock.Of(), Mock.Of()), - new ServiceContext()); + new MembershipHelper(new TestUmbracoContextAccessor(umbracoContext), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), null, Mock.Of(), Mock.Of()), + ServiceContext.CreatePartial()); Assert.Pass(); } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index c7c493c64d..2216ba6c1f 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -4,15 +4,15 @@ using System.Linq; using System.Reflection; using AutoMapper; using Examine; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Components; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.CompositionRoots; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; @@ -22,7 +22,6 @@ using Umbraco.Core.Manifest; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.PropertyEditors; @@ -30,18 +29,18 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.Services; -using Umbraco.Examine; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web.Actions; -using Umbraco.Web.Composing.CompositionRoots; +using Umbraco.Web.Composing.Composers; using Umbraco.Web.ContentApps; - using Current = Umbraco.Core.Composing.Current; using Umbraco.Web.Routing; +using Umbraco.Web.Trees; namespace Umbraco.Tests.Testing { @@ -79,7 +78,9 @@ namespace Umbraco.Tests.Testing // test feature, and no test "base" class should be. only actual test feature classes // should be marked with that attribute. - protected ServiceContainer Container { get; private set; } + protected Composition Composition { get; private set; } + + protected IFactory Factory { get; private set; } protected UmbracoTestAttribute Options { get; private set; } @@ -95,17 +96,17 @@ namespace Umbraco.Tests.Testing #region Accessors - protected ILogger Logger => Container.GetInstance(); + protected ILogger Logger => Factory.GetInstance(); - protected IProfiler Profiler => Container.GetInstance(); + protected IProfiler Profiler => Factory.GetInstance(); - protected virtual ProfilingLogger ProfilingLogger => Container.GetInstance(); + protected virtual IProfilingLogger ProfilingLogger => Factory.GetInstance(); - protected CacheHelper CacheHelper => Container.GetInstance(); + protected AppCaches AppCaches => Factory.GetInstance(); - protected virtual ISqlSyntaxProvider SqlSyntax => Container.GetInstance(); + protected virtual ISqlSyntaxProvider SqlSyntax => Factory.GetInstance(); - protected IMapperCollection Mappers => Container.GetInstance(); + protected IMapperCollection Mappers => Factory.GetInstance(); #endregion @@ -118,24 +119,36 @@ namespace Umbraco.Tests.Testing // but hey, never know, better avoid garbage-in Reset(); - Container = new ServiceContainer(); - Container.ConfigureUmbracoCore(); - - TestObjects = new TestObjects(Container); - // get/merge the attributes marking the method and/or the classes Options = TestOptionAttributeBase.GetTestOptions(); + // fixme - align to runtimes & components - don't redo everything here + + var (logger, profiler) = GetLoggers(Options.Logger); + var proflogger = new ProfilingLogger(logger, profiler); + var appCaches = GetAppCaches(); + var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); + var typeLoader = GetTypeLoader(appCaches.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); + + var register = RegisterFactory.Create(); + + Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + Composition.RegisterUnique(typeLoader); + Composition.RegisterUnique(logger); + Composition.RegisterUnique(profiler); + Composition.RegisterUnique(proflogger); + Composition.RegisterUnique(appCaches); + + TestObjects = new TestObjects(register); Compose(); + Current.Factory = Factory = Composition.CreateFactory(); Initialize(); } protected virtual void Compose() { - ComposeLogging(Options.Logger); - ComposeCacheHelper(); ComposeAutoMapper(Options.AutoMapper); - ComposeTypeLoader(Options.TypeLoader); ComposeDatabase(Options.Database); ComposeApplication(Options.WithApplication); @@ -144,8 +157,7 @@ namespace Umbraco.Tests.Testing ComposeWtf(); // not sure really - var composition = new Composition(Container, RuntimeLevel.Run); - Compose(composition); + Compose(Composition); } protected virtual void Compose(Composition composition) @@ -161,41 +173,59 @@ namespace Umbraco.Tests.Testing #region Compose - protected virtual void ComposeLogging(UmbracoTestOptions.Logger option) + protected virtual (ILogger, IProfiler) GetLoggers(UmbracoTestOptions.Logger option) { - if (option == UmbracoTestOptions.Logger.Mock) + ILogger logger; + IProfiler profiler; + + switch (option) { - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterSingleton(f => Mock.Of()); - } - else if (option == UmbracoTestOptions.Logger.Serilog) - { - Container.RegisterSingleton(f => new SerilogLogger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config")))); - Container.RegisterSingleton(f => new LogProfiler(f.GetInstance())); - } - else if (option == UmbracoTestOptions.Logger.Console) - { - Container.RegisterSingleton(f => new ConsoleLogger()); - Container.RegisterSingleton(f => new LogProfiler(f.GetInstance())); + case UmbracoTestOptions.Logger.Mock: + logger = Mock.Of(); + profiler = Mock.Of(); + break; + case UmbracoTestOptions.Logger.Serilog: + logger = new SerilogLogger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config"))); + profiler = new LogProfiler(logger); + break; + case UmbracoTestOptions.Logger.Console: + logger = new ConsoleLogger(); + profiler = new LogProfiler(logger); + break; + default: + throw new NotSupportedException($"Logger option {option} is not supported."); } - Container.RegisterSingleton(f => new ProfilingLogger(f.GetInstance(), f.GetInstance())); + return (logger, profiler); + } + + protected virtual AppCaches GetAppCaches() + { + return AppCaches.Disabled; } protected virtual void ComposeWeb() { - //TODO: Should we 'just' register the WebRuntimeComponent? - // imported from TestWithSettingsBase // which was inherited by TestWithApplicationBase so pretty much used everywhere Umbraco.Web.Composing.Current.UmbracoContextAccessor = new TestUmbracoContextAccessor(); // web - Container.Register(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor); - Container.RegisterSingleton(); - Container.RegisterCollectionBuilder(); - Container.Register(); - Container.Register(); + Composition.RegisterUnique(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor); + Composition.RegisterUnique(); + Composition.WithCollectionBuilder(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); + + // register back office sections in the order we want them rendered + Composition.WithCollectionBuilder().Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + Composition.RegisterUnique(); } protected virtual void ComposeWtf() @@ -203,59 +233,52 @@ namespace Umbraco.Tests.Testing // what else? var runtimeStateMock = new Mock(); runtimeStateMock.Setup(x => x.Level).Returns(RuntimeLevel.Run); - Container.RegisterSingleton(f => runtimeStateMock.Object); + Composition.RegisterUnique(f => runtimeStateMock.Object); // ah... - Container.RegisterCollectionBuilder(); - Container.RegisterCollectionBuilder(); - Container.RegisterSingleton(); + Composition.WithCollectionBuilder(); + Composition.WithCollectionBuilder(); + Composition.RegisterUnique(); - Container.RegisterSingleton(); + Composition.RegisterUnique(); // register empty content apps collection - Container.RegisterCollectionBuilder(); - } - - protected virtual void ComposeCacheHelper() - { - Container.RegisterSingleton(f => CacheHelper.CreateDisabledCacheHelper()); - Container.RegisterSingleton(f => f.GetInstance().RuntimeCache); + Composition.WithCollectionBuilder(); } protected virtual void ComposeAutoMapper(bool configure) { if (configure == false) return; - Container.RegisterFrom(); - Container.RegisterFrom(); + Composition + .ComposeCoreMappingProfiles() + .ComposeWebMappingProfiles(); } - protected virtual void ComposeTypeLoader(UmbracoTestOptions.TypeLoader typeLoader) + protected virtual TypeLoader GetTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger, UmbracoTestOptions.TypeLoader option) { - Container.RegisterSingleton(f => + switch (option) { - switch (typeLoader) - { - case UmbracoTestOptions.TypeLoader.Default: - return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(f)); - case UmbracoTestOptions.TypeLoader.PerFixture: - return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(f)); - case UmbracoTestOptions.TypeLoader.PerTest: - return CreateTypeLoader(f); - default: - throw new ArgumentOutOfRangeException(nameof(typeLoader)); - } - }); + case UmbracoTestOptions.TypeLoader.Default: + return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(runtimeCache, globalSettings, logger)); + case UmbracoTestOptions.TypeLoader.PerFixture: + return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(runtimeCache, globalSettings, logger)); + case UmbracoTestOptions.TypeLoader.PerTest: + return CreateTypeLoader(runtimeCache, globalSettings, logger); + default: + throw new ArgumentOutOfRangeException(nameof(option)); + } } - protected virtual TypeLoader CreateTypeLoader(IServiceFactory f) + protected virtual TypeLoader CreateTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - return CreateCommonTypeLoader(f); + return CreateCommonTypeLoader(runtimeCache, globalSettings, logger); } - private static TypeLoader CreateCommonTypeLoader(IServiceFactory f) + // common to all tests = cannot be overriden + private static TypeLoader CreateCommonTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - return new TypeLoader(f.GetInstance().RuntimeCache, f.GetInstance(), f.GetInstance(), false) + return new TypeLoader(runtimeCache, globalSettings.LocalTempStorageLocation, logger, false) { AssembliesToScan = new[] { @@ -272,80 +295,73 @@ namespace Umbraco.Tests.Testing // create the file // create the schema + } + protected virtual void ComposeSettings() + { + Composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings); + Composition.Configs.Add(SettingsForTests.GetDefaultGlobalSettings); } protected virtual void ComposeApplication(bool withApplication) { + ComposeSettings(); + if (withApplication == false) return; - var umbracoSettings = SettingsForTests.GetDefaultUmbracoSettings(); - var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); - //apply these globally - SettingsForTests.ConfigureSettings(umbracoSettings); - SettingsForTests.ConfigureSettings(globalSettings); - // default Datalayer/Repositories/SQL/Database/etc... - Container.RegisterFrom(); + Composition.ComposeRepositories(); // register basic stuff that might need to be there for some container resolvers to work - Container.RegisterSingleton(factory => umbracoSettings); - Container.RegisterSingleton(factory => globalSettings); - Container.RegisterSingleton(factory => umbracoSettings.Content); - Container.RegisterSingleton(factory => umbracoSettings.Templates); - Container.RegisterSingleton(factory => umbracoSettings.WebRouting); - Container.Register(factory => new MediaFileSystem(Mock.Of())); - Container.RegisterSingleton(factory => ExamineManager.Instance); + Composition.RegisterUnique(factory => factory.GetInstance().Content); + Composition.RegisterUnique(factory => factory.GetInstance().WebRouting); - // replace some stuff - Container.RegisterSingleton(factory => Mock.Of(), "ScriptFileSystem"); - Container.RegisterSingleton(factory => Mock.Of(), "PartialViewFileSystem"); - Container.RegisterSingleton(factory => Mock.Of(), "PartialViewMacroFileSystem"); - Container.RegisterSingleton(factory => Mock.Of(), "StylesheetFileSystem"); + Composition.RegisterUnique(factory => ExamineManager.Instance); - // need real file systems here as templates content is on-disk only - //Container.RegisterSingleton(factory => Mock.Of(), "MasterpageFileSystem"); - //Container.RegisterSingleton(factory => Mock.Of(), "ViewFileSystem"); - Container.RegisterSingleton(factory => new PhysicalFileSystem("Views", "/views"), "ViewFileSystem"); - Container.RegisterSingleton(factory => new PhysicalFileSystem("MasterPages", "/masterpages"), "MasterpageFileSystem"); - Container.RegisterSingleton(factory => new PhysicalFileSystem("Xslt", "/xslt"), "XsltFileSystem"); + // register filesystems + Composition.RegisterUnique(factory => TestObjects.GetFileSystemsMock()); + + var logger = Mock.Of(); + var scheme = Mock.Of(); + var config = Mock.Of(); + + var mediaFileSystem = new MediaFileSystem(Mock.Of(), config, scheme, logger); + Composition.RegisterUnique(factory => mediaFileSystem); // no factory (noop) - Container.RegisterSingleton(); + Composition.RegisterUnique(); // register application stuff (database factory & context, services...) - Container.RegisterCollectionBuilder() - .AddCore(); + Composition.WithCollectionBuilder() + .AddCoreMappers(); - Container.RegisterSingleton(_ => new TransientEventMessagesFactory()); - var sqlSyntaxProviders = TestObjects.GetDefaultSqlSyntaxProviders(Logger); - Container.RegisterSingleton(_ => sqlSyntaxProviders.OfType().First()); - Container.RegisterSingleton(f => new UmbracoDatabaseFactory( + Composition.RegisterUnique(_ => new TransientEventMessagesFactory()); + Composition.RegisterUnique(f => new UmbracoDatabaseFactory( Constants.System.UmbracoConnectionName, - sqlSyntaxProviders, Logger, - Mock.Of())); - Container.RegisterSingleton(f => f.TryGetInstance().SqlContext); + new Lazy(Mock.Of))); + Composition.RegisterUnique(f => f.TryGetInstance().SqlContext); - Container.RegisterCollectionBuilder(); // empty - Container.RegisterSingleton(factory => new FileSystems(factory.TryGetInstance())); - Container.RegisterSingleton(factory + Composition.WithCollectionBuilder(); // empty + + Composition.RegisterUnique(factory => TestObjects.GetScopeProvider(factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance())); - Container.RegisterSingleton(factory => (IScopeAccessor) factory.GetInstance()); + Composition.RegisterUnique(factory => (IScopeAccessor) factory.GetInstance()); + + Composition.ComposeServices(); - Container.RegisterFrom(); // composition root is doing weird things, fix - Container.RegisterSingleton(); - Container.RegisterSingleton(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); // somehow property editor ends up wanting this - Container.RegisterCollectionBuilder(); - Container.RegisterSingleton(); + Composition.WithCollectionBuilder(); + Composition.RegisterUnique(); // note - don't register collections, use builders - Container.RegisterCollectionBuilder(); - Container.RegisterSingleton(); - Container.RegisterSingleton(); + Composition.WithCollectionBuilder(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); } #endregion @@ -358,7 +374,7 @@ namespace Umbraco.Tests.Testing Mapper.Initialize(configuration => { - var profiles = Container.GetAllInstances(); + var profiles = Factory.GetAllInstances(); foreach (var profile in profiles) configuration.AddProfile(profile); }); @@ -395,9 +411,9 @@ namespace Umbraco.Tests.Testing // reset and dispose scopes // ensures we don't leak an opened database connection // which would lock eg SqlCe .sdf files - if (Container?.TryGetInstance() is ScopeProvider scopeProvider) + if (Factory?.TryGetInstance() is ScopeProvider scopeProvider) { - Core.Scoping.Scope scope; + Scope scope; while ((scope = scopeProvider.AmbientScope) != null) { scope.Reset(); @@ -405,10 +421,7 @@ namespace Umbraco.Tests.Testing } } - Current.Reset(); - - Container?.Dispose(); - Container = null; + Current.Reset(); // disposes the factory // reset all other static things that should not be static ;( UriUtility.ResetAppDomainAppVirtualPath(); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs b/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs index 2d62f104d1..da3ffccc55 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs @@ -28,11 +28,11 @@ public enum TypeLoader { - // the default, global plugin manager for tests + // the default, global type loader for tests Default, - // create one plugin manager for the feature + // create one type loader for the feature PerFixture, - // create one plugin manager for each test + // create one type loader for each test PerTest } } diff --git a/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs b/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs deleted file mode 100644 index 951246c535..0000000000 --- a/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs +++ /dev/null @@ -1,397 +0,0 @@ -using System.IO; -using NUnit.Framework; -using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; -using System; -using System.Linq; -using System.Threading; -using Umbraco.Tests.Testing; -using Umbraco.Web.Services; -using Current = Umbraco.Web.Composing.Current; - -namespace Umbraco.Tests.TreesAndSections -{ - - - /// - ///This is a test class for ApplicationTreeTest and is intended - ///to contain all ApplicationTreeTest Unit Tests - /// - [TestFixture] - [Apartment(ApartmentState.STA)] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class ApplicationTreeTest : TestWithDatabaseBase - { - public override void SetUp() - { - base.SetUp(); - - var treesConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/trees.config"); - var appConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/applications.config"); - Directory.CreateDirectory(TestHelper.MapPathForTest("~/TEMP/TreesAndSections")); - using (var writer = File.CreateText(treesConfig)) - { - writer.Write(ResourceFiles.trees); - } - using (var writer = File.CreateText(appConfig)) - { - writer.Write(ResourceFiles.applications); - } - - ApplicationTreeService.TreeConfigFilePath = treesConfig; - SectionService.AppConfigFilePath = appConfig; - } - - public override void TearDown() - { - base.TearDown(); - - if (Directory.Exists(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"))) - { - Directory.Delete(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"), true); - } - ApplicationTreeService.TreeConfigFilePath = null; - SectionService.AppConfigFilePath = null; - } - - /// - /// Creates a new app tree linked to an application, then delete the application and make sure the tree is gone as well - /// - [Test()] - public void ApplicationTree_Make_New_Then_Delete_App() - { - //create new app - var appName = Guid.NewGuid().ToString("N"); - var treeName = Guid.NewGuid().ToString("N"); - Current.Services.SectionService.MakeNew(appName, appName, "icon.jpg"); - - //check if it exists - var app = Current.Services.SectionService.GetByAlias(appName); - Assert.IsNotNull(app); - - //create the new app tree assigned to the new app - Current.Services.ApplicationTreeService.MakeNew(false, 0, app.Alias, treeName, treeName, "icon.jpg", "icon.jpg", "Umbraco.Web.Trees.ContentTreeController, Umbraco.Web"); - var tree = Current.Services.ApplicationTreeService.GetByAlias(treeName); - Assert.IsNotNull(tree); - - //now delete the app - Current.Services.SectionService.DeleteSection(app); - - //check that the tree is gone - Assert.AreEqual(0, Current.Services.ApplicationTreeService.GetApplicationTrees(treeName).Count()); - } - - - #region Tests to write - ///// - /////A test for ApplicationTree Constructor - ///// - //[TestMethod()] - //public void ApplicationTreeConstructorTest() - //{ - // bool silent = false; // TODO: Initialize to an appropriate value - // bool initialize = false; // TODO: Initialize to an appropriate value - // byte sortOrder = 0; // TODO: Initialize to an appropriate value - // string applicationAlias = string.Empty; // TODO: Initialize to an appropriate value - // string alias = string.Empty; // TODO: Initialize to an appropriate value - // string title = string.Empty; // TODO: Initialize to an appropriate value - // string iconClosed = string.Empty; // TODO: Initialize to an appropriate value - // string iconOpened = string.Empty; // TODO: Initialize to an appropriate value - // string assemblyName = string.Empty; // TODO: Initialize to an appropriate value - // string type = string.Empty; // TODO: Initialize to an appropriate value - // string action = string.Empty; // TODO: Initialize to an appropriate value - // ApplicationTree target = new ApplicationTree(silent, initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, assemblyName, type, action); - // Assert.Inconclusive("TODO: Implement code to verify target"); - //} - - ///// - /////A test for ApplicationTree Constructor - ///// - //[TestMethod()] - //public void ApplicationTreeConstructorTest1() - //{ - // ApplicationTree target = new ApplicationTree(); - // Assert.Inconclusive("TODO: Implement code to verify target"); - //} - - ///// - /////A test for Delete - ///// - //[TestMethod()] - //public void DeleteTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // target.Delete(); - // Assert.Inconclusive("A method that does not return a value cannot be verified."); - //} - - - ///// - /////A test for Save - ///// - //[TestMethod()] - //public void SaveTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // target.Save(); - // Assert.Inconclusive("A method that does not return a value cannot be verified."); - //} - - ///// - /////A test for getAll - ///// - //[TestMethod()] - //public void getAllTest() - //{ - // ApplicationTree[] expected = null; // TODO: Initialize to an appropriate value - // ApplicationTree[] actual; - // actual = ApplicationTree.getAll(); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for getApplicationTree - ///// - //[TestMethod()] - //public void getApplicationTreeTest() - //{ - // string applicationAlias = string.Empty; // TODO: Initialize to an appropriate value - // ApplicationTree[] expected = null; // TODO: Initialize to an appropriate value - // ApplicationTree[] actual; - // actual = ApplicationTree.getApplicationTree(applicationAlias); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for getApplicationTree - ///// - //[TestMethod()] - //public void getApplicationTreeTest1() - //{ - // string applicationAlias = string.Empty; // TODO: Initialize to an appropriate value - // bool onlyInitializedApplications = false; // TODO: Initialize to an appropriate value - // ApplicationTree[] expected = null; // TODO: Initialize to an appropriate value - // ApplicationTree[] actual; - // actual = ApplicationTree.getApplicationTree(applicationAlias, onlyInitializedApplications); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for getByAlias - ///// - //[TestMethod()] - //public void getByAliasTest() - //{ - // string treeAlias = string.Empty; // TODO: Initialize to an appropriate value - // ApplicationTree expected = null; // TODO: Initialize to an appropriate value - // ApplicationTree actual; - // actual = ApplicationTree.getByAlias(treeAlias); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Action - ///// - //[TestMethod()] - //public void ActionTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.Action = expected; - // actual = target.Action; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Alias - ///// - //[TestMethod()] - //public void AliasTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string actual; - // actual = target.Alias; - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for ApplicationAlias - ///// - //[TestMethod()] - //public void ApplicationAliasTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string actual; - // actual = target.ApplicationAlias; - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for AssemblyName - ///// - //[TestMethod()] - //public void AssemblyNameTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.AssemblyName = expected; - // actual = target.AssemblyName; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for IconClosed - ///// - //[TestMethod()] - //public void IconClosedTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.IconClosed = expected; - // actual = target.IconClosed; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for IconOpened - ///// - //[TestMethod()] - //public void IconOpenedTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.IconOpened = expected; - // actual = target.IconOpened; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Initialize - ///// - //[TestMethod()] - //public void InitializeTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // bool expected = false; // TODO: Initialize to an appropriate value - // bool actual; - // target.Initialize = expected; - // actual = target.Initialize; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Silent - ///// - //[TestMethod()] - //public void SilentTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // bool expected = false; // TODO: Initialize to an appropriate value - // bool actual; - // target.Silent = expected; - // actual = target.Silent; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for SortOrder - ///// - //[TestMethod()] - //public void SortOrderTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // byte expected = 0; // TODO: Initialize to an appropriate value - // byte actual; - // target.SortOrder = expected; - // actual = target.SortOrder; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for SqlHelper - ///// - //[TestMethod()] - //public void SqlHelperTest() - //{ - // ISqlHelper actual; - // actual = ApplicationTree.SqlHelper; - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Title - ///// - //[TestMethod()] - //public void TitleTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.Title = expected; - // actual = target.Title; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Type - ///// - //[TestMethod()] - //public void TypeTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.Type = expected; - // actual = target.Type; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - #endregion - - #region Additional test attributes - // - //You can use the following additional attributes as you write your tests: - // - //Use ClassInitialize to run code before running the first test in the class - //[ClassInitialize()] - //public static void MyClassInitialize(TestContext testContext) - //{ - //} - // - //Use ClassCleanup to run code after all tests in a class have run - //[ClassCleanup()] - //public static void MyClassCleanup() - //{ - //} - // - //Use TestInitialize to run code before running each test - //[TestInitialize()] - //public void MyTestInitialize() - //{ - //} - // - //Use TestCleanup to run code after each test has run - //[TestCleanup()] - //public void MyTestCleanup() - //{ - //} - // - #endregion - } -} diff --git a/src/Umbraco.Tests/TreesAndSections/ResourceFiles.Designer.cs b/src/Umbraco.Tests/TreesAndSections/ResourceFiles.Designer.cs deleted file mode 100644 index 02bc84649e..0000000000 --- a/src/Umbraco.Tests/TreesAndSections/ResourceFiles.Designer.cs +++ /dev/null @@ -1,91 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Tests.TreesAndSections { - 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", "15.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class ResourceFiles { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal ResourceFiles() { - } - - /// - /// 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.Tests.TreesAndSections.ResourceFiles", typeof(ResourceFiles).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" encoding="utf-8"?> - ///<applications> - /// <add alias="content" name="content" icon="file" sortOrder="0" /> - /// <add alias="0b486ac9c0f1456996192c6ed1f03e57" name="0b486ac9c0f1456996192c6ed1f03e57" icon="icon.jpg" sortOrder="1" /> - /// <add alias="1ffbc301744c4e75ae3054d741954c7b" name="1ffbc301744c4e75ae3054d741954c7b" icon="icon.jpg" sortOrder="2" /> - /// <add alias="1838c3e1591f4008bbafe59a06a00a31" name="1838c3e1591f4008bbafe59a06a00a31" icon="icon.jpg" sortOrder="3" /> - /// <add alias="e5badae2 [rest of string was truncated]";. - /// - internal static string applications { - get { - return ResourceManager.GetString("applications", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> - ///<trees> - /// <add initialize="false" sortOrder="0" alias="1838c3e1591f4008bbafe59a06a00a31" application="1838c3e1591f4008bbafe59a06a00a31" title="1838c3e1591f4008bbafe59a06a00a31" iconClosed="icon.jpg" iconOpen="icon.jpg" type="nulltype" /> - /// <add initialize="false" sortOrder="0" alias="e5badae2fc5e4cd7acb3700320e33b8b" application="e5badae2fc5e4cd7acb3700320e33b8b" title="e5badae2fc5e4cd7acb3700320e33b8b" iconClosed="icon.jpg" iconOpen="icon.jpg" type="nulltype" /> - /// [rest of string was truncated]";. - /// - internal static string trees { - get { - return ResourceManager.GetString("trees", resourceCulture); - } - } - } -} diff --git a/src/Umbraco.Tests/TreesAndSections/ResourceFiles.resx b/src/Umbraco.Tests/TreesAndSections/ResourceFiles.resx deleted file mode 100644 index fac58dc842..0000000000 --- a/src/Umbraco.Tests/TreesAndSections/ResourceFiles.resx +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - - - applications.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 - - - trees.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 - - \ No newline at end of file diff --git a/src/Umbraco.Tests/TreesAndSections/SectionTests.cs b/src/Umbraco.Tests/TreesAndSections/SectionTests.cs deleted file mode 100644 index e35bd5fd45..0000000000 --- a/src/Umbraco.Tests/TreesAndSections/SectionTests.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System.IO; -using NUnit.Framework; -using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Tests.Testing; -using Umbraco.Web.Services; - -namespace Umbraco.Tests.TreesAndSections -{ - /// - ///This is a test class for ApplicationTest and is intended - ///to contain all ApplicationTest Unit Tests - /// - [TestFixture] - [UmbracoTest(AutoMapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class SectionTests : TestWithDatabaseBase - { - protected override void Compose() - { - base.Compose(); - Container.Register(); - } - - public override void SetUp() - { - base.SetUp(); - - var treesConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/trees.config"); - var appConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/applications.config"); - Directory.CreateDirectory(TestHelper.MapPathForTest("~/TEMP/TreesAndSections")); - using (var writer = File.CreateText(treesConfig)) - { - writer.Write(ResourceFiles.trees); - } - using (var writer = File.CreateText(appConfig)) - { - writer.Write(ResourceFiles.applications); - } - - ApplicationTreeService.TreeConfigFilePath = treesConfig; - SectionService.AppConfigFilePath = appConfig; - } - - public override void TearDown() - { - base.TearDown(); - - if (Directory.Exists(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"))) - { - Directory.Delete(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"), true); - } - ApplicationTreeService.TreeConfigFilePath = null; - SectionService.AppConfigFilePath = null; - } - - /// - /// Create a new application and delete it - /// - [Test()] - public void Application_Make_New() - { - var name = Guid.NewGuid().ToString("N"); - ServiceContext.SectionService.MakeNew(name, name, "icon.jpg"); - - //check if it exists - var app = ServiceContext.SectionService.GetByAlias(name); - Assert.IsNotNull(app); - - //now remove it - ServiceContext.SectionService.DeleteSection(app); - Assert.IsNull(ServiceContext.SectionService.GetByAlias(name)); - } - - #region Tests to write - - - ///// - /////A test for Application Constructor - ///// - //[TestMethod()] - //public void ApplicationConstructorTest() - //{ - // string name = string.Empty; // TODO: Initialize to an appropriate value - // string alias = string.Empty; // TODO: Initialize to an appropriate value - // string icon = string.Empty; // TODO: Initialize to an appropriate value - // Application target = new Application(name, alias, icon); - // Assert.Inconclusive("TODO: Implement code to verify target"); - //} - - ///// - /////A test for Application Constructor - ///// - //[TestMethod()] - //public void ApplicationConstructorTest1() - //{ - // Application target = new Application(); - // Assert.Inconclusive("TODO: Implement code to verify target"); - //} - - ///// - /////A test for Delete - ///// - //[TestMethod()] - //public void DeleteTest() - //{ - // Application target = new Application(); // TODO: Initialize to an appropriate value - // target.Delete(); - // Assert.Inconclusive("A method that does not return a value cannot be verified."); - //} - - - - ///// - /////A test for MakeNew - ///// - //[TestMethod()] - //public void MakeNewTest1() - //{ - // string name = string.Empty; // TODO: Initialize to an appropriate value - // string alias = string.Empty; // TODO: Initialize to an appropriate value - // string icon = string.Empty; // TODO: Initialize to an appropriate value - // Application.MakeNew(name, alias, icon); - // Assert.Inconclusive("A method that does not return a value cannot be verified."); - //} - - ///// - /////A test for RegisterIApplications - ///// - //[TestMethod()] - //public void RegisterIApplicationsTest() - //{ - // Application.RegisterIApplications(); - // Assert.Inconclusive("A method that does not return a value cannot be verified."); - //} - - ///// - /////A test for getAll - ///// - //[TestMethod()] - //public void getAllTest() - //{ - // List expected = null; // TODO: Initialize to an appropriate value - // List actual; - // actual = Application.getAll(); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for getByAlias - ///// - //[TestMethod()] - //public void getByAliasTest() - //{ - // string appAlias = string.Empty; // TODO: Initialize to an appropriate value - // Application expected = null; // TODO: Initialize to an appropriate value - // Application actual; - // actual = Application.getByAlias(appAlias); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for SqlHelper - ///// - //[TestMethod()] - //public void SqlHelperTest() - //{ - // ISqlHelper actual; - // actual = Application.SqlHelper; - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for alias - ///// - //[TestMethod()] - //public void aliasTest() - //{ - // Application target = new Application(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.alias = expected; - // actual = target.alias; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for icon - ///// - //[TestMethod()] - //public void iconTest() - //{ - // Application target = new Application(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.icon = expected; - // actual = target.icon; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for name - ///// - //[TestMethod()] - //public void nameTest() - //{ - // Application target = new Application(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.name = expected; - // actual = target.name; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - #endregion - - #region Additional test attributes - // - //You can use the following additional attributes as you write your tests: - // - //Use ClassInitialize to run code before running the first test in the class - //[ClassInitialize()] - //public static void MyClassInitialize(TestContext testContext) - //{ - //} - // - //Use ClassCleanup to run code after all tests in a class have run - //[ClassCleanup()] - //public static void MyClassCleanup() - //{ - //} - // - //Use TestInitialize to run code before running each test - //[TestInitialize()] - //public void MyTestInitialize() - //{ - //} - // - //Use TestCleanup to run code after each test has run - //[TestCleanup()] - //public void MyTestCleanup() - //{ - //} - // - #endregion - } -} diff --git a/src/Umbraco.Tests/TreesAndSections/applications.config b/src/Umbraco.Tests/TreesAndSections/applications.config deleted file mode 100644 index aadd1c5407..0000000000 --- a/src/Umbraco.Tests/TreesAndSections/applications.config +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Tests/TreesAndSections/trees.config b/src/Umbraco.Tests/TreesAndSections/trees.config deleted file mode 100644 index d21fea28a9..0000000000 --- a/src/Umbraco.Tests/TreesAndSections/trees.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Tests/UI/LegacyDialogTests.cs b/src/Umbraco.Tests/UI/LegacyDialogTests.cs index be9b0d4d7e..5c8a621e10 100644 --- a/src/Umbraco.Tests/UI/LegacyDialogTests.cs +++ b/src/Umbraco.Tests/UI/LegacyDialogTests.cs @@ -24,7 +24,6 @@ namespace Umbraco.Tests.UI } [TestCase(typeof(macroTasks), Constants.Applications.Settings)] - [TestCase(typeof(CreatedPackageTasks), Constants.Applications.Packages)] public void Check_Assigned_Apps_For_Tasks(Type taskType, string app) { var task = (LegacyDialogTask)Activator.CreateInstance(taskType); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index d58cf2a5b0..5f79d15a9e 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -62,6 +62,8 @@ + + @@ -77,7 +79,7 @@ - + 1.8.9 @@ -116,6 +118,8 @@ + + @@ -132,10 +136,13 @@ + + + @@ -218,7 +225,7 @@ - + @@ -278,12 +285,12 @@ - - - - - - + + + + + + @@ -298,8 +305,6 @@ - - @@ -347,7 +352,6 @@ - @@ -416,13 +420,6 @@ - - True - True - ResourceFiles.resx - - - @@ -437,7 +434,7 @@ - + True True @@ -446,7 +443,6 @@ - @@ -518,8 +514,6 @@ Designer Always - - Designer @@ -554,10 +548,6 @@ ImportResources.Designer.cs Designer - - ResXFileCodeGenerator - ResourceFiles.Designer.cs - ResXFileCodeGenerator TestFiles.Designer.cs diff --git a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs index 5ca195849b..5be3fe2e7e 100644 --- a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs @@ -18,8 +18,8 @@ namespace Umbraco.Tests.UmbracoExamine _profilingLogger = new ProfilingLogger(logger, new LogProfiler(logger)); } - private ProfilingLogger _profilingLogger; - protected override ProfilingLogger ProfilingLogger => _profilingLogger; + private IProfilingLogger _profilingLogger; + protected override IProfilingLogger ProfilingLogger => _profilingLogger; /// /// sets up resolvers before resolution is frozen @@ -28,7 +28,7 @@ namespace Umbraco.Tests.UmbracoExamine { base.Compose(); - Container.RegisterSingleton(_ => new DefaultShortStringHelper(SettingsForTests.GetDefaultUmbracoSettings())); + Composition.RegisterUnique(_ => new DefaultShortStringHelper(SettingsForTests.GetDefaultUmbracoSettings())); } } } diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 4435a5e829..3019138809 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -31,7 +31,7 @@ namespace Umbraco.Tests.UmbracoExamine { public static ContentValueSetBuilder GetContentValueSetBuilder(PropertyEditorCollection propertyEditors, bool publishedValuesOnly) { - var contentValueSetBuilder = new ContentValueSetBuilder(propertyEditors, new[] { new DefaultUrlSegmentProvider() }, GetMockUserService(), publishedValuesOnly); + var contentValueSetBuilder = new ContentValueSetBuilder(propertyEditors, new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), GetMockUserService(), publishedValuesOnly); return contentValueSetBuilder; } @@ -44,7 +44,7 @@ namespace Umbraco.Tests.UmbracoExamine public static MediaIndexPopulator GetMediaIndexRebuilder(PropertyEditorCollection propertyEditors, IMediaService mediaService) { - var mediaValueSetBuilder = new MediaValueSetBuilder(propertyEditors, new[] { new DefaultUrlSegmentProvider() }, GetMockUserService()); + var mediaValueSetBuilder = new MediaValueSetBuilder(propertyEditors, new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), GetMockUserService()); var mediaIndexDataSource = new MediaIndexPopulator(null, mediaService, mediaValueSetBuilder); return mediaIndexDataSource; } @@ -147,7 +147,7 @@ namespace Umbraco.Tests.UmbracoExamine } public static UmbracoContentIndex GetUmbracoIndexer( - ProfilingLogger profilingLogger, + IProfilingLogger profilingLogger, Directory luceneDir, Analyzer analyzer = null, ILocalizationService languageService = null, @@ -161,11 +161,11 @@ namespace Umbraco.Tests.UmbracoExamine if (validator == null) validator = new ContentValueSetValidator(true); - + var i = new UmbracoContentIndex( "testIndexer", - new UmbracoFieldDefinitionCollection(), luceneDir, + new UmbracoFieldDefinitionCollection(), analyzer, profilingLogger, languageService, diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 1bc51b6173..ba6a83adff 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -4,12 +4,11 @@ using Examine; using Examine.LuceneEngine.Providers; using Lucene.Net.Index; using Lucene.Net.Search; -using Lucene.Net.Store; using NUnit.Framework; using Umbraco.Tests.Testing; using Umbraco.Examine; +using Umbraco.Core.Composing; using Umbraco.Core.PropertyEditors; -using LightInject; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Models; using Newtonsoft.Json; @@ -26,11 +25,10 @@ namespace Umbraco.Tests.UmbracoExamine [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class IndexTest : ExamineBaseTest { - [Test] public void Index_Property_Data_With_Value_Indexer() { - var contentValueSetBuilder = IndexInitializer.GetContentValueSetBuilder(Container.GetInstance(), false); + var contentValueSetBuilder = IndexInitializer.GetContentValueSetBuilder(Factory.GetInstance(), false); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -122,8 +120,8 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Rebuild_Index() { - var contentRebuilder = IndexInitializer.GetContentIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); - var mediaRebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var contentRebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var mediaRebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -150,7 +148,7 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Index_Protected_Content_Not_Indexed() { - var rebuilder = IndexInitializer.GetContentIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); using (var luceneDir = new RandomIdRamDirectory()) @@ -275,7 +273,7 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Index_Reindex_Content() { - var rebuilder = IndexInitializer.GetContentIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, validator: new ContentValueSetValidator(false))) @@ -316,7 +314,7 @@ namespace Umbraco.Tests.UmbracoExamine public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed() { - var rebuilder = IndexInitializer.GetContentIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir)) diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index 747bab3c6d..7aa36f16e3 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using Examine; using Examine.Search; using NUnit.Framework; @@ -11,6 +10,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Services; using Umbraco.Tests.Testing; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Composing; using Umbraco.Examine; namespace Umbraco.Tests.UmbracoExamine @@ -53,7 +53,7 @@ namespace Umbraco.Tests.UmbracoExamine == allRecs); - var propertyEditors = Container.GetInstance(); + var propertyEditors = Factory.GetInstance(); var rebuilder = IndexInitializer.GetContentIndexRebuilder(propertyEditors, contentService, ScopeProvider.SqlContext, true); using (var luceneDir = new RandomIdRamDirectory()) @@ -62,7 +62,7 @@ namespace Umbraco.Tests.UmbracoExamine { indexer.CreateIndex(); rebuilder.Populate(indexer); - + var searcher = indexer.GetSearcher(); var numberSortedCriteria = searcher.CreateQuery() diff --git a/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs b/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs index 8d7a446ccb..8bdb0c71c7 100644 --- a/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs @@ -179,7 +179,7 @@ namespace Umbraco.Tests.UmbracoExamine { ["hello"] = "world", ["path"] = "-1,555", - [UmbracoExamineIndex.PublishedFieldName] = 1 + [UmbracoExamineIndex.PublishedFieldName] = "y" })); Assert.AreEqual(ValueSetValidationResult.Valid, result); } @@ -213,7 +213,7 @@ namespace Umbraco.Tests.UmbracoExamine { ["hello"] = "world", ["path"] = "-1,555", - [UmbracoExamineIndex.PublishedFieldName] = 0 + [UmbracoExamineIndex.PublishedFieldName] = "n" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); @@ -222,7 +222,7 @@ namespace Umbraco.Tests.UmbracoExamine { ["hello"] = "world", ["path"] = "-1,555", - [UmbracoExamineIndex.PublishedFieldName] = 1 + [UmbracoExamineIndex.PublishedFieldName] = "y" })); Assert.AreEqual(ValueSetValidationResult.Valid, result); } @@ -237,8 +237,8 @@ namespace Umbraco.Tests.UmbracoExamine { ["hello"] = "world", ["path"] = "-1,555", - [UmbracoContentIndex.VariesByCultureFieldName] = 1, - [UmbracoExamineIndex.PublishedFieldName] = 0 + [UmbracoContentIndex.VariesByCultureFieldName] = "y", + [UmbracoExamineIndex.PublishedFieldName] = "n" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); @@ -247,8 +247,8 @@ namespace Umbraco.Tests.UmbracoExamine { ["hello"] = "world", ["path"] = "-1,555", - [UmbracoContentIndex.VariesByCultureFieldName] = 1, - [UmbracoExamineIndex.PublishedFieldName] = 1 + [UmbracoContentIndex.VariesByCultureFieldName] = "y", + [UmbracoExamineIndex.PublishedFieldName] = "y" })); Assert.AreEqual(ValueSetValidationResult.Valid, result); @@ -257,14 +257,14 @@ namespace Umbraco.Tests.UmbracoExamine { ["hello"] = "world", ["path"] = "-1,555", - [UmbracoContentIndex.VariesByCultureFieldName] = 1, - [$"{UmbracoExamineIndex.PublishedFieldName}_en-us"] = 1, + [UmbracoContentIndex.VariesByCultureFieldName] = "y", + [$"{UmbracoExamineIndex.PublishedFieldName}_en-us"] = "y", ["hello_en-us"] = "world", ["title_en-us"] = "my title", - [$"{UmbracoExamineIndex.PublishedFieldName}_es-es"] = 0, + [$"{UmbracoExamineIndex.PublishedFieldName}_es-es"] = "n", ["hello_es-ES"] = "world", ["title_es-ES"] = "my title", - [UmbracoExamineIndex.PublishedFieldName] = 1 + [UmbracoExamineIndex.PublishedFieldName] = "y" }); Assert.AreEqual(10, valueSet.Values.Count()); Assert.IsTrue(valueSet.Values.ContainsKey($"{UmbracoExamineIndex.PublishedFieldName}_es-es")); diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs index 9bd00f3d96..7bd47bed66 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs @@ -81,14 +81,14 @@ namespace Umbraco.Tests.Web.Controllers var textService = new Mock(); textService.Setup(x => x.Localize(It.IsAny(), It.IsAny(), It.IsAny>())).Returns("text"); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterSingleton(f => userServiceMock.Object); - Container.RegisterSingleton(f => entityService.Object); - Container.RegisterSingleton(f => dataTypeService.Object); - Container.RegisterSingleton(f => langService.Object); - Container.RegisterSingleton(f => textService.Object); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterSingleton(f => new UmbracoApiControllerTypeCollection(new[] { typeof(ContentTreeController) })); + Composition.RegisterUnique(f => Mock.Of()); + Composition.RegisterUnique(f => userServiceMock.Object); + Composition.RegisterUnique(f => entityService.Object); + Composition.RegisterUnique(f => dataTypeService.Object); + Composition.RegisterUnique(f => langService.Object); + Composition.RegisterUnique(f => textService.Object); + Composition.RegisterUnique(f => Mock.Of()); + Composition.RegisterUnique(f => new UmbracoApiControllerTypeCollection(new[] { typeof(ContentTreeController) })); } private MultipartFormDataContent GetMultiPartRequestContent(string json) @@ -213,7 +213,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } @@ -240,7 +239,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } @@ -272,7 +270,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } @@ -310,7 +307,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } @@ -342,7 +338,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } @@ -378,7 +373,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } diff --git a/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs b/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs index 669c607aea..01525f12da 100644 --- a/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs @@ -54,8 +54,8 @@ namespace Umbraco.Tests.Web.Controllers public class Plugin1Controller : PluginController { public Plugin1Controller(UmbracoContext umbracoContext) + : base(umbracoContext, null, null, null, null, null) { - UmbracoContext = umbracoContext; } } @@ -63,8 +63,8 @@ namespace Umbraco.Tests.Web.Controllers public class Plugin2Controller : PluginController { public Plugin2Controller(UmbracoContext umbracoContext) + : base(umbracoContext, null, null, null, null, null) { - UmbracoContext = umbracoContext; } } @@ -72,16 +72,16 @@ namespace Umbraco.Tests.Web.Controllers public class Plugin3Controller : PluginController { public Plugin3Controller(UmbracoContext umbracoContext) + : base(umbracoContext, null, null, null, null, null) { - UmbracoContext = umbracoContext; } } public class Plugin4Controller : PluginController { public Plugin4Controller(UmbracoContext umbracoContext) + : base(umbracoContext, null, null, null, null, null) { - UmbracoContext = umbracoContext; } } diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 6604e25aa2..857e922ac9 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -6,6 +6,7 @@ using System.Web.Http; using Moq; using Newtonsoft.Json; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -37,12 +38,12 @@ namespace Umbraco.Tests.Web.Controllers // replace the true IUserService implementation with a mock // so that each test can configure the service to their liking - Container.RegisterSingleton(f => Mock.Of()); + Composition.RegisterUnique(f => Mock.Of()); // kill the true IEntityService too - Container.RegisterSingleton(f => Mock.Of()); - - Container.RegisterSingleton(); + Composition.RegisterUnique(f => Mock.Of()); + + Composition.RegisterUnique(); } [Test] @@ -68,7 +69,6 @@ namespace Umbraco.Tests.Web.Controllers .Returns((int id) => id == 1234 ? new User(1234, "Test", "test@test.com", "test@test.com", "", new List(), new int[0], new int[0]) : null); var usersController = new UsersController(); - Container.InjectProperties(usersController); return usersController; } @@ -125,7 +125,6 @@ namespace Umbraco.Tests.Web.Controllers ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) { var usersController = new UsersController(); - Container.InjectProperties(usersController); return usersController; } @@ -153,7 +152,6 @@ namespace Umbraco.Tests.Web.Controllers .Returns(() => users); var usersController = new UsersController(); - Container.InjectProperties(usersController); return usersController; } diff --git a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs index ba19f41e74..cc83dcb1c9 100644 --- a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs @@ -29,5 +29,30 @@ namespace Umbraco.Tests.Web.Mvc var output = _htmlHelper.Wrap("div", "hello world", new {style = "color:red;", onclick = "void();"}); Assert.AreEqual("
hello world
", output.ToHtmlString()); } + + [Test] + public void GetRelatedLinkHtml_Simple() + { + var relatedLink = new Umbraco.Web.Models.RelatedLink { + Caption = "Link Caption", + NewWindow = true, + Link = "https://www.google.com/" + }; + var output = _htmlHelper.GetRelatedLinkHtml(relatedLink); + Assert.AreEqual("Link Caption", output.ToHtmlString()); + } + + [Test] + public void GetRelatedLinkHtml_HtmlAttributes() + { + var relatedLink = new Umbraco.Web.Models.RelatedLink + { + Caption = "Link Caption", + NewWindow = true, + Link = "https://www.google.com/" + }; + var output = _htmlHelper.GetRelatedLinkHtml(relatedLink, new { @class = "test-class"}); + Assert.AreEqual("Link Caption", output.ToHtmlString()); + } } } diff --git a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs index f4c15f7c19..c6609f38c9 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -9,6 +9,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Profiling; @@ -17,12 +18,12 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; -using Umbraco.Web.Composing; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; +using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Web.Mvc { @@ -33,6 +34,7 @@ namespace Umbraco.Tests.Web.Mvc public void SetUp() { Current.UmbracoContextAccessor = new TestUmbracoContextAccessor(); + Core.Composing.Current.Factory = Mock.Of(); } [TearDown] @@ -156,7 +158,8 @@ namespace Umbraco.Tests.Web.Mvc } public class MatchesDefaultIndexController : RenderMvcController - { } + { + } public class MatchesOverriddenIndexController : RenderMvcController { diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index dce975d0c4..03844b5d72 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -7,8 +7,10 @@ using System.Web.Security; using Moq; using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dictionary; +using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; @@ -16,11 +18,11 @@ using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; -using Umbraco.Web.Composing; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; +using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Web.Mvc { @@ -49,7 +51,7 @@ namespace Umbraco.Tests.Web.Mvc new TestVariationContextAccessor(), true); - var ctrl = new TestSurfaceController { UmbracoContext = umbracoContext }; + var ctrl = new TestSurfaceController(umbracoContext); var result = ctrl.Index(); @@ -71,7 +73,7 @@ namespace Umbraco.Tests.Web.Mvc new TestVariationContextAccessor(), true); - var ctrl = new TestSurfaceController { UmbracoContext = umbCtx }; + var ctrl = new TestSurfaceController(umbCtx); Assert.IsNotNull(ctrl.UmbracoContext); } @@ -91,9 +93,8 @@ namespace Umbraco.Tests.Web.Mvc new TestVariationContextAccessor(), true); - var controller = new TestSurfaceController { UmbracoContext = umbracoContext }; - Container.Register(_ => umbracoContext); - Container.InjectProperties(controller); + var controller = new TestSurfaceController(umbracoContext); + Composition.Register(_ => umbracoContext); Assert.IsNotNull(controller.Umbraco); } @@ -131,10 +132,10 @@ namespace Umbraco.Tests.Web.Mvc Mock.Of(), Mock.Of(), Mock.Of(), - new MembershipHelper(umbracoContext, Mock.Of(), Mock.Of()), - new ServiceContext()); + new MembershipHelper(new TestUmbracoContextAccessor(umbracoContext), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), null, Mock.Of(), Mock.Of()), + ServiceContext.CreatePartial()); - var ctrl = new TestSurfaceController { UmbracoContext = umbracoContext, Umbraco = helper }; + var ctrl = new TestSurfaceController(umbracoContext, helper); var result = ctrl.GetContent(2) as PublishedContentResult; Assert.IsNotNull(result); @@ -173,7 +174,7 @@ namespace Umbraco.Tests.Web.Mvc var routeData = new RouteData(); routeData.DataTokens.Add(Core.Constants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); - var ctrl = new TestSurfaceController { UmbracoContext = umbracoContext, Umbraco = new UmbracoHelper() }; + var ctrl = new TestSurfaceController(umbracoContext, new UmbracoHelper()); ctrl.ControllerContext = new ControllerContext(contextBase, routeData, ctrl); var result = ctrl.GetContentFromCurrentPage() as PublishedContentResult; @@ -183,6 +184,15 @@ namespace Umbraco.Tests.Web.Mvc public class TestSurfaceController : SurfaceController { + public TestSurfaceController(UmbracoContext ctx, UmbracoHelper helper = null) + : base(ctx, null, ServiceContext.CreatePartial(), Mock.Of(), null, null) + { + if (helper != null) + { + Umbraco = helper; + } + } + public ActionResult Index() { return View(); diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs index a4b210dbef..133cbb2ee7 100644 --- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs @@ -3,18 +3,13 @@ using System.Globalization; using System.Linq; using System.Web.Mvc; using System.Web.Routing; -using LightInject; using Moq; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; @@ -420,15 +415,17 @@ namespace Umbraco.Tests.Web.Mvc // CacheHelper.CreateDisabledCacheHelper(), // new ProfilingLogger(logger, Mock.Of())) { /*IsReady = true*/ }; - var cache = NullCacheProvider.Instance; + var cache = NoAppCache.Instance; //var provider = new ScopeUnitOfWorkProvider(databaseFactory, new RepositoryFactory(Mock.Of())); var scopeProvider = TestObjects.GetScopeProvider(Mock.Of()); var factory = Mock.Of(); - _service = new PublishedSnapshotService(svcCtx, factory, scopeProvider, cache, Enumerable.Empty(), + _service = new PublishedSnapshotService(svcCtx, factory, scopeProvider, cache, null, null, null, null, null, new TestDefaultCultureAccessor(), - Current.Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper(), null, true, false); // no events + Current.Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper(), + Factory.GetInstance(), + null, true, false); // no events var http = GetHttpContextFactory(url, routeData).HttpContext; diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index d98afa6bcc..e95ae7b785 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Web; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -35,20 +34,22 @@ namespace Umbraco.Tests.Web // should not depend on more than IdkMap maybe - fix this! var entityService = new Mock(); entityService.Setup(x => x.GetId(It.IsAny(), It.IsAny())).Returns(Attempt.Fail()); - var serviceContext = new ServiceContext(entityService: entityService.Object); + var serviceContext = ServiceContext.CreatePartial(entityService: entityService.Object); // fixme - bad in a unit test - but Udi has a static ctor that wants it?! - var container = new Mock(); - container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(NullCacheProvider.Instance, SettingsForTests.GenerateMockGlobalSettings(), new ProfilingLogger(Mock.Of(), Mock.Of()))); - container.Setup(x => x.GetInstance(typeof (ServiceContext))).Returns(serviceContext); - Current.Container = container.Object; + var factory = new Mock(); + factory.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( + new TypeLoader(NoAppCache.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()))); + factory.Setup(x => x.GetInstance(typeof (ServiceContext))).Returns(serviceContext); + + var settings = SettingsForTests.GetDefaultUmbracoSettings(); + factory.Setup(x => x.GetInstance(typeof(IUmbracoSettingsSection))).Returns(settings); + + Current.Factory = factory.Object; Umbraco.Web.Composing.Current.UmbracoContextAccessor = new TestUmbracoContextAccessor(); Udi.ResetUdiTypes(); - - UmbracoConfig.For.SetUmbracoSettings(SettingsForTests.GetDefaultUmbracoSettings()); } [TearDown] @@ -82,7 +83,7 @@ namespace Umbraco.Tests.Web .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlProviderMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url")); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - + var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var publishedContent = Mock.Of(); Mock.Get(publishedContent).Setup(x => x.Id).Returns(1234); diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js new file mode 100755 index 0000000000..c27a2c5f53 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -0,0 +1,52 @@ +'use strict'; + +module.exports = { + sources: { + + //less files used by backoffice and preview + //processed in the less task + less: { + installer: { files: ["./src/less/installer.less"], out: "installer.css" }, + nonodes: { files: ["./src/less/pages/nonodes.less"], out: "nonodes.style.min.css"}, + preview: { files: ["./src/less/canvas-designer.less"], out: "canvasdesigner.css" }, + umbraco: { files: ["./src/less/belle.less"], out: "umbraco.css" } + }, + + //js files for backoffie + //processed in the js task + js: { + preview: { files: ["./src/preview/**/*.js"], out: "umbraco.preview.js" }, + installer: { files: ["./src/installer/**/*.js"], out: "umbraco.installer.js" }, + controllers: { files: ["./src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" }, + directives: { files: ["./src/common/directives/**/*.js"], out: "umbraco.directives.js" }, + filters: { files: ["./src/common/filters/**/*.js"], out: "umbraco.filters.js" }, + resources: { files: ["./src/common/resources/**/*.js"], out: "umbraco.resources.js" }, + services: { files: ["./src/common/services/**/*.js"], out: "umbraco.services.js" }, + security: { files: ["./src/common/interceptors/**/*.js"], out: "umbraco.interceptors.js" } + }, + + //selectors for copying all views into the build + //processed in the views task + views:{ + umbraco: {files: ["./src/views/**/*.html"], folder: ""}, + installer: {files: ["./src/installer/steps/*.html"], folder: "install/"} + }, + + //globs for file-watching + globs:{ + views: "./src/views/**/*.html", + less: "./src/less/**/*.less", + js: "./src/*.js", + lib: "./lib/**/*", + assets: "./src/assets/**" + } + }, + root: "../Umbraco.Web.UI/Umbraco/", + targets: { + js: "js/", + lib: "lib/", + views: "views/", + css: "assets/css/", + assets: "assets/" + } +}; diff --git a/src/Umbraco.Web.UI.Client/gulp/index.js b/src/Umbraco.Web.UI.Client/gulp/index.js new file mode 100755 index 0000000000..9dd80598af --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/index.js @@ -0,0 +1,10 @@ +'use strict'; + +var fs = require('fs'); + +var onlyScripts = require('./util/scriptFilter'); +var tasks = fs.readdirSync('./gulp/tasks/').filter(onlyScripts); + +tasks.forEach(function(task) { + require('./tasks/' + task); +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/build.js b/src/Umbraco.Web.UI.Client/gulp/tasks/build.js new file mode 100644 index 0000000000..4e519c58e9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/build.js @@ -0,0 +1,10 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); +var runSequence = require('run-sequence'); + +// Build - build the files ready for production +gulp.task('build', function(cb) { + runSequence(["dependencies", "js", "less", "views"], cb); +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js new file mode 100644 index 0000000000..8032274ee7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -0,0 +1,288 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); + +var MergeStream = require('merge-stream'); + +var imagemin = require('gulp-imagemin'); + +/************************** + * Task processes and copies all dependencies, either installed by npm or stored locally in the project + **************************/ +gulp.task('dependencies', function () { + + //as we do multiple things in this task, we merge the multiple streams + var stream = new MergeStream(); + + // Pick the dependencies we need from each package + // so we don't just ship with a lot of files that aren't needed + const nodeModules = [ + { + "name": "ace-builds", + "src": [ + "./node_modules/ace-builds/src-min-noconflict/ace.js", + "./node_modules/ace-builds/src-min-noconflict/ext-language_tools.js", + "./node_modules/ace-builds/src-min-noconflict/ext-searchbox.js", + "./node_modules/ace-builds/src-min-noconflict/ext-settings_menu.js", + "./node_modules/ace-builds/src-min-noconflict/snippets/text.js", + "./node_modules/ace-builds/src-min-noconflict/snippets/javascript.js", + "./node_modules/ace-builds/src-min-noconflict/snippets/css.js", + "./node_modules/ace-builds/src-min-noconflict/theme-chrome.js", + "./node_modules/ace-builds/src-min-noconflict/mode-razor.js", + "./node_modules/ace-builds/src-min-noconflict/mode-javascript.js", + "./node_modules/ace-builds/src-min-noconflict/mode-css.js", + "./node_modules/ace-builds/src-min-noconflict/worker-javascript.js", + "./node_modules/ace-builds/src-min-noconflict/worker-css.js" + ], + "base": "./node_modules/ace-builds" + }, + { + "name": "angular", + "src": ["./node_modules/angular/angular.js"], + "base": "./node_modules/angular" + }, + { + "name": "angular-cookies", + "src": ["./node_modules/angular-cookies/angular-cookies.js"], + "base": "./node_modules/angular-cookies" + }, + { + "name": "angular-dynamic-locale", + "src": [ + "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js", + "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js.map" + ], + "base": "./node_modules/angular-dynamic-locale/dist" + }, + { + "name": "angular-sanitize", + "src": ["./node_modules/angular-sanitize/angular-sanitize.js"], + "base": "./node_modules/angular-sanitize" + }, + { + "name": "angular-touch", + "src": ["./node_modules/angular-touch/angular-touch.js"], + "base": "./node_modules/angular-touch" + }, + { + "name": "angular-ui-sortable", + "src": ["./node_modules/angular-ui-sortable/dist/sortable.js"], + "base": "./node_modules/angular-ui-sortable/dist" + }, + { + "name": "angular-route", + "src": ["./node_modules/angular-route/angular-route.js"], + "base": "./node_modules/angular-route" + }, + { + "name": "angular-animate", + "src": ["./node_modules/angular-animate/angular-animate.js"], + "base": "./node_modules/angular-animate" + }, + { + "name": "angular-i18n", + "src": [ + "./node_modules/angular-i18n/angular-i18n.js", + "./node_modules/angular-i18n/angular-locale_*.js" + ], + "base": "./node_modules/angular-i18n" + }, + { + "name": "angular-local-storage", + "src": [ + "./node_modules/angular-local-storage/dist/angular-local-storage.min.js", + "./node_modules/angular-local-storage/dist/angular-local-storage.min.js.map" + ], + "base": "./node_modules/angular-local-storage/dist" + }, + { + "name": "angular-messages", + "src": ["./node_modules/angular-messages/angular-messages.js"], + "base": "./node_modules/angular-messages" + }, + { + "name": "angular-mocks", + "src": ["./node_modules/angular-mocks/angular-mocks.js"], + "base": "./node_modules/angular-mocks" + }, + { + "name": "animejs", + "src": ["./node_modules/animejs/anime.min.js"], + "base": "./node_modules/animejs" + }, + { + "name": "bootstrap-social", + "src": ["./node_modules/bootstrap-social/bootstrap-social.css"], + "base": "./node_modules/bootstrap-social" + }, + + { + "name": "angular-chart.js", + "src": ["./node_modules/angular-chart.js/dist/angular-chart.min.js"], + "base": "./node_modules/angular-chart.js/dist" + }, + { + "name": "chart.js", + "src": ["./node_modules/chart.js/dist/chart.min.js"], + "base": "./node_modules/chart.js/dist" + }, + { + "name": "clipboard", + "src": ["./node_modules/clipboard/dist/clipboard.min.js"], + "base": "./node_modules/clipboard/dist" + }, + { + "name": "jsdiff", + "src": ["./node_modules/diff/dist/diff.min.js"], + "base": "./node_modules/diff/dist" + }, + { + "name": "flatpickr", + "src": [ + "./node_modules/flatpickr/dist/flatpickr.js", + "./node_modules/flatpickr/dist/flatpickr.css" + ], + "base": "./node_modules/flatpickr/dist" + }, + { + "name": "font-awesome", + "src": [ + "./node_modules/font-awesome/fonts/*", + "./node_modules/font-awesome/css/font-awesome.min.css" + ], + "base": "./node_modules/font-awesome" + }, + { + "name": "jquery", + "src": [ + "./node_modules/jquery/dist/jquery.min.js", + "./node_modules/jquery/dist/jquery.min.map" + ], + "base": "./node_modules/jquery/dist" + }, + { + "name": "jquery-ui", + "src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"], + "base": "./node_modules/jquery-ui-dist" + }, + { + "name": "jquery-ui-touch-punch", + "src": ["./node_modules/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"], + "base": "./node_modules/jquery-ui-touch-punch" + }, + { + "name": "lazyload-js", + "src": ["./node_modules/lazyload-js/lazyload.min.js"], + "base": "./node_modules/lazyload-js" + }, + { + "name": "moment", + "src": ["./node_modules/moment/min/moment.min.js"], + "base": "./node_modules/moment/min" + }, + { + "name": "moment", + "src": ["./node_modules/moment/locale/*.js"], + "base": "./node_modules/moment/locale" + }, + { + "name": "ng-file-upload", + "src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"], + "base": "./node_modules/ng-file-upload/dist" + }, + { + "name": "nouislider", + "src": [ + "./node_modules/nouislider/distribute/nouislider.min.js", + "./node_modules/nouislider/distribute/nouislider.min.css" + ], + "base": "./node_modules/nouislider/distribute" + }, + { + "name": "signalr", + "src": ["./node_modules/signalr/jquery.signalR.js"], + "base": "./node_modules/signalr" + }, + { + "name": "spectrum", + "src": [ + "./node_modules/spectrum-colorpicker/spectrum.js", + "./node_modules/spectrum-colorpicker/spectrum.css" + ], + "base": "./node_modules/spectrum-colorpicker" + }, + { + "name": "tinymce", + "src": [ + "./node_modules/tinymce/tinymce.min.js", + "./node_modules/tinymce/plugins/**", + "./node_modules/tinymce/skins/**", + "./node_modules/tinymce/themes/**" + ], + "base": "./node_modules/tinymce" + }, + { + "name": "typeahead.js", + "src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"], + "base": "./node_modules/typeahead.js/dist" + }, + { + "name": "underscore", + "src": ["node_modules/underscore/underscore-min.js"], + "base": "./node_modules/underscore" + } + ]; + + // add streams for node modules + nodeModules.forEach(module => { + stream.add( + gulp.src(module.src, + { base: module.base }) + .pipe(gulp.dest(config.root + config.targets.lib + "/" + module.name)) + ); + }); + + //copy over libs which are not on npm (/lib) + stream.add( + gulp.src(config.sources.globs.lib) + .pipe(gulp.dest(config.root + config.targets.lib)) + ); + + //Copies all static assets into /root / assets folder + //css, fonts and image files + stream.add( + gulp.src(config.sources.globs.assets) + .pipe(imagemin([ + imagemin.gifsicle({interlaced: true}), + imagemin.jpegtran({progressive: true}), + imagemin.optipng({optimizationLevel: 5}), + imagemin.svgo({ + plugins: [ + {removeViewBox: true}, + {cleanupIDs: false} + ] + }) + ])) + .pipe(gulp.dest(config.root + config.targets.assets)) + ); + + // Copies all the less files related to the preview into their folder + //these are not pre-processed as preview has its own less combiler client side + stream.add( + gulp.src("src/canvasdesigner/editors/*.less") + .pipe(gulp.dest(config.root + config.targets.assets + "/less")) + ); + + // Todo: check if we need these fileSize + stream.add( + gulp.src("src/views/propertyeditors/grid/config/*.*") + .pipe(gulp.dest(config.root + config.targets.views + "/propertyeditors/grid/config")) + ); + stream.add( + gulp.src("src/views/dashboard/default/*.jpg") + .pipe(gulp.dest(config.root + config.targets.views + "/dashboard/default")) + ); + + return stream; +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dev.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dev.js new file mode 100644 index 0000000000..bca4da8c43 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dev.js @@ -0,0 +1,10 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); +var runSequence = require('run-sequence'); + +// Dev - build the files ready for development and start watchers +gulp.task('dev', function(cb) { + runSequence(["dependencies", "js", "less", "views"], "watch", cb); +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/docs.js b/src/Umbraco.Web.UI.Client/gulp/tasks/docs.js new file mode 100644 index 0000000000..34271a018c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/docs.js @@ -0,0 +1,55 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); + +var connect = require('gulp-connect'); +var open = require('gulp-open'); +var gulpDocs = require('gulp-ngdocs'); + +/************************** + * Build Backoffice UI API documentation + **************************/ +gulp.task('docs', [], function (cb) { + + var options = { + html5Mode: false, + startPage: '/api', + title: "Umbraco Backoffice UI API Documentation", + dest: 'docs/api', + styles: ['docs/umb-docs.css'], + image: "https://our.umbraco.com/assets/images/logo.svg" + } + + return gulpDocs.sections({ + api: { + glob: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'], + api: true, + title: 'API Documentation' + } + }) + .pipe(gulpDocs.process(options)) + .pipe(gulp.dest('docs/api')); + cb(); +}); + +gulp.task('connect:docs', function (cb) { + connect.server({ + root: 'docs/api', + livereload: true, + fallback: 'docs/api/index.html', + port: 8880 + }); + cb(); +}); + +gulp.task('open:docs', function (cb) { + + var options = { + uri: 'http://localhost:8880/index.html' + }; + + gulp.src(__filename) + .pipe(open(options)); + cb(); +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/docserve.js b/src/Umbraco.Web.UI.Client/gulp/tasks/docserve.js new file mode 100644 index 0000000000..a402003383 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/docserve.js @@ -0,0 +1,10 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); +var runSequence = require('run-sequence'); + +// Docserve - build and open the back office documentation +gulp.task('docserve', function(cb) { + runSequence('docs', 'connect:docs', 'open:docs', cb); +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/fastdev.js b/src/Umbraco.Web.UI.Client/gulp/tasks/fastdev.js new file mode 100644 index 0000000000..888ed38fec --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/fastdev.js @@ -0,0 +1,13 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); +var runSequence = require('run-sequence'); + +// Dev - build the files ready for development and start watchers +gulp.task('fastdev', function(cb) { + + global.isProd = false; + + runSequence(["dependencies", "js", "less", "views"], "watch", cb); +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/js.js b/src/Umbraco.Web.UI.Client/gulp/tasks/js.js new file mode 100644 index 0000000000..e3ea0cc9d8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/js.js @@ -0,0 +1,29 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); + +var _ = require('lodash'); +var MergeStream = require('merge-stream'); + +var processJs = require('../util/processJs'); + +/************************** + * Copies all angular JS files into their seperate umbraco.*.js file + **************************/ +gulp.task('js', function () { + + //we run multiple streams, so merge them all together + var stream = new MergeStream(); + + stream.add( + gulp.src(config.sources.globs.js) + .pipe(gulp.dest(config.root + config.targets.js)) + ); + + _.forEach(config.sources.js, function (group) { + stream.add (processJs(group.files, group.out) ); + }); + + return stream; +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/less.js b/src/Umbraco.Web.UI.Client/gulp/tasks/less.js new file mode 100644 index 0000000000..3d19716b67 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/less.js @@ -0,0 +1,20 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); + +var _ = require('lodash'); +var MergeStream = require('merge-stream'); + +var processLess = require('../util/processLess'); + +gulp.task('less', function () { + + var stream = new MergeStream(); + + _.forEach(config.sources.less, function (group) { + stream.add( processLess(group.files, group.out) ); + }); + + return stream; +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js new file mode 100644 index 0000000000..e40d4c436a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js @@ -0,0 +1,26 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); +var karmaServer = require('karma').Server; + +/************************** + * Build tests + **************************/ + + // Karma test +gulp.task('test:unit', function() { + new karmaServer({ + configFile: __dirname + "/test/config/karma.conf.js", + keepalive: true + }) + .start(); +}); + +gulp.task('test:e2e', function() { + new karmaServer({ + configFile: __dirname + "/test/config/e2e.js", + keepalive: true + }) + .start(); +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/views.js b/src/Umbraco.Web.UI.Client/gulp/tasks/views.js new file mode 100644 index 0000000000..05246370c2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/views.js @@ -0,0 +1,25 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); + +var _ = require('lodash'); +var MergeStream = require('merge-stream'); + +gulp.task('views', function () { + + var stream = new MergeStream(); + + _.forEach(config.sources.views, function (group) { + + console.log("copying " + group.files + " to " + config.root + config.targets.views + group.folder) + + stream.add ( + gulp.src(group.files) + .pipe( gulp.dest(config.root + config.targets.views + group.folder) ) + ); + + }); + + return stream; +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/watch.js b/src/Umbraco.Web.UI.Client/gulp/tasks/watch.js new file mode 100644 index 0000000000..a5c476f7d2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/watch.js @@ -0,0 +1,58 @@ +'use strict'; + +var config = require('../config'); +var gulp = require('gulp'); + +var _ = require('lodash'); +var MergeStream = require('merge-stream'); + +var processJs = require('../util/processJs'); + +var watch = require('gulp-watch'); + +gulp.task('watch', function () { + + var stream = new MergeStream(); + var watchInterval = 500; + + //Setup a watcher for all groups of javascript files + _.forEach(config.sources.js, function (group) { + + if(group.watch !== false){ + + stream.add( + + watch(group.files, { ignoreInitial: true, interval: watchInterval }, function (file) { + + console.info(file.path + " has changed, added to: " + group.out); + processJs(group.files, group.out); + + }) + + ); + + } + + }); + + stream.add( + //watch all less files and trigger the less task + watch(config.sources.globs.less, { ignoreInitial: true, interval: watchInterval }, function () { + gulp.run(['less']); + }) + ); + + //watch all views - copy single file changes + stream.add( + watch(config.sources.globs.views, { interval: watchInterval }) + .pipe(gulp.dest(config.root + config.targets.views)) + ); + + //watch all app js files that will not be merged - copy single file changes + stream.add( + watch(config.sources.globs.js, { interval: watchInterval }) + .pipe(gulp.dest(config.root + config.targets.js)) + ); + + return stream; +}); diff --git a/src/Umbraco.Web.UI.Client/gulp/util/handleErrors.js b/src/Umbraco.Web.UI.Client/gulp/util/handleErrors.js new file mode 100755 index 0000000000..3c32a928dd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/util/handleErrors.js @@ -0,0 +1,18 @@ +'use strict'; + +var notify = require('gulp-notify'); + +module.exports = function(error) { + + var args = Array.prototype.slice.call(arguments); + + // Send error to notification center with gulp-notify + notify.onError({ + title: 'Compile Error', + message: '<%= error.message %>' + }).apply(this, args); + + // Keep gulp from hanging on this task + this.emit('end'); + +}; diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js new file mode 100644 index 0000000000..45927dc0e6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js @@ -0,0 +1,32 @@ + +var config = require('../config'); +var gulp = require('gulp'); + +var eslint = require('gulp-eslint'); +var babel = require("gulp-babel"); +var sort = require('gulp-sort'); +var concat = require('gulp-concat'); +var wrap = require("gulp-wrap-js"); + +module.exports = function(files, out) { + + var task = gulp.src(files); + + if (global.isProd === true) { + // check for js errors + task = task.pipe(eslint()); + // outputs the lint results to the console + task = task.pipe(eslint.format()); + } + + // sort files in stream by path or any custom sort comparator + task = task.pipe(babel()) + .pipe(sort()) + .pipe(concat(out)) + .pipe(wrap('(function(){\n%= body %\n})();')) + .pipe(gulp.dest(config.root + config.targets.js)); + + + return task; + +}; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js new file mode 100644 index 0000000000..26f69865d9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js @@ -0,0 +1,33 @@ + +var config = require('../config'); +var gulp = require('gulp'); + +var postcss = require('gulp-postcss'); +var less = require('gulp-less'); +var autoprefixer = require('autoprefixer'); +var cssnano = require('cssnano'); +var cleanCss = require("gulp-clean-css"); +var rename = require('gulp-rename'); + +module.exports = function(files, out) { + + var processors = [ + autoprefixer, + cssnano({zindex: false}) + ]; + + var task = gulp.src(files) + .pipe(less()); + + + if (global.isProd === true) { + task = task.pipe(cleanCss()); + } + + task = task.pipe(postcss(processors)) + .pipe(rename(out)) + .pipe(gulp.dest(config.root + config.targets.css)); + + return task; + +}; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/gulp/util/scriptFilter.js b/src/Umbraco.Web.UI.Client/gulp/util/scriptFilter.js new file mode 100755 index 0000000000..7d8ce45f82 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/util/scriptFilter.js @@ -0,0 +1,9 @@ +'use strict'; + +var path = require('path'); + +// Filters out non .js files. Prevents +// accidental inclusion of possible hidden files +module.exports = function(name) { + return /(\.(js)$)/i.test(path.extname(name)); +}; diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js old mode 100644 new mode 100755 index 0af327d148..79a9d04156 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -1,576 +1,16 @@ -var gulp = require('gulp'); -var watch = require('gulp-watch'); -var concat = require('gulp-concat'); -var rename = require('gulp-rename'); -var wrap = require("gulp-wrap-js"); -var sort = require('gulp-sort'); -var connect = require('gulp-connect'); -var open = require('gulp-open'); -var babel = require("gulp-babel"); -var runSequence = require('run-sequence'); -var imagemin = require('gulp-imagemin'); - -var _ = require('lodash'); -var MergeStream = require('merge-stream'); - -// js -var eslint = require('gulp-eslint'); - -//Less + css -var postcss = require('gulp-postcss'); -var less = require('gulp-less'); -var autoprefixer = require('autoprefixer'); -var cssnano = require('cssnano'); -var cleanCss = require("gulp-clean-css"); - -// Documentation -var gulpDocs = require('gulp-ngdocs'); - -// Testing -var karmaServer = require('karma').Server; - -/*************************************************************** -Helper functions -***************************************************************/ -function processJs(files, out) { - - return gulp.src(files) - // check for js errors - .pipe(eslint()) - // outputs the lint results to the console - .pipe(eslint.format()) - // sort files in stream by path or any custom sort comparator - .pipe(babel()) - .pipe(sort()) - .pipe(concat(out)) - .pipe(wrap('(function(){\n%= body %\n})();')) - .pipe(gulp.dest(root + targets.js)); - - console.log(out + " compiled"); -} - -function processLess(files, out) { - var processors = [ - autoprefixer, - cssnano({zindex: false}) - ]; - - return gulp.src(files) - .pipe(less()) - .pipe(cleanCss()) - .pipe(postcss(processors)) - .pipe(rename(out)) - .pipe(gulp.dest(root + targets.css)); - - console.log(out + " compiled"); -} - -/*************************************************************** -Paths and destinations -Each group is iterated automatically in the setup tasks below -***************************************************************/ -var sources = { - - //less files used by backoffice and preview - //processed in the less task - less: { - installer: { files: ["src/less/installer.less"], out: "installer.css" }, - nonodes: { files: ["src/less/pages/nonodes.less"], out: "nonodes.style.min.css"}, - preview: { files: ["src/less/canvas-designer.less"], out: "canvasdesigner.css" }, - umbraco: { files: ["src/less/belle.less"], out: "umbraco.css" } - }, - - //js files for backoffie - //processed in the js task - js: { - preview: { files: ["src/preview/**/*.js"], out: "umbraco.preview.js" }, - installer: { files: ["src/installer/**/*.js"], out: "umbraco.installer.js" }, - controllers: { files: ["src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" }, - directives: { files: ["src/common/directives/**/*.js"], out: "umbraco.directives.js" }, - filters: { files: ["src/common/filters/**/*.js"], out: "umbraco.filters.js" }, - resources: { files: ["src/common/resources/**/*.js"], out: "umbraco.resources.js" }, - services: { files: ["src/common/services/**/*.js"], out: "umbraco.services.js" }, - security: { files: ["src/common/interceptors/**/*.js"], out: "umbraco.interceptors.js" } - }, - - //selectors for copying all views into the build - //processed in the views task - views:{ - umbraco: {files: ["src/views/**/*.html"], folder: ""}, - installer: {files: ["src/installer/steps/*.html"], folder: "install"} - }, - - //globs for file-watching - globs:{ - views: "./src/views/**/*.html", - less: "./src/less/**/*.less", - js: "./src/*.js", - lib: "./lib/**/*", - assets: "./src/assets/**" - } -}; - -var root = "../Umbraco.Web.UI/Umbraco/"; -var targets = { - js: "js/", - lib: "lib/", - views: "views/", - css: "assets/css/", - assets: "assets/" -}; - - -/************************** - * Main tasks for the project to prepare backoffice files - **************************/ - - // Build - build the files ready for production -gulp.task('build', function(cb) { - runSequence(["dependencies", "js", "less", "views"], cb); -}); - -// Dev - build the files ready for development and start watchers -gulp.task('dev', function(cb) { - runSequence(["dependencies", "js", "less", "views"], "watch", cb); -}); - -// Docserve - build and open the back office documentation -gulp.task('docserve', function(cb) { - runSequence('docs', 'connect:docs', 'open:docs', cb); -}); - -/************************** - * Task processes and copies all dependencies, either installed by npm or stored locally in the project - **************************/ -gulp.task('dependencies', function () { - - //as we do multiple things in this task, we merge the multiple streams - var stream = new MergeStream(); - - // Pick the dependencies we need from each package - // so we don't just ship with a lot of files that aren't needed - const nodeModules = [ - { - "name": "ace-builds", - "src": [ - "./node_modules/ace-builds/src-min-noconflict/ace.js", - "./node_modules/ace-builds/src-min-noconflict/ext-language_tools.js", - "./node_modules/ace-builds/src-min-noconflict/ext-searchbox.js", - "./node_modules/ace-builds/src-min-noconflict/ext-settings_menu.js", - "./node_modules/ace-builds/src-min-noconflict/snippets/text.js", - "./node_modules/ace-builds/src-min-noconflict/snippets/javascript.js", - "./node_modules/ace-builds/src-min-noconflict/snippets/css.js", - "./node_modules/ace-builds/src-min-noconflict/theme-chrome.js", - "./node_modules/ace-builds/src-min-noconflict/mode-razor.js", - "./node_modules/ace-builds/src-min-noconflict/mode-javascript.js", - "./node_modules/ace-builds/src-min-noconflict/mode-css.js", - "./node_modules/ace-builds/src-min-noconflict/worker-javascript.js", - "./node_modules/ace-builds/src-min-noconflict/worker-css.js" - ], - "base": "./node_modules/ace-builds" - }, - { - "name": "angular", - "src": ["./node_modules/angular/angular.js"], - "base": "./node_modules/angular" - }, - { - "name": "angular-cookies", - "src": ["./node_modules/angular-cookies/angular-cookies.js"], - "base": "./node_modules/angular-cookies" - }, - { - "name": "angular-dynamic-locale", - "src": [ - "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js", - "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js.map" - ], - "base": "./node_modules/angular-dynamic-locale/dist" - }, - { - "name": "angular-sanitize", - "src": ["./node_modules/angular-sanitize/angular-sanitize.js"], - "base": "./node_modules/angular-sanitize" - }, - { - "name": "angular-touch", - "src": ["./node_modules/angular-touch/angular-touch.js"], - "base": "./node_modules/angular-touch" - }, - { - "name": "angular-ui-sortable", - "src": ["./node_modules/angular-ui-sortable/dist/sortable.js"], - "base": "./node_modules/angular-ui-sortable/dist" - }, - { - "name": "angular-route", - "src": ["./node_modules/angular-route/angular-route.js"], - "base": "./node_modules/angular-route" - }, - { - "name": "angular-animate", - "src": ["./node_modules/angular-animate/angular-animate.js"], - "base": "./node_modules/angular-animate" - }, - { - "name": "angular-i18n", - "src": [ - "./node_modules/angular-i18n/angular-i18n.js", - "./node_modules/angular-i18n/angular-locale_*.js" - ], - "base": "./node_modules/angular-i18n" - }, - { - "name": "angular-local-storage", - "src": [ - "./node_modules/angular-local-storage/dist/angular-local-storage.min.js", - "./node_modules/angular-local-storage/dist/angular-local-storage.min.js.map" - ], - "base": "./node_modules/angular-local-storage/dist" - }, - { - "name": "angular-messages", - "src": ["./node_modules/angular-messages/angular-messages.js"], - "base": "./node_modules/angular-messages" - }, - { - "name": "angular-mocks", - "src": ["./node_modules/angular-mocks/angular-mocks.js"], - "base": "./node_modules/angular-mocks" - }, - { - "name": "animejs", - "src": ["./node_modules/animejs/anime.min.js"], - "base": "./node_modules/animejs" - }, - { - "name": "bootstrap-social", - "src": ["./node_modules/bootstrap-social/bootstrap-social.css"], - "base": "./node_modules/bootstrap-social" - }, - { - "name": "clipboard", - "src": ["./node_modules/clipboard/dist/clipboard.min.js"], - "base": "./node_modules/clipboard/dist" - }, - { - "name": "jsdiff", - "src": ["./node_modules/diff/dist/diff.min.js"], - "base": "./node_modules/diff/dist" - }, - { - "name": "flatpickr", - "src": [ - "./node_modules/flatpickr/dist/flatpickr.js", - "./node_modules/flatpickr/dist/flatpickr.css" - ], - "base": "./node_modules/flatpickr/dist" - }, - { - "name": "font-awesome", - "src": [ - "./node_modules/font-awesome/fonts/*", - "./node_modules/font-awesome/css/font-awesome.min.css" - ], - "base": "./node_modules/font-awesome" - }, - { - "name": "jquery", - "src": [ - "./node_modules/jquery/dist/jquery.min.js", - "./node_modules/jquery/dist/jquery.min.map" - ], - "base": "./node_modules/jquery/dist" - }, - { - "name": "jquery-ui", - "src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"], - "base": "./node_modules/jquery-ui-dist" - }, - { - "name": "jquery-ui-touch-punch", - "src": ["./node_modules/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"], - "base": "./node_modules/jquery-ui-touch-punch" - }, - { - "name": "lazyload-js", - "src": ["./node_modules/lazyload-js/lazyload.min.js"], - "base": "./node_modules/lazyload-js" - }, - { - "name": "moment", - "src": ["./node_modules/moment/min/moment.min.js"], - "base": "./node_modules/moment/min" - }, - { - "name": "moment", - "src": ["./node_modules/moment/locale/*.js"], - "base": "./node_modules/moment/locale" - }, - { - "name": "ng-file-upload", - "src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"], - "base": "./node_modules/ng-file-upload/dist" - }, - { - "name": "nouislider", - "src": [ - "./node_modules/nouislider/distribute/nouislider.min.js", - "./node_modules/nouislider/distribute/nouislider.min.css" - ], - "base": "./node_modules/nouislider/distribute" - }, - { - "name": "signalr", - "src": ["./node_modules/signalr/jquery.signalR.js"], - "base": "./node_modules/signalr" - }, - { - "name": "spectrum", - "src": [ - "./node_modules/spectrum-colorpicker/spectrum.js", - "./node_modules/spectrum-colorpicker/spectrum.css" - ], - "base": "./node_modules/spectrum-colorpicker" - }, - { - "name": "tinymce", - "src": [ - "./node_modules/tinymce/tinymce.min.js", - "./node_modules/tinymce/plugins/**", - "./node_modules/tinymce/skins/**", - "./node_modules/tinymce/themes/**" - ], - "base": "./node_modules/tinymce" - }, - { - "name": "typeahead.js", - "src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"], - "base": "./node_modules/typeahead.js/dist" - }, - { - "name": "underscore", - "src": ["node_modules/underscore/underscore-min.js"], - "base": "./node_modules/underscore" - } - ]; - - // add streams for node modules - nodeModules.forEach(module => { - stream.add( - gulp.src(module.src, - { base: module.base }) - .pipe(gulp.dest(root + targets.lib + "/" + module.name)) - ); - }); - - //copy over libs which are not on npm (/lib) - stream.add( - gulp.src(sources.globs.lib) - .pipe(gulp.dest(root + targets.lib)) - ); - - //Copies all static assets into /root / assets folder - //css, fonts and image files - stream.add( - gulp.src(sources.globs.assets) - .pipe(imagemin([ - imagemin.gifsicle({interlaced: true}), - imagemin.jpegtran({progressive: true}), - imagemin.optipng({optimizationLevel: 5}), - imagemin.svgo({ - plugins: [ - {removeViewBox: true}, - {cleanupIDs: false} - ] - }) - ])) - .pipe(gulp.dest(root + targets.assets)) - ); - - // Copies all the less files related to the preview into their folder - //these are not pre-processed as preview has its own less combiler client side - stream.add( - gulp.src("src/canvasdesigner/editors/*.less") - .pipe(gulp.dest(root + targets.assets + "/less")) - ); - - // Todo: check if we need these fileSize - stream.add( - gulp.src("src/views/propertyeditors/grid/config/*.*") - .pipe(gulp.dest(root + targets.views + "/propertyeditors/grid/config")) - ); - stream.add( - gulp.src("src/views/dashboard/default/*.jpg") - .pipe(gulp.dest(root + targets.views + "/dashboard/default")) - ); - - return stream; -}); - - -/************************** - * Copies all angular JS files into their seperate umbraco.*.js file - **************************/ -gulp.task('js', function () { - - //we run multiple streams, so merge them all together - var stream = new MergeStream(); - - stream.add( - gulp.src(sources.globs.js) - .pipe(gulp.dest(root + targets.js)) - ); - - _.forEach(sources.js, function (group) { - stream.add (processJs(group.files, group.out) ); - }); - - return stream; -}); - -gulp.task('less', function () { - - var stream = new MergeStream(); - - _.forEach(sources.less, function (group) { - stream.add( processLess(group.files, group.out) ); - }); - - return stream; -}); - - -gulp.task('views', function () { - - var stream = new MergeStream(); - - _.forEach(sources.views, function (group) { - - console.log("copying " + group.files + " to " + root + targets.views + group.folder) - - stream.add ( - gulp.src(group.files) - .pipe( gulp.dest(root + targets.views + group.folder) ) - ); - - }); - - return stream; -}); - - -gulp.task('watch', function () { - - var stream = new MergeStream(); - var watchInterval = 500; - - //Setup a watcher for all groups of javascript files - _.forEach(sources.js, function (group) { - - if(group.watch !== false){ - - stream.add( - - watch(group.files, { ignoreInitial: true, interval: watchInterval }, function (file) { - - console.info(file.path + " has changed, added to: " + group.out); - processJs(group.files, group.out); - - }) - - ); - - } - - }); - - stream.add( - //watch all less files and trigger the less task - watch(sources.globs.less, { ignoreInitial: true, interval: watchInterval }, function () { - gulp.run(['less']); - }) - ); - - //watch all views - copy single file changes - stream.add( - watch(sources.globs.views, { interval: watchInterval }) - .pipe(gulp.dest(root + targets.views)) - ); - - //watch all app js files that will not be merged - copy single file changes - stream.add( - watch(sources.globs.js, { interval: watchInterval }) - .pipe(gulp.dest(root + targets.js)) - ); - - return stream; -}); - -/************************** - * Build Backoffice UI API documentation - **************************/ -gulp.task('docs', [], function (cb) { - - var options = { - html5Mode: false, - startPage: '/api', - title: "Umbraco Backoffice UI API Documentation", - dest: 'docs/api', - styles: ['docs/umb-docs.css'], - image: "https://our.umbraco.com/assets/images/logo.svg" - } - - return gulpDocs.sections({ - api: { - glob: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'], - api: true, - title: 'API Documentation' - } - }) - .pipe(gulpDocs.process(options)) - .pipe(gulp.dest('docs/api')); - cb(); -}); - -gulp.task('connect:docs', function (cb) { - connect.server({ - root: 'docs/api', - livereload: true, - fallback: 'docs/api/index.html', - port: 8880 - }); - cb(); -}); - -gulp.task('open:docs', function (cb) { - - var options = { - uri: 'http://localhost:8880/index.html' - }; - - gulp.src(__filename) - .pipe(open(options)); - cb(); -}); - -/************************** - * Build tests - **************************/ - - // Karma test -gulp.task('test:unit', function() { - new karmaServer({ - configFile: __dirname + "/test/config/karma.conf.js", - keepalive: true - }) - .start(); -}); - -gulp.task('test:e2e', function() { - new karmaServer({ - configFile: __dirname + "/test/config/e2e.js", - keepalive: true - }) - .start(); -}); +'use strict'; + +/* + * gulpfile.js + * =========== + * Rather than manage one giant configuration file responsible + * for creating multiple tasks, each task has been broken out into + * its own file in gulp/tasks. Any file in that folder gets automatically + * required by the loop in ./gulp/index.js (required below). + * + * To add a new task, simply add a new task file to gulp/tasks. + */ + +global.isProd = true; + +require('./gulp'); diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 9e9d5779b6..ed28a93caf 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -852,6 +852,26 @@ "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz", "integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ==" }, + "angular-chart.js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/angular-chart.js/-/angular-chart.js-1.1.1.tgz", + "integrity": "sha1-SfDhjQgXYrbUyXkeSHr/L7sw9a4=", + "requires": { + "angular": "1.x", + "chart.js": "2.3.x" + }, + "dependencies": { + "chart.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.3.0.tgz", + "integrity": "sha1-QEYOSOLEF8BfwzJc2E97AA3H19Y=", + "requires": { + "chartjs-color": "^2.0.0", + "moment": "^2.10.6" + } + } + } + }, "angular-cookies": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz", @@ -917,7 +937,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -935,7 +955,7 @@ }, "ansi-escapes": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, @@ -1196,7 +1216,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", "dev": true }, "asap": { @@ -1249,7 +1269,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=", "dev": true }, "asynckit": { @@ -1970,6 +1990,39 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chart.js": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.3.tgz", + "integrity": "sha512-3+7k/DbR92m6BsMUYP6M0dMsMVZpMnwkUyNSAbqolHKsbIzH2Q4LWVEHHYq7v0fmEV8whXE0DrjANulw9j2K5g==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz", + "integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=", + "requires": { + "chartjs-color-string": "^0.5.0", + "color-convert": "^0.5.3" + }, + "dependencies": { + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + } + } + }, + "chartjs-color-string": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz", + "integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==", + "requires": { + "color-name": "^1.0.0" + } + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -2216,8 +2269,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { "version": "1.5.3", @@ -2327,7 +2379,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -2450,7 +2502,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "integrity": "sha1-+XJgj/DOrWi4QaFqky0LGDeRgU4=", "dev": true }, "core-util-is": { @@ -3373,7 +3425,7 @@ "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", "dev": true, "requires": { "esutils": "^2.0.2" @@ -3901,7 +3953,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -3937,7 +3989,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -4101,7 +4153,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true, "optional": true } @@ -4196,7 +4248,7 @@ "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4206,13 +4258,13 @@ "eslint-utils": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=", "dev": true }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", "dev": true }, "espree": { @@ -4235,7 +4287,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -4244,7 +4296,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -4304,13 +4356,13 @@ "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=", "dev": true }, "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -4519,7 +4571,7 @@ "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { "is-number": "^2.1.0", @@ -5063,10 +5115,16 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, + "fs": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.2.tgz", + "integrity": "sha1-4fJE7zkzwbKmS9R5kTYGDQ9ZFPg=", + "dev": true + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", "dev": true }, "fs-extra": { @@ -6067,6 +6125,12 @@ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", "dev": true }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, "gulp": { "version": "3.9.1", "resolved": "http://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", @@ -6397,7 +6461,7 @@ "gulp-eslint": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", - "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", + "integrity": "sha1-KiaECV93Syz3kxAmIHjFbMehK1I=", "dev": true, "requires": { "eslint": "^5.0.1", @@ -6864,6 +6928,102 @@ } } }, + "gulp-notify": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gulp-notify/-/gulp-notify-3.2.0.tgz", + "integrity": "sha512-qEocs1UVoDKKUjfsxJNMNwkRla0PbsyJwsqNNXpzYWsLQ29LhxRMY3wnTGZcc4hMHtalnvah/Dwlwb4NijH/0A==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "fancy-log": "^1.3.2", + "lodash.template": "^4.4.0", + "node-notifier": "^5.2.1", + "node.extend": "^2.0.0", + "plugin-error": "^0.1.2", + "through2": "^2.0.3" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "lodash.template": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", + "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=", + "dev": true, + "requires": { + "lodash._reinterpolate": "~3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", + "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", + "dev": true, + "requires": { + "lodash._reinterpolate": "~3.0.0" + } + }, + "node.extend": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", + "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", + "dev": true, + "requires": { + "has": "^1.0.3", + "is": "^3.2.1" + } + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + } + } + }, "gulp-open": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/gulp-open/-/gulp-open-3.0.1.tgz", @@ -7255,7 +7415,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", "dev": true, "requires": { "isarray": "2.0.1" @@ -7406,7 +7566,7 @@ "http-proxy": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=", "dev": true, "requires": { "eventemitter3": "^3.0.0", @@ -8004,7 +8164,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", "dev": true }, "is-retry-allowed": { @@ -8209,7 +8369,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8336,7 +8496,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -8944,7 +9104,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", "dev": true }, "lpad-align": { @@ -8969,7 +9129,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "requires": { "pify": "^3.0.0" @@ -9167,7 +9327,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", "dev": true }, "minimatch": { @@ -9316,6 +9476,18 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-notifier": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.3.0.tgz", + "integrity": "sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, "node-releases": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.5.tgz", @@ -12683,7 +12855,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", "dev": true }, "posix-character-classes": { @@ -13106,7 +13278,7 @@ }, "pretty-hrtime": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, @@ -13173,7 +13345,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", "dev": true }, "qs": { @@ -13369,7 +13541,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -13867,7 +14039,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", "dev": true }, "sax": { @@ -14054,7 +14226,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", "dev": true }, "shebang-command": { @@ -14072,6 +14244,12 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", @@ -14338,7 +14516,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -14618,7 +14796,7 @@ "stream-consume": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", - "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", + "integrity": "sha1-0721mMK9CugrjKx6xQsRB6eZbEg=", "dev": true }, "stream-shift": { @@ -14630,7 +14808,7 @@ "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", + "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", "dev": true, "requires": { "date-format": "^1.2.0", @@ -14657,7 +14835,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -14672,7 +14850,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -14689,7 +14867,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -14879,7 +15057,7 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, "requires": { "escape-string-regexp": "^1.0.2" @@ -15016,7 +15194,7 @@ "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "dev": true, "requires": { "once": "^1.4.0" @@ -15231,7 +15409,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -15266,7 +15444,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true }, "to-fast-properties": { @@ -15437,7 +15615,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", "dev": true }, "unc-path-regex": { @@ -15599,13 +15777,13 @@ "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", "dev": true, "requires": { "punycode": "^2.1.0" @@ -15660,7 +15838,7 @@ "dependencies": { "lru-cache": { "version": "2.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "resolved": "http://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=", "dev": true } @@ -16040,7 +16218,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", "dev": true, "requires": { "async-limiter": "~1.0.0", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 2002cca0a1..3f4314db0e 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -2,12 +2,16 @@ "private": true, "scripts": { "test": "karma start test/config/karma.conf.js --singlerun", - "build": "gulp" + "build": "gulp build", + "dev": "gulp dev", + "fastdev": "gulp fastdev", + "docs": "gulp docs" }, "dependencies": { "ace-builds": "1.4.2", "angular": "1.7.5", "angular-animate": "1.7.5", + "angular-chart.js": "^1.1.1", "angular-cookies": "1.7.5", "angular-dynamic-locale": "0.1.37", "angular-i18n": "1.7.5", @@ -20,6 +24,7 @@ "angular-ui-sortable": "0.19.0", "animejs": "2.2.0", "bootstrap-social": "5.1.1", + "chart.js": "^2.7.3", "clipboard": "2.0.4", "diff": "3.5.0", "flatpickr": "4.5.2", @@ -60,6 +65,7 @@ "gulp-watch": "5.0.1", "gulp-wrap": "0.14.0", "gulp-wrap-js": "0.4.1", + "gulp-notify": "^3.0.0", "jasmine-core": "3.3.0", "karma": "3.1.1", "karma-jasmine": "2.0.1", @@ -68,6 +74,7 @@ "lodash": "4.17.11", "marked": "^0.5.2", "merge-stream": "1.0.1", - "run-sequence": "2.2.1" + "run-sequence": "2.2.1", + "fs": "0.0.2" } } diff --git a/src/Umbraco.Web.UI.Client/src/app.js b/src/Umbraco.Web.UI.Client/src/app.js index c7b813c1bf..8e0eaa4943 100644 --- a/src/Umbraco.Web.UI.Client/src/app.js +++ b/src/Umbraco.Web.UI.Client/src/app.js @@ -14,7 +14,8 @@ var app = angular.module('umbraco', [ 'ngMessages', 'tmh.dynamicLocale', 'ngFileUpload', - 'LocalStorageModule' + 'LocalStorageModule', + 'chart.js' ]); app.config(['$compileProvider', function ($compileProvider) { @@ -76,7 +77,7 @@ angular.module("umbraco.viewcache", []) var _op = (url.indexOf("?") > 0) ? "&" : "?"; url += _op + "umb__rnd=" + rnd; } - + return get(url, config); }; return $delegate; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js index e3c4cbf40c..9390d64cdb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js @@ -18,6 +18,7 @@ v.active); - // find the urls for the currently selected language - if(scope.node.variants.length > 1) { - // nodes with variants - scope.currentUrls = _.filter(scope.node.urls, (url) => scope.currentVariant.language.culture === url.culture); - } else { - // invariant nodes - scope.currentUrls = scope.node.urls; - } + updateCurrentUrls(); // if there are any infinite editors open we are in infinite editing scope.isInfiniteMode = editorService.getNumberOfEditors() > 0 ? true : false; - userService.getCurrentUser().then(function(user){ + userService.getCurrentUser().then(function (user) { // only allow change of media type if user has access to the settings sections const hasAccessToSettings = user.allowedSections.indexOf("settings") !== -1 ? true : false; scope.allowChangeDocumentType = hasAccessToSettings; @@ -42,8 +35,8 @@ }); var keys = [ - "general_deleted", - "content_unpublished", + "general_deleted", + "content_unpublished", "content_published", "content_publishedPendingChanges", "content_notCreated", @@ -55,7 +48,7 @@ ]; localizationService.localizeMany(keys) - .then(function(data){ + .then(function (data) { labels.deleted = data[0]; labels.unpublished = data[1]; //aka draft labels.published = data[2]; @@ -64,9 +57,9 @@ labels.unsavedChanges = data[5]; labels.doctypeChangeWarning = data[6]; labels.notPublished = data[9]; - + scope.historyLabel = scope.node.variants && scope.node.variants.length === 1 ? data[7] : data[8]; - + setNodePublishStatus(); if (scope.currentUrls.length === 0) { @@ -104,9 +97,9 @@ } //load in the audit trail if we are currently looking at the INFO tab - if (umbVariantContentCtrl) { + if (umbVariantContentCtrl && umbVariantContentCtrl.editor) { var activeApp = _.find(umbVariantContentCtrl.editor.content.apps, a => a.active); - if (activeApp.alias === "umbInfo") { + if (activeApp && activeApp.alias === "umbInfo") { isInfoTab = true; loadAuditTrail(); loadRedirectUrls(); @@ -123,23 +116,23 @@ scope.openDocumentType = function (documentType) { - const variantIsDirty = _.some(scope.node.variants, function(variant) { + const variantIsDirty = _.some(scope.node.variants, function (variant) { return variant.isDirty; }); // add confirmation dialog before opening the doc type editor - if(variantIsDirty) { + if (variantIsDirty) { const confirm = { title: labels.unsavedChanges, view: "default", content: labels.doctypeChangeWarning, submitButtonLabelKey: "general_continue", closeButtonLabelKey: "general_cancel", - submit: function() { + submit: function () { openDocTypeEditor(documentType); overlayService.close(); }, - close: function() { + close: function () { overlayService.close(); } }; @@ -153,12 +146,12 @@ function openDocTypeEditor(documentType) { const editor = { id: documentType.id, - submit: function(model) { + submit: function (model) { const args = { node: scope.node }; eventsService.emit('editors.content.reload', args); editorService.close(); }, - close: function() { + close: function () { editorService.close(); } }; @@ -168,10 +161,10 @@ scope.openTemplate = function () { var templateEditor = { id: scope.node.templateId, - submit: function(model) { + submit: function (model) { editorService.close(); }, - close: function() { + close: function () { editorService.close(); } }; @@ -183,16 +176,16 @@ scope.node.template = templateAlias; }; - scope.openRollback = function() { - + scope.openRollback = function () { + var rollback = { node: scope.node, - submit: function(model) { + submit: function (model) { const args = { node: scope.node }; eventsService.emit("editors.content.reload", args); editorService.close(); }, - close: function() { + close: function () { editorService.close(); } }; @@ -304,6 +297,17 @@ }); } + function updateCurrentUrls() { + // find the urls for the currently selected language + if (scope.node.variants.length > 1) { + // nodes with variants + scope.currentUrls = _.filter(scope.node.urls, (url) => scope.currentVariant.language.culture === url.culture); + } else { + // invariant nodes + scope.currentUrls = scope.node.urls; + } + } + // load audit trail and redirects when on the info tab evts.push(eventsService.on("app.tabChange", function (event, args) { $timeout(function () { @@ -328,6 +332,7 @@ loadAuditTrail(); loadRedirectUrls(); setNodePublishStatus(); + updateCurrentUrls(); } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js index 1987c897f0..99d6b939e0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js @@ -215,9 +215,15 @@ * @param {any} selectedVariant */ function openSplitView(selectedVariant) { - var selectedCulture = selectedVariant.language.culture; + //Find the whole variant model based on the culture that was chosen + var variant = _.find(vm.content.variants, function (v) { + return v.language.culture === selectedCulture; + }); + + insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length)); + //only the content app can be selected since no other apps are shown, and because we copy all of these apps //to the "editors" we need to update this across all editors for (var e = 0; e < vm.editors.length; e++) { @@ -233,13 +239,6 @@ } } - //Find the whole variant model based on the culture that was chosen - var variant = _.find(vm.content.variants, function (v) { - return v.language.culture === selectedCulture; - }); - - insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length)); - //TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular editor.collapsed = true; editor.loading = true; @@ -260,6 +259,8 @@ vm.editors.splice(editorIndex, 1); //remove variant from open variants vm.openVariants.splice(editorIndex, 1); + //update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant) + $location.search("cculture", vm.openVariants[0]); splitViewChanged(); }, 400); } @@ -270,7 +271,7 @@ * @param {any} editorIndex The index of the editor being changed */ function selectVariant(variant, editorIndex) { - + // prevent variants already open in a split view to be opened if(vm.openVariants.indexOf(variant.language.culture) !== -1) { return; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 7578ade867..6e75973ae7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -136,7 +136,7 @@ angular.module('umbraco.directives') return; } - angularHelper.safeApply(scope, attrs.onOutsideClick); + scope.$apply(attrs.onOutsideClick); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index 0dddada8ad..372fb472fa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -55,7 +55,7 @@ angular.module("umbraco.directives") //initialize the standard editor functionality for Umbraco tinyMceService.initializeEditor({ editor: editor, - value: scope.value, + model: scope, currentForm: angularHelper.getCurrentForm(scope) }); @@ -67,7 +67,7 @@ angular.module("umbraco.directives") $timeout(function () { if (scope.value === null) { - editor.trigger("focus"); + editor.focus(); } }, 400); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js index 8bad5ae8fd..3d784c999f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js @@ -130,8 +130,7 @@ if (!changes.value.isFirstChange() && changes.value.currentValue !== changes.value.previousValue) { configureViewModel(); - //this is required to re-validate - vm.tagEditorForm.tagCount.$setViewValue(vm.viewModel.length); + reValidate() } } @@ -182,6 +181,8 @@ else { vm.onValueChanged({ value: [] }); } + + reValidate(); } /** @@ -189,7 +190,7 @@ */ function validateMandatory() { return { - isValid: !vm.validation.mandatory || (vm.viewModel != null && vm.viewModel.length > 0), + isValid: !vm.validation.mandatory || (vm.viewModel != null && vm.viewModel.length > 0)|| (vm.value != null && vm.value.length > 0), errorMsg: "Value cannot be empty", errorKey: "required" }; @@ -271,6 +272,10 @@ }); } + function reValidate() { + //this is required to re-validate + vm.tagEditorForm.tagCount.$setViewValue(vm.viewModel.length); + } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js index 47d1431e13..2bc1c636b5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js @@ -44,6 +44,7 @@ the directive will use {@link umbraco.directives.directive:umbLockedField umbLoc @param {string} alias (binding): The model where the alias is bound. @param {string} aliasFrom (binding): The model to generate the alias from. +@param {string} validationPosition (binding): The position of the validation. Set to 'left' or 'right'. @param {boolean=} enableLock (binding): Set to true to add a lock next to the alias from where it can be unlocked and changed. **/ @@ -57,6 +58,7 @@ angular.module("umbraco.directives") alias: '=', aliasFrom: '=', enableLock: '=?', + validationPosition: '=?', serverValidationField: '@' }, link: function (scope, element, attrs, ctrl) { @@ -86,20 +88,21 @@ angular.module("umbraco.directives") function generateAlias(value) { if (generateAliasTimeout) { - $timeout.cancel(generateAliasTimeout); + $timeout.cancel(generateAliasTimeout); } - if( value !== undefined && value !== "" && value !== null) { + if (value !== undefined && value !== "" && value !== null) { - scope.alias = ""; + scope.alias = ""; scope.placeholderText = scope.labels.busy; generateAliasTimeout = $timeout(function () { updateAlias = true; entityResource.getSafeAlias(value, true).then(function (safeAlias) { if (updateAlias) { - scope.alias = safeAlias.alias; - } + scope.alias = safeAlias.alias; + } + scope.placeholderText = scope.labels.idle; }); }, 500); @@ -108,7 +111,6 @@ angular.module("umbraco.directives") scope.alias = ""; scope.placeholderText = scope.labels.idle; } - } // if alias gets unlocked - stop watching alias @@ -119,17 +121,17 @@ angular.module("umbraco.directives") })); // validate custom entered alias - eventBindings.push(scope.$watch('alias', function(newValue, oldValue){ - - if(scope.alias === "" && bindWatcher === true || scope.alias === null && bindWatcher === true) { - // add watcher - eventBindings.push(scope.$watch('aliasFrom', function(newValue, oldValue) { - if(bindWatcher) { - generateAlias(newValue); - } - })); - } - + eventBindings.push(scope.$watch('alias', function (newValue, oldValue) { + if (scope.alias === "" || scope.alias === null || scope.alias === undefined) { + if (bindWatcher === true) { + // add watcher + eventBindings.push(scope.$watch('aliasFrom', function (newValue, oldValue) { + if (bindWatcher) { + generateAlias(newValue); + } + })); + } + } })); // clean up diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js index 89f6ebcd6a..1cbedaf26e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js @@ -33,10 +33,11 @@ Use this directive to generate color swatches to pick from. scope.useColorClass = false; } - scope.setColor = function (color) { + scope.setColor = function (color, $index, $event) { scope.selectedColor = color; if (scope.onSelect) { - scope.onSelect(color); + scope.onSelect(color, $index, $event); + $event.stopPropagation(); } }; } @@ -50,7 +51,7 @@ Use this directive to generate color swatches to pick from. colors: '=?', size: '@', selectedColor: '=', - onSelect: '&', + onSelect: '=', useLabel: '=', useColorClass: '=?' }, diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js index cc5a1eb2b1..f19e2c810b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js @@ -40,6 +40,7 @@ Use this directive to render a value with a lock next to it. When the lock is cl @param {boolean=} locked (binding): true by default. Set to false to unlock the text. @param {string=} placeholderText (binding): If ngModel is empty this text will be shown. @param {string=} regexValidation (binding): Set a regex expression for validation of the field. +@param {string} validationPosition (binding): The position of the validation. Set to 'left' or 'right'. @param {string=} serverValidationField (attribute): Set a server validation field. **/ @@ -70,7 +71,11 @@ Use this directive to render a value with a lock next to it. When the lock is cl // if locked state is not defined as an attr set default state if (scope.placeholderText === undefined || scope.placeholderText === null) { scope.placeholderText = "Enter value..."; - } + } + + if (scope.validationPosition === undefined || scope.validationPosition === null) { + scope.validationPosition = "left"; + } } @@ -93,9 +98,10 @@ Use this directive to render a value with a lock next to it. When the lock is cl templateUrl: 'views/components/umb-locked-field.html', scope: { ngModel: "=", - locked: "=?", + locked: "=?", placeholderText: "=?", - regexValidation: "=?", + regexValidation: "=?", + validationPosition: "=?", serverValidationField: "@" }, link: link diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js index eaf67bcb91..29920ebf00 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js @@ -146,7 +146,6 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location var infiniteEditors = editorService.getEditors(); if (!formCtrl.$dirty && infiniteEditors.length === 0 || isSavingNewItem && infiniteEditors.length === 0) { - confirmed = true; return; } diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/safehtml.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/safehtml.filter.js new file mode 100644 index 0000000000..50d8574306 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/filters/safehtml.filter.js @@ -0,0 +1,6 @@ +angular.module('umbraco.filters') + .filter('safe_html', ['$sce', function($sce){ + return function(text) { + return $sce.trustAsHtml(text); + }; + }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js index 9724c222b6..6bc7855528 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js @@ -11,24 +11,24 @@ angular.module('umbraco.mocks'). var menu = [ { name: "Create", cssclass: "plus", alias: "create", metaData: {} }, - { seperator: true, name: "Delete", cssclass: "remove", alias: "delete", metaData: {} }, + { separator: true, name: "Delete", cssclass: "remove", alias: "delete", metaData: {} }, { name: "Move", cssclass: "move", alias: "move", metaData: {} }, { name: "Copy", cssclass: "copy", alias: "copy", metaData: {} }, { name: "Sort", cssclass: "sort", alias: "sort", metaData: {} }, - { seperator: true, name: "Publish", cssclass: "globe", alias: "publish", metaData: {} }, + { separator: true, name: "Publish", cssclass: "globe", alias: "publish", metaData: {} }, { name: "Rollback", cssclass: "undo", alias: "rollback", metaData: {} }, - { seperator: true, name: "Permissions", cssclass: "lock", alias: "permissions", metaData: {} }, + { separator: true, name: "Permissions", cssclass: "lock", alias: "permissions", metaData: {} }, { name: "Audit Trail", cssclass: "time", alias: "audittrail", metaData: {} }, { name: "Notifications", cssclass: "envelope", alias: "notifications", metaData: {} }, - { seperator: true, name: "Hostnames", cssclass: "home", alias: "hostnames", metaData: {} }, + { separator: true, name: "Hostnames", cssclass: "home", alias: "hostnames", metaData: {} }, { name: "Public Access", cssclass: "group", alias: "publicaccess", metaData: {} }, - { seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} }, + { separator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} }, - { seperator: true, name: "Empty Recycle Bin", cssclass: "trash", alias: "emptyrecyclebin", metaData: {} } + { separator: true, name: "Empty Recycle Bin", cssclass: "trash", alias: "emptyrecyclebin", metaData: {} } ]; var result = { @@ -94,7 +94,7 @@ angular.module('umbraco.mocks'). jsAction: "umbracoMenuActions.CreateChildEntity" } }, - { seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} } + { separator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} } ]; return [200, menu, null]; diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js index b6c87027b1..0ff01780ae 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js @@ -203,7 +203,6 @@ angular.module('umbraco.mocks'). "defaultdialogs_siterepublishHelp": "The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished.", "defaultdialogs_tableColumns": "Number of columns", "defaultdialogs_tableRows": "Number of rows", - "defaultdialogs_templateContentAreaHelp": "Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates, by refering this ID using a <asp:content /> element.", "defaultdialogs_templateContentPlaceHolderHelp": "Select a placeholder id from the list below. You can only choose Id's from the current template's master.", "defaultdialogs_thumbnailimageclickfororiginal": "Click on the image to see full size", "defaultdialogs_treepicker": "Pick item", @@ -453,13 +452,11 @@ angular.module('umbraco.mocks'). "notifications_notifications": "Notifications", "packager_chooseLocalPackageText": " Choose Package from your machine, by clicking the Browse
button and locating the package. Umbraco packages usually have a '.umb' or '.zip' extension. ", "packager_packageAuthor": "Author", - "packager_packageDemonstration": "Demonstration", "packager_packageDocumentation": "Documentation", "packager_packageMetaData": "Package meta data", "packager_packageName": "Package name", "packager_packageNoItemsHeader": "Package doesn't contain any items", "packager_packageNoItemsText": "This package file doesn't contain any items to uninstall.

You can safely remove this from the system by clicking 'uninstall package' below.", - "packager_packageNoUpgrades": "No upgrades available", "packager_packageOptions": "Package options", "packager_packageReadme": "Package readme", "packager_packageRepository": "Package repository", @@ -468,13 +465,7 @@ angular.module('umbraco.mocks'). "packager_packageUninstalledText": "The package was successfully uninstalled", "packager_packageUninstallHeader": "Uninstall package", "packager_packageUninstallText": "You can unselect items you do not wish to remove, at this time, below. When you click 'confirm uninstall' all checked-off items will be removed.
Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, so uninstall with caution. If in doubt, contact the package author.", - "packager_packageUpgradeDownload": "Download update from the repository", - "packager_packageUpgradeHeader": "Upgrade package", - "packager_packageUpgradeInstructions": "Upgrade instructions", - "packager_packageUpgradeText": " There's an upgrade available for this package. You can download it directly from the Umbraco package repository.", "packager_packageVersion": "Package version", - "packager_packageVersionHistory": "Package version history", - "packager_viewPackageWebsite": "View package website", "paste_doNothing": "Paste with full formatting (Not recommended)", "paste_errorMessage": "The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web.", "paste_removeAll": "Paste as raw text without any formatting at all", @@ -641,7 +632,7 @@ angular.module('umbraco.mocks'). "templateEditor_urlEncodeHelp": "Will format special characters in URLs", "templateEditor_usedIfAllEmpty": "Will only be used when the field values above are empty", "templateEditor_usedIfEmpty": "This field will only be used if the primary field is empty", - "templateEditor_withTime": "Yes, with time. Seperator: ", + "templateEditor_withTime": "Yes, with time. Separator: ", "translation_details": "Translation details", "translation_DownloadXmlDTD": "Download xml DTD", "translation_fields": "Fields", diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js index bb1dad1dbd..1fe739154a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js @@ -3,7 +3,7 @@ * @name umbraco.resources.codefileResource * @description Loads in data for files that contain code such as js scripts, partial views and partial view macros **/ -function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) { +function codefileResource($q, $http, umbDataFormatter, umbRequestHelper, localizationService) { return { @@ -106,13 +106,16 @@ function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) { * */ deleteByPath: function (type, virtualpath) { + + var promise = localizationService.localize("codefile_deleteItemFailed", [virtualpath]); + return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "codeFileApiBaseUrl", "Delete", [{ type: type }, { virtualPath: virtualpath}])), - "Failed to delete item: " + virtualpath); + promise); }, /** @@ -236,13 +239,19 @@ function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) { * */ - createContainer: function(type, parentId, name) { + createContainer: function (type, parentId, name) { + + // Is the parent ID numeric? + var key = "codefile_createFolderFailedBy" + (isNaN(parseInt(parentId)) ? "Name" : "Id"); + + var promise = localizationService.localize(key, [parentId]); + return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl( "codeFileApiBaseUrl", "PostCreateContainer", { type: type, parentId: parentId, name: encodeURIComponent(name) })), - 'Failed to create a folder under parent id ' + parentId); + promise); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js index 8fd6836884..7519341327 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js @@ -19,7 +19,7 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( - "packageInstallApiBaseUrl", + "packageApiBaseUrl", "GetInstalled")), 'Failed to get installed packages'); }, @@ -33,15 +33,6 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to validate package ' + name); }, - deleteCreatedPackage: function (packageId) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "packageInstallApiBaseUrl", - "DeleteCreatedPackage", { packageId: packageId })), - 'Failed to delete package ' + packageId); - }, - uninstall: function(packageId) { return umbRequestHelper.resourcePromise( $http.post( @@ -151,6 +142,94 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { "packageInstallApiBaseUrl", "CleanUp"), umbPackage), 'Failed to install package. Error during the step "CleanUp" '); + }, + + /** + * @ngdoc method + * @name umbraco.resources.packageInstallResource#getCreated + * @methodOf umbraco.resources.packageInstallResource + * + * @description + * Gets a list of created packages + */ + getAllCreated: function() { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "packageApiBaseUrl", + "GetCreatedPackages")), + 'Failed to get created packages'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.packageInstallResource#getCreatedById + * @methodOf umbraco.resources.packageInstallResource + * + * @description + * Gets a created package by id + */ + getCreatedById: function(id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "packageApiBaseUrl", + "GetCreatedPackageById", + { id: id })), + 'Failed to get package'); + }, + + getInstalledById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "packageApiBaseUrl", + "GetInstalledPackageById", + { id: id })), + 'Failed to get package'); + }, + + getEmpty: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "packageApiBaseUrl", + "getEmpty")), + 'Failed to get scaffold'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.packageInstallResource#savePackage + * @methodOf umbraco.resources.packageInstallResource + * + * @description + * Creates or updates a package + */ + savePackage: function (umbPackage) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "packageApiBaseUrl", + "PostSavePackage"), umbPackage), + 'Failed to create package'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.packageInstallResource#deleteCreatedPackage + * @methodOf umbraco.resources.packageInstallResource + * + * @description + * Detes a created package + */ + deleteCreatedPackage: function (packageId) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "packageApiBaseUrl", + "DeleteCreatedPackage", { packageId: packageId })), + 'Failed to delete package ' + packageId); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/relation.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/relation.resource.js index 19bfded340..c791197e20 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/relation.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/relation.resource.js @@ -26,7 +26,7 @@ function relationResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "relationApiBaseUrl", "GetByChildId", - [{ childId: id, relationTypeAlias: alias }])), + { childId: id, relationTypeAlias: alias })), "Failed to get relation by child ID " + id + " and type of " + alias); }, @@ -62,4 +62,4 @@ function relationResource($q, $http, umbRequestHelper) { }; } -angular.module('umbraco.resources').factory('relationResource', relationResource); \ No newline at end of file +angular.module('umbraco.resources').factory('relationResource', relationResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js index f969864ba1..377bb415fc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js @@ -3,7 +3,7 @@ * @name umbraco.resources.templateResource * @description Loads in data for templates **/ -function templateResource($q, $http, umbDataFormatter, umbRequestHelper) { +function templateResource($q, $http, umbDataFormatter, umbRequestHelper, localizationService) { return { @@ -152,13 +152,16 @@ function templateResource($q, $http, umbDataFormatter, umbRequestHelper) { * */ deleteById: function(id) { + + var promise = localizationService.localize("template_deleteByIdFailed", [id]); + return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "templateApiBaseUrl", "DeleteById", [{ id: id }])), - "Failed to delete item " + id); + promise); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 47e5a24180..1258ec4099 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -421,7 +421,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica */ reBindChangedProperties: function (origContent, savedContent) { - //TODO: We should probably split out this logic to deal with media/members seperately to content + //TODO: We should probably split out this logic to deal with media/members separately to content //a method to ignore built-in prop changes var shouldIgnore = function (propName) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 0d6432b01f..eecdd7eadc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -162,7 +162,7 @@ When building a custom infinite editor view you can use the same components as a (function () { "use strict"; - function editorService(eventsService, keyboardService) { + function editorService(eventsService, keyboardService, $timeout) { let editorsKeyboardShorcuts = []; var editors = []; @@ -245,10 +245,14 @@ When building a custom infinite editor view you can use the same components as a // emit event to let components know an editor has been removed eventsService.emit("appState.editors.close", args); - - // rebind keyboard shortcuts for the new editor in focus - rebindKeyboardShortcuts(); + // delay required to map the properties to the correct editor due + // to another delay in the closing animation of the editor + $timeout(function() { + // rebind keyboard shortcuts for the new editor in focus + rebindKeyboardShortcuts(); + }, 0); + } /** @@ -748,6 +752,29 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#memberPicker + * @methodOf umbraco.services.editorService + * + * @description + * Opens a member picker in infinite editing, the submit callback returns an array of selected items + * + * @param {Object} editor rendering options + * @param {Boolean} editor.multiPicker Pick one or multiple items + * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object + * @param {Function} editor.close Callback function when the close button is clicked. + * + * @returns {Object} editor object + */ + function memberPicker(editor) { + editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; + editor.size = "small"; + editor.section = "member"; + editor.treeAlias = "member"; + open(editor); + } + /////////////////////// /** @@ -824,7 +851,8 @@ When building a custom infinite editor view you can use the same components as a userPicker: userPicker, itemPicker: itemPicker, macroPicker: macroPicker, - memberGroupPicker: memberGroupPicker + memberGroupPicker: memberGroupPicker, + memberPicker: memberPicker }; return service; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js index 6bab8fda81..51f63e6787 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js @@ -6,7 +6,6 @@ app.ready app.authenticated app.notAuthenticated - app.ysod app.reInitialize app.userRefresh app.navigationReady diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index f3b64e0c28..191e0a22c0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -110,7 +110,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService //Or, some strange server error if (err.status === 400) { //now we need to look through all the validation errors - if (err.data && (err.data.ModelState)) { + if (err.data && err.data.ModelState) { //wire up the server validation errs this.handleServerValidation(err.data.ModelState); @@ -118,9 +118,11 @@ function formHelper(angularHelper, serverValidationManager, notificationsService //execute all server validation events and subscribers serverValidationManager.notifyAndClearAllSubscriptions(); } - else { - overlayService.ysod(err); - } + } + else { + + //TODO: All YSOD handling should be done with an interceptor + overlayService.ysod(err); } }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index 6c50e58490..6de0b4170b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -27,6 +27,11 @@ overlay.position = "center"; } + // use a default empty view if nothing is set + if(!overlay.view) { + overlay.view = "views/common/overlays/default/default.html"; + } + // option to disable backdrop clicks if(overlay.disableBackdropClick) { backdropOptions.disableEventsOnClick = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index f6c2e93182..97d939bac1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -1194,7 +1194,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s .css("top", "auto") .css("margin-top", "0") .css("width", tinyMceWidth); - } + } }, @@ -1214,15 +1214,15 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s if (!args.editor) { throw "args.editor is required"; } - //if (!args.value) { - // throw "args.value is required"; + //if (!args.model.value) { + // throw "args.model.value is required"; //} var unwatch = null; //Starts a watch on the model value so that we can update TinyMCE if the model changes behind the scenes or from the server function startWatch() { - unwatch = $rootScope.$watch(() => args.value, function (newVal, oldVal) { + unwatch = $rootScope.$watch(() => args.model.value, function (newVal, oldVal) { if (newVal !== oldVal) { //update the display val again if it has changed from the server; //uses an empty string in the editor when the value is null @@ -1247,7 +1247,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //stop watching before we update the value stopWatch(); angularHelper.safeApply($rootScope, function () { - args.value = args.editor.getContent(); + args.model.value = args.editor.getContent(); }); //re-watch the value startWatch(); @@ -1255,8 +1255,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s args.editor.on('init', function (e) { - if (args.value) { - args.editor.setContent(args.value); + if (args.model.value) { + args.editor.setContent(args.model.value); } //enable browser based spell checking args.editor.getBody().setAttribute('spellcheck', true); @@ -1316,7 +1316,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], startNodeIsVirtual: userData.startMediaIds.length !== 1, submit: function (model) { - self.insertMediaInEditor(args.editor, model.selectedImages[0]); + self.insertMediaInEditor(args.editor, model.selection[0]); editorService.close(); }, close: function () { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index e31742e660..1e6fc5d643 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -64,7 +64,7 @@ var saveModel = _.pick(displayModel, 'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes', 'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed', - 'key', 'parentId', 'alias', 'path', 'allowCultureVariant'); + 'key', 'parentId', 'alias', 'path', 'allowCultureVariant', 'isElement'); //TODO: Map these saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; }); @@ -262,7 +262,7 @@ saveModel[props[m]] = startId.id; } - saveModel.parentId = -1; + saveModel.parentId = -1; return saveModel; }, @@ -293,7 +293,7 @@ }); saveModel.email = propEmail.value.trim(); saveModel.username = propLogin.value.trim(); - + saveModel.password = this.formatChangePasswordModel(propPass.value); var selectedGroups = []; @@ -336,7 +336,7 @@ /** formats the display model used to display the media to the model used to save the media */ formatMediaPostData: function (displayModel, action) { - //NOTE: the display model inherits from the save model so we can in theory just post up the display model but + //NOTE: the display model inherits from the save model so we can in theory just post up the display model but // we don't want to post all of the data as it is unecessary. var saveModel = { id: displayModel.id, @@ -354,7 +354,7 @@ /** formats the display model used to display the content to the model used to save the content */ formatContentPostData: function (displayModel, action) { - //NOTE: the display model inherits from the save model so we can in theory just post up the display model but + //NOTE: the display model inherits from the save model so we can in theory just post up the display model but // we don't want to post all of the data as it is unecessary. var saveModel = { id: displayModel.id, @@ -379,7 +379,7 @@ var propExpireDate = displayModel.removeDate; var propReleaseDate = displayModel.releaseDate; var propTemplate = displayModel.template; - + saveModel.expireDate = propExpireDate ? propExpireDate : null; saveModel.releaseDate = propReleaseDate ? propReleaseDate : null; saveModel.templateAlias = propTemplate ? propTemplate : null; @@ -389,8 +389,8 @@ /** * This formats the server GET response for a content display item - * @param {} displayModel - * @returns {} + * @param {} displayModel + * @returns {} */ formatContentGetData: function(displayModel) { @@ -418,7 +418,7 @@ } }); }); - + //now assign this same invariant property instance to the same index of the other variants property array for (var j = 1; j < displayModel.variants.length; j++) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index fcb5585d5d..fb1a1b8d5e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -3,7 +3,7 @@ * @name umbraco.services.umbRequestHelper * @description A helper object used for sending requests to the server **/ -function umbRequestHelper($http, $q, notificationsService, eventsService, formHelper) { +function umbRequestHelper($http, $q, notificationsService, eventsService, formHelper, overlayService) { return { @@ -176,11 +176,9 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe //show a ysod dialog if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - eventsService.emit('app.ysod', - { - errorMsg: 'An error occured', - data: response.data - }); + const error = { errorMsg: 'An error occured', data: response.data }; + //TODO: All YSOD handling should be done with an interceptor + overlayService.ysod(error); } else { //show a simple error notification @@ -290,11 +288,9 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe } else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { //show a ysod dialog - eventsService.emit('app.ysod', - { - errorMsg: 'An error occured', - data: response.data - }); + const error = { errorMsg: 'An error occured', data: response.data }; + //TODO: All YSOD handling should be done with an interceptor + overlayService.ysod(error); } else { //show a simple error notification diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js index 30a7e2ac7d..c2b2ba26d7 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js @@ -103,14 +103,6 @@ function MainController($scope, $location, appState, treeService, notificationsS })); - evts.push(eventsService.on("app.ysod", function (name, error) { - $scope.ysodOverlay = { - view: "ysod", - error: error, - show: true - }; - })); - // events for search evts.push(eventsService.on("appState.searchState.changed", function (e, args) { if (args.key === "show") { diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 06b82d6eab..e023c6d23c 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -9,7 +9,7 @@ * * @param {navigationService} navigationService A reference to the navigationService */ -function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource) { +function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource) { //this is used to trigger the tree to start loading once everything is ready var treeInitPromise = $q.defer(); @@ -344,9 +344,6 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar $scope.languages = languages; if ($scope.languages.length > 1) { - var defaultLang = _.find($scope.languages, function (l) { - return l.isDefault; - }); //if there's already one set, check if it exists var currCulture = null; var mainCulture = $location.search().mculture; @@ -356,7 +353,20 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar }); } if (!currCulture) { - $location.search("mculture", defaultLang ? defaultLang.culture : null); + // no culture in the request, let's look for one in the cookie that's set when changing language + var defaultCulture = $cookies.get("UMB_MCULTURE"); + if (!defaultCulture || !_.find($scope.languages, function (l) { + return l.culture.toLowerCase() === defaultCulture.toLowerCase(); + })) { + // no luck either, look for the default language + var defaultLang = _.find($scope.languages, function (l) { + return l.isDefault; + }); + if (defaultLang) { + defaultCulture = defaultLang.culture; + } + } + $location.search("mculture", defaultCulture ? defaultCulture : null); } } @@ -391,6 +401,10 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar $scope.selectLanguage = function (language) { $location.search("mculture", language.culture); + // add the selected culture to a cookie so the user will log back into the same culture later on (cookie lifetime = one year) + var expireDate = new Date(); + expireDate.setDate(expireDate.getDate() + 365); + $cookies.put("UMB_MCULTURE", language.culture, {path: "/", expires: expireDate}); // close the language selector $scope.page.languageSelectorIsOpen = false; 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 a8e0530417..50b9f2f3c0 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -1,4 +1,4 @@ -angular.module("umbraco.install").factory('installerService', function($rootScope, $q, $timeout, $http, $location, $log){ +angular.module("umbraco.install").factory('installerService', function ($rootScope, $q, $timeout, $http, $templateRequest){ var _status = { index: 0, @@ -16,17 +16,17 @@ angular.module("umbraco.install").factory('installerService', function($rootScop }; //add to umbraco installer facts here - var facts = ['Umbraco helped millions of people watch a man jump from the edge of space', - 'Over 440 000 websites are currently powered by Umbraco', + var facts = ["Umbraco helped millions of people watch a man jump from the edge of space", + "Over 500 000 websites are currently powered by Umbraco", "At least 2 people have named their cat 'Umbraco'", - 'On an average day, more than 1000 people download Umbraco', - 'umbraco.tv is the premier source of Umbraco video tutorials to get you started', - 'You can find the world\'s friendliest CMS community at our.umbraco.com', - 'You can become a certified Umbraco developer by attending one of the official courses', - 'Umbraco works really well on tablets', - 'You have 100% control over your markup and design when crafting a website in Umbraco', - '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", + "On an average day more than 1000 people download Umbraco", + "umbraco.tv is the premier source of Umbraco video tutorials to get you started", + "You can find the world's friendliest CMS community at our.umbraco.com", + "You can become a certified Umbraco developer by attending one of the official courses", + "Umbraco works really well on tablets", + "You have 100% control over your markup and design when crafting a website in Umbraco", + "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 4 people have the Umbraco logo tattooed on them", "'Umbraco' is the Danish name for an allen key", @@ -106,19 +106,26 @@ angular.module("umbraco.install").factory('installerService', function($rootScop //loads the needed steps and sets the intial state init : function(){ service.status.loading = true; - if(!_status.all){ - service.getSteps().then(function(response){ - service.status.steps = response.data.steps; - service.status.index = 0; - _installerModel.installId = response.data.installId; - service.findNextStep(); + if (!_status.all) { + //pre-load the error page, if an error occurs, the page might not be able to load + // so we want to make sure it's available in the templatecache first + $templateRequest("views/install/error.html").then(x => { + service.getSteps().then(response => { + service.status.steps = response.data.steps; + service.status.index = 0; + _installerModel.installId = response.data.installId; + service.findNextStep(); - $timeout(function(){ - service.status.loading = false; - service.status.configuring = true; - }, 2000); - }); - } + $timeout(function() { + service.status.loading = false; + service.status.configuring = true; + }, + 2000); + }); + }); + + + } }, //loads available packages from our.umbraco.com diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index a28c128706..a65b94444e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -161,6 +161,7 @@ @import "components/umb-file-dropzone.less"; @import "components/umb-node-preview.less"; @import "components/umb-mini-editor.less"; +@import "components/umb-property-file-upload.less"; @import "components/users/umb-user-cards.less"; @import "components/users/umb-user-details.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less index 150963cbb2..4c30ae583c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less @@ -28,7 +28,8 @@ .umb-toggle__toggle { cursor: pointer; - display: inline-block; + align-items: center; + display: flex; width: 48px; height: 24px; background: @gray-8; @@ -41,6 +42,11 @@ background-color: @green; } +.umb-toggle--disabled .umb-toggle__toggle { + cursor: not-allowed; + opacity: 0.8; +} + .umb-toggle--checked .umb-toggle__handler { transform: translate3d(24px, 0, 0) rotate(0); } @@ -63,7 +69,7 @@ .umb-toggle__icon { position: absolute; - top: 3px; + line-height: 1em; text-decoration: none; transition: all 0.2s ease; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less index ef8834f767..c9aff190ce 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less @@ -8,4 +8,14 @@ .umb-alert--info { background-color: @turquoise-washed; border: 1px solid @turquoise; -} \ No newline at end of file +} + +.umb-alert--warning { + background-color: @yellow-washed; + border: 1px solid @yellow; +} + +.umb-alert--danger { + background-color: @red-washed; + border: 1px solid @red; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 4705b8cc3e..bd493cf0d3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -125,6 +125,8 @@ body.touch .umb-tree { position: inherit; display: inherit; + list-style: none; + h6 { padding: 10px 0 10px 20px; font-weight: inherit; @@ -137,7 +139,15 @@ body.touch .umb-tree { } &-item { - padding-left: 20px; + padding: 4px 0; + + &:hover { + background-color: @gray-10; + } + + &-link { + display: block; + } } &-link { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less index 0cb7bc9fe4..ba04069c6b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -28,7 +28,7 @@ color: @black; } -.umb-breadcrumbs__seperator { +.umb-breadcrumbs__separator { position: relative; top: 1px; margin-left: 5px; @@ -41,4 +41,4 @@ input.umb-breadcrumbs__add-ancestor { margin-top: -2px; margin-left: 3px; width: 100px; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less index 3b49dceeb2..d8b83af67a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less @@ -19,6 +19,7 @@ align-items: center; justify-content: center; height: @editorHeaderHeight; + position: relative; } .umb-sub-views-nav-item:focus { @@ -46,6 +47,33 @@ margin-bottom: 7px; } +.umb-sub-views-nav-item .badge { + position: absolute; + top: 6px; + right: 6px; + min-width: 16px; + color: @white; + background-color: @turquoise-d1; + border: 2px solid @white; + border-radius: 50%; + font-size: 10px; + font-weight: bold; + padding: 2px; + line-height: 16px; + display: block; + + &.-type-alert { + background-color: @red-l1; + } + &.-type-warning { + background-color: @yellow-d2; + } + &:empty { + height: 12px; + min-width: 12px; + } +} + .umb-sub-views-nav-item-text { font-size: 12px; line-height: 1em; @@ -80,4 +108,4 @@ grid-template-columns: 1fr 1fr 1fr; min-width: auto; margin-top: 10px; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less index 4803c05f6e..b7c58ad3cf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less @@ -1,5 +1,5 @@ -.umb-file-dropzone-directive{ +.umb-file-dropzone { // drop zone // tall and small version - animate height diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less index 9001f1d473..8d9ae86ce7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less @@ -38,6 +38,7 @@ input.umb-locked-field__input { transition: color 0.25s; padding: 0; height: auto; + max-width: 300px; } input.umb-locked-field__input:focus { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index df8977a2bf..6ddd9d8d50 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -3,6 +3,13 @@ position: relative; } +.umb-nested-content-property-container { + position: relative; + &:not(:last-child){ + margin-bottom: 12px; + } +} + .umb-nested-content--not-supported { opacity: 0.3; pointer-events: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less index 75f58f983b..99759fcee7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less @@ -15,7 +15,7 @@ height: 300px; border: 2px dashed @gray-8; border-radius: 3px; - background: @gray-10; + background: @white; display: flex; flex-direction: column; justify-content: center; @@ -74,7 +74,6 @@ // Info state .umb-info-local-items { - border: 2px solid @gray-8; border-radius: 3px; background: @gray-10; display: flex; @@ -84,6 +83,8 @@ margin: 0 20px; width: 100%; max-width: 540px; + background: @white; + box-shadow: 0 1px 1px 0 rgba(0,0,0,.16); } .umb-info-local-items a { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less index e2cfb0bded..a517605c4a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -23,9 +23,7 @@ .umb-packages-search { width: 100%; - background: @gray-10; border-radius: 3px; - padding: 30px; box-sizing: border-box; } @@ -49,60 +47,14 @@ } .umb-packages { - margin: 0 -10px; - display: flex; - flex-wrap: wrap; + display: grid; + grid-gap: 20px; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); } // Cards .umb-package { - padding: 10px; box-sizing: border-box; - flex: 0 0 100%; - max-width: 100%; -} - -@media (min-width: 768px) { - .umb-package { - flex: 0 0 50%; - max-width: 50%; - } -} - -@media (min-width: 1200px) { - .umb-package { - flex: 0 0 33.33%; - max-width: 33.33%; - } -} - -@media (min-width: 1400px) { - .umb-package { - flex: 0 0 25%; - max-width: 25%; - } -} - -@media (min-width: 1700px) { - .umb-package { - flex: 0 0 20%; - max-width: 20%; - } -} - - -@media (min-width: 1900px) { - .umb-package { - flex: 0 0 16.66%; - max-width: 16.66%; - } -} - -@media (min-width: 2200px) { - .umb-package { - flex: 0 0 14.28%; - max-width: 14.28%; - } } .umb-package-link { @@ -114,10 +66,11 @@ box-sizing: border-box; height: 100%; width: 100%; - border: 1px solid @gray-9; border-radius: 3px; text-decoration: none !important; transition: border-color 100ms ease; + background-color: @white; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); &:hover { border-color: @turquoise; @@ -151,15 +104,8 @@ // Info .umb-package-info { - padding-right: 15px; - padding-bottom: 15px; - padding-left: 15px; - padding-top: 15px; + padding: 15px; text-align: center; - background: @gray-10; - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; - border-top: 1px solid @gray-9; } @@ -251,6 +197,7 @@ border-bottom: 1px solid @gray-8; border-right: 1px solid @gray-8; padding: 10px 0; + background: @white; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less new file mode 100644 index 0000000000..08b1a1b5e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less @@ -0,0 +1,28 @@ +.umb-property-file-upload { + + .umb-upload-button-big { + display: block; + padding: 20px; + opacity: 1; + border: 1px dashed @gray-8; + background: none; + text-align: center; + font-size: 14px; + + &, &:hover { + color: @gray-8; + } + + i.icon { + font-size: 55px; + line-height: 70px + } + + input { + left: 0; + bottom: 0; + height: 100%; + width: 100%; + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less index 59ded555a6..ad7f4ea1a2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less @@ -38,10 +38,12 @@ align-items: center; } -.umb-stylesheet-rule-overlay { - textarea { - width: 300px; - height: 120px; - resize: none; - } +textarea.umb-stylesheet-rule-styles { + width: 300px; + height: 100px; + resize: none; +} + +.umb-stylesheet-rule-preview { + line-height: normal; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less index f5a81e3393..0d74986d65 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less @@ -4,10 +4,6 @@ padding: 7px 0; } -.umb-permission--disabled { - opacity: 0.8; -} - .umb-permission:last-of-type { border-bottom: none; } @@ -25,6 +21,12 @@ cursor: pointer; } +.umb-permission--disabled .umb-permission__toggle, +.umb-permission--disabled .umb-permission__content { + cursor: not-allowed; + opacity: 0.8; +} + .umb-permission__description { font-size: 13px; color: @gray-4; diff --git a/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less b/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less index 417447c3dc..9b4bac723b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less @@ -1,46 +1,67 @@ .umb-validation-label { - position: absolute; - top: 27px; - width: 200px; - padding: 1px 5px; - background: @red; - color: @white; - font-size: 11px; - line-height: 1.5em; + position: absolute; + top: 27px; + min-width: 100px; + max-width: 200px; + padding: 1px 5px; + background: @red; + color: @white; + font-size: 11px; + line-height: 1.5em; } .umb-validation-label:after { - bottom: 100%; - left: 10px; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - border-color: rgba(255, 255, 255, 0); - border-bottom-color: @red; - border-width: 4px; - margin-left: -4px; + bottom: 100%; + left: 10px; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(255, 255, 255, 0); + border-bottom-color: @red; + border-width: 4px; + margin-left: -4px; +} + +.umb-validation-label.-left { + left: 0; + right: auto; + + &:after { + left: 10px; + right: auto; + } +} + +.umb-validation-label.-right { + right: 0; + left: auto; + + &:after { + right: 10px; + left: auto; + } } .umb-validation-label.-arrow-left { - margin-left: 10px; + margin-left: 10px; } .umb-validation-label.-arrow-left:after { - right: 100%; - top: 50%; - left: auto; - bottom: auto; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - border-color: rgba(255, 255, 255, 0); - border-right-color: @red; - border-width: 4px; - margin-top: -4px; + right: 100%; + top: 50%; + left: auto; + bottom: auto; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(255, 255, 255, 0); + border-right-color: @red; + border-width: 4px; + margin-top: -4px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 3d9df196d7..ae930d6fb0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -373,10 +373,6 @@ margin-bottom: 0; } -.umb-panel-header-alias .umb-validation-label:after { - visibility: hidden; -} - .umb-panel-header-alias .umb-locked-field:after { display: none; } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index e2d4cb708d..3ec8c383da 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -1,3 +1,5 @@ +@checkered-background: url(../img/checkered-background.png); + // // Container styles // -------------------------------------------------- @@ -353,7 +355,7 @@ max-height:100%; margin:auto; display:block; - background-image: url(../img/checkered-background.png); + background-image: @checkered-background; } .umb-sortable-thumbnails li .trashed { @@ -576,12 +578,18 @@ vertical-align: top; } - .gravity-container .viewport { - max-width: 600px; - } + .gravity-container { + border: 1px solid @gray-8; + line-height: 0; - .gravity-container .viewport:hover { - cursor: pointer; + .viewport { + max-width: 600px; + background: @checkered-background; + + &:hover { + cursor: pointer; + } + } } .imagecropper { @@ -594,6 +602,10 @@ float: left; max-width: 100%; } + + .viewport img { + background: @checkered-background; + } } .imagecropper .umb-cropper__container { @@ -687,7 +699,7 @@ // // folder-browser // -------------------------------------------------- -.umb-folderbrowser .add-link{ +.umb-folderbrowser .add-link { display: inline-block; height: 120px; width: 120px; @@ -696,17 +708,6 @@ line-height: 120px } -.umb-upload-button-big:hover{color: @gray-8;} - -.umb-upload-button-big {display: block} -.umb-upload-button-big input { - left: 0; - bottom: 0; - height: 100%; - width: 100%; -} - - // // File upload // -------------------------------------------------- @@ -724,6 +725,10 @@ list-style: none; vertical-align: middle; margin-bottom: 0; + + img { + background: @checkered-background; + } } .umb-fileupload label { diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less index 64d86d7b6f..69bbeef0af 100644 --- a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less @@ -1,7 +1,5 @@ /* - Spacing - */ @spacing-none: 0; @@ -37,13 +35,11 @@ 7 = 7th step in spacing scale */ - .m-center { margin-left: auto; margin-right: auto; } - .mt0 { margin-top: @spacing-none; } .mt1 { margin-top: @spacing-extra-small; } .mt2 { margin-top: @spacing-small; } @@ -52,3 +48,12 @@ .mt5 { margin-top: @spacing-extra-large; } .mt6 { margin-top: @spacing-extra-extra-large; } .mt7 { margin-top: @spacing-extra-extra-extra-large; } + +.mb0 { margin-bottom: @spacing-none; } +.mb1 { margin-bottom: @spacing-extra-small; } +.mb2 { margin-bottom: @spacing-small; } +.mb3 { margin-bottom: @spacing-medium; } +.mb4 { margin-bottom: @spacing-large; } +.mb5 { margin-bottom: @spacing-extra-large; } +.mb6 { margin-bottom: @spacing-extra-extra-large; } +.mb7 { margin-bottom: @spacing-extra-extra-extra-large; } diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 2d8ad46371..c2d3ea2df8 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -149,14 +149,22 @@ app.config(function ($routeProvider) { .when('/:section/:tree/:method?', { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
", - //This controller will execute for this route, then we replace the template dynamnically based on the current tree. - controller: function ($scope, $route, $routeParams, treeService) { + //This controller will execute for this route, then we replace the template dynamically based on the current tree. + controller: function ($scope, $routeParams, treeService) { if (!$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; + return; } - // Here we need to figure out if this route is for a package tree and if so then we need + //special case for the package section + var packagePages = ["edit", "options"]; + if ($routeParams.section.toLowerCase() === "packages" && $routeParams.tree.toLowerCase() === "packages" && packagePages.indexOf($routeParams.method.toLowerCase()) === -1) { + $scope.templateUrl = "views/packages/overview.html"; + return; + } + + // Here we need to figure out if this route is for a user's package tree and if so then we need // to change it's convention view path to: // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html index 84fbab7cb2..bf74431d96 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html @@ -110,6 +110,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> @@ -138,4 +139,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html index b4ff894c4d..620f9f1731 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html @@ -52,6 +52,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.html index f14fb364ab..e48ec84b25 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.html @@ -54,6 +54,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> + size="s" + on-select="vm.selectColor"> @@ -68,11 +69,19 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> + + - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html index 2ccbf11cc1..ea247c77e5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html @@ -47,6 +47,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html index 56bd498fd1..16f4bfb919 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html @@ -207,6 +207,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index 81dfcfd5d3..6057b671bd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -33,31 +33,51 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.showTarget = $scope.model.hideTarget !== true; + // this ensures that we only sync the tree once and only when it's ready + var oneTimeTreeSync = { + executed: false, + treeReady: false, + sync: function () { + // don't run this if: + // - it was already run once + // - the tree isn't ready yet + // - the model path hasn't been loaded yet + if (this.executed || !this.treeReady || !($scope.model.target && $scope.model.target.path)) { + return; + } + + this.executed = true; + // sync the tree to the model path + $scope.dialogTreeApi.syncTree({ + path: $scope.model.target.path, + tree: "content" + }); + } + }; + if (dialogOptions.currentTarget) { - $scope.model.target = dialogOptions.currentTarget; + // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target + $scope.model.target = angular.copy(dialogOptions.currentTarget); //if we have a node ID, we fetch the current node to build the form data if ($scope.model.target.id || $scope.model.target.udi) { //will be either a udi or an int var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; - if (!$scope.model.target.path) { - + // is it a content link? + if (!$scope.model.target.isMedia) { + // get the content path entityResource.getPath(id, "Document").then(function (path) { $scope.model.target.path = path; - //now sync the tree to this path - $scope.dialogTreeApi.syncTree({ - path: $scope.model.target.path, - tree: "content" - }); + oneTimeTreeSync.sync(); + }); + + // get the content properties to build the anchor name list + contentResource.getById(id).then(function (resp) { + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + $scope.model.target.url = resp.urls[0]; }); } - - // if a link exists, get the properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.model.target.url = resp.urls[0]; - }); } else if ($scope.model.target.url.length) { // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs // only do the substring if there's a # or a ? @@ -72,6 +92,11 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.anchorValues = dialogOptions.anchors; } + function treeLoadedHandler(args) { + oneTimeTreeSync.treeReady = true; + oneTimeTreeSync.sync(); + } + function nodeSelectHandler(args) { if (args && args.event) { args.event.preventDefault(); @@ -118,7 +143,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], startNodeIsVirtual: userData.startMediaIds.length !== 1, submit: function (model) { - var media = model.selectedImages[0]; + var media = model.selection[0]; $scope.model.target.id = media.id; $scope.model.target.udi = media.udi; @@ -127,6 +152,12 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.model.target.url = mediaHelper.resolveFile(media); editorService.close(); + + // make sure the content tree has nothing highlighted + $scope.dialogTreeApi.syncTree({ + path: "-1", + tree: "content" + }); }, close: function() { editorService.close(); @@ -146,7 +177,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", // method to select a search result $scope.selectResult = function (evt, result) { result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { + nodeSelectHandler({ event: evt, node: result }); @@ -159,6 +190,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", }; $scope.onTreeInit = function () { + $scope.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler); $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler); } @@ -166,7 +198,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", // Mini list view $scope.selectListViewNode = function (node) { node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { + nodeSelectHandler({ node: node }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html index a50ab4242d..71fcf2f493 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html @@ -120,6 +120,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> i; i++) { - var imageInSelection = $scope.model.selectedImages[i]; + for (var i = 0; $scope.model.selection.length > i; i++) { + var imageInSelection = $scope.model.selection[i]; if (image.key === imageInSelection.key) { image.selected = false; - $scope.model.selectedImages.splice(i, 1); + $scope.model.selection.splice(i, 1); } } } else { if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); + deselectAllImages($scope.model.selection); } image.selected = true; - $scope.model.selectedImages.push(image); + $scope.model.selection.push(image); } } @@ -238,7 +238,7 @@ angular.module("umbraco") $scope.onUploadComplete = function(files) { $scope.gotoFolder($scope.currentFolder).then(function() { - if (files.length === 1 && $scope.model.selectedImages.length === 0) { + if (files.length === 1 && $scope.model.selection.length === 0) { var image = $scope.images[$scope.images.length - 1]; $scope.target = image; $scope.target.url = mediaHelper.resolveFile(image); @@ -275,7 +275,7 @@ angular.module("umbraco") $scope.mediaPickerDetailsOverlay.show = true; $scope.mediaPickerDetailsOverlay.submit = function(model) { - $scope.model.selectedImages.push($scope.target); + $scope.model.selection.push($scope.target); $scope.model.submit($scope.model); $scope.mediaPickerDetailsOverlay.show = false; @@ -384,11 +384,11 @@ angular.module("umbraco") var folderImage = $scope.images[folderImageIndex]; var imageIsSelected = false; - if ($scope.model && angular.isArray($scope.model.selectedImages)) { + if ($scope.model && angular.isArray($scope.model.selection)) { for (var selectedImageIndex = 0; - selectedImageIndex < $scope.model.selectedImages.length; + selectedImageIndex < $scope.model.selection.length; selectedImageIndex++) { - var selectedImage = $scope.model.selectedImages[selectedImageIndex]; + var selectedImage = $scope.model.selection[selectedImageIndex]; if (folderImage.key === selectedImage.key) { imageIsSelected = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html index 3bb694cac6..b5dd23b1fd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html @@ -55,12 +55,12 @@
  • Media - / + /
  • {{item.name}} - / + /
  • @@ -171,6 +171,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/membergrouppicker/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/membergrouppicker/membergrouppicker.html index 13af3e4108..7240ec8f06 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/membergrouppicker/membergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/membergrouppicker/membergrouppicker.html @@ -32,6 +32,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index df5bbe8ca5..4c7f2613b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -155,6 +155,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html index 4b6c6cc179..725871337d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html @@ -193,6 +193,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> @@ -209,4 +210,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index b5b925b266..d7ba57c1af 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -86,6 +86,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.html index 8ca1993dcc..2e88bf709c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.html @@ -38,6 +38,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html index d6e3996287..5b946976d7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html @@ -87,6 +87,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html index e97d80648b..e2ae1ab524 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html @@ -84,6 +84,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.html index bc6c8b5761..e39d693b47 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.html @@ -74,6 +74,7 @@ type="button" button-style="link" label-key="general_close" + shortcut="esc" action="vm.close()"> - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html index 2e7048eefa..92da12c423 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html @@ -5,7 +5,7 @@
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html index 267d805d0a..3754b66c53 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -45,6 +45,7 @@ alias="$parent.alias" alias-from="$parent.name" enable-lock="true" + validation-position="'right'" server-validation-field="Alias"> diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html index 032e4cd6c3..54a33a705f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html @@ -9,7 +9,7 @@ - + {{action.name}} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html index ee46a89490..e278a8c401 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html @@ -12,6 +12,7 @@ ng-class="{'is-active': item.active, '-has-error': item.hasError}"> {{ item.name }} +
    {{item.badge.count}}
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html index 2e09fbfa3b..4004c2b958 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html @@ -5,16 +5,16 @@ -