diff --git a/build/BuildBelle.bat b/build/BuildBelle.bat index 0d58204f65..6c11cc9fc5 100644 --- a/build/BuildBelle.bat +++ b/build/BuildBelle.bat @@ -1,4 +1,6 @@ @ECHO OFF +SETLOCAL + SET release=%1 ECHO Installing Npm NuGet Package @@ -11,12 +13,9 @@ ECHO Current folder: %CD% for /f "delims=" %%A in ('dir %nuGetFolder%node.js.* /b') do set "nodePath=%nuGetFolder%%%A\" for /f "delims=" %%A in ('dir %nuGetFolder%npm.js.* /b') do set "npmPath=%nuGetFolder%%%A\tools\" -ECHO Temporarily adding Npm and Node to path -SET oldPath=%PATH% - -path=%npmPath%;%nodePath%;%PATH% - -ECHO %path% +ECHO Adding Npm and Node to path +REM SETLOCAL is on, so changes to the path not persist to the actual user's path +PATH=%npmPath%;%nodePath%;%PATH% SET buildFolder=%CD% @@ -29,8 +28,5 @@ call npm install -g grunt-cli --quiet call npm install -g bower --quiet call grunt build --buildversion=%release% -ECHO Reset path to what it was before -path=%oldPath% - ECHO Move back to the build folder CD %buildFolder% \ No newline at end of file diff --git a/build/InstallGit.cmd b/build/InstallGit.cmd index 2bd6d7cc35..b6ba71df9b 100644 --- a/build/InstallGit.cmd +++ b/build/InstallGit.cmd @@ -1,5 +1,6 @@ @ECHO OFF -SET oldPath=%PATH% +SETLOCAL +REM SETLOCAL is on, so changes to the path not persist to the actual user's path git.exe 2> NUL if %ERRORLEVEL%==9009 GOTO :trydefaultpath @@ -12,8 +13,7 @@ if %ERRORLEVEL%==9009 GOTO :showerror GOTO :EOF :showerror -path=%oldPath% -ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd +ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd nor in C:\Program Files\Git\cmd set /p install=" Do you want to install Git through Chocolatey [y/n]? " %=% if %install%==y ( GOTO :installgit @@ -29,5 +29,4 @@ GOTO :EOF ECHO Installing Chocolatey first @powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin ECHO Installing Git through Chocolatey -choco install git -path=C:\Program Files (x86)\Git\cmd;%path% +choco install git \ No newline at end of file diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 6852b04510..148e65d369 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -33,8 +33,8 @@ - - + + diff --git a/build/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt index f5a807b4bf..580c619547 100644 --- a/build/NuSpecs/tools/trees.config.install.xdt +++ b/build/NuSpecs/tools/trees.config.install.xdt @@ -95,19 +95,19 @@ xdt:Transform="SetAttributes()" /> - - - diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 28b314750a..3d330cc173 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -2,7 +2,7 @@ using System.Resources; [assembly: AssemblyCompany("Umbraco")] -[assembly: AssemblyCopyright("Copyright © Umbraco 2015")] +[assembly: AssemblyCopyright("Copyright © Umbraco 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index a05abe2a50..e47ef04650 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -406,7 +406,8 @@ namespace Umbraco.Core //clear the cache if (ApplicationCache != null) { - ApplicationCache.ClearAllCache(); + ApplicationCache.RuntimeCache.ClearAllCache(); + ApplicationCache.IsolatedRuntimeCache.ClearAllCaches(); } //reset all resolvers ResolverCollection.ResetAll(); diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index 88d570beff..0c1a202b66 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -1,8 +1,9 @@ using System; +using System.ComponentModel; +using Umbraco.Core.CodeAnnotations; namespace Umbraco.Core.Cache { - /// /// Constants storing cache keys used in caching /// @@ -12,52 +13,78 @@ namespace Umbraco.Core.Cache public const string ApplicationsCacheKey = "ApplicationCache"; [Obsolete("This is no longer used and will be removed from the codebase in the future")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string UserTypeCacheKey = "UserTypeCache"; + [Obsolete("This is no longer used and will be removed from the codebase in the future - it is referenced but no cache is stored against this key")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string ContentItemCacheKey = "contentItem"; + [UmbracoWillObsolete("This cache key is only used for the legacy 'library' caching, remove in v8")] public const string MediaCacheKey = "UL_GetMedia"; public const string MacroXsltCacheKey = "macroXslt_"; + + [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] public const string MacroCacheKey = "UmbracoMacroCache"; + public const string MacroHtmlCacheKey = "macroHtml_"; public const string MacroControlCacheKey = "macroControl_"; public const string MacroHtmlDateAddedCacheKey = "macroHtml_DateAdded_"; public const string MacroControlDateAddedCacheKey = "macroControl_DateAdded_"; + [UmbracoWillObsolete("This cache key is only used for legacy 'library' member caching, remove in v8")] public const string MemberLibraryCacheKey = "UL_GetMember"; + + [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] public const string MemberBusinessLogicCacheKey = "MemberCacheItem_"; - + + [UmbracoWillObsolete("This cache key is only used for legacy template business logic caching, remove in v8")] public const string TemplateFrontEndCacheKey = "template"; [Obsolete("This is no longer used and will be removed from the codebase in the future")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string TemplateBusinessLogicCacheKey = "UmbracoTemplateCache"; + [Obsolete("This is no longer used and will be removed from the codebase in the future")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string UserContextCacheKey = "UmbracoUserContext"; + public const string UserContextTimeoutCacheKey = "UmbracoUserContextTimeout"; [Obsolete("This is no longer used and will be removed from the codebase in the future")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string UserCacheKey = "UmbracoUser"; public const string UserPermissionsCacheKey = "UmbracoUserPermissions"; + [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] public const string ContentTypeCacheKey = "UmbracoContentType"; + [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:"; + [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] public const string PropertyTypeCacheKey = "UmbracoPropertyTypeCache"; [Obsolete("This is no longer used and will be removed from the codebase in the future")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string LanguageCacheKey = "UmbracoLanguageCache"; [Obsolete("This is no longer used and will be removed from the codebase in the future")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string DomainCacheKey = "UmbracoDomainList"; [Obsolete("This is no longer used and will be removed from the codebase in the future")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string StylesheetCacheKey = "UmbracoStylesheet"; + [Obsolete("This is no longer used and will be removed from the codebase in the future")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string StylesheetPropertyCacheKey = "UmbracoStylesheetProperty"; + [Obsolete("This is no longer used and will be removed from the codebase in the future")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string DataTypeCacheKey = "UmbracoDataTypeDefinition"; public const string DataTypePreValuesCacheKey = "UmbracoPreVal"; diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs index 2931805b08..60ad69b6fc 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs @@ -2,6 +2,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Sync; using umbraco.interfaces; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Cache { @@ -63,5 +64,15 @@ namespace Umbraco.Core.Cache { OnCacheUpdated(Instance, new CacheRefresherEventArgs(id, MessageType.RefreshById)); } + + /// + /// Clears the cache for all repository entities of this type + /// + /// + internal void ClearAllIsolatedCacheByEntityType() + where TEntity : class, IAggregateRoot + { + ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.ClearCache(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs similarity index 98% rename from src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs rename to src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs index 0b5b42660d..861c6b803e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs @@ -2,11 +2,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Web.Caching; -using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; -namespace Umbraco.Core.Persistence.Repositories +namespace Umbraco.Core.Cache { /// /// A wrapper for any IRuntimeCacheProvider that ensures that all inserts and returns diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs new file mode 100644 index 0000000000..fda57cefee --- /dev/null +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Cache +{ + /// + /// The default cache policy for retrieving a single entity + /// + /// + /// + internal class DefaultRepositoryCachePolicy : DisposableObject, IRepositoryCachePolicy + where TEntity : class, IAggregateRoot + { + private readonly RepositoryCachePolicyOptions _options; + protected IRuntimeCacheProvider Cache { get; private set; } + private Action _action; + + public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) + { + _options = options; + Cache = cache; + } + + public string GetCacheIdKey(object id) + { + return string.Format("{0}{1}", GetCacheTypeKey(), id); + } + + public string GetCacheTypeKey() + { + return string.Format("uRepo_{0}_", typeof(TEntity).Name); + } + + public void CreateOrUpdate(TEntity entity, Action persistMethod) + { + var cacheKey = GetCacheIdKey(entity.Id); + + try + { + persistMethod(entity); + + //set the disposal action + SetCacheAction(() => + { + Cache.InsertCacheItem(cacheKey, () => entity); + //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared + Cache.ClearCacheItem(GetCacheTypeKey()); + }); + + } + catch + { + //set the disposal action + SetCacheAction(() => + { + //if an exception is thrown we need to remove the entry from cache, this is ONLY a work around because of the way + // that we cache entities: http://issues.umbraco.org/issue/U4-4259 + Cache.ClearCacheItem(cacheKey); + + //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared + Cache.ClearCacheItem(GetCacheTypeKey()); + }); + + throw; + } + } + + public void Remove(TEntity entity, Action persistMethod) + { + persistMethod(entity); + + //set the disposal action + var cacheKey = GetCacheIdKey(entity.Id); + SetCacheAction(() => + { + Cache.ClearCacheItem(cacheKey); + //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared + Cache.ClearCacheItem(GetCacheTypeKey()); + }); + } + + public TEntity Get(TId id, Func getFromRepo) + { + var cacheKey = GetCacheIdKey(id); + var fromCache = Cache.GetCacheItem(cacheKey); + if (fromCache != null) + return fromCache; + + var entity = getFromRepo(id); + + //set the disposal action + SetCacheAction(cacheKey, entity); + + return entity; + } + + public TEntity Get(TId id) + { + var cacheKey = GetCacheIdKey(id); + return Cache.GetCacheItem(cacheKey); + } + + public bool Exists(TId id, Func getFromRepo) + { + var cacheKey = GetCacheIdKey(id); + var fromCache = Cache.GetCacheItem(cacheKey); + return fromCache != null || getFromRepo(id); + } + + public virtual TEntity[] GetAll(TId[] ids, Func> getFromRepo) + { + if (ids.Any()) + { + var entities = ids.Select(Get).ToArray(); + if (ids.Length.Equals(entities.Length) && entities.Any(x => x == null) == false) + return entities; + } + else + { + var allEntities = GetAllFromCache(); + if (allEntities.Any()) + { + if (_options.GetAllCacheValidateCount) + { + //Get count of all entities of current type (TEntity) to ensure cached result is correct + var totalCount = _options.PerformCount(); + if (allEntities.Length == totalCount) + return allEntities; + } + else + { + return allEntities; + } + } + else if (_options.GetAllCacheAllowZeroCount) + { + //if the repository allows caching a zero count, then check the zero count cache + var zeroCount = Cache.GetCacheItem(GetCacheTypeKey()); + if (zeroCount != null && zeroCount.Any() == false) + { + //there is a zero count cache so return an empty list + return new TEntity[] {}; + } + } + } + + //we need to do the lookup from the repo + var entityCollection = getFromRepo(ids) + //ensure we don't include any null refs in the returned collection! + .WhereNotNull() + .ToArray(); + + //set the disposal action + SetCacheAction(ids, entityCollection); + + return entityCollection; + } + + /// + /// Performs the lookup for all entities of this type from the cache + /// + /// + protected virtual TEntity[] GetAllFromCache() + { + var allEntities = Cache.GetCacheItemsByKeySearch(GetCacheTypeKey()) + .WhereNotNull() + .ToArray(); + return allEntities.Any() ? allEntities : new TEntity[] {}; + } + + /// + /// The disposal performs the caching + /// + protected override void DisposeResources() + { + if (_action != null) + { + _action(); + } + } + + /// + /// Sets the action to execute on disposal for a single entity + /// + /// + /// + protected virtual void SetCacheAction(string cacheKey, TEntity entity) + { + SetCacheAction(() => Cache.InsertCacheItem(cacheKey, () => entity)); + } + + /// + /// Sets the action to execute on disposal for an entity collection + /// + /// + /// + protected virtual void SetCacheAction(TId[] ids, TEntity[] entityCollection) + { + SetCacheAction(() => + { + //This option cannot execute if we are looking up specific Ids + if (ids.Any() == false && entityCollection.Length == 0 && _options.GetAllCacheAllowZeroCount) + { + //there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache + // to signify that there is a zero count cache + Cache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {}); + } + else + { + //This is the default behavior, we'll individually cache each item so that if/when these items are resolved + // by id, they are returned from the already existing cache. + foreach (var entity in entityCollection.WhereNotNull()) + { + var localCopy = entity; + Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy); + } + } + }); + } + + /// + /// Sets the action to execute on disposal + /// + /// + protected void SetCacheAction(Action action) + { + _action = action; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs new file mode 100644 index 0000000000..5c02e41a48 --- /dev/null +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs @@ -0,0 +1,27 @@ +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Cache +{ + /// + /// Creates cache policies + /// + /// + /// + internal class DefaultRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory + where TEntity : class, IAggregateRoot + { + private readonly IRuntimeCacheProvider _runtimeCache; + private readonly RepositoryCachePolicyOptions _options; + + public DefaultRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, RepositoryCachePolicyOptions options) + { + _runtimeCache = runtimeCache; + _options = options; + } + + public virtual IRepositoryCachePolicy CreatePolicy() + { + return new DefaultRepositoryCachePolicy(_runtimeCache, _options); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs new file mode 100644 index 0000000000..c4c86b2ec7 --- /dev/null +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -0,0 +1,57 @@ +using System.Linq; +using Umbraco.Core.Collections; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Cache +{ + /// + /// A caching policy that caches an entire dataset as a single collection + /// + /// + /// + internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy + where TEntity : class, IAggregateRoot + { + public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, new RepositoryCachePolicyOptions()) + { + } + + /// + /// For this type of caching policy, we don't cache individual items + /// + /// + /// + protected override void SetCacheAction(string cacheKey, TEntity entity) + { + //do nothing + } + + /// + /// Sets the action to execute on disposal for an entity collection + /// + /// + /// + protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection) + { + //for this type of caching policy, we don't want to cache any GetAll request containing specific Ids + if (ids.Any()) return; + + //set the disposal action + SetCacheAction(() => + { + //We want to cache the result as a single collection + Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection)); + }); + } + + /// + /// This policy will cache the full data set as a single collection + /// + /// + protected override TEntity[] GetAllFromCache() + { + var found = Cache.GetCacheItem>(GetCacheTypeKey()); + return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs new file mode 100644 index 0000000000..470db33b6a --- /dev/null +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs @@ -0,0 +1,25 @@ +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Cache +{ + /// + /// Creates cache policies + /// + /// + /// + internal class FullDataSetRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory + where TEntity : class, IAggregateRoot + { + private readonly IRuntimeCacheProvider _runtimeCache; + + public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache) + { + _runtimeCache = runtimeCache; + } + + public virtual IRepositoryCachePolicy CreatePolicy() + { + return new FullDataSetRepositoryCachePolicy(_runtimeCache); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs new file mode 100644 index 0000000000..97844933b7 --- /dev/null +++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Cache +{ + internal interface IRepositoryCachePolicy : IDisposable + where TEntity : class, IAggregateRoot + { + TEntity Get(TId id, Func getFromRepo); + TEntity Get(TId id); + bool Exists(TId id, Func getFromRepo); + + string GetCacheIdKey(object id); + string GetCacheTypeKey(); + void CreateOrUpdate(TEntity entity, Action persistMethod); + void Remove(TEntity entity, Action persistMethod); + TEntity[] GetAll(TId[] ids, Func> getFromRepo); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs new file mode 100644 index 0000000000..2d69704b63 --- /dev/null +++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Cache +{ + internal interface IRepositoryCachePolicyFactory where TEntity : class, IAggregateRoot + { + IRepositoryCachePolicy CreatePolicy(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs new file mode 100644 index 0000000000..103f90345d --- /dev/null +++ b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs @@ -0,0 +1,91 @@ +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 + { + private readonly Func _cacheFactory; + + /// + /// 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(); + } + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs new file mode 100644 index 0000000000..b24838bc3b --- /dev/null +++ b/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs @@ -0,0 +1,27 @@ +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Cache +{ + /// + /// Creates cache policies + /// + /// + /// + internal class OnlySingleItemsRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory + where TEntity : class, IAggregateRoot + { + private readonly IRuntimeCacheProvider _runtimeCache; + private readonly RepositoryCachePolicyOptions _options; + + public OnlySingleItemsRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, RepositoryCachePolicyOptions options) + { + _runtimeCache = runtimeCache; + _options = options; + } + + public virtual IRepositoryCachePolicy CreatePolicy() + { + return new SingleItemsOnlyRepositoryCachePolicy(_runtimeCache, _options); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs similarity index 57% rename from src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs rename to src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs index 9ac8aa6abd..e8c6ac02b0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs @@ -1,17 +1,34 @@ -namespace Umbraco.Core.Persistence.Repositories +using System; + +namespace Umbraco.Core.Cache { - internal class RepositoryCacheOptions + internal class RepositoryCachePolicyOptions { /// - /// Constructor sets defaults + /// Ctor - sets GetAllCacheValidateCount = true /// - public RepositoryCacheOptions() + public RepositoryCachePolicyOptions(Func performCount) { + PerformCount = performCount; GetAllCacheValidateCount = true; GetAllCacheAllowZeroCount = false; - GetAllCacheThresholdLimit = 100; } + /// + /// Ctor - sets GetAllCacheValidateCount = false + /// + public RepositoryCachePolicyOptions() + { + PerformCount = null; + GetAllCacheValidateCount = false; + GetAllCacheAllowZeroCount = false; + } + + /// + /// Callback required to get count for GetAllCacheValidateCount + /// + public Func PerformCount { get; private set; } + /// /// True/false as to validate the total item count when all items are returned from cache, the default is true but this /// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the normal @@ -21,16 +38,11 @@ namespace Umbraco.Core.Persistence.Repositories /// setting this to return false will improve performance of GetAll cache with no params but should only be used /// for specific circumstances /// - public bool GetAllCacheValidateCount { get; set; } + public bool GetAllCacheValidateCount { get; private set; } /// /// True if the GetAll method will cache that there are zero results so that the db is not hit when there are no results found /// public bool GetAllCacheAllowZeroCount { get; set; } - - /// - /// The threshold entity count for which the GetAll method will cache entities - /// - public int GetAllCacheThresholdLimit { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs new file mode 100644 index 0000000000..9566cd6e7f --- /dev/null +++ b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs @@ -0,0 +1,24 @@ +using System.Linq; +using Umbraco.Core.Collections; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Cache +{ + /// + /// A caching policy that ignores all caches for GetAll - it will only cache calls for individual items + /// + /// + /// + internal class SingleItemsOnlyRepositoryCachePolicy : DefaultRepositoryCachePolicy + where TEntity : class, IAggregateRoot + { + public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) : base(cache, options) + { + } + + protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection) + { + //do nothing + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs index 51cf37aa23..303cf234fd 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/CacheHelper.cs @@ -1,6 +1,8 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -11,20 +13,19 @@ using Umbraco.Core.Logging; namespace Umbraco.Core { - /// /// Class that is exposed by the ApplicationContext for application wide caching purposes /// public class CacheHelper { - private readonly bool _enableCache; + private readonly IsolatedRuntimeCache _isolatedCacheManager; private readonly ICacheProvider _requestCache; - private readonly ICacheProvider _nullRequestCache = new NullCacheProvider(); + private static readonly ICacheProvider NullRequestCache = new NullCacheProvider(); private readonly ICacheProvider _staticCache; - private readonly ICacheProvider _nullStaticCache = new NullCacheProvider(); - private readonly IRuntimeCacheProvider _httpCache; - private readonly IRuntimeCacheProvider _nullHttpCache = new NullCacheProvider(); - + private static readonly ICacheProvider NullStaticCache = new NullCacheProvider(); + private readonly IRuntimeCacheProvider _runtimeCache; + private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider(); + /// /// Creates a cache helper with disabled caches /// @@ -34,7 +35,7 @@ namespace Umbraco.Core /// public static CacheHelper CreateDisabledCacheHelper() { - return new CacheHelper(null, null, null, false); + return new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, new IsolatedRuntimeCache(t => NullRuntimeCache)); } /// @@ -44,7 +45,8 @@ namespace Umbraco.Core : this( new HttpRuntimeCacheProvider(HttpRuntime.Cache), new StaticCacheProvider(), - new HttpRequestCacheProvider()) + new HttpRequestCacheProvider(), + new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) { } @@ -56,51 +58,42 @@ namespace Umbraco.Core : this( new HttpRuntimeCacheProvider(cache), new StaticCacheProvider(), - new HttpRequestCacheProvider()) + new HttpRequestCacheProvider(), + new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) { } - /// - /// Initializes a new instance based on the provided providers - /// - /// - /// - /// + [Obsolete("Use the constructor the specifies all dependencies")] + [EditorBrowsable(EditorBrowsableState.Never)] public CacheHelper( IRuntimeCacheProvider httpCacheProvider, ICacheProvider staticCacheProvider, ICacheProvider requestCacheProvider) - : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, true) + : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) { } - /// - /// Private ctor used for creating a disabled cache helper - /// - /// - /// - /// - /// - private CacheHelper( + /// + /// Initializes a new instance based on the provided providers + /// + /// + /// + /// + /// + public CacheHelper( IRuntimeCacheProvider httpCacheProvider, ICacheProvider staticCacheProvider, - ICacheProvider requestCacheProvider, - bool enableCache) + ICacheProvider requestCacheProvider, + IsolatedRuntimeCache isolatedCacheManager) { - if (enableCache) - { - _httpCache = httpCacheProvider; - _staticCache = staticCacheProvider; - _requestCache = requestCacheProvider; - } - else - { - _httpCache = null; - _staticCache = null; - _requestCache = null; - } - - _enableCache = enableCache; + 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; + _isolatedCacheManager = isolatedCacheManager; } /// @@ -108,15 +101,15 @@ namespace Umbraco.Core /// public ICacheProvider RequestCache { - get { return _enableCache ? _requestCache : _nullRequestCache; } + get { return _requestCache; } } - + /// /// Returns the current Runtime cache /// public ICacheProvider StaticCache { - get { return _enableCache ? _staticCache : _nullStaticCache; } + get { return _staticCache; } } /// @@ -124,8 +117,16 @@ namespace Umbraco.Core /// public IRuntimeCacheProvider RuntimeCache { - get { return _enableCache ? _httpCache : _nullHttpCache; } + get { return _runtimeCache; } } + + /// + /// Returns the current Isolated Runtime cache manager + /// + public IsolatedRuntimeCache IsolatedRuntimeCache + { + get { return _isolatedCacheManager; } + } #region Legacy Runtime/Http Cache accessors @@ -133,16 +134,11 @@ namespace Umbraco.Core /// Clears the item in umbraco's runtime cache /// [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public void ClearAllCache() { - if (_enableCache == false) - { - _nullHttpCache.ClearAllCache(); - } - else - { - _httpCache.ClearAllCache(); - } + _runtimeCache.ClearAllCache(); + _isolatedCacheManager.ClearAllCaches(); } /// @@ -150,16 +146,10 @@ namespace Umbraco.Core /// /// Key [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheItem(string key) { - if (_enableCache == false) - { - _nullHttpCache.ClearCacheItem(key); - } - else - { - _httpCache.ClearCacheItem(key); - } + _runtimeCache.ClearCacheItem(key); } @@ -171,30 +161,17 @@ namespace Umbraco.Core [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public void ClearCacheObjectTypes(string typeName) { - if (_enableCache == false) - { - _nullHttpCache.ClearCacheObjectTypes(typeName); - } - else - { - _httpCache.ClearCacheObjectTypes(typeName); - } + _runtimeCache.ClearCacheObjectTypes(typeName); } /// /// Clears all objects in the System.Web.Cache with the System.Type specified /// [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheObjectTypes() { - if (_enableCache == false) - { - _nullHttpCache.ClearCacheObjectTypes(); - } - else - { - _httpCache.ClearCacheObjectTypes(); - } + _runtimeCache.ClearCacheObjectTypes(); } /// @@ -202,16 +179,10 @@ namespace Umbraco.Core /// /// The start of the key [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheByKeySearch(string keyStartsWith) { - if (_enableCache == false) - { - _nullHttpCache.ClearCacheByKeySearch(keyStartsWith); - } - else - { - _httpCache.ClearCacheByKeySearch(keyStartsWith); - } + _runtimeCache.ClearCacheByKeySearch(keyStartsWith); } /// @@ -219,29 +190,17 @@ namespace Umbraco.Core /// /// [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheByKeyExpression(string regexString) { - if (_enableCache == false) - { - _nullHttpCache.ClearCacheByKeyExpression(regexString); - } - else - { - _httpCache.ClearCacheByKeyExpression(regexString); - } + _runtimeCache.ClearCacheByKeyExpression(regexString); } [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { - if (_enableCache == false) - { - return _nullHttpCache.GetCacheItemsByKeySearch(keyStartsWith); - } - else - { - return _httpCache.GetCacheItemsByKeySearch(keyStartsWith); - } + return _runtimeCache.GetCacheItemsByKeySearch(keyStartsWith); } /// @@ -251,16 +210,10 @@ namespace Umbraco.Core /// /// [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey) { - if (_enableCache == false) - { - return _nullHttpCache.GetCacheItem(cacheKey); - } - else - { - return _httpCache.GetCacheItem(cacheKey); - } + return _runtimeCache.GetCacheItem(cacheKey); } /// @@ -271,16 +224,11 @@ namespace Umbraco.Core /// /// [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey, Func getCacheItem) { - if (_enableCache == false) - { - return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem); - } - else - { - return _httpCache.GetCacheItem(cacheKey, getCacheItem); - } + return _runtimeCache.GetCacheItem(cacheKey, getCacheItem); + } /// @@ -292,17 +240,12 @@ namespace Umbraco.Core /// /// [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey, TimeSpan timeout, Func getCacheItem) { - if (_enableCache == false) - { - return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout); - } - else - { - return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout); - } + return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); + } /// @@ -315,18 +258,13 @@ namespace Umbraco.Core /// /// [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan timeout, Func getCacheItem) { - if (!_enableCache) - { - return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); - } - else - { - return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); - } + return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); + } /// @@ -340,18 +278,13 @@ namespace Umbraco.Core /// /// [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, Func getCacheItem) { - if (_enableCache == false) - { - return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); - } - else - { - return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); - } + return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); + } /// @@ -373,20 +306,13 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - if (_enableCache == false) + var cache = _runtimeCache as HttpRuntimeCacheProvider; + if (cache != null) { - return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction, null); - } - else - { - var cache = _httpCache as HttpRuntimeCacheProvider; - if (cache != null) - { - var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); - return result == null ? default(TT) : result.TryConvertTo().Result; - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); + return result == null ? default(TT) : result.TryConvertTo().Result; } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); } /// @@ -404,20 +330,13 @@ namespace Umbraco.Core CacheDependency cacheDependency, Func getCacheItem) { - if (!_enableCache) + var cache = _runtimeCache as HttpRuntimeCacheProvider; + if (cache != null) { - return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, null, false, priority, null, null); - } - else - { - var cache = _httpCache as HttpRuntimeCacheProvider; - if (cache != null) - { - var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency); - return result == null ? default(TT) : result.TryConvertTo().Result; - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency); + return result == null ? default(TT) : result.TryConvertTo().Result; } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); } /// @@ -427,18 +346,14 @@ namespace Umbraco.Core /// /// /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem) { - if (_enableCache == false) - { - _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); - } - else - { - _httpCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); - } + _runtimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); + } /// @@ -449,19 +364,14 @@ namespace Umbraco.Core /// /// This will set an absolute expiration from now until the timeout /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] public void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan timeout, Func getCacheItem) { - if (_enableCache == false) - { - _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); - } - else - { - _httpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); - } + _runtimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); } /// @@ -480,19 +390,12 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - if (_enableCache == false) + var cache = _runtimeCache as HttpRuntimeCacheProvider; + if (cache != null) { - _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority, dependentFiles:null); - } - else - { - var cache = _httpCache as HttpRuntimeCacheProvider; - if (cache != null) - { - cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency); - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency); } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); } /// @@ -513,19 +416,12 @@ namespace Umbraco.Core TimeSpan? timeout, Func getCacheItem) { - if (_enableCache == false) + var cache = _runtimeCache as HttpRuntimeCacheProvider; + if (cache != null) { - _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction, null); - } - else - { - var cache = _httpCache as HttpRuntimeCacheProvider; - if (cache != null) - { - cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); } #endregion diff --git a/src/Umbraco.Core/Collections/DeepCloneableList.cs b/src/Umbraco.Core/Collections/DeepCloneableList.cs new file mode 100644 index 0000000000..365bf53b06 --- /dev/null +++ b/src/Umbraco.Core/Collections/DeepCloneableList.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Collections +{ + /// + /// A List that can be deep cloned with deep cloned elements and can reset the collection's items dirty flags + /// + /// + internal class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty + { + /// + /// Initializes a new instance of the class that is empty and has the default initial capacity. + /// + public DeepCloneableList() + { + } + + /// + /// Initializes a new instance of the class that contains elements copied from the specified collection and has sufficient capacity to accommodate the number of elements copied. + /// + /// The collection whose elements are copied to the new list. is null. + public DeepCloneableList(IEnumerable collection) : base(collection) + { + } + + /// + /// Creates a new list and adds each element as a deep cloned element if it is of type IDeepCloneable + /// + /// + public object DeepClone() + { + var newList = new DeepCloneableList(); + foreach (var item in this) + { + var dc = item as IDeepCloneable; + if (dc != null) + { + newList.Add((T) dc.DeepClone()); + } + else + { + newList.Add(item); + } + } + return newList; + } + + public bool IsDirty() + { + return this.OfType().Any(x => x.IsDirty()); + } + + public bool WasDirty() + { + return this.OfType().Any(x => x.WasDirty()); + } + + /// + /// Always returns false, the list has no properties we need to report + /// + /// + /// + public bool IsPropertyDirty(string propName) + { + return false; + } + + /// + /// Always returns false, the list has no properties we need to report + /// + /// + /// + public bool WasPropertyDirty(string propertyName) + { + return false; + } + + public void ResetDirtyProperties() + { + foreach (var dc in this.OfType()) + { + dc.ResetDirtyProperties(); + } + } + + public void ForgetPreviouslyDirtyProperties() + { + foreach (var dc in this.OfType()) + { + dc.ForgetPreviouslyDirtyProperties(); + } + } + + public void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + { + foreach (var dc in this.OfType()) + { + dc.ResetDirtyProperties(rememberPreviouslyChangedProperties); + } + } + } +} diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 49829513bb..d2dea09698 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -188,10 +188,16 @@ namespace Umbraco.Core protected virtual CacheHelper CreateApplicationCache() { var cacheHelper = new CacheHelper( - new ObjectCacheRuntimeCacheProvider(), + //we need to have the dep clone runtime cache provider to ensure + //all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), new StaticCacheProvider(), //we have no request based cache when not running in web-based context - new NullCacheProvider()); + new NullCacheProvider(), + new IsolatedRuntimeCache(type => + //we need to have the dep clone runtime cache provider to ensure + //all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); return cacheHelper; } diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs index 452c05adb8..1be504ea45 100644 --- a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs +++ b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs @@ -75,7 +75,7 @@ namespace Umbraco.Core.Media.Exif { if (items.ContainsKey (key)) items.Remove (key); - if (key == ExifTag.WindowsTitle || key == ExifTag.WindowsTitle || key == ExifTag.WindowsComment || key == ExifTag.WindowsAuthor || key == ExifTag.WindowsKeywords || key == ExifTag.WindowsSubject) { + if (key == ExifTag.WindowsTitle || key == ExifTag.WindowsComment || key == ExifTag.WindowsAuthor || key == ExifTag.WindowsKeywords || key == ExifTag.WindowsSubject) { items.Add (key, new WindowsByteString (key, value)); } else { items.Add (key, new ExifAscii (key, value, parent.Encoding)); diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 367e897c35..749c629d19 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -14,6 +14,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class DictionaryItem : Entity, IDictionaryItem { + public Func GetLanguage { get; set; } private Guid? _parentId; private string _itemKey; private IEnumerable _translations; @@ -78,7 +79,17 @@ namespace Umbraco.Core.Models { SetPropertyValueAndDetectChanges(o => { - _translations = value; + var asArray = value.ToArray(); + //ensure the language callback is set on each translation + if (GetLanguage != null) + { + foreach (var translation in asArray.OfType()) + { + translation.GetLanguage = GetLanguage; + } + } + + _translations = asArray; return _translations; }, _translations, TranslationsSelector, //Custom comparer for enumerable diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index 782ff14413..59f96dbe85 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -13,13 +13,18 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class DictionaryTranslation : Entity, IDictionaryTranslation { + internal Func GetLanguage { get; set; } + private ILanguage _language; private string _value; + //note: this will be memberwise cloned + private int _languageId; public DictionaryTranslation(ILanguage language, string value) { if (language == null) throw new ArgumentNullException("language"); _language = language; + _languageId = _language.Id; _value = value; } @@ -27,6 +32,20 @@ namespace Umbraco.Core.Models { if (language == null) throw new ArgumentNullException("language"); _language = language; + _languageId = _language.Id; + _value = value; + Key = uniqueId; + } + + internal DictionaryTranslation(int languageId, string value) + { + _languageId = languageId; + _value = value; + } + + internal DictionaryTranslation(int languageId, string value, Guid uniqueId) + { + _languageId = languageId; _value = value; Key = uniqueId; } @@ -37,20 +56,43 @@ namespace Umbraco.Core.Models /// /// Gets or sets the for the translation /// + /// + /// Marked as DoNotClone - TODO: this member shouldn't really exist here in the first place, the DictionaryItem + /// class will have a deep hierarchy of objects which all get deep cloned which we don't want. This should have simply + /// just referenced a language ID not the actual language object. In v8 we need to fix this. + /// We're going to have to do the same hacky stuff we had to do with the Template/File contents so that this is returned + /// on a callback. + /// [DataMember] + [DoNotClone] public ILanguage Language { - get { return _language; } + get + { + if (_language != null) + return _language; + + // else, must lazy-load + if (GetLanguage != null && _languageId > 0) + _language = GetLanguage(_languageId); + return _language; + } set { SetPropertyValueAndDetectChanges(o => { _language = value; + _languageId = _language == null ? -1 : _language.Id; return _language; }, _language, LanguageSelector); } } + public int LanguageId + { + get { return _languageId; } + } + /// /// Gets or sets the translated text /// @@ -68,5 +110,23 @@ namespace Umbraco.Core.Models } } + public override object DeepClone() + { + var clone = (DictionaryTranslation)base.DeepClone(); + + // clear fields that were memberwise-cloned and that we don't want to clone + clone._language = null; + + // turn off change tracking + clone.DisableChangeTracking(); + + // this shouldn't really be needed since we're not tracking + clone.ResetDirtyProperties(false); + + // re-enable tracking + clone.EnableChangeTracking(); + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IDictionaryTranslation.cs b/src/Umbraco.Core/Models/IDictionaryTranslation.cs index cf813bf72f..25aa1e4395 100644 --- a/src/Umbraco.Core/Models/IDictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/IDictionaryTranslation.cs @@ -12,6 +12,8 @@ namespace Umbraco.Core.Models [DataMember] ILanguage Language { get; set; } + int LanguageId { get; } + /// /// Gets or sets the translated text /// diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 90d173b49a..a8a1e80eb7 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -1,15 +1,10 @@ using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; -using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; -using System.Security; using System.Xml; namespace Umbraco.Core @@ -51,7 +46,7 @@ namespace Umbraco.Core public static Attempt TryConvertTo(this object input) { var result = TryConvertTo(input, typeof(T)); - if (!result.Success) + if (result.Success == false) { //just try a straight up conversion try @@ -64,7 +59,7 @@ namespace Umbraco.Core return Attempt.Fail(e); } } - return !result.Success ? Attempt.Fail() : Attempt.Succeed((T)result.Result); + return result.Success == false ? Attempt.Fail() : Attempt.Succeed((T)result.Result); } /// @@ -117,7 +112,7 @@ namespace Umbraco.Core } // we've already dealed with nullables, so any other generic types need to fall through - if (!destinationType.IsGenericType) + if (destinationType.IsGenericType == false) { if (input is string) { @@ -335,11 +330,11 @@ namespace Umbraco.Core return null; // we can't decide... } - private readonly static char[] NumberDecimalSeparatorsToNormalize = new[] {'.', ','}; + private static readonly char[] NumberDecimalSeparatorsToNormalize = new[] {'.', ','}; private static string NormalizeNumberDecimalSeparator(string s) { - var normalized = System.Threading.Thread.CurrentThread.CurrentUICulture.NumberFormat.NumberDecimalSeparator[0]; + var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0]; return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized); } @@ -442,7 +437,7 @@ namespace Umbraco.Core { var props = TypeDescriptor.GetProperties(o); var d = new Dictionary(); - foreach (var prop in props.Cast().Where(x => !ignoreProperties.Contains(x.Name))) + foreach (var prop in props.Cast().Where(x => ignoreProperties.Contains(x.Name) == false)) { var val = prop.GetValue(o); if (val != null) @@ -478,13 +473,13 @@ namespace Umbraco.Core var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList(); - return items.Count() > 0 + return items.Any() ? "{{ {0} }}".InvariantFormat(String.Join(", ", items)) : null; } var props = obj.GetType().GetProperties(); - if ((props.Count() == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2) + if ((props.Length == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2) { try { @@ -500,12 +495,12 @@ namespace Umbraco.Core if (levels > -1) { var items = - from propertyInfo in props + (from propertyInfo in props let value = GetPropertyDebugString(propertyInfo, obj, levels) where value != null - select "{0}={1}".InvariantFormat(propertyInfo.Name, value); + select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray(); - return items.Count() > 0 + return items.Any() ? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, String.Join(", ", items)) : null; } diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs index 14dca6b366..1b9d73bdd4 100644 --- a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs @@ -42,7 +42,7 @@ namespace Umbraco.Core.Persistence.Factories { var text = new LanguageTextDto { - LanguageId = translation.Language.Id, + LanguageId = translation.LanguageId, UniqueId = translation.Key, Value = translation.Value }; diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs index 8dca2494d0..65297b9529 100644 --- a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs @@ -7,20 +7,19 @@ namespace Umbraco.Core.Persistence.Factories internal class DictionaryTranslationFactory { private readonly Guid _uniqueId; - private ILanguage _language; - public DictionaryTranslationFactory(Guid uniqueId, ILanguage language) + public DictionaryTranslationFactory(Guid uniqueId) { _uniqueId = uniqueId; - _language = language; } #region Implementation of IEntityFactory public IDictionaryTranslation BuildEntity(LanguageTextDto dto) { - var item = new DictionaryTranslation(_language, dto.Value, _uniqueId) + var item = new DictionaryTranslation(dto.LanguageId, dto.Value, _uniqueId) {Id = dto.PrimaryKey}; + //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 item.ResetDirtyProperties(false); @@ -31,7 +30,7 @@ namespace Umbraco.Core.Persistence.Factories { var text = new LanguageTextDto { - LanguageId = entity.Language.Id, + LanguageId = entity.LanguageId, UniqueId = _uniqueId, Value = entity.Value }; diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 499e2ba849..398308c832 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -26,17 +26,15 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class DataTypeDefinitionRepository : PetaPocoRepositoryBase, IDataTypeDefinitionRepository { - private readonly CacheHelper _cacheHelper; private readonly IContentTypeRepository _contentTypeRepository; private readonly DataTypePreValueRepository _preValRepository; - public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cache, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax, + public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentTypeRepository contentTypeRepository) : base(work, cache, logger, sqlSyntax) { - _cacheHelper = cacheHelper; _contentTypeRepository = contentTypeRepository; - _preValRepository = new DataTypePreValueRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + _preValRepository = new DataTypePreValueRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); } #region Overrides of RepositoryBase @@ -239,7 +237,7 @@ AND umbracoNode.id <> @id", //NOTE: This is a special case, we need to clear the custom cache for pre-values here so they are not stale if devs // are querying for them in the Saved event (before the distributed call cache is clearing it) - _cacheHelper.RuntimeCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id)); + RuntimeCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id)); entity.ResetDirtyProperties(); } @@ -278,7 +276,7 @@ AND umbracoNode.id <> @id", public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId) { - var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); + var cached = RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); if (cached != null && cached.Any()) { //return from the cache, ensure it's a cloned result @@ -297,7 +295,7 @@ AND umbracoNode.id <> @id", { //We need to see if we can find the cached PreValueCollection based on the cache key above - var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId)); + var cached = RuntimeCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId)); if (cached != null && cached.Any()) { //return from the cache @@ -457,7 +455,7 @@ AND umbracoNode.id <> @id", + string.Join(",", collection.FormatAsDictionary().Select(x => x.Value.Id).ToArray()); //store into cache - _cacheHelper.RuntimeCache.InsertCacheItem(key, () => collection, + RuntimeCache.InsertCacheItem(key, () => collection, //30 mins new TimeSpan(0, 0, 30), //sliding is true diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs index 1985875717..971efc4f2d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -19,30 +20,40 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class DictionaryRepository : PetaPocoRepositoryBase, IDictionaryRepository { - private readonly ILanguageRepository _languageRepository; - - public DictionaryRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider syntax, ILanguageRepository languageRepository) + public DictionaryRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider syntax) : base(work, cache, logger, syntax) - { - _languageRepository = languageRepository; - } + { + } + + private IRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //custom cache policy which will not cache any results for GetAll + return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory( + RuntimeCache, + new RepositoryCachePolicyOptions + { + //allow zero to be cached + GetAllCacheAllowZeroCount = true + })); + } + } #region Overrides of RepositoryBase protected override IDictionaryItem PerformGet(int id) { var sql = GetBaseQuery(false) - .Where(GetBaseWhereClause(), new {Id = id}) + .Where(GetBaseWhereClause(), new { Id = id }) .OrderBy(x => x.UniqueId, SqlSyntax); var dto = Database.Fetch(new DictionaryLanguageTextRelator().Map, sql).FirstOrDefault(); if (dto == null) return null; - - //This will be cached - var allLanguages = _languageRepository.GetAll().ToArray(); - - var entity = ConvertFromDto(dto, allLanguages); + + var entity = ConvertFromDto(dto); //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -56,14 +67,11 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false).Where("cmsDictionary.pk > 0"); if (ids.Any()) { - sql.Where("cmsDictionary.pk in (@ids)", new { ids = ids }); + sql.Where("cmsDictionary.pk in (@ids)", new { ids = ids }); } - //This will be cached - var allLanguages = _languageRepository.GetAll().ToArray(); - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(dto => ConvertFromDto(dto, allLanguages)); + .Select(dto => ConvertFromDto(dto)); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -72,12 +80,9 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); sql.OrderBy(x => x.UniqueId, SqlSyntax); - - //This will be cached - var allLanguages = _languageRepository.GetAll().ToArray(); - + return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(x => ConvertFromDto(x, allLanguages)); + .Select(x => ConvertFromDto(x)); } #endregion @@ -87,7 +92,7 @@ namespace Umbraco.Core.Persistence.Repositories protected override Sql GetBaseQuery(bool isCount) { var sql = new Sql(); - if(isCount) + if (isCount) { sql.Select("COUNT(*)") .From(SqlSyntax); @@ -123,26 +128,28 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistNewItem(IDictionaryItem entity) { - ((DictionaryItem)entity).AddingEntity(); + var dictionaryItem = ((DictionaryItem) entity); - foreach (var translation in entity.Translations) + dictionaryItem.AddingEntity(); + + foreach (var translation in dictionaryItem.Translations) translation.Value = translation.Value.ToValidXmlString(); var factory = new DictionaryItemFactory(); - var dto = factory.BuildDto(entity); + var dto = factory.BuildDto(dictionaryItem); var id = Convert.ToInt32(Database.Insert(dto)); - entity.Id = id; + dictionaryItem.Id = id; - var translationFactory = new DictionaryTranslationFactory(entity.Key, null); - foreach (var translation in entity.Translations) + var translationFactory = new DictionaryTranslationFactory(dictionaryItem.Key); + foreach (var translation in dictionaryItem.Translations) { var textDto = translationFactory.BuildDto(translation); translation.Id = Convert.ToInt32(Database.Insert(textDto)); - translation.Key = entity.Key; + translation.Key = dictionaryItem.Key; } - entity.ResetDirtyProperties(); + dictionaryItem.ResetDirtyProperties(); } protected override void PersistUpdatedItem(IDictionaryItem entity) @@ -157,11 +164,11 @@ namespace Umbraco.Core.Persistence.Repositories Database.Update(dto); - var translationFactory = new DictionaryTranslationFactory(entity.Key, null); + var translationFactory = new DictionaryTranslationFactory(entity.Key); foreach (var translation in entity.Translations) { var textDto = translationFactory.BuildDto(translation); - if(translation.HasIdentity) + if (translation.HasIdentity) { Database.Update(textDto); } @@ -183,7 +190,7 @@ namespace Umbraco.Core.Persistence.Repositories { RecursiveDelete(entity.Key); - Database.Delete("WHERE UniqueId = @Id", new { Id = entity.Key}); + Database.Delete("WHERE UniqueId = @Id", new { Id = entity.Key }); Database.Delete("WHERE id = @Id", new { Id = entity.Key }); //Clear the cache entries that exist by uniqueid/item key @@ -193,7 +200,7 @@ namespace Umbraco.Core.Persistence.Repositories private void RecursiveDelete(Guid parentId) { - var list = Database.Fetch("WHERE parent = @ParentId", new {ParentId = parentId}); + var list = Database.Fetch("WHERE parent = @ParentId", new { ParentId = parentId }); foreach (var dto in list) { RecursiveDelete(dto.UniqueId); @@ -209,20 +216,18 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - protected IDictionaryItem ConvertFromDto(DictionaryDto dto, ILanguage[] allLanguages) + protected IDictionaryItem ConvertFromDto(DictionaryDto dto) { var factory = new DictionaryItemFactory(); var entity = factory.BuildEntity(dto); var list = new List(); foreach (var textDto in dto.LanguageTextDtos) - { - //Assuming this is cached! - var language = allLanguages.FirstOrDefault(x => x.Id == textDto.LanguageId); - if (language == null) + { + if (textDto.LanguageId <= 0) continue; - var translationFactory = new DictionaryTranslationFactory(dto.UniqueId, language); + var translationFactory = new DictionaryTranslationFactory(dto.UniqueId); list.Add(translationFactory.BuildEntity(textDto)); } entity.Translations = list; @@ -234,7 +239,7 @@ namespace Umbraco.Core.Persistence.Repositories { using (var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) { - return uniqueIdRepo.Get(uniqueId); + return uniqueIdRepo.Get(uniqueId); } } @@ -242,10 +247,10 @@ namespace Umbraco.Core.Persistence.Repositories { using (var keyRepo = new DictionaryByKeyRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) { - return keyRepo.Get(key); + return keyRepo.Get(key); } } - + private IEnumerable GetRootDictionaryItems() { var query = Query.Builder.Where(x => x.ParentId == null); @@ -254,9 +259,6 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetDictionaryItemDescendants(Guid? parentId) { - //This will be cached - var allLanguages = _languageRepository.GetAll().ToArray(); - //This methods will look up children at each level, since we do not store a path for dictionary (ATM), we need to do a recursive // lookup to get descendants. Currently this is the most efficient way to do it @@ -275,7 +277,7 @@ namespace Umbraco.Core.Persistence.Repositories sql.OrderBy(x => x.UniqueId, SqlSyntax); return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(x => ConvertFromDto(x, allLanguages)); + .Select(x => ConvertFromDto(x)); }); }; @@ -284,7 +286,7 @@ namespace Umbraco.Core.Persistence.Repositories : getItemsFromParents(new[] { parentId.Value }); return childItems.SelectRecursive(items => getItemsFromParents(items.Select(x => x.Key).ToArray())).SelectMany(items => items); - + } private class DictionaryByUniqueIdRepository : SimpleGetRepository @@ -314,20 +316,34 @@ namespace Umbraco.Core.Persistence.Repositories protected override IDictionaryItem ConvertToEntity(DictionaryDto dto) { - //This will be cached - var allLanguages = _dictionaryRepository._languageRepository.GetAll().ToArray(); - return _dictionaryRepository.ConvertFromDto(dto, allLanguages); + return _dictionaryRepository.ConvertFromDto(dto); } protected override object GetBaseWhereClauseArguments(Guid id) { - return new {Id = id}; + return new { Id = id }; } protected override string GetWhereInClauseForGetAll() { return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " in (@ids)"; } + + private IRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //custom cache policy which will not cache any results for GetAll + return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory( + RuntimeCache, + new RepositoryCachePolicyOptions + { + //allow zero to be cached + GetAllCacheAllowZeroCount = true + })); + } + } } private class DictionaryByKeyRepository : SimpleGetRepository @@ -357,9 +373,7 @@ namespace Umbraco.Core.Persistence.Repositories protected override IDictionaryItem ConvertToEntity(DictionaryDto dto) { - //This will be cached - var allLanguages = _dictionaryRepository._languageRepository.GetAll().ToArray(); - return _dictionaryRepository.ConvertFromDto(dto, allLanguages); + return _dictionaryRepository.ConvertFromDto(dto); } protected override object GetBaseWhereClauseArguments(string id) @@ -371,17 +385,24 @@ namespace Umbraco.Core.Persistence.Repositories { return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " in (@ids)"; } + + private IRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //custom cache policy which will not cache any results for GetAll + return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory( + RuntimeCache, + new RepositoryCachePolicyOptions + { + //allow zero to be cached + GetAllCacheAllowZeroCount = true + })); + } + } } - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - _languageRepository.Dispose(); - } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs index 21ba8b4baf..6abab73dd7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs @@ -18,25 +18,19 @@ namespace Umbraco.Core.Persistence.Repositories internal class DomainRepository : PetaPocoRepositoryBase, IDomainRepository { - private readonly RepositoryCacheOptions _cacheOptions; - public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - //Custom cache options for better performance - _cacheOptions = new RepositoryCacheOptions - { - GetAllCacheAllowZeroCount = true, - GetAllCacheValidateCount = false - }; + { } - /// - /// Returns the repository cache options - /// - protected override RepositoryCacheOptions RepositoryCacheOptions + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory { - get { return _cacheOptions; } + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } } protected override IDomain PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index fcdd2bd3d2..fd693df182 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -47,6 +47,12 @@ namespace Umbraco.Core.Persistence.Repositories return nodeDto == null ? null : CreateEntity(nodeDto); } + public IEnumerable Get(string name, int level, Guid umbracoObjectTypeId) + { + var sql = GetBaseQuery(false).Where("text=@name AND level=@level AND nodeObjectType=@umbracoObjectTypeId", new { name, level, umbracoObjectTypeId }); + return Database.Fetch(sql).Select(CreateEntity); + } + protected override IEnumerable PerformGetAll(params int[] ids) { throw new NotImplementedException(); diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs index 147e0b40f8..48b0cf66b7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using Umbraco.Core.Models; @@ -23,6 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// [Obsolete("Use GetDescendants instead")] + [EditorBrowsable(EditorBrowsableState.Never)] TemplateNode GetTemplateNode(string alias); /// @@ -32,6 +34,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// [Obsolete("Use GetDescendants instead")] + [EditorBrowsable(EditorBrowsableState.Never)] TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias); /// diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index 2f379b4587..6fc9bd5ebc 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -20,23 +21,17 @@ namespace Umbraco.Core.Persistence.Repositories { public LanguageRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - //Custom cache options for better performance - _cacheOptions = new RepositoryCacheOptions - { - GetAllCacheAllowZeroCount = true, - GetAllCacheValidateCount = false - }; + { } - private readonly RepositoryCacheOptions _cacheOptions; - - /// - /// Returns the repository cache options - /// - protected override RepositoryCacheOptions RepositoryCacheOptions + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory { - get { return _cacheOptions; } + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } } #region Overrides of RepositoryBase @@ -159,13 +154,13 @@ namespace Umbraco.Core.Persistence.Repositories public ILanguage GetByCultureName(string cultureName) { - //use the underlying GetAll which will force cache all domains + //use the underlying GetAll which will force cache all languages return GetAll().FirstOrDefault(x => x.CultureName.InvariantEquals(cultureName)); } public ILanguage GetByIsoCode(string isoCode) { - //use the underlying GetAll which will force cache all domains + //use the underlying GetAll which will force cache all languages return GetAll().FirstOrDefault(x => x.IsoCode.InvariantEquals(isoCode)); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 264409ddf4..40121223a2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -20,13 +20,9 @@ namespace Umbraco.Core.Persistence.Repositories internal class MemberGroupRepository : PetaPocoRepositoryBase, IMemberGroupRepository { - private readonly CacheHelper _cacheHelper; - - public MemberGroupRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, CacheHelper cacheHelper) + public MemberGroupRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { - if (cacheHelper == null) throw new ArgumentNullException("cacheHelper"); - _cacheHelper = cacheHelper; } private readonly MemberGroupFactory _modelFactory = new MemberGroupFactory(); @@ -135,7 +131,7 @@ namespace Umbraco.Core.Persistence.Repositories public IMemberGroup GetByName(string name) { - return _cacheHelper.RuntimeCache.GetCacheItem( + return RuntimeCache.GetCacheItem( string.Format("{0}.{1}", typeof (IMemberGroup).FullName, name), () => { diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index 0463312982..aa671dccec 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -26,13 +26,14 @@ namespace Umbraco.Core.Persistence.Repositories where TEntity : class, IAggregateRoot { private readonly IDatabaseUnitOfWork _unitOfWork; - private readonly CacheHelper _cache; + private readonly IRuntimeCacheProvider _runtimeCache; private readonly ISqlSyntaxProvider _sqlSyntax; internal PermissionRepository(IDatabaseUnitOfWork unitOfWork, CacheHelper cache, ISqlSyntaxProvider sqlSyntax) { _unitOfWork = unitOfWork; - _cache = cache; + //Make this repository use an isolated cache + _runtimeCache = cache.IsolatedRuntimeCache.GetOrCreateCache(); _sqlSyntax = sqlSyntax; } @@ -45,7 +46,7 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetUserPermissionsForEntities(int userId, params int[] entityIds) { var entityIdKey = string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture))); - return _cache.RuntimeCache.GetCacheItem>( + return _runtimeCache.GetCacheItem>( string.Format("{0}{1}{2}", CacheKeys.UserPermissionsCacheKey, userId, entityIdKey), () => { diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index 295ff8b408..1086b9cee0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -17,33 +17,23 @@ namespace Umbraco.Core.Persistence.Repositories { public PublicAccessRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - _options = new RepositoryCacheOptions - { - //We want to ensure that a zero count gets cached, even if there is nothing in the db we don't want it to lookup nothing each time - GetAllCacheAllowZeroCount = true, - //Set to 1000 just to ensure that all of them are cached, The GetAll on this repository gets called *A lot*, we want max performance - GetAllCacheThresholdLimit = 1000, - //Override to false so that a Count check against the db is NOT performed when doing a GetAll without params, we just want to - // return the raw cache without validation. The GetAll on this repository gets called *A lot*, we want max performance - GetAllCacheValidateCount = false - }; + { } - private readonly RepositoryCacheOptions _options; + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } + } protected override PublicAccessEntry PerformGet(Guid id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - - var taskDto = Database.Fetch(new AccessRulesRelator().Map, sql).FirstOrDefault(); - if (taskDto == null) - return null; - - var factory = new PublicAccessEntryFactory(); - var entity = factory.BuildEntity(taskDto); - return entity; + //return from GetAll - this will be cached as a collection + return GetAll().FirstOrDefault(x => x.Key == id); } protected override IEnumerable PerformGetAll(params Guid[] ids) @@ -102,15 +92,6 @@ namespace Umbraco.Core.Persistence.Repositories get { throw new NotImplementedException(); } } - /// - /// Returns the repository cache options - /// - protected override RepositoryCacheOptions RepositoryCacheOptions - { - get { return _options; } - } - - protected override void PersistNewItem(PublicAccessEntry entity) { entity.AddingEntity(); diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 124aaeb035..f6bcc46d06 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -1,7 +1,9 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Cache; +using Umbraco.Core.Collections; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; @@ -22,11 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories if (logger == null) throw new ArgumentNullException("logger"); Logger = logger; _work = work; - - //IMPORTANT: We will force the DeepCloneRuntimeCacheProvider to be used here which is a wrapper for the underlying - // runtime cache to ensure that anything that can be deep cloned in/out is done so, this also ensures that our tracks - // changes entities are reset. - _cache = new CacheHelper(new DeepCloneRuntimeCacheProvider(cache.RuntimeCache), cache.StaticCache, cache.RequestCache); + _cache = cache; } /// @@ -84,9 +82,37 @@ namespace Umbraco.Core.Persistence.Repositories { } - private readonly RepositoryCacheOptions _cacheOptions = new RepositoryCacheOptions(); + - #region IRepository Members + /// + /// The runtime cache used for this repo by default is the isolated cache for this type + /// + protected override IRuntimeCacheProvider RuntimeCache + { + get { return RepositoryCache.IsolatedRuntimeCache.GetOrCreateCache(); } + } + + private IRepositoryCachePolicyFactory _cachePolicyFactory; + /// + /// Returns the Cache Policy for the repository + /// + /// + /// The Cache Policy determines how each entity or entity collection is cached + /// + protected virtual IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + return _cachePolicyFactory ?? (_cachePolicyFactory = new DefaultRepositoryCachePolicyFactory( + RuntimeCache, + new RepositoryCachePolicyOptions(() => + { + //Get count of all entities of current type (TEntity) to ensure cached result is correct + var query = Query.Builder.Where(x => x.Id != 0); + return PerformCount(query); + }))); + } + } /// /// Adds or Updates an entity of type TEntity @@ -119,23 +145,16 @@ namespace Umbraco.Core.Persistence.Repositories protected abstract TEntity PerformGet(TId id); /// - /// Gets an entity by the passed in Id utilizing the repository's runtime cache + /// Gets an entity by the passed in Id utilizing the repository's cache policy /// /// /// public TEntity Get(TId id) { - var cacheKey = GetCacheIdKey(id); - var fromCache = RuntimeCache.GetCacheItem(cacheKey); - - if (fromCache != null) return fromCache; - - var entity = PerformGet(id); - if (entity == null) return null; - - RuntimeCache.InsertCacheItem(cacheKey, () => entity); - - return entity; + using (var p = CachePolicyFactory.CreatePolicy()) + { + return p.Get(id, PerformGet); + } } protected abstract IEnumerable PerformGetAll(params TId[] ids); @@ -158,88 +177,12 @@ namespace Umbraco.Core.Persistence.Repositories throw new InvalidOperationException("Cannot perform a query with more than 2000 parameters"); } - if (ids.Any()) + using (var p = CachePolicyFactory.CreatePolicy()) { - var entities = ids.Select(x => RuntimeCache.GetCacheItem(GetCacheIdKey(x))).ToArray(); - - if (ids.Count().Equals(entities.Count()) && entities.Any(x => x == null) == false) - return entities; - } - else - { - var allEntities = RuntimeCache.GetCacheItemsByKeySearch(GetCacheTypeKey()) - .WhereNotNull() - .ToArray(); - - if (allEntities.Any()) - { - - if (RepositoryCacheOptions.GetAllCacheValidateCount) - { - //Get count of all entities of current type (TEntity) to ensure cached result is correct - var query = Query.Builder.Where(x => x.Id != 0); - int totalCount = PerformCount(query); - - if (allEntities.Count() == totalCount) - return allEntities; - } - else - { - return allEntities; - } - } - else if (RepositoryCacheOptions.GetAllCacheAllowZeroCount) - { - //if the repository allows caching a zero count, then check the zero count cache - var zeroCount = RuntimeCache.GetCacheItem(GetCacheTypeKey()); - if (zeroCount != null && zeroCount.Any() == false) - { - //there is a zero count cache so return an empty list - return Enumerable.Empty(); - } - } - - } - - var entityCollection = PerformGetAll(ids) - //ensure we don't include any null refs in the returned collection! - .WhereNotNull() - .ToArray(); - - //We need to put a threshold here! IF there's an insane amount of items - // coming back here we don't want to chuck it all into memory, this added cache here - // is more for convenience when paging stuff temporarily - - if (entityCollection.Length > RepositoryCacheOptions.GetAllCacheThresholdLimit) - return entityCollection; - - if (entityCollection.Length == 0 && RepositoryCacheOptions.GetAllCacheAllowZeroCount) - { - //there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache - // to signify that there is a zero count cache - RuntimeCache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {}); - } - - foreach (var entity in entityCollection) - { - if (entity != null) - { - var localCopy = entity; - RuntimeCache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy); - } - } - - return entityCollection; + return p.GetAll(ids, PerformGetAll); + } } - - /// - /// Returns the repository cache options - /// - protected virtual RepositoryCacheOptions RepositoryCacheOptions - { - get { return _cacheOptions; } - } - + protected abstract IEnumerable PerformGetByQuery(IQuery query); /// /// Gets a list of entities by the passed in query @@ -261,12 +204,10 @@ namespace Umbraco.Core.Persistence.Repositories /// public bool Exists(TId id) { - var fromCache = RuntimeCache.GetCacheItem(GetCacheIdKey(id)); - if (fromCache != null) + using (var p = CachePolicyFactory.CreatePolicy()) { - return true; + return p.Exists(id, PerformExists); } - return PerformExists(id); } protected abstract int PerformCount(IQuery query); @@ -279,34 +220,19 @@ namespace Umbraco.Core.Persistence.Repositories { return PerformCount(query); } - - #endregion - - #region IUnitOfWorkRepository Members - + /// /// Unit of work method that tells the repository to persist the new entity /// /// public virtual void PersistNewItem(IEntity entity) { - try - { - PersistNewItem((TEntity)entity); - RuntimeCache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity); - //If there's a GetAll zero count cache, ensure it is cleared - RuntimeCache.ClearCacheItem(GetCacheTypeKey()); - } - catch (Exception ex) - { - //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 - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Id)); - //If there's a GetAll zero count cache, ensure it is cleared - RuntimeCache.ClearCacheItem(GetCacheTypeKey()); - throw; - } + var casted = (TEntity)entity; + using (var p = CachePolicyFactory.CreatePolicy()) + { + p.CreateOrUpdate(casted, PersistNewItem); + } } /// @@ -315,23 +241,12 @@ namespace Umbraco.Core.Persistence.Repositories /// public virtual void PersistUpdatedItem(IEntity entity) { - try - { - PersistUpdatedItem((TEntity)entity); - RuntimeCache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity); - //If there's a GetAll zero count cache, ensure it is cleared - RuntimeCache.ClearCacheItem(GetCacheTypeKey()); - } - catch (Exception) - { - //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 - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Id)); - //If there's a GetAll zero count cache, ensure it is cleared - RuntimeCache.ClearCacheItem(GetCacheTypeKey()); - throw; - } + var casted = (TEntity)entity; + using (var p = CachePolicyFactory.CreatePolicy()) + { + p.CreateOrUpdate(casted, PersistUpdatedItem); + } } /// @@ -340,21 +255,19 @@ namespace Umbraco.Core.Persistence.Repositories /// public virtual void PersistDeletedItem(IEntity entity) { - PersistDeletedItem((TEntity)entity); - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Id)); - //If there's a GetAll zero count cache, ensure it is cleared - RuntimeCache.ClearCacheItem(GetCacheTypeKey()); + var casted = (TEntity)entity; + + using (var p = CachePolicyFactory.CreatePolicy()) + { + p.Remove(casted, PersistDeletedItem); + } } - - #endregion - - #region Abstract IUnitOfWorkRepository Methods + protected abstract void PersistNewItem(TEntity item); protected abstract void PersistUpdatedItem(TEntity item); protected abstract void PersistDeletedItem(TEntity item); - #endregion /// /// Dispose disposable properties diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index da18a16598..7addacdd87 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -32,7 +33,6 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ITemplatesSection _templateConfig; private readonly ViewHelper _viewHelper; private readonly MasterPageHelper _masterPageHelper; - private readonly RepositoryCacheOptions _cacheOptions; internal TemplateRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IFileSystem masterpageFileSystem, IFileSystem viewFileSystem, ITemplatesSection templateConfig) : base(work, cache, logger, sqlSyntax) @@ -41,41 +41,26 @@ namespace Umbraco.Core.Persistence.Repositories _viewsFileSystem = viewFileSystem; _templateConfig = templateConfig; _viewHelper = new ViewHelper(_viewsFileSystem); - _masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); - - _cacheOptions = new RepositoryCacheOptions - { - //Allow a zero count cache entry because GetAll() gets used quite a lot and we want to ensure - // if there are no templates, that it doesn't keep going to the db. - GetAllCacheAllowZeroCount = true, - //GetAll gets called a lot, we want to ensure that all templates are in the cache, default is 100 which - // would normally be fine but we'll increase it in case people have a ton of templates. - GetAllCacheThresholdLimit = 500 - }; + _masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); } - /// - /// Returns the repository cache options - /// - protected override RepositoryCacheOptions RepositoryCacheOptions + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory { - get { return _cacheOptions; } + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } } #region Overrides of RepositoryBase protected override ITemplate PerformGet(int id) { - var sql = GetBaseQuery(false).Where(x => x.NodeId == id); - var result = Database.Fetch(sql).FirstOrDefault(); - if (result == null) return null; - - //look up the simple template definitions that have a master template assigned, this is used - // later to populate the template item's properties - var childIds = GetAxisDefinitions(result).ToArray(); - - return MapFromDto(result, childIds); + //use the underlying GetAll which will force cache all templates + return base.GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -496,126 +481,102 @@ namespace Umbraco.Core.Persistence.Repositories public ITemplate Get(string alias) { - var sql = GetBaseQuery(false).Where(x => x.Alias == alias); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - return MapFromDto(dto, GetAxisDefinitions(dto).ToArray()); + return GetAll(alias).FirstOrDefault(); } public IEnumerable GetAll(params string[] aliases) { - var sql = GetBaseQuery(false); + //We must call the base (normal) GetAll method + // which is cached. This is a specialized method and unfortunatley with the params[] it + // overlaps with the normal GetAll method. + if (aliases.Any() == false) return base.GetAll(); - if (aliases.Any()) - { - sql.Where("cmsTemplate.alias IN (@aliases)", new {aliases = aliases}); - } - - var dtos = Database.Fetch(sql).ToArray(); - if (dtos.Length == 0) return Enumerable.Empty(); - - var axisDefos = GetAxisDefinitions(dtos).ToArray(); - return dtos.Select(x => MapFromDto(x, axisDefos)); + //return from base.GetAll, this is all cached + return base.GetAll().Where(x => aliases.Contains(x.Alias)); } public IEnumerable GetChildren(int masterTemplateId) { - var sql = GetBaseQuery(false); - if (masterTemplateId <= 0) - { - sql.Where(x => x.ParentId <= 0); - } - else - { - sql.Where(x => x.ParentId == masterTemplateId); - } + //return from base.GetAll, this is all cached + var all = base.GetAll().ToArray(); - var dtos = Database.Fetch(sql).ToArray(); - if (dtos.Length == 0) return Enumerable.Empty(); + if (masterTemplateId <= 0) return all.Where(x => x.MasterTemplateAlias.IsNullOrWhiteSpace()); - var axisDefos = GetAxisDefinitions(dtos).ToArray(); - return dtos.Select(x => MapFromDto(x, axisDefos)); + var parent = all.FirstOrDefault(x => x.Id == masterTemplateId); + if (parent == null) return Enumerable.Empty(); + + var children = all.Where(x => x.MasterTemplateAlias == parent.Alias); + return children; } public IEnumerable GetChildren(string alias) { - var sql = GetBaseQuery(false); - if (alias.IsNullOrWhiteSpace()) - { - sql.Where(x => x.ParentId <= 0); - } - else - { - //unfortunately SQLCE doesn't support scalar subqueries in the where clause, otherwise we could have done this - // in a single query, now we have to lookup the path to acheive the same thing - var parent = Database.ExecuteScalar(new Sql().Select("nodeId").From(SqlSyntax).Where(dto => dto.Alias == alias)); - if (parent.HasValue == false) return Enumerable.Empty(); - - sql.Where(x => x.ParentId == parent.Value); - } - - var dtos = Database.Fetch(sql).ToArray(); - if (dtos.Length == 0) return Enumerable.Empty(); - - var axisDefos = GetAxisDefinitions(dtos).ToArray(); - return dtos.Select(x => MapFromDto(x, axisDefos)); + //return from base.GetAll, this is all cached + return base.GetAll().Where(x => alias.IsNullOrWhiteSpace() + ? x.MasterTemplateAlias.IsNullOrWhiteSpace() + : x.MasterTemplateAlias == alias); } public IEnumerable GetDescendants(int masterTemplateId) { - var sql = GetBaseQuery(false); + //return from base.GetAll, this is all cached + var all = base.GetAll().ToArray(); + var descendants = new List(); if (masterTemplateId > 0) { - //unfortunately SQLCE doesn't support scalar subqueries in the where clause, otherwise we could have done this - // in a single query, now we have to lookup the path to acheive the same thing - var path = Database.ExecuteScalar( - new Sql().Select(SqlSyntax.GetQuotedColumnName("path")) - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId) - .Where(dto => dto.NodeId == masterTemplateId)); - - if (path.IsNullOrWhiteSpace()) return Enumerable.Empty(); - - sql.Where(@"(umbracoNode." + SqlSyntax.GetQuotedColumnName("path") + @" LIKE @query)", new { query = path + ",%" }); + var parent = all.FirstOrDefault(x => x.Id == masterTemplateId); + if (parent == null) return Enumerable.Empty(); + //recursively add all children with a level + AddChildren(all, descendants, parent.Alias); + } + else + { + descendants.AddRange(all.Where(x => x.MasterTemplateAlias.IsNullOrWhiteSpace())); + foreach (var parent in descendants) + { + //recursively add all children with a level + AddChildren(all, descendants, parent.Alias); + } } - sql.OrderBy("umbracoNode." + SqlSyntax.GetQuotedColumnName("level")); - - var dtos = Database.Fetch(sql).ToArray(); - if (dtos.Length == 0) return Enumerable.Empty(); - - var axisDefos = GetAxisDefinitions(dtos).ToArray(); - return dtos.Select(x => MapFromDto(x, axisDefos)); - + //return the list - it will be naturally ordered by level + return descendants; } - + public IEnumerable GetDescendants(string alias) { - var sql = GetBaseQuery(false); + var all = base.GetAll().ToArray(); + var descendants = new List(); if (alias.IsNullOrWhiteSpace() == false) { - //unfortunately SQLCE doesn't support scalar subqueries in the where clause, otherwise we could have done this - // in a single query, now we have to lookup the path to acheive the same thing - var path = Database.ExecuteScalar( - "SELECT umbracoNode.path FROM cmsTemplate INNER JOIN umbracoNode ON cmsTemplate.nodeId = umbracoNode.id WHERE cmsTemplate.alias = @alias", new { alias = alias }); - - if (path.IsNullOrWhiteSpace()) return Enumerable.Empty(); - - sql.Where(@"(umbracoNode." + SqlSyntax.GetQuotedColumnName("path") + @" LIKE @query)", new {query = path + ",%" }); + var parent = all.FirstOrDefault(x => x.Alias == alias); + if (parent == null) return Enumerable.Empty(); + //recursively add all children + AddChildren(all, descendants, parent.Alias); } + else + { + descendants.AddRange(all.Where(x => x.MasterTemplateAlias.IsNullOrWhiteSpace())); + foreach (var parent in descendants) + { + //recursively add all children with a level + AddChildren(all, descendants, parent.Alias); + } + } + //return the list - it will be naturally ordered by level + return descendants; + } - sql.OrderBy("umbracoNode." + SqlSyntax.GetQuotedColumnName("level")); - - var dtos = Database.Fetch(sql).ToArray(); - if (dtos.Length == 0) return Enumerable.Empty(); - - var axisDefos = GetAxisDefinitions(dtos).ToArray(); - return dtos.Select(x => MapFromDto(x, axisDefos)); + private void AddChildren(ITemplate[] all, List descendants, string masterAlias) + { + var c = all.Where(x => x.MasterTemplateAlias == masterAlias).ToArray(); + descendants.AddRange(c); + if (c.Any() == false) return; + //recurse through all children + foreach (var child in c) + { + AddChildren(all, descendants, child.Alias); + } } /// @@ -627,7 +588,7 @@ namespace Umbraco.Core.Persistence.Repositories public TemplateNode GetTemplateNode(string alias) { //first get all template objects - var allTemplates = GetAll().ToArray(); + var allTemplates = base.GetAll().ToArray(); var selfTemplate = allTemplates.SingleOrDefault(x => x.Alias == alias); if (selfTemplate == null) @@ -650,6 +611,7 @@ namespace Umbraco.Core.Persistence.Repositories return FindTemplateInTree(topNode, alias); } + [Obsolete("Only used by obsolete code")] private static TemplateNode WalkTree(TemplateNode current, string alias) { //now walk the tree to find the node @@ -700,7 +662,7 @@ namespace Umbraco.Core.Persistence.Repositories { var engine = _templateConfig.DefaultRenderingEngine; var viewHelper = new ViewHelper(_viewsFileSystem); - if (!viewHelper.ViewExists(template)) + if (viewHelper.ViewExists(template) == false) { if (template.Content.IsNullOrWhiteSpace() == false && MasterPageHelper.IsMasterPageSyntax(template.Content)) { diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index faad2bd1fa..a37472b09a 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -1,5 +1,6 @@ using Umbraco.Core.Configuration; using System; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -18,6 +19,7 @@ namespace Umbraco.Core.Persistence private readonly ILogger _logger; private readonly ISqlSyntaxProvider _sqlSyntax; private readonly CacheHelper _cacheHelper; + private readonly CacheHelper _noCache; private readonly IUmbracoSettingsSection _settings; #region Ctors @@ -30,6 +32,29 @@ namespace Umbraco.Core.Persistence if (settings == null) throw new ArgumentNullException("settings"); _cacheHelper = cacheHelper; + + //IMPORTANT: We will force the DeepCloneRuntimeCacheProvider to be used here which is a wrapper for the underlying + // runtime cache to ensure that anything that can be deep cloned in/out is done so, this also ensures that our tracks + // changes entities are reset. + if ((_cacheHelper.RuntimeCache is DeepCloneRuntimeCacheProvider) == false) + { + var originalHelper = cacheHelper; + + _cacheHelper = new CacheHelper( + new DeepCloneRuntimeCacheProvider(originalHelper.RuntimeCache), + originalHelper.StaticCache, + originalHelper.RequestCache, + new IsolatedRuntimeCache(type => + { + var cache = originalHelper.IsolatedRuntimeCache.GetOrCreateCache(type); + return (cache is DeepCloneRuntimeCacheProvider) == false + //wrap the original if it's not DeepCloneRuntimeCacheProvider + ? new DeepCloneRuntimeCacheProvider(cache) + : cache; + })); + } + + _noCache = CacheHelper.CreateDisabledCacheHelper(); _logger = logger; _sqlSyntax = sqlSyntax; _settings = settings; @@ -53,6 +78,7 @@ namespace Umbraco.Core.Persistence { if (cacheHelper == null) throw new ArgumentNullException("cacheHelper"); _cacheHelper = cacheHelper; + _noCache = CacheHelper.CreateDisabledCacheHelper(); } [Obsolete("Use the ctor specifying all dependencies instead")] @@ -80,15 +106,15 @@ namespace Umbraco.Core.Persistence public virtual ITaskRepository CreateTaskRepository(IDatabaseUnitOfWork uow) { - return new TaskRepository(uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + return new TaskRepository(uow, + _noCache, //never cache _logger, _sqlSyntax); } public virtual IAuditRepository CreateAuditRepository(IDatabaseUnitOfWork uow) { return new AuditRepository(uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + _noCache, //never cache _logger, _sqlSyntax); } @@ -128,7 +154,6 @@ namespace Umbraco.Core.Persistence { return new DataTypeDefinitionRepository( uow, - _cacheHelper, _cacheHelper, _logger, _sqlSyntax, CreateContentTypeRepository(uow)); @@ -140,8 +165,7 @@ namespace Umbraco.Core.Persistence uow, _cacheHelper, _logger, - _sqlSyntax, - CreateLanguageRepository(uow)); + _sqlSyntax); } public virtual ILanguageRepository CreateLanguageRepository(IDatabaseUnitOfWork uow) @@ -175,7 +199,7 @@ namespace Umbraco.Core.Persistence { return new RelationRepository( uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + _noCache, //never cache _logger, _sqlSyntax, CreateRelationTypeRepository(uow)); } @@ -184,7 +208,7 @@ namespace Umbraco.Core.Persistence { return new RelationTypeRepository( uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + _noCache, //never cache _logger, _sqlSyntax); } @@ -222,7 +246,7 @@ namespace Umbraco.Core.Persistence { return new MigrationEntryRepository( uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + _noCache, //never cache _logger, _sqlSyntax); } @@ -283,8 +307,7 @@ namespace Umbraco.Core.Persistence { return new MemberGroupRepository(uow, _cacheHelper, - _logger, _sqlSyntax, - _cacheHelper); + _logger, _sqlSyntax); } public virtual IEntityRepository CreateEntityRepository(IDatabaseUnitOfWork uow) @@ -299,8 +322,8 @@ namespace Umbraco.Core.Persistence public ITaskTypeRepository CreateTaskTypeRepository(IDatabaseUnitOfWork uow) { - return new TaskTypeRepository(uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + return new TaskTypeRepository(uow, + _noCache, //never cache _logger, _sqlSyntax); } diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index e37f9d1a54..c6714e256a 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Data; +using System.Data.Common; using System.Linq; using System.Threading.Tasks; using System.Web.Security; @@ -97,6 +99,8 @@ namespace Umbraco.Core.Security } _userService.Save(member); + if (member.Id == 0) throw new DataException("Could not create the user, check logs for details"); + //re-assign id user.Id = member.Id; diff --git a/src/Umbraco.Core/Services/ApplicationTreeService.cs b/src/Umbraco.Core/Services/ApplicationTreeService.cs index f640fa8d22..c9ce839450 100644 --- a/src/Umbraco.Core/Services/ApplicationTreeService.cs +++ b/src/Umbraco.Core/Services/ApplicationTreeService.cs @@ -315,7 +315,7 @@ namespace Umbraco.Core.Services //remove the cache now that it has changed SD: I'm leaving this here even though it // is taken care of by events as well, I think unit tests may rely on it being cleared here. - _cache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); + _cache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); } } } diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index eff7202201..92a2d6516e 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using System.Xml.Linq; using System.Threading; +using AutoMapper; using Umbraco.Core.Auditing; using Umbraco.Core.Configuration; using Umbraco.Core.Events; @@ -141,16 +142,25 @@ namespace Umbraco.Core.Services } } + public IEnumerable GetMediaTypeContainers(string name, int level) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow)) + { + return repo.Get(name, level, Constants.ObjectTypes.MediaTypeContainerGuid); + } + } + public EntityContainer GetContentTypeContainer(Guid containerId) { return GetContainer(containerId, Constants.ObjectTypes.DocumentTypeGuid); } - + public EntityContainer GetMediaTypeContainer(Guid containerId) { return GetContainer(containerId, Constants.ObjectTypes.MediaTypeGuid); } - + private EntityContainer GetContainer(Guid containerId, Guid containedObjectType) { var uow = UowProvider.GetUnitOfWork(); @@ -163,6 +173,15 @@ namespace Umbraco.Core.Services } } + public IEnumerable GetContentTypeContainers(string name, int level) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow)) + { + return repo.Get(name, level, Constants.ObjectTypes.DocumentTypeContainerGuid); + } + } + public void DeleteContentTypeContainer(int containerId, int userId = 0) { var uow = UowProvider.GetUnitOfWork(); @@ -460,6 +479,22 @@ namespace Umbraco.Core.Services } + public int CountContentTypes() + { + using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + { + return repository.Count(Query.Builder); + } + } + + public int CountMediaTypes() + { + using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + { + return repository.Count(Query.Builder); + } + } + /// /// Validates the composition, if its invalid a list of property type aliases that were duplicated is returned /// diff --git a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs new file mode 100644 index 0000000000..d16e7946b9 --- /dev/null +++ b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public static class ContentTypeServiceExtensions + { + /// + /// Returns the available composite content types for a given content type + /// + /// + public static IEnumerable GetAvailableCompositeContentTypes(this IContentTypeService ctService, + IContentTypeComposition source, + IContentTypeComposition[] allContentTypes) + { + //below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic + + // note: there are many sanity checks missing here and there ;-(( + // make sure once and for all + //if (allContentTypes.Any(x => x.ParentId > 0 && x.ContentTypeComposition.Any(y => y.Id == x.ParentId) == false)) + // throw new Exception("A parent does not belong to a composition."); + + if (source != null) + { + // find out if any content type uses this content type + var isUsing = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == source.Id)).ToArray(); + if (isUsing.Length > 0) + { + //if already in use a composition, do not allow any composited types + return new List(); + } + } + + // if it is not used then composition is possible + // hashset guarantees unicity on Id + var list = new HashSet(new DelegateEqualityComparer( + (x, y) => x.Id == y.Id, + x => x.Id)); + + // usable types are those that are top-level + var usableContentTypes = allContentTypes + .Where(x => x.ContentTypeComposition.Any() == false).ToArray(); + foreach (var x in usableContentTypes) + list.Add(x); + + // indirect types are those that we use, directly or indirectly + var indirectContentTypes = GetDirectOrIndirect(source).ToArray(); + foreach (var x in indirectContentTypes) + list.Add(x); + + //// directContentTypes are those we use directly + //// they are already in indirectContentTypes, no need to add to the list + //var directContentTypes = source.ContentTypeComposition.ToArray(); + + //var enabled = usableContentTypes.Select(x => x.Id) // those we can use + // .Except(indirectContentTypes.Select(x => x.Id)) // except those that are indirectly used + // .Union(directContentTypes.Select(x => x.Id)) // but those that are directly used + // .Where(x => x != source.ParentId) // but not the parent + // .Distinct() + // .ToArray(); + + return list + .Where(x => x.Id != (source != null ? source.Id : 0)) + .OrderBy(x => x.Name) + .ToList(); + } + + /// + /// Get those that we use directly or indirectly + /// + /// + /// + private static IEnumerable GetDirectOrIndirect(IContentTypeComposition ctype) + { + // hashset guarantees unicity on Id + var all = new HashSet(new DelegateEqualityComparer( + (x, y) => x.Id == y.Id, + x => x.Id)); + + var stack = new Stack(); + + if (ctype != null) + { + foreach (var x in ctype.ContentTypeComposition) + stack.Push(x); + } + + while (stack.Count > 0) + { + var x = stack.Pop(); + all.Add(x); + foreach (var y in x.ContentTypeComposition) + stack.Push(y); + } + + return all; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index bd505ec283..4259a17035 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -78,6 +78,15 @@ namespace Umbraco.Core.Services } } + public IEnumerable GetContainers(string name, int level) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow)) + { + return repo.Get(name, level, Constants.ObjectTypes.DataTypeContainerGuid); + } + } + public void SaveContainer(EntityContainer container, int userId = 0) { if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index 712f98aeb2..193fa24577 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Net.Http.Formatting; using System.Xml; using System.Xml.Linq; using Umbraco.Core.Configuration; @@ -138,6 +140,18 @@ namespace Umbraco.Core.Services /// IDataTypeDefinition type to export /// containing the xml representation of the IDataTypeDefinition object public XElement Serialize(IDataTypeService dataTypeService, IDataTypeDefinition dataTypeDefinition) + { + return Serialize(dataTypeService, dataTypeDefinition, string.Empty); + } + + /// + /// Exports an item to xml as an + /// + /// + /// IDataTypeDefinition type to export + /// The path of folders for this data type separated by a backslash, for example: `SEO/Meta` + /// containing the xml representation of the IDataTypeDefinition object + public XElement Serialize(IDataTypeService dataTypeService, IDataTypeDefinition dataTypeDefinition, string folders) { var prevalues = new XElement("PreValues"); var prevalueList = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id) @@ -161,6 +175,8 @@ namespace Umbraco.Core.Services xml.Add(new XAttribute("Id", dataTypeDefinition.PropertyEditorAlias)); xml.Add(new XAttribute("Definition", dataTypeDefinition.Key)); xml.Add(new XAttribute("DatabaseType", dataTypeDefinition.DatabaseType.ToString())); + if(string.IsNullOrWhiteSpace(folders) == false) + xml.Add(new XAttribute("Folders", folders)); return xml; } @@ -324,6 +340,18 @@ namespace Umbraco.Core.Services /// Content type to export /// containing the xml representation of the IContentType object public XElement Serialize(IDataTypeService dataTypeService, IContentType contentType) + { + return Serialize(dataTypeService, contentType, string.Empty); + } + + /// + /// Exports an item to xml as an + /// + /// + /// Content type to export + /// The path of folders for this content type separated by a backslash, for example: `SEO/Meta` + /// containing the xml representation of the IContentType object + public XElement Serialize(IDataTypeService dataTypeService, IContentType contentType, string folders) { var info = new XElement("Info", new XElement("Name", contentType.Name), @@ -379,9 +407,11 @@ namespace Umbraco.Core.Services new XElement("Type", propertyType.PropertyEditorAlias), new XElement("Definition", definition.Key), new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name), + new XElement("SortOrder", propertyType.SortOrder), new XElement("Mandatory", propertyType.Mandatory.ToString()), - new XElement("Validation", propertyType.ValidationRegExp), - new XElement("Description", new XCData(propertyType.Description))); + propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null, + propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null); + genericProperties.Add(genericProperty); } @@ -395,11 +425,16 @@ namespace Umbraco.Core.Services tabs.Add(tab); } - return new XElement("DocumentType", - info, - structure, - genericProperties, - tabs); + var xml = new XElement("DocumentType", + info, + structure, + genericProperties, + tabs); + + if(string.IsNullOrWhiteSpace(folders) == false) + xml.Add(new XAttribute("Folders", folders)); + + return xml; } /// diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 5c2aaff930..37c078bd22 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.Remoting.Messaging; @@ -385,6 +386,8 @@ namespace Umbraco.Core.Services /// /// /// + [Obsolete("Use GetDescendants instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public TemplateNode GetTemplateNode(string alias) { using (var repository = _repositoryFactory.CreateTemplateRepository(_dataUowProvider.GetUnitOfWork())) @@ -399,6 +402,8 @@ namespace Umbraco.Core.Services /// /// /// + [Obsolete("Use GetDescendants instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias) { using (var repository = _repositoryFactory.CreateTemplateRepository(_dataUowProvider.GetUnitOfWork())) diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 9e59308a39..5fdadb6208 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -11,6 +11,9 @@ namespace Umbraco.Core.Services /// public interface IContentTypeService : IService { + int CountContentTypes(); + int CountMediaTypes(); + /// /// Validates the composition, if its invalid a list of property type aliases that were duplicated is returned /// @@ -24,8 +27,10 @@ namespace Umbraco.Core.Services void SaveMediaTypeContainer(EntityContainer container, int userId = 0); EntityContainer GetContentTypeContainer(int containerId); EntityContainer GetContentTypeContainer(Guid containerId); + IEnumerable GetContentTypeContainers(string folderName, int level); EntityContainer GetMediaTypeContainer(int containerId); EntityContainer GetMediaTypeContainer(Guid containerId); + IEnumerable GetMediaTypeContainers(string folderName, int level); void DeleteMediaTypeContainer(int folderId, int userId = 0); void DeleteContentTypeContainer(int containerId, int userId = 0); diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index ce9e4a3901..6a204667b4 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -14,6 +14,7 @@ namespace Umbraco.Core.Services void SaveContainer(EntityContainer container, int userId = 0); EntityContainer GetContainer(int containerId); EntityContainer GetContainer(Guid containerId); + IEnumerable GetContainers(string folderName, int level); void DeleteContainer(int containerId, int userId = 0); /// diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs index 6d6cf0c101..261db8672b 100644 --- a/src/Umbraco.Core/Services/LocalizationService.cs +++ b/src/Umbraco.Core/Services/LocalizationService.cs @@ -94,6 +94,9 @@ namespace Umbraco.Core.Services repository.AddOrUpdate(item); uow.Commit(); + //ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(item); + SavedDictionaryItem.RaiseEvent(new SaveEventArgs(item), this); return item; @@ -109,7 +112,10 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { - return repository.Get(id); + var item = repository.Get(id); + //ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(item); + return item; } } @@ -122,7 +128,10 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { - return repository.Get(id); + var item = repository.Get(id); + //ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(item); + return item; } } @@ -135,7 +144,10 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { - return repository.Get(key); + var item = repository.Get(key); + //ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(item); + return item; } } @@ -149,8 +161,9 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.ParentId == parentId); - var items = repository.GetByQuery(query); - + var items = repository.GetByQuery(query).ToArray(); + //ensure the lazy Language callback is assigned + items.ForEach(EnsureDictionaryItemLanguageCallback); return items; } } @@ -164,7 +177,10 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { - return repository.GetDictionaryItemDescendants(parentId); + var items = repository.GetDictionaryItemDescendants(parentId).ToArray(); + //ensure the lazy Language callback is assigned + items.ForEach(EnsureDictionaryItemLanguageCallback); + return items; } } @@ -177,8 +193,9 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.ParentId == null); - var items = repository.GetByQuery(query); - + var items = repository.GetByQuery(query).ToArray(); + //ensure the lazy Language callback is assigned + items.ForEach(EnsureDictionaryItemLanguageCallback); return items; } } @@ -211,6 +228,8 @@ namespace Umbraco.Core.Services { repository.AddOrUpdate(dictionaryItem); uow.Commit(); + //ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(dictionaryItem); } SavedDictionaryItem.RaiseEvent(new SaveEventArgs(dictionaryItem, false), this); @@ -265,10 +284,6 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateLanguageRepository(UowProvider.GetUnitOfWork())) { return repository.GetByCultureName(cultureName); - //var query = Query.Builder.Where(x => x.CultureName == cultureName); - //var items = repository.GetByQuery(query); - - //return items.FirstOrDefault(); } } @@ -282,10 +297,6 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateLanguageRepository(UowProvider.GetUnitOfWork())) { return repository.GetByIsoCode(isoCode); - //var query = Query.Builder.Where(x => x.IsoCode == isoCode); - //var items = repository.GetByQuery(query); - - //return items.FirstOrDefault(); } } @@ -357,6 +368,24 @@ namespace Umbraco.Core.Services } } + /// + /// This is here to take care of a hack - the DictionaryTranslation model contains an ILanguage reference which we don't want but + /// we cannot remove it because it would be a large breaking change, so we need to make sure it's resolved lazily. This is because + /// if developers have a lot of dictionary items and translations, the caching and cloning size gets much much larger because of + /// the large object graphs. So now we don't cache or clone the attached ILanguage + /// + private void EnsureDictionaryItemLanguageCallback(IDictionaryItem d) + { + var item = d as DictionaryItem; + if (item == null) return; + + item.GetLanguage = GetLanguageById; + foreach (var trans in item.Translations.OfType()) + { + trans.GetLanguage = GetLanguageById; + } + } + #region Event Handlers /// /// Occurs before Delete diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs index 292f8c585e..6d8215492b 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs @@ -2,10 +2,10 @@ using System.Globalization; using System.Linq; using System.Threading; +using Umbraco.Core.Dictionary; namespace Umbraco.Core.Services { - /// /// Extension methods for ILocalizedTextService /// @@ -61,5 +61,43 @@ namespace Umbraco.Core.Services return variables.Select((s, i) => new { index = i.ToString(CultureInfo.InvariantCulture), value = s }) .ToDictionary(keyvals => keyvals.index, keyvals => keyvals.value); } + + private static ICultureDictionary _cultureDictionary; + + /// + /// TODO: We need to refactor how we work with ICultureDictionary - this is supposed to be the 'fast' way to + /// do readonly access to the Dictionary without using the ILocalizationService. See TODO Notes in `DefaultCultureDictionary` + /// Also NOTE that the ICultureDictionary is based on the ILocalizationService not the ILocalizedTextService (which is used + /// only for the localization files - not the dictionary) + /// + /// + /// + /// + internal static string UmbracoDictionaryTranslate(this ILocalizedTextService manager, string text) + { + var cultureDictionary = CultureDictionary; + return UmbracoDictionaryTranslate(text, cultureDictionary); + } + + private static string UmbracoDictionaryTranslate(string text, ICultureDictionary cultureDictionary) + { + if (text == null) + return null; + + if (text.StartsWith("#") == false) + return text; + + text = text.Substring(1); + return cultureDictionary[text].IfNullOrWhiteSpace(text); + } + + private static ICultureDictionary CultureDictionary + { + get + { + return _cultureDictionary + ?? (_cultureDictionary = CultureDictionaryFactoryResolver.Current.Factory.CreateDictionary()); + } + } } } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index fdfe401ca9..4f0647dbf7 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -111,7 +111,7 @@ namespace Umbraco.Core.Services member.RawPasswordValue = result.RawPasswordValue; member.LastPasswordChangeDate = result.LastPasswordChangeDate; - member.UpdateDate = member.UpdateDate; + member.UpdateDate = result.UpdateDate; } /// diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 8ca9d3e3da..e42cd02b4e 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using System.Web; +using System.Web.UI.WebControls; using System.Xml.Linq; using System.Xml.XPath; using Newtonsoft.Json; @@ -11,12 +13,14 @@ using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Packaging; using Umbraco.Core.Packaging.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; +using Content = Umbraco.Core.Models.Content; namespace Umbraco.Core.Services { @@ -34,6 +38,7 @@ namespace Umbraco.Core.Services private readonly IDataTypeService _dataTypeService; private readonly IFileService _fileService; private readonly ILocalizationService _localizationService; + private readonly IEntityService _entityService; private readonly RepositoryFactory _repositoryFactory; private readonly IDatabaseUnitOfWorkProvider _uowProvider; private Dictionary _importedContentTypes; @@ -50,6 +55,7 @@ namespace Umbraco.Core.Services IDataTypeService dataTypeService, IFileService fileService, ILocalizationService localizationService, + IEntityService entityService, IUserService userService, RepositoryFactory repositoryFactory, IDatabaseUnitOfWorkProvider uowProvider) @@ -62,6 +68,7 @@ namespace Umbraco.Core.Services _dataTypeService = dataTypeService; _fileService = fileService; _localizationService = localizationService; + _entityService = entityService; _repositoryFactory = repositoryFactory; _uowProvider = uowProvider; _userService = userService; @@ -344,6 +351,9 @@ namespace Umbraco.Core.Services //Otherwise something like uSync won't work. var fields = new List>(); var isSingleDocTypeImport = unsortedDocumentTypes.Count == 1; + + var importedFolders = CreateContentTypeFolderStructure(unsortedDocumentTypes); + if (isSingleDocTypeImport == false) { //NOTE Here we sort the doctype XElements based on dependencies @@ -404,6 +414,15 @@ namespace Umbraco.Core.Services } } + 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); @@ -435,6 +454,65 @@ namespace Umbraco.Core.Services return list; } + private Dictionary CreateContentTypeFolderStructure(IEnumerable unsortedDocumentTypes) + { + var importedFolders = new Dictionary(); + foreach (var documentType in unsortedDocumentTypes) + { + var foldersAttribute = documentType.Attribute("Folders"); + if (foldersAttribute != null) + { + 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.GetContentTypeContainers(rootFolder, 1).FirstOrDefault(); + + if (current == null) + { + var tryCreateFolder = _contentTypeService.CreateContentTypeContainer(-1, rootFolder); + if (tryCreateFolder == false) + { + _logger.Error("Could not create folder: " + rootFolder, tryCreateFolder.Exception); + throw tryCreateFolder.Exception; + } + var rootFolderId = tryCreateFolder.Result; + current = _contentTypeService.GetContentTypeContainer(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.GetContentTypeContainer(containerId); + } + + var tryCreateFolder = _contentTypeService.CreateContentTypeContainer(current.Id, folderName); + if (tryCreateFolder == false) + { + _logger.Error("Could not create folder: " + folderName, tryCreateFolder.Exception); + throw tryCreateFolder.Exception; + } + return _contentTypeService.GetContentTypeContainer(tryCreateFolder.Result); + } + private IContentType CreateContentTypeFromXml(XElement documentType) { var infoElement = documentType.Element("Info"); @@ -652,13 +730,18 @@ namespace Umbraco.Core.Services 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 = property.Element("Description").Value, - Mandatory = property.Element("Mandatory").Value.ToLowerInvariant().Equals("true"), - ValidationRegExp = property.Element("Validation").Value, - }; + { + Name = property.Element("Name").Value, + Description = property.Element("Description") != null ? property.Element("Description").Value : null, + Mandatory = property.Element("Mandatory") != null ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true") : false, + ValidationRegExp = property.Element("Validation") != null ? property.Element("Validation").Value : null, + SortOrder = sortOrder + }; var tab = property.Element("Tab").Value; if (string.IsNullOrEmpty(tab)) @@ -796,6 +879,8 @@ namespace Umbraco.Core.Services ? (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; @@ -806,6 +891,10 @@ namespace Umbraco.Core.Services 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.GetDataTypeDefinitionById(dataTypeDefinitionId); //If the datatypedefinition doesn't already exist we create a new new according to the one in the package xml if (definition == null) @@ -818,11 +907,12 @@ namespace Umbraco.Core.Services if (legacyPropertyEditorId != Guid.Empty) { var dataTypeDefinition = new DataTypeDefinition(-1, legacyPropertyEditorId) - { - Key = dataTypeDefinitionId, - Name = dataTypeDefinitionName, - DatabaseType = databaseType - }; + { + Key = dataTypeDefinitionId, + Name = dataTypeDefinitionName, + DatabaseType = databaseType, + ParentId = parentId + }; dataTypes.Add(dataTypeDefinitionName, dataTypeDefinition); } else @@ -832,12 +922,18 @@ namespace Umbraco.Core.Services { Key = dataTypeDefinitionId, Name = dataTypeDefinitionName, - DatabaseType = databaseType + DatabaseType = databaseType, + ParentId = parentId }; dataTypes.Add(dataTypeDefinitionName, dataTypeDefinition); } } + else + { + definition.ParentId = parentId; + _dataTypeService.Save(definition, userId); + } } var list = dataTypes.Select(x => x.Value).ToList(); @@ -860,6 +956,64 @@ namespace Umbraco.Core.Services return list; } + 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("Could not create folder: " + rootFolder, tryCreateFolder.Exception); + throw tryCreateFolder.Exception; + } + current = _dataTypeService.GetContainer(tryCreateFolder.Result); + } + + 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("Could not create folder: " + folderName, tryCreateFolder.Exception); + throw tryCreateFolder.Exception; + } + return _dataTypeService.GetContainer(tryCreateFolder.Result); + } + private void SavePrevaluesFromXml(List dataTypes, IEnumerable dataTypeElements) { foreach (var dataTypeElement in dataTypeElements) @@ -1099,9 +1253,9 @@ namespace Umbraco.Core.Services if (existingLanguage == null) { var langauge = new Language(isoCode) - { - CultureName = languageElement.Attribute("FriendlyName").Value - }; + { + CultureName = languageElement.Attribute("FriendlyName").Value + }; _localizationService.Save(langauge); list.Add(langauge); } @@ -1383,11 +1537,11 @@ namespace Umbraco.Core.Services } var field = new TopologicalSorter.DependencyField - { - Alias = elementCopy.Element("Alias").Value, - Item = new Lazy(() => elementCopy), - DependsOn = dependencies.ToArray() - }; + { + Alias = elementCopy.Element("Alias").Value, + Item = new Lazy(() => elementCopy), + DependsOn = dependencies.ToArray() + }; fields.Add(field); } diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index cc25387a00..b810840873 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -171,7 +171,7 @@ namespace Umbraco.Core.Services //remove the cache so it gets re-read ... SD: I'm leaving this here even though it // is taken care of by events as well, I think unit tests may rely on it being cleared here. - _cache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); + _cache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); } } } diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index f8a78aed3d..395cb23e32 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -279,14 +279,15 @@ namespace Umbraco.Core.Services if (_localizationService == null) _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory, logger, eventMessagesFactory)); - if (_packagingService == null) - _packagingService = new Lazy(() => new PackagingService(logger, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _macroService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, _userService.Value, repositoryFactory, provider)); - if (_entityService == null) _entityService = new Lazy(() => new EntityService( - provider, repositoryFactory, logger, eventMessagesFactory, + provider, repositoryFactory, logger, eventMessagesFactory, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _memberService.Value, _memberTypeService.Value, + //TODO: Consider making this an isolated cache instead of using the global one cache.RuntimeCache)); + + if (_packagingService == null) + _packagingService = new Lazy(() => new PackagingService(logger, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _macroService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, _entityService.Value, _userService.Value, repositoryFactory, provider)); if (_relationService == null) _relationService = new Lazy(() => new RelationService(provider, repositoryFactory, logger, eventMessagesFactory, _entityService.Value)); diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 55d7dd9742..c7a63a884b 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -266,7 +266,7 @@ namespace Umbraco.Core.Services //should never be null but it could have been deleted by another thread. user.RawPasswordValue = result.RawPasswordValue; user.LastPasswordChangeDate = result.LastPasswordChangeDate; - user.UpdateDate = user.UpdateDate; + user.UpdateDate = result.UpdateDate; } } diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index e38464f5df..a4b4d35d10 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using umbraco.interfaces; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Sync { @@ -277,12 +278,42 @@ namespace Umbraco.Core.Sync } /// - /// Remove old instructions from the database. + /// Remove old instructions from the database /// + /// + /// Always leave the last (most recent) record in the db table, this is so that not all instructions are removed which would cause + /// the site to cold boot if there's been no instruction activity for more than DaysToRetainInstructions. + /// See: http://issues.umbraco.org/issue/U4-7643#comment=67-25085 + /// private void PruneOldInstructions() { - _appContext.DatabaseContext.Database.Delete("WHERE utcStamp < @pruneDate", - new { pruneDate = DateTime.UtcNow.AddDays(-_options.DaysToRetainInstructions) }); + var pruneDate = DateTime.UtcNow.AddDays(-_options.DaysToRetainInstructions); + var sqlSyntax = _appContext.DatabaseContext.SqlSyntax; + + //NOTE: this query could work on SQL server and MySQL: + /* + SELECT id + FROM umbracoCacheInstruction + WHERE utcStamp < getdate() + AND id <> (SELECT MAX(id) FROM umbracoCacheInstruction) + */ + // However, this will not work on SQLCE and in fact it will be slower than the query we are + // using if the SQL server doesn't perform it's own query optimizations (i.e. since the above + // query could actually execute a sub query for every row found). So we've had to go with an + // inner join which is faster and works on SQLCE but it's uglier to read. + + var deleteQuery = new Sql().Select("cacheIns.id") + .From("umbracoCacheInstruction cacheIns") + .InnerJoin("(SELECT MAX(id) id FROM umbracoCacheInstruction) tMax") + .On("cacheIns.id <> tMax.id") + .Where("cacheIns.utcStamp < @pruneDate", new {pruneDate = pruneDate}); + + var deleteSql = sqlSyntax.GetDeleteSubquery( + "umbracoCacheInstruction", + "id", + deleteQuery); + + _appContext.DatabaseContext.Database.Execute(deleteSql); } /// @@ -298,8 +329,9 @@ namespace Umbraco.Core.Sync .Where(dto => dto.Id == _lastId); var dtos = _appContext.DatabaseContext.Database.Fetch(sql); + if (dtos.Count == 0) - _lastId = -1; + _lastId = -1; } /// @@ -469,4 +501,5 @@ namespace Umbraco.Core.Sync #endregion } -} \ No newline at end of file +} + \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f6ffa0a0ff..c13799f326 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -156,26 +156,38 @@ + + + + + + + + + + + + @@ -456,7 +468,6 @@ - @@ -470,7 +481,6 @@ - @@ -497,6 +507,7 @@ + diff --git a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs index 44deb1da40..63225f6725 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs @@ -4,9 +4,11 @@ using System.Web; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Collections; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Tests.Collections; namespace Umbraco.Tests.Cache { @@ -36,6 +38,23 @@ namespace Umbraco.Tests.Cache get { return _provider; } } + [Test] + public void Clones_List() + { + var original = new DeepCloneableList(); + original.Add(new DeepCloneableListTests.TestClone()); + original.Add(new DeepCloneableListTests.TestClone()); + original.Add(new DeepCloneableListTests.TestClone()); + + var val = _provider.GetCacheItem>("test", () => original); + + Assert.AreEqual(original.Count, val.Count); + foreach (var item in val) + { + Assert.IsTrue(item.IsClone); + } + } + [Test] public void Ensures_Cloned_And_Reset() { diff --git a/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs new file mode 100644 index 0000000000..32381b593b --- /dev/null +++ b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Web.Caching; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class DefaultCachePolicyTests + { + [Test] + public void Caches_Single() + { + var isCached = false; + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(() => + { + isCached = true; + }); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.Get(1, o => new AuditItem(1, "blah", AuditType.Copy, 123)); + } + Assert.IsTrue(isCached); + } + + [Test] + public void Get_Single_From_Cache() + { + var cache = new Mock(); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem(1, "blah", AuditType.Copy, 123)); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.Get(1, o => (AuditItem) null); + Assert.IsNotNull(found); + } + } + + [Test] + 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(), + 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[] {}); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] {}, o => new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }); + } + + Assert.AreEqual(2, cached.Count); + } + + [Test] + public void Get_All_Without_Ids_From_Cache() + { + var cache = new Mock(); + cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] {}, o => new[] {(AuditItem) null}); + Assert.AreEqual(2, found.Length); + } + } + + [Test] + public void If_CreateOrUpdate_Throws_Cache_Is_Removed() + { + var cacheCleared = false; + var cache = new Mock(); + cache.Setup(x => x.ClearCacheItem(It.IsAny())) + .Callback(() => + { + cacheCleared = true; + }); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + try + { + using (defaultPolicy) + { + defaultPolicy.CreateOrUpdate(new AuditItem(1, "blah", AuditType.Copy, 123), item => + { + throw new Exception("blah!"); + }); + } + } + catch + { + //we need this catch or nunit throw up + } + finally + { + Assert.IsTrue(cacheCleared); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs new file mode 100644 index 0000000000..7fd894e2a9 --- /dev/null +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Web.Caching; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Cache; +using Umbraco.Core.Collections; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class FullDataSetCachePolicyTests + { + [Test] + public void Get_All_Caches_As_Single_List() + { + var cached = new List(); + IList list = null; + + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(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); + + list = o() as IList; + }); + cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new AuditItem[] { }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] { }, o => new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }); + } + + Assert.AreEqual(1, cached.Count); + Assert.IsNotNull(list); + } + + [Test] + public void Get_All_Without_Ids_From_Cache() + { + var cache = new Mock(); + + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => new DeepCloneableList + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] { }, o => new[] { (AuditItem)null }); + Assert.AreEqual(2, found.Length); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs b/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs new file mode 100644 index 0000000000..b8e77e9267 --- /dev/null +++ b/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Web.Caching; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class SingleItemsOnlyCachePolicyTests + { + [Test] + 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(), + 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[] { }); + + var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] { }, o => new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }); + } + + Assert.AreEqual(0, cached.Count); + } + + [Test] + public void Caches_Single() + { + var isCached = false; + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(() => + { + isCached = true; + }); + + var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.Get(1, o => new AuditItem(1, "blah", AuditType.Copy, 123)); + } + Assert.IsTrue(isCached); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs b/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs new file mode 100644 index 0000000000..fcc50df60c --- /dev/null +++ b/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Umbraco.Core.Collections; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Collections +{ + [TestFixture] + public class DeepCloneableListTests + { + [Test] + public void Deep_Clones_All_Elements() + { + var list = new DeepCloneableList(); + list.Add(new TestClone()); + list.Add(new TestClone()); + list.Add(new TestClone()); + + var cloned = list.DeepClone() as DeepCloneableList; + + Assert.IsNotNull(cloned); + Assert.AreNotSame(list, cloned); + Assert.AreEqual(list.Count, cloned.Count); + } + + [Test] + public void Clones_Each_Item() + { + var list = new DeepCloneableList(); + list.Add(new TestClone()); + list.Add(new TestClone()); + list.Add(new TestClone()); + + var cloned = (DeepCloneableList) list.DeepClone(); + + foreach (var item in cloned) + { + Assert.IsTrue(item.IsClone); + } + } + + [Test] + public void Cloned_Sequence_Equals() + { + var list = new DeepCloneableList(); + list.Add(new TestClone()); + list.Add(new TestClone()); + list.Add(new TestClone()); + + var cloned = (DeepCloneableList)list.DeepClone(); + + //Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) + Assert.IsTrue(list.SequenceEqual(cloned)); + + //Test that each instance in the list is not the same one + foreach (var item in list) + { + var clone = cloned.Single(x => x.Id == item.Id); + Assert.AreNotSame(item, clone); + } + } + + public class TestClone : IDeepCloneable, IEquatable + { + public TestClone(Guid id) + { + Id = id; + IsClone = true; + } + + public TestClone() + { + Id = Guid.NewGuid(); + } + + public Guid Id { get; private set; } + public bool IsClone { get; private set; } + + public object DeepClone() + { + return new TestClone(Id); + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(TestClone other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id.Equals(other.Id); + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + 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((TestClone)obj); + } + + /// + /// Serves as the default hash function. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + public static bool operator ==(TestClone left, TestClone right) + { + return Equals(left, right); + } + + public static bool operator !=(TestClone left, TestClone right) + { + return Equals(left, right) == false; + } + } + } +} diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index 4f0b91d4ca..23c1c7f1f1 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -27,7 +27,8 @@ namespace Umbraco.Tests.Macros var cacheHelper = new CacheHelper( new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), - new NullCacheProvider()); + new NullCacheProvider(), + new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); ApplicationContext.Current = new ApplicationContext(cacheHelper, new ProfilingLogger(Mock.Of(), Mock.Of())); UmbracoConfig.For.SetUmbracoSettings(SettingsForTests.GetDefault()); @@ -36,7 +37,7 @@ namespace Umbraco.Tests.Macros [TearDown] public void TearDown() { - ApplicationContext.Current.ApplicationCache.ClearAllCache(); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearAllCache(); ApplicationContext.Current.DisposeIfDisposable(); ApplicationContext.Current = null; } @@ -136,11 +137,11 @@ namespace Umbraco.Tests.Macros public void Macro_Needs_Removing_Based_On_Macro_File(int minutesToNow, bool expectedResult) { var now = DateTime.Now; - ApplicationContext.Current.ApplicationCache.InsertCacheItem( + ApplicationContext.Current.ApplicationCache.RuntimeCache.InsertCacheItem( "TestDate", - CacheItemPriority.NotRemovable, - new TimeSpan(0, 0, 60), - () => now.AddMinutes(minutesToNow)); //add a datetime value of 'now' with the minutes offset + priority: CacheItemPriority.NotRemovable, + timeout: new TimeSpan(0, 0, 60), + getCacheItem: () => now.AddMinutes(minutesToNow)); //add a datetime value of 'now' with the minutes offset //now we need to update a file's date to 'now' to compare var path = Path.Combine(TestHelpers.TestHelper.CurrentAssemblyDirectory, "temp.txt"); diff --git a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs index d0a8c21581..46d6ea1022 100644 --- a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs +++ b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -36,12 +37,15 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.Key, item.Key); Assert.AreEqual(clone.UpdateDate, item.UpdateDate); Assert.AreNotSame(clone.Language, item.Language); - Assert.AreEqual(clone.Language, item.Language); + //This is null because we are ignoring it from cloning due to caching/cloning issues - we don't really want + // this entity attached to this item but we're stuck with it for now + Assert.IsNull(clone.Language); + Assert.AreEqual(clone.LanguageId, item.LanguageId); Assert.AreEqual(clone.Value, item.Value); //This double verifies by reflection var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + foreach (var propertyInfo in allProps.Where(x => x.Name != "Language")) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } diff --git a/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs index fd2dc02292..e6c497d1b6 100644 --- a/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs @@ -393,6 +393,7 @@ namespace Umbraco.Tests.Mvc new Mock().Object, new Mock().Object, new Mock().Object, + new Mock().Object, new Mock().Object, new RepositoryFactory(CacheHelper.CreateDisabledCacheHelper(), logger, Mock.Of(), umbracoSettings), new Mock().Object), diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index aa0b8a8ffe..49f3abc97f 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -35,7 +35,6 @@ namespace Umbraco.Tests.Persistence.Repositories { var dataTypeDefinitionRepository = new DataTypeDefinitionRepository( unitOfWork, CacheHelper.CreateDisabledCacheHelper(), - CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, new ContentTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, new TemplateRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()))); @@ -503,10 +502,14 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var cache = new CacheHelper(new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new StaticCacheProvider()); + var cache = new CacheHelper( + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + new StaticCacheProvider(), + new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); Func creator = () => new DataTypeDefinitionRepository( - unitOfWork, CacheHelper.CreateDisabledCacheHelper(), + unitOfWork, cache, Mock.Of(), SqlSyntax, new ContentTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, @@ -529,7 +532,8 @@ namespace Umbraco.Tests.Persistence.Repositories var collection = repository.GetPreValuesCollectionByDataTypeId(dtd.Id); } - var cached = cache.RuntimeCache.GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); + var cached = cache.IsolatedRuntimeCache.GetCache().Result + .GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); Assert.IsNotNull(cached); Assert.AreEqual(1, cached.Count()); @@ -542,10 +546,14 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var cache = new CacheHelper(new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new StaticCacheProvider()); + var cache = new CacheHelper( + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + new StaticCacheProvider(), + new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); Func creator = () => new DataTypeDefinitionRepository( - unitOfWork, CacheHelper.CreateDisabledCacheHelper(), + unitOfWork, cache, Mock.Of(), SqlSyntax, new ContentTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, @@ -568,7 +576,8 @@ namespace Umbraco.Tests.Persistence.Repositories var val = repository.GetPreValueAsString(Convert.ToInt32(id)); } - var cached = cache.RuntimeCache.GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); + var cached = cache.IsolatedRuntimeCache.GetCache().Result + .GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); Assert.IsNotNull(cached); Assert.AreEqual(1, cached.Count()); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs index d21da8c6e2..6b56181eac 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs @@ -27,10 +27,9 @@ namespace Umbraco.Tests.Persistence.Repositories CreateTestData(); } - private DictionaryRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out LanguageRepository languageRepository) + private DictionaryRepository CreateRepository(IDatabaseUnitOfWork unitOfWork) { - languageRepository = new LanguageRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); - var dictionaryRepository = new DictionaryRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), new SqlCeSyntaxProvider(), languageRepository); + var dictionaryRepository = new DictionaryRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), new SqlCeSyntaxProvider()); return dictionaryRepository; } @@ -41,8 +40,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + + using (var repository = CreateRepository(unitOfWork)) { var dictionaryItem = (IDictionaryItem)new DictionaryItem("Testing1235") { @@ -74,8 +73,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + + using (var repository = CreateRepository(unitOfWork)) { var dictionaryItem = (IDictionaryItem)new DictionaryItem("Testing1235") { @@ -107,8 +106,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + + using (var repository = CreateRepository(unitOfWork)) { var dictionaryItem = (IDictionaryItem)new DictionaryItem("Testing1235") { @@ -141,8 +140,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + + using (var repository = CreateRepository(unitOfWork)) { var dictionaryItem = (IDictionaryItem) new DictionaryItem("Testing1235"); @@ -168,8 +167,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -190,8 +188,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -211,8 +208,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -232,8 +228,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -251,8 +246,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var languageRepository = new LanguageRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax)) + using (var repository = CreateRepository(unitOfWork)) { var language = languageRepository.Get(1); @@ -282,8 +277,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -310,8 +304,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var languageRepository = new LanguageRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); - var repository = new DictionaryRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), new SqlCeSyntaxProvider(), languageRepository); + var repository = new DictionaryRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), new SqlCeSyntaxProvider()); var languageNo = new Language("nb-NO") { CultureName = "nb-NO" }; ServiceContext.LocalizationService.Save(languageNo); @@ -325,12 +318,12 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(item); unitOfWork.Commit(); - var dictionaryItem = repository.Get(1); - + var dictionaryItem = (DictionaryItem)repository.Get(1); + // Assert Assert.That(dictionaryItem, Is.Not.Null); Assert.That(dictionaryItem.Translations.Count(), Is.EqualTo(3)); - Assert.That(dictionaryItem.Translations.Single(t => t.Language.IsoCode == "nb-NO").Value, Is.EqualTo("Les mer")); + Assert.That(dictionaryItem.Translations.Single(t => t.LanguageId == languageNo.Id).Value, Is.EqualTo("Les mer")); } [Test] @@ -339,8 +332,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -361,8 +353,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 64e8587e00..0d3b4e4e47 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.Persistence.Repositories private MemberRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out MemberTypeRepository memberTypeRepository, out MemberGroupRepository memberGroupRepository) { memberTypeRepository = new MemberTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); - memberGroupRepository = new MemberGroupRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, CacheHelper.CreateDisabledCacheHelper()); + memberGroupRepository = new MemberGroupRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); var tagRepo = new TagRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); var repository = new MemberRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); return repository; diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index 7bf8f1dd30..26f1665fe2 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -17,6 +18,8 @@ namespace Umbraco.Tests.PropertyEditors [SetUp] public virtual void TestSetup() { + //normalize culture + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault())); Resolution.Freeze(); } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceExtensionsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceExtensionsTests.cs new file mode 100644 index 0000000000..fbdb45114e --- /dev/null +++ b/src/Umbraco.Tests/Services/ContentTypeServiceExtensionsTests.cs @@ -0,0 +1,148 @@ +using System.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + public class ContentTypeServiceExtensionsTests : BaseUmbracoApplicationTest + { + [Test] + public void GetAvailableCompositeContentTypes_Not_Itself() + { + var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1", null); + var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2", null); + var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3", null); + ct1.Id = 1; + ct2.Id = 2; + ct3.Id = 3; + + var service = new Mock(); + + var availableTypes = service.Object.GetAvailableCompositeContentTypes( + ct1, + new[] {ct1, ct2, ct3}); + + Assert.AreEqual(2, availableTypes.Count()); + Assert.AreEqual(ct2.Id, availableTypes.ElementAt(0).Id); + Assert.AreEqual(ct3.Id, availableTypes.ElementAt(1).Id); + } + + //This shows that a nested comp is not allowed + [Test] + public void GetAvailableCompositeContentTypes_No_Results_If_Already_A_Composition_By_Parent() + { + var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1"); + var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2", ct1); + var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3"); + ct1.Id = 1; + ct2.Id = 2; + ct3.Id = 3; + + var service = new Mock(); + + var availableTypes = service.Object.GetAvailableCompositeContentTypes( + ct1, + new[] { ct1, ct2, ct3 }); + + Assert.AreEqual(0, availableTypes.Count()); + } + + //This shows that a nested comp is not allowed + [Test] + public void GetAvailableCompositeContentTypes_No_Results_If_Already_A_Composition() + { + var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1"); + var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2"); + var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3"); + ct1.Id = 1; + ct2.Id = 2; + ct3.Id = 3; + + ct2.AddContentType(ct1); + + var service = new Mock(); + + var availableTypes = service.Object.GetAvailableCompositeContentTypes( + ct1, + new[] { ct1, ct2, ct3 }); + + Assert.AreEqual(0, availableTypes.Count()); + } + + [Test] + public void GetAvailableCompositeContentTypes_Do_Not_Include_Other_Composed_Types() + { + var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1"); + var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2"); + var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3"); + ct1.Id = 1; + ct2.Id = 2; + ct3.Id = 3; + + ct2.AddContentType(ct3); + + var service = new Mock(); + + var availableTypes = service.Object.GetAvailableCompositeContentTypes( + ct1, + new[] { ct1, ct2, ct3 }); + + Assert.AreEqual(1, availableTypes.Count()); + Assert.AreEqual(ct3.Id, availableTypes.Single().Id); + } + + [Test] + public void GetAvailableCompositeContentTypes_Include_Direct_Composed_Types() + { + var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1"); + var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2"); + var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3"); + ct1.Id = 1; + ct2.Id = 2; + ct3.Id = 3; + + ct1.AddContentType(ct3); + + var service = new Mock(); + + var availableTypes = service.Object.GetAvailableCompositeContentTypes( + ct1, + new[] { ct1, ct2, ct3 }); + + Assert.AreEqual(2, availableTypes.Count()); + Assert.AreEqual(ct2.Id, availableTypes.ElementAt(0).Id); + Assert.AreEqual(ct3.Id, availableTypes.ElementAt(1).Id); + } + + [Test] + public void GetAvailableCompositeContentTypes_Include_Indirect_Composed_Types() + { + var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1"); + var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2"); + var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3"); + var ct4 = MockedContentTypes.CreateBasicContentType("ct4", "CT4"); + ct1.Id = 1; + ct2.Id = 2; + ct3.Id = 3; + ct4.Id = 4; + + ct1.AddContentType(ct3); //ct3 is direct to ct1 + ct3.AddContentType(ct4); //ct4 is indirect to ct1 + + var service = new Mock(); + + var availableTypes = service.Object.GetAvailableCompositeContentTypes( + ct1, + new[] { ct1, ct2, ct3 }); + + Assert.AreEqual(3, availableTypes.Count()); + Assert.AreEqual(ct2.Id, availableTypes.ElementAt(0).Id); + Assert.AreEqual(ct3.Id, availableTypes.ElementAt(1).Id); + Assert.AreEqual(ct4.Id, availableTypes.ElementAt(2).Id); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/UI/LegacyDialogTests.cs b/src/Umbraco.Tests/UI/LegacyDialogTests.cs index 11633b7f88..0b1514d8a5 100644 --- a/src/Umbraco.Tests/UI/LegacyDialogTests.cs +++ b/src/Umbraco.Tests/UI/LegacyDialogTests.cs @@ -28,25 +28,17 @@ namespace Umbraco.Tests.UI } [TestCase(typeof(UserTypeTasks), DefaultApps.users)] - [TestCase(typeof(contentItemTypeTasks), DefaultApps.member)] - [TestCase(typeof(contentItemTasks), DefaultApps.member)] [TestCase(typeof(XsltTasks), DefaultApps.developer)] [TestCase(typeof(userTasks), DefaultApps.users)] [TestCase(typeof(templateTasks), DefaultApps.settings)] [TestCase(typeof(StylesheetTasks), DefaultApps.settings)] [TestCase(typeof(stylesheetPropertyTasks), DefaultApps.settings)] [TestCase(typeof(ScriptTasks), DefaultApps.settings)] - [TestCase(typeof(nodetypeTasks), DefaultApps.settings)] - [TestCase(typeof(PythonTasks), DefaultApps.developer)] - [TestCase(typeof(MemberTypeTasks), DefaultApps.member)] - [TestCase(typeof(memberTasks), DefaultApps.member)] [TestCase(typeof(MemberGroupTasks), DefaultApps.member)] - [TestCase(typeof(MediaTypeTasks), DefaultApps.settings)] [TestCase(typeof(dictionaryTasks), DefaultApps.settings)] [TestCase(typeof(macroTasks), DefaultApps.developer)] [TestCase(typeof(languageTasks), DefaultApps.settings)] [TestCase(typeof(DLRScriptingTasks), DefaultApps.developer)] - [TestCase(typeof(DataTypeTasks), DefaultApps.developer)] [TestCase(typeof(CreatedPackageTasks), DefaultApps.developer)] [TestCase(typeof(PartialViewTasks), DefaultApps.settings)] public void Check_Assigned_Apps_For_Tasks(Type taskType, DefaultApps app) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index f3ed7d225b..5bb59eaa04 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -176,6 +176,10 @@ + + + + @@ -191,6 +195,7 @@ + diff --git a/src/Umbraco.Tests/UriExtensionsTests.cs b/src/Umbraco.Tests/UriExtensionsTests.cs index 4ea8e8875a..91f727f5f9 100644 --- a/src/Umbraco.Tests/UriExtensionsTests.cs +++ b/src/Umbraco.Tests/UriExtensionsTests.cs @@ -141,7 +141,7 @@ namespace Umbraco.Tests public void GetAbsolutePathDecoded(string input, string expected) { var source = new Uri(input, UriKind.RelativeOrAbsolute); - var output = source.GetSafeAbsolutePathDecoded(); + var output = source.GetAbsolutePathDecoded(); Assert.AreEqual(expected, output); } diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 4c57235c69..ab78d10380 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -1,30 +1,31 @@ { - "name": "Umbraco", - "version": "7", - "homepage": "https://github.com/umbraco/Umbraco-CMS", - "authors": [ - "Shannon " - ], - "description": "Umbraco CMS", - "license": "MIT", - "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "typeahead.js": "~0.10.5", - "underscore": "~1.7.0", - "rgrove-lazyload": "*", - "bootstrap-social": "~4.8.0", - "jquery": "2.0.3", - "jquery-ui": "1.11.4", - "angular-dynamic-locale": "0.1.28", - "ng-file-upload": "~7.3.8", - "tinymce": "~4.1.10", - "codemirror": "~5.3.0" - } + "name": "Umbraco", + "version": "7", + "homepage": "https://github.com/umbraco/Umbraco-CMS", + "authors": [ + "Shannon " + ], + "description": "Umbraco CMS", + "license": "MIT", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "typeahead.js": "~0.10.5", + "underscore": "~1.7.0", + "rgrove-lazyload": "*", + "bootstrap-social": "~4.8.0", + "jquery": "2.0.3", + "jquery-ui": "1.11.4", + "angular-dynamic-locale": "0.1.28", + "ng-file-upload": "~7.3.8", + "tinymce": "~4.1.10", + "codemirror": "~5.3.0", + "angular-local-storage": "~0.2.3" + } } diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index 2fb9b026a5..0500e24c31 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -492,6 +492,10 @@ module.exports = function (grunt) { keepExpandedHierarchy: false, files: ['ng-file-upload.min.js'] }, + 'angular-local-storage': { + keepExpandedHierarchy: false, + files: ['dist/angular-local-storage.min.js'] + }, 'codemirror': { files: [ 'lib/codemirror.js', diff --git a/src/Umbraco.Web.UI.Client/src/app.js b/src/Umbraco.Web.UI.Client/src/app.js index 60583852b3..e2a12bc876 100644 --- a/src/Umbraco.Web.UI.Client/src/app.js +++ b/src/Umbraco.Web.UI.Client/src/app.js @@ -10,7 +10,8 @@ var app = angular.module('umbraco', [ 'ngSanitize', 'ngMobile', 'tmh.dynamicLocale', - 'ngFileUpload' + 'ngFileUpload', + 'LocalStorageModule' ]); var packages = angular.module("umbraco.packages", []); diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js index c7cc3ff6ed..59d5a4bef5 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js @@ -11,12 +11,10 @@ LazyLoad.js([ '../js/umbraco.security.js', '../ServerVariables', '../lib/spectrum/spectrum.js', - + '../js/canvasdesigner.panel.js', ], function () { jQuery(document).ready(function () { angular.bootstrap(document, ['Umbraco.canvasdesigner']); }); }); - -LazyLoad.js(['https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js']); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js index ceee2a2a9e..d23fdcbf84 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js @@ -102,6 +102,8 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', // Load parameters from GetLessParameters and init data of the Canvasdesigner config $scope.initCanvasdesigner = function () { + LazyLoad.js(['https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js']); + $http.get(apiController + 'Load', { params: { pageId: $scope.pageId } }) .success(function (data) { @@ -419,6 +421,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', var webFontScriptLoaded = false; var loadGoogleFont = function (font) { + if (!webFontScriptLoaded) { $.getScript('https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js') .done(function () { @@ -446,6 +449,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', } }); } + }; /*****************************************************************************/ @@ -453,9 +457,11 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', /*****************************************************************************/ // Preload of the google font - $http.get(apiController + 'GetGoogleFont').success(function (data) { - $scope.googleFontFamilies = data; - }); + if ($scope.showStyleEditor) { + $http.get(apiController + 'GetGoogleFont').success(function (data) { + $scope.googleFontFamilies = data; + }); + } // watch framLoaded, only if iframe page have enableCanvasdesigner() $scope.$watch("enableCanvasdesigner", function () { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index 31b7cb9506..5ce3eac8a0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -88,44 +88,54 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se scope.avatarClick = function(){ + if(scope.helpDialog) { + closeHelpDialog(); + } + if(!scope.userDialog) { - - scope.userDialog = {}; - scope.userDialog.view = "user"; - scope.userDialog.show = true; - - scope.userDialog.close = function(oldModel) { - scope.userDialog.show = false; - scope.userDialog = null; + scope.userDialog = { + view: "user", + show: true, + close: function(oldModel) { + closeUserDialog(); + } }; - } else { - scope.userDialog.show = false; - scope.userDialog = null; + closeUserDialog(); } }; + function closeUserDialog() { + scope.userDialog.show = false; + scope.userDialog = null; + } + scope.helpClick = function(){ + if(scope.userDialog) { + closeUserDialog(); + } + if(!scope.helpDialog) { - - scope.helpDialog = {}; - scope.helpDialog.show = true; - scope.helpDialog.view = "help"; - - scope.helpDialog.close = function(oldModel) { - scope.helpDialog.show = false; - scope.helpDialog = null; + scope.helpDialog = { + view: "help", + show: true, + close: function(oldModel) { + closeHelpDialog(); + } }; - } else { - scope.helpDialog.show = false; - scope.helpDialog = null; + closeHelpDialog(); } }; + function closeHelpDialog() { + scope.helpDialog.show = false; + scope.helpDialog = null; + } + scope.sectionClick = function (event, section) { if (event.ctrlKey || @@ -136,12 +146,11 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se return; } - - if (navigationService.userDialog) { - navigationService.userDialog.close(); + if (scope.userDialog) { + closeUserDialog(); } - if (navigationService.helpDialog) { - navigationService.helpDialog.close(); + if (scope.helpDialog) { + closeHelpDialog(); } navigationService.hideSearch(); @@ -155,11 +164,11 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se scope.trayClick = function () { // close dialogs - if (navigationService.userDialog) { - navigationService.userDialog.close(); + if (scope.userDialog) { + closeUserDialog(); } - if (navigationService.helpDialog) { - navigationService.helpDialog.close(); + if (scope.helpDialog) { + closeHelpDialog(); } if (appState.getGlobalState("showTray") === true) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditortoolbar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditortoolbar.directive.js deleted file mode 100644 index 82ea69acae..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditortoolbar.directive.js +++ /dev/null @@ -1,20 +0,0 @@ -(function() { - 'use strict'; - - function EditorToolbarDirective() { - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-toolbar.html', - scope: { - tools: "=" - } - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorToolbar', EditorToolbarDirective); - -})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js index aad87178cc..92d67de2bd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js @@ -3,11 +3,20 @@ function EditorViewDirective() { + function link(scope, el, attr) { + + if(attr.footer) { + scope.footer = attr.footer; + } + + } + var directive = { transclude: true, restrict: 'E', replace: true, - templateUrl: 'views/components/editor/umb-editor-view.html' + templateUrl: 'views/components/editor/umb-editor-view.html', + link: link }; return directive; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js index 294e2767ac..4741458c90 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js @@ -11,6 +11,10 @@ function link(scope, el, attr, ctrl) { + scope.directive = { + enableConfirmButton: false + }; + var overlayNumber = 0; var numberOfOverlays = 0; var isRegistered = false; @@ -23,8 +27,6 @@ setButtonText(); - registerOverlay(); - modelCopy = makeModelCopy(scope.model); $timeout(function() { @@ -33,6 +35,10 @@ setTargetPosition(); } + // this has to be done inside a timeout to ensure the destroy + // event on other overlays is run before registering a new one + registerOverlay(); + setOverlayIndent(); }); @@ -220,21 +226,23 @@ } scope.submitForm = function(model) { - if(scope.model.submit) { + if (formHelper.submitForm({scope: scope})) { + formHelper.resetForm({ scope: scope }); - if (formHelper.submitForm({scope: scope})) { + if(scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) { + scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton); + } else { + unregisterOverlay(); + scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton); + } - formHelper.resetForm({ scope: scope }); - - unregisterOverlay(); - - scope.model.submit(model); - - } - - } + } + } + }; + scope.cancelConfirmSubmit = function() { + scope.model.confirmSubmit.show = false; }; scope.closeOverLay = function() { 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 26164baccb..b9d633b3d8 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 @@ -7,7 +7,8 @@ angular.module("umbraco.directives") scope: { alias: '=', aliasFrom: '=', - enableLock: '=?' + enableLock: '=?', + serverValidationField: '@' }, link: function (scope, element, attrs, ctrl) { @@ -32,7 +33,7 @@ angular.module("umbraco.directives") generateAliasTimeout = $timeout(function () { updateAlias = true; entityResource.getSafeAlias(value, true).then(function (safeAlias) { - if(updateAlias) { + if (updateAlias) { scope.alias = safeAlias.alias; } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdisableformvalidation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdisableformvalidation.directive.js new file mode 100644 index 0000000000..c101504ea7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdisableformvalidation.directive.js @@ -0,0 +1,20 @@ +(function() { + 'use strict'; + + function UmbDisableFormValidation() { + + var directive = { + restrict: 'A', + require: '?form', + link: function (scope, elm, attrs, ctrl) { + //override the $setValidity function of the form to disable validation + ctrl.$setValidity = function () { }; + } + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbDisableFormValidation', UmbDisableFormValidation); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbemptystate.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbemptystate.directive.js new file mode 100644 index 0000000000..6f5cbd2166 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbemptystate.directive.js @@ -0,0 +1,22 @@ +(function() { + 'use strict'; + + function EmptyStateDirective() { + + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/umb-empty-state.html', + scope: { + size: '@', + position: '@' + } + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEmptyState', EmptyStateDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbfoldergrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbfoldergrid.directive.js index 06475e9c45..9f18f91e26 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbfoldergrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbfoldergrid.directive.js @@ -5,17 +5,17 @@ function link(scope, el, attr, ctrl) { - scope.clickFolder = function(folder) { + scope.clickFolder = function(folder, $event, $index) { if(scope.onClick) { - scope.onClick(folder); + scope.onClick(folder, $event, $index); } }; - scope.selectFolder = function(folder, $event, $index) { - if(scope.onSelect) { - scope.onSelect(folder, $event, $index); + scope.clickFolderName = function(folder, $event, $index) { + if(scope.onClickName) { + scope.onClickName(folder, $event, $index); + $event.stopPropagation(); } - $event.stopPropagation(); }; } @@ -26,8 +26,8 @@ templateUrl: 'views/components/umb-folder-grid.html', scope: { folders: '=', - onSelect: '=', - onClick: "=" + onClick: "=", + onClickName: "=" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index d18251da0b..c8ea32110e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -1,7 +1,7 @@ (function() { 'use strict'; - function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, dataTypeHelper, dataTypeResource, $filter) { + function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, dataTypeHelper, dataTypeResource, $filter, iconHelper, $q) { function link(scope, el, attr, ctrl) { @@ -9,11 +9,10 @@ scope.toolbar = []; scope.sortableOptionsGroup = {}; scope.sortableOptionsProperty = {}; + scope.sortingButtonLabel = "Reorder"; function activate() { - setToolbar(); - setSortingOptions(); // set placeholder property on each group @@ -28,36 +27,6 @@ } - function setToolbar() { - - scope.toolbar = []; - - var compositionTool = { - "name": "Compositions", - "icon": "icon-merge", - "action": function(tool) { - scope.openCompositionsDialog(tool); - } - }; - - var sortingTool = { - "name": "Reorder", - "icon": "icon-navigation", - "action": function(tool) { - scope.toggleSortingMode(tool); - } - }; - - if(scope.compositions || scope.compositions === undefined) { - scope.toolbar.push(compositionTool); - } - - if(scope.sorting || scope.sorting === undefined) { - scope.toolbar.push(sortingTool); - } - - } - function setSortingOptions() { scope.sortableOptionsGroup = { @@ -174,74 +143,121 @@ scope.sortingMode = !scope.sortingMode; if(scope.sortingMode === true) { - tool.name = "I'm done reordering"; + scope.sortingButtonLabel = "I'm done reordering"; } else { - tool.name = "Reorder"; + scope.sortingButtonLabel = "Reorder"; } }; scope.openCompositionsDialog = function() { - scope.compositionsDialogModel = {}; - scope.compositionsDialogModel.title = "Compositions"; - scope.compositionsDialogModel.contentType = scope.model; - scope.compositionsDialogModel.availableCompositeContentTypes = scope.model.availableCompositeContentTypes; - scope.compositionsDialogModel.compositeContentTypes = scope.model.compositeContentTypes; - scope.compositionsDialogModel.view = "views/common/overlays/contenttypeeditor/compositions/compositions.html"; - scope.compositionsDialogModel.show = true; - scope.compositionsDialogModel.submit = function(model) { + scope.compositionsDialogModel = { + title: "Compositions", + contentType: scope.model, + compositeContentTypes: scope.model.compositeContentTypes, + view: "views/common/overlays/contenttypeeditor/compositions/compositions.html", + confirmSubmit: { + title: "Warning", + description: "Removing a composition will delete all the associated property data. Once you save the document type there's no way back, are you sure?", + checkboxLabel: "I know what I'm doing", + enable: true + }, + submit: function(model, oldModel, confirmed) { - // make sure that all tabs has an init property - if (scope.model.groups.length !== 0) { - angular.forEach(scope.model.groups, function(group) { - addInitProperty(group); - }); - } + var compositionRemoved = false; - // remove overlay - scope.compositionsDialogModel.show = false; - scope.compositionsDialogModel = null; - }; + // check if any compositions has been removed + for(var i = 0; oldModel.compositeContentTypes.length > i; i++) { - scope.compositionsDialogModel.close = function(oldModel) { - // reset composition changes - scope.model.groups = oldModel.contentType.groups; - scope.model.compositeContentTypes = oldModel.contentType.compositeContentTypes; + var oldComposition = oldModel.compositeContentTypes[i]; - // remove overlay - scope.compositionsDialogModel.show = false; - scope.compositionsDialogModel = null; - }; + if(_.contains(model.compositeContentTypes, oldComposition) === false) { + compositionRemoved = true; + } - scope.compositionsDialogModel.selectCompositeContentType = function(compositeContentType) { + } - if (scope.model.compositeContentTypes.indexOf(compositeContentType.alias) === -1) { - //merge composition with content type + // show overlay confirm box if compositions has been removed. + if(compositionRemoved && confirmed === false) { - if(scope.contentType === "documentType") { + scope.compositionsDialogModel.confirmSubmit.show = true; - contentTypeResource.getById(compositeContentType.id).then(function(composition){ - contentTypeHelper.mergeCompositeContentType(scope.model, composition); - }); + // submit overlay if no compositions has been removed + // or the action has been confirmed + } else { + + // make sure that all tabs has an init property + if (scope.model.groups.length !== 0) { + angular.forEach(scope.model.groups, function(group) { + addInitProperty(group); + }); + } + + // remove overlay + scope.compositionsDialogModel.show = false; + scope.compositionsDialogModel = null; + } + + }, + close: function(oldModel) { + + // reset composition changes + scope.model.groups = oldModel.contentType.groups; + scope.model.compositeContentTypes = oldModel.contentType.compositeContentTypes; + + // remove overlay + scope.compositionsDialogModel.show = false; + scope.compositionsDialogModel = null; + + }, + selectCompositeContentType: function(compositeContentType) { + + if (scope.model.compositeContentTypes.indexOf(compositeContentType.alias) === -1) { + //merge composition with content type + + if(scope.contentType === "documentType") { + + contentTypeResource.getById(compositeContentType.id).then(function(composition){ + contentTypeHelper.mergeCompositeContentType(scope.model, composition); + }); - } else if(scope.contentType === "mediaType") { + } else if(scope.contentType === "mediaType") { - mediaTypeResource.getById(compositeContentType.id).then(function(composition){ - contentTypeHelper.mergeCompositeContentType(scope.model, composition); - }); + mediaTypeResource.getById(compositeContentType.id).then(function(composition){ + contentTypeHelper.mergeCompositeContentType(scope.model, composition); + }); + + } + + + } else { + // split composition from content type + contentTypeHelper.splitCompositeContentType(scope.model, compositeContentType); + } } - - - } else { - // split composition from content type - contentTypeHelper.splitCompositeContentType(scope.model, compositeContentType); - } - }; + var availableContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes; + var countContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getCount : mediaTypeResource.getCount; + $q.all([ + //get available composite types + availableContentTypeResource(scope.model.id).then(function (result) { + scope.compositionsDialogModel.availableCompositeContentTypes = result; + // convert icons for composite content types + iconHelper.formatContentTypeIcons(scope.compositionsDialogModel.availableCompositeContentTypes); + }), + //get content type count + countContentTypeResource().then(function (result) { + scope.compositionsDialogModel.totalContentTypes = parseInt(result, 10); + }) + ]).then(function () { + //resolves when both other promises are done, now show it + scope.compositionsDialogModel.show = true; + }); + }; 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 04fde87de5..870a7d45d6 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 @@ -11,8 +11,24 @@ function LockedFieldDirective($timeout, localizationService) { - function link(scope, el, attr, ngModel) { - + function link(scope, el, attr, ngModelCtrl) { + + //watch the ngModel so we can manually update the textbox view value when it changes + // this ensures that the normal flow (i.e. a user editing the text box) occurs so that + // the parsers, validators and viewchangelisteners execute + scope.$watch("ngModel", function (newValue, oldValue) { + if (newValue !== oldValue) { + //Hack: in order for the pipeline to execute for setViewValue, the underlying $modelValue cannot + // match the value being set with the newValue, so we'll se it to undefined first. + // We could avoid this hack by setting the ngModel of the lockedField input field to a custom + // scope object, but that would mean we'd have to watch that value too in order to set the outer + // ngModelCtrl.$modelValue. It's seems like less overhead to just do this and not have 2x watches. + scope.lockedFieldForm.lockedField.$modelValue = undefined; + scope.lockedFieldForm.lockedField.$setViewValue(newValue); + scope.lockedFieldForm.lockedField.$render(); + } + }); + var input = el.find('.umb-locked-field__input'); function activate() { @@ -79,7 +95,7 @@ replace: true, templateUrl: 'views/components/umb-locked-field.html', scope: { - model: '=ngModel', + ngModel: "=", locked: "=?", placeholderText: "=?", regexValidation: "=?", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 2626fc7f33..5175dd3b4d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -7,8 +7,8 @@ var itemDefaultHeight = 200; var itemDefaultWidth = 200; - var itemMaxWidth = 300; - var itemMaxHeight = 300; + var itemMaxWidth = 200; + var itemMaxHeight = 200; function activate() { @@ -60,14 +60,18 @@ item.aspectRatio = item.width / item.height; // set max width and height - if(item.width > itemMaxWidth) { - item.width = itemMaxWidth; - item.height = itemMaxWidth / item.aspectRatio; - } - - if(item.height > itemMaxHeight) { - item.height = itemMaxHeight; - item.width = itemMaxHeight * item.aspectRatio; + // landscape + if(item.aspectRatio >= 1) { + if(item.width > itemMaxWidth) { + item.width = itemMaxWidth; + item.height = itemMaxWidth / item.aspectRatio; + } + // portrait + } else { + if(item.height > itemMaxHeight) { + item.height = itemMaxHeight; + item.width = itemMaxHeight * item.aspectRatio; + } } } @@ -100,7 +104,8 @@ var flexStyle = { "flex": flex + " 1 " + imageMinWidth + "px", - "max-width": mediaItem.width + "px" + "max-width": mediaItem.width + "px", + "min-width": "125px" }; mediaItem.flexStyle = flexStyle; @@ -109,16 +114,16 @@ } - scope.selectItem = function(item, $event, $index) { - if(scope.onSelect) { - scope.onSelect(item, $event, $index); - $event.stopPropagation(); + scope.clickItem = function(item, $event, $index) { + if(scope.onClick) { + scope.onClick(item, $event, $index); } }; - scope.clickItem = function(item) { - if(scope.onClick) { - scope.onClick(item); + scope.clickItemName = function(item, $event, $index) { + if(scope.onClickName) { + scope.onClickName(item, $event, $index); + $event.stopPropagation(); } }; @@ -147,8 +152,8 @@ scope: { items: '=', onDetailsHover: "=", - onSelect: '=', onClick: '=', + onClickName: "=", filterBy: "=" }, link: link diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js index 4dd539d147..176e1681b8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js @@ -19,8 +19,12 @@ function setTooltipPosition(event) { - var viewportWidth = null; - var viewportHeight = null; + var container = $("#contentwrapper"); + var containerLeft = container[0].offsetLeft; + var containerRight = containerLeft + container[0].offsetWidth; + var containerTop = container[0].offsetTop; + var containerBottom = containerTop + container[0].offsetHeight; + var elementHeight = null; var elementWidth = null; @@ -31,10 +35,6 @@ bottom: "inherit" }; - // viewport size - viewportWidth = $(window).innerWidth(); - viewportHeight = $(window).innerHeight(); - // element size elementHeight = el.context.clientHeight; elementWidth = el.context.clientWidth; @@ -44,19 +44,33 @@ // check to see if element is outside screen // outside right - if (position.left + elementWidth > viewportWidth) { - position.right = 0; + if (position.left + elementWidth > containerRight) { + position.right = 10; position.left = "inherit"; } // outside bottom - if (position.top + elementHeight > viewportHeight) { - position.bottom = 0; + if (position.top + elementHeight > containerBottom) { + position.bottom = 10; position.top = "inherit"; } + // outside left + if (position.left < containerLeft) { + position.left = containerLeft + 10; + position.right = "inherit"; + } + + // outside top + if (position.top < containerTop) { + position.top = 10; + position.bottom = "inherit"; + } + scope.tooltipStyles = position; + el.css(position); + } activate(); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index 4b1ed8f9c1..a59bc83ccb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -7,6 +7,15 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { return { + getCount: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetCount")), + 'Failed to retrieve count'); + }, + getAvailableCompositeContentTypes: function (contentTypeId) { return umbRequestHelper.resourcePromise( $http.get( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js index 555889e5a3..39315c6074 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js @@ -7,6 +7,15 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { return { + getCount: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetCount")), + 'Failed to retrieve count'); + }, + getAvailableCompositeContentTypes: function (contentTypeId) { return umbRequestHelper.resourcePromise( $http.get( 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 d21f297a91..4a690bf0c5 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 @@ -46,6 +46,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica throw "args.saveMethod is not defined"; } + var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true; + var self = this; //we will use the default one for content if not specified @@ -75,7 +77,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica }, function (err) { self.handleSaveError({ - redirectOnFailure: true, + redirectOnFailure: redirectOnFailure, err: err, rebindCallback: function() { rebindCallback.apply(self, [args.content, err.data]); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js index e7bfe59d42..428b9f4323 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -1,16 +1,17 @@ (function() { 'use strict'; - function listViewHelper($cookieStore) { + function listViewHelper(localStorageService) { var firstSelectedIndex = 0; + var localStorageKey = "umblistViewLayout"; function getLayout(nodeId, availableLayouts) { var storedLayouts = []; - if ($cookieStore.get("umblistViewLayout")) { - storedLayouts = $cookieStore.get("umblistViewLayout"); + if(localStorageService.get(localStorageKey)) { + storedLayouts = localStorageService.get(localStorageKey); } if (storedLayouts && storedLayouts.length > 0) { @@ -47,19 +48,18 @@ activeLayout = getFirstAllowedLayout(availableLayouts); } - setLayoutCookie(nodeId, activeLayout); + saveLayoutInLocalStorage(nodeId, activeLayout); return activeLayout; } - function setLayoutCookie(nodeId, selectedLayout) { - + function saveLayoutInLocalStorage(nodeId, selectedLayout) { var layoutFound = false; var storedLayouts = []; - if($cookieStore.get("umblistViewLayout")) { - storedLayouts = $cookieStore.get("umblistViewLayout"); + if(localStorageService.get(localStorageKey)) { + storedLayouts = localStorageService.get(localStorageKey); } if(storedLayouts.length > 0) { @@ -73,14 +73,14 @@ } if(!layoutFound) { - var cookieObject = { + var storageObject = { "nodeId": nodeId, "path": selectedLayout.path }; - storedLayouts.push(cookieObject); + storedLayouts.push(storageObject); } - document.cookie="umblistViewLayout=" + JSON.stringify(storedLayouts); + localStorageService.set(localStorageKey, storedLayouts); } @@ -106,7 +106,7 @@ var item = null; if ($event.shiftKey === true) { - + if(selectedIndex > firstSelectedIndex) { start = firstSelectedIndex; @@ -266,7 +266,7 @@ getLayout: getLayout, getFirstAllowedLayout: getFirstAllowedLayout, setLayout: setLayout, - setLayoutCookie: setLayoutCookie, + saveLayoutInLocalStorage: saveLayoutInLocalStorage, selectHandler: selectHandler, selectItem: selectItem, deselectItem: deselectItem, diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 191d75dd4f..dc6743c57d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -84,7 +84,6 @@ @import "components/umb-sub-views.less"; @import "components/umb-editor-navigation.less"; @import "components/umb-editor-sub-views.less"; -@import "components/umb-editor-toolbar.less"; @import "components/editor/subheader/umb-editor-sub-header.less"; @import "components/umb-grid-selector.less"; @import "components/umb-child-selector.less"; @@ -106,6 +105,7 @@ @import "components/tooltip/umb-tooltip-list.less"; @import "components/overlays/umb-overlay-backdrop.less"; @import "components/umb-grid.less"; +@import "components/umb-empty-state.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index 3fac647746..81fc540f9e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -9,7 +9,6 @@ // Core .btn { display: inline-block; - .ie7-inline-block(); padding: 4px 12px; margin-bottom: 0; // For input.btn font-size: @baseFontSize; @@ -20,47 +19,42 @@ background: @btnBackground; color: @black; border: 1px solid @btnBorder; - *border: 0; // Remove the border to prevent IE7's black border on input:focus - .border-radius(@baseBorderRadius); - .ie7-restore-left-whitespace(); // Give IE7 some love - -webkit-box-shadow: none; box-shadow: none; + // Hover/focus state + &:hover, + &:focus { + background: @btnBackgroundHighlight; + color: @grayDark; + background-position: 0 -15px; + text-decoration: none; - // Hover/focus state - &:hover, - &:focus { - background: @btnBackgroundHighlight; - color: @grayDark; - text-decoration: none; - background-position: 0 -15px; + // transition is only when going to hover/focus, otherwise the background + // behind the gradient (there for IE<=9 fallback) gets mismatched + .transition(background-position .1s linear); + } - // transition is only when going to hover/focus, otherwise the background - // behind the gradient (there for IE<=9 fallback) gets mismatched - .transition(background-position .1s linear); - } + // Focus state for keyboard and accessibility + &:focus { + .tab-focus(); + } - // Focus state for keyboard and accessibility - &:focus { - .tab-focus(); - } + // Active state + &.active, + &:active { + background-image: none; + outline: 0; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); + } - // Active state - &.active, - &:active { - background-image: none; - outline: 0; - .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); - } - - // Disabled state - &.disabled, - &[disabled] { - cursor: default; - background-image: none; - .opacity(65); - .box-shadow(none); - } + // Disabled state + &.disabled, + &[disabled] { + cursor: default; + background-image: none; + .opacity(65); + .box-shadow(none); + } } @@ -69,6 +63,12 @@ -webkit-box-shadow:none; } +.btn-group > .btn:first-child, +.btn-group > .btn:last-child, +.btn-group > .dropdown-toggle { + border-radius: 0; +} + // Button Sizes @@ -78,11 +78,11 @@ .btn-large { padding: @paddingLarge; font-size: @fontSizeLarge; - .border-radius(@borderRadiusLarge); } .btn-large [class^="icon-"], .btn-large [class*=" icon-"] { margin-top: 4px; + .border-radius(@borderRadiusLarge); } // Small @@ -91,6 +91,7 @@ font-size: @fontSizeSmall; .border-radius(@borderRadiusSmall); } + .btn-small [class^="icon-"], .btn-small [class*=" icon-"] { margin-top: 0; @@ -146,7 +147,6 @@ input[type="button"] { height: 32px; width: 32px; overflow: hidden; - text-decoration: none; display: inline-block; z-index: 6666; } @@ -292,3 +292,12 @@ input[type="submit"].btn { text-decoration:none; } } + +.btn-link.-underline { + display: inline-block; + text-decoration: underline; + + &:hover { + text-decoration: none; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less index d08864f5f7..96bcfe077a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less @@ -10,6 +10,13 @@ .umb-button__content { opacity: 1; transition: opacity 0.25s ease; + + display: flex; + flex-wrap: wrap; +} + +.umb-button__icon { + margin-right: 5px; } .umb-button__content.-hidden { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index 9a06c0d2dc..6e1603b3d8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -111,12 +111,13 @@ flex: 0 0 33.33%; } -.umb-card-grid li:hover, .umb-card-grid li:hover *{ +.umb-card-grid li:hover, .umb-card-grid li:hover * { background: @blue; color: white; + cursor: pointer; } -.umb-card-grid a{ +.umb-card-grid a { color: #222; text-decoration: none; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index b90271b5e5..6d43286f2d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -20,16 +20,20 @@ } -.umb-editor-container{ +.umb-editor-container { top: 101px; left: 0px; right: 0px; - bottom: 60px; + bottom: 52px; position: absolute; clear: both; overflow: auto; } +.umb-editor-wrapper.-no-footer .umb-editor-container { + bottom: 0; +} + .umb-editor-container.-stop-scrolling { overflow: hidden; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 49153ea6a8..40b6ddea99 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -7,11 +7,20 @@ box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } +.umb-overlay__form { + display: flex; + flex-wrap: nowrap; + flex-direction: column; + height: 100%; +} + .umb-overlay .umb-overlay-header { background: @grayLighter; border-bottom: 1px solid @grayLight; padding: 10px; margin-top: 0; + flex-grow: 0; + flex-shrink: 0; } @@ -29,27 +38,31 @@ } .umb-overlay .umb-overlay-container { - top: 50px; - left: 7px; - right: 7px; - bottom: 7px; - position: absolute; + flex-grow: 1; + flex-shrink: 1; + flex-basis: auto; overflow-y: auto; overflow-x: hidden; + position: relative; + height: auto; } .umb-overlay .umb-overlay-drawer { - position: absolute; - right: 0; - bottom: 0; - left: 0; - height: 31px; - padding: 10px; + flex-grow: 0; + flex-shrink: 0; + flex-basis: 31px; + padding: 10px 20px; margin: 0; - background: #ffffff; + + background: @grayLighter; + border-top: 1px solid @grayLight; } -.umb-overlay .umb-overlay-drawer .umb-overlay-drawer-content { +.umb-overlay .umb-overlay-drawer.-auto-height { + flex-basis: auto; +} + +.umb-overlay .umb-overlay-drawer .umb-overlay-drawer__align-right { display: flex; justify-content: flex-end; } @@ -74,7 +87,6 @@ } .umb-overlay.umb-overlay-center .umb-overlay-container { - top: 68px; padding: 20px; } @@ -90,8 +102,7 @@ } .umb-overlay.umb-overlay-target .umb-overlay-container { - top: 68px; - bottom: 58px; + padding: 10px; } /* ---------- OVERLAY RIGHT ---------- */ @@ -105,16 +116,12 @@ } .umb-overlay.umb-overlay-right .umb-overlay-header { - height: 100px; + flex-basis: 100px; padding: 20px; box-sizing: border-box; } .umb-overlay.umb-overlay-right .umb-overlay-container { - top: 100px; - left: 0; - right: 0; - bottom: 51px; padding: 20px; } @@ -130,16 +137,12 @@ } .umb-overlay.umb-overlay-left .umb-overlay-header { - height: 100px; + flex-basis: 100px; padding: 20px; box-sizing: border-box; } .umb-overlay.umb-overlay-left .umb-overlay-container { - top: 100px; - left: 0; - right: 0; - bottom: 51px; padding: 20px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-toolbar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-toolbar.less deleted file mode 100644 index a4f3c902ed..0000000000 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-toolbar.less +++ /dev/null @@ -1,18 +0,0 @@ -.umb-editor-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: 20px; - height: 25px; -} - -.umb-editor-toolbar__tool { - margin-left: 20px; - cursor: pointer; - display: flex; - align-items: center; -} - -.umb-editor-toolbar__tool-icon { - font-size: 20px; - margin-right: 5px; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-empty-state.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-empty-state.less new file mode 100644 index 0000000000..9ed61bcb39 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-empty-state.less @@ -0,0 +1,23 @@ +.umb-empty-state { + font-size: @fontSizeMedium; + line-height: 1.8em; + color: @grayMed; + text-align: center; +} + +.umb-empty-state.-small { + font-size: @fontSizeSmall; +} + +.umb-empty-state.-large { + font-size: @fontSizeLarge; +} + +.umb-empty-state.-center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%,-50%); + width: 80%; + max-width: 400px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less index b82a18e3bb..14e343652d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less @@ -29,7 +29,7 @@ .umb-folder-grid__folder:hover { text-decoration: none; - border: 1px solid @blue; + background-color: darken(@grayLighter, 5%); } .umb-folder-grid__folder-description { @@ -39,12 +39,18 @@ .umb-folder-grid__folder-icon { font-size: 20px; - color: @gray; - margin-right: 20px; + margin-right: 15px; + color: @black; } .umb-folder-grid__folder-name { font-size: 13px; + color: @black; + font-weight: bold; +} + +.umb-folder-grid__folder-name:hover { + text-decoration: underline; } .umb-folder-grid__action { @@ -63,19 +69,7 @@ cursor: pointer; } -.umb-folder-grid__action:hover { - background: @blue; - transition: background 0.1s; -} - .umb-folder-grid__action.-selected { opacity: 1; background: @blue; } - -.umb-folder-grid__folder:hover .umb-folder-grid__action:not(.-selected) { - opacity: 1; - animation: fadeIn; - animation-duration: 0.2s; - animation-timing-function: ease-in-out; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less index 99e6378cca..6dda61928e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -223,7 +223,6 @@ padding: 5px; border: 1px dashed #ccc; margin: 10px; - border-radius: 5px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index 2768c370c9..002e857b3c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -279,6 +279,10 @@ input.umb-group-builder__group-sort-value { cursor: auto; } +.umb-group-builder__property-preview .help-inline { + display:none !important; +} + .umb-group-builder__property-preview-overlay { position: absolute; top: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index 01c1cf5206..43cb5a7f6e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -9,10 +9,25 @@ } .umb-media-grid__item { - margin: 2px; + margin: 10px; position: relative; - background: @grayLighter; overflow: hidden; + cursor: pointer; + display: flex; + align-content: center; + align-items: center; + align-self: stretch; + box-shadow: 0 1px 1px 0 rgba(0,0,0,.2); + justify-content: center; + transition: box-shadow 100ms; +} + +.umb-media-grid__item.-file { + background-color: @grayLighter; +} + +.umb-media-grid__item.-selected { + box-shadow: 0 2px 8px 0 rgba(0,0,0,.35); } .umb-media-grid__item:hover { @@ -20,20 +35,32 @@ } .umb-media-grid__item-image { - width: 100%; - max-width: 100%; + max-width: 100% !important; height: auto; + position: relative; } -.umb-media-grid__item-image.-faded { - opacity: 0.5; - transition: opacity 100ms ease-in; +.umb-media-grid__item-image-placeholder { + width: 100%; + max-width: 100%; + height: auto; + position: relative; +} + +.umb-media-grid__image-background { + content: ""; + background: url("../img/checkered-background.png"); + background-repeat: repeat; + opacity: 0.5; + top: 0; + left: 0; + bottom: 0; + right: 0; + position: absolute; } .umb-media-grid__item-overlay { display: flex; - justify-content: center; - align-items: center; opacity: 0; position: absolute; right: 0; @@ -42,19 +69,36 @@ z-index: 100; padding: 5px 10px; box-sizing: border-box; - font-size: 13px; + font-size: 11px; overflow: hidden; color: white; white-space: nowrap; - background: rgba(0, 0, 0, 0.4); - background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.75)); + background: @blue; + transition: opacity 150ms; +} + +.umb-media-grid__item.-file .umb-media-grid__item-overlay { + opacity: 1; + color: @gray; + background: @grayLighter; +} + +.umb-media-grid__item.-file:hover .umb-media-grid__item-overlay, +.umb-media-grid__item.-file.-selected .umb-media-grid__item-overlay { + color: @white; + background: @blue; +} + +.umb-media-grid__info { + margin-right: 5px; +} + +.umb-media-grid__item-overlay.-locked { + opacity: 1; } .umb-media-grid__item:hover .umb-media-grid__item-overlay { opacity: 1; - animation: fadeIn; - animation-duration: 0.2s; - animation-timing-function: ease-in-out; } .umb-media-grid__item-name { @@ -63,76 +107,30 @@ text-overflow: ellipsis; } -.umb-media-grid__item-non-image-name { - position: absolute; - top: 20px; - font-size: 12px; - text-align: center; - width: 100%; - padding: 0 10px; - box-sizing: border-box; -} - - .umb-media-grid__item-icon { color: @gray; position: absolute; - top: 50%; + top: 45%; left: 50%; - font-size: 40px; + font-size: 40px !important; transform: translate(-50%,-50%); } -.umb-media-grid__actions { - position: absolute; - z-index: 2; - width: 100%; - top: 0; - padding: 10px; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: flex-end; -} - -.umb-media-grid__action { - opacity: 0; - border: 1px solid #ffffff; - width: 25px; - height: 25px; - background: rgba(0, 0, 0, 0.4); - border-radius: 50px; - box-sizing: border-box; - display: flex; - justify-content: center; - align-items: center; - color: #ffffff; - margin-left: 7px; - cursor: pointer; -} - -.umb-media-grid__action.-not-clickable { - cursor: default; -} - -.umb-media-grid__action:first-child { - margin-left: 0; -} - -.umb-media-grid__action:hover { - background: @blue; - transition: background 0.1s; -} - -.umb-media-grid__action.-selected { - opacity: 1; - background: @blue; -} - -.umb-media-grid__item:hover .umb-media-grid__action:not(.-selected), -.umb-media-grid__folder:hover .umb-media-grid__action:not(.-selected) { - opacity: 1; - animation: fadeIn; - animation-duration: 0.2s; - animation-timing-function: ease-in-out; +.umb-media-grid__checkmark { + position: absolute; + z-index: 2; + top: 10px; + right: 10px; + width: 25px; + height: 25px; + border: 1px solid #ffffff; + background: @blue; + border-radius: 50px; + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; + color: #ffffff; + margin-left: 7px; + transition: background 100ms; } diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 056dc0fc8a..44c8474759 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -17,7 +17,9 @@ label small, .guiDialogTiny { } label.control-label { - padding: 8px 10px 0 0 !important; + padding: 0 10px 0 0 !important; + font-weight: bold; + } .umb-status-label{ @@ -66,6 +68,7 @@ label.control-label { top: 6px; left: 4px; color: @grayLight; + pointer-events: none; } .form-search input { width: 90%; @@ -79,6 +82,28 @@ label.control-label { } +.form-search .search-input { + font-weight: bold; + border-color: @grayLight; + + &:hover, + &:focus, + &:focus:hover { + border-color: #ccc; + } + + &:-moz-placeholder { + font-weight: normal; + } + &:-ms-input-placeholder { + font-weight: normal; + } + &::-webkit-input-placeholder { + font-weight: normal; + } +} + + // GENERAL STYLES // -------------- @@ -175,6 +200,7 @@ input.-full-width-input { width: 100%; box-sizing: border-box; padding: 4px 6px; + height: 30px; } // Reset appearance properties for textual inputs and textarea @@ -447,7 +473,7 @@ input[type="checkbox"][readonly] { // -------------------------- // Error -.show-validation.ng-invalid .control-group.error, +.show-validation.ng-invalid .control-group.error, .show-validation.ng-invalid .umb-panel-header-content-wrapper { .formFieldState(@formErrorText, @formErrorText, @formErrorBackground); } @@ -537,7 +563,6 @@ input[type="checkbox"][readonly] { margin-bottom: 0; // prevent bottom margin from screwing up alignment in stacked forms *margin-left: 0; vertical-align: top; - .border-radius(0 @inputBorderRadius @inputBorderRadius 0); // Make input on top when focused so blue border and shadow always show &:focus { z-index: 2; @@ -574,31 +599,14 @@ input[type="checkbox"][readonly] { .btn { margin-right: -1px; } - .add-on:first-child, - .btn:first-child { - .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); - } } .input-append { - input, - select, - .uneditable-input { - .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); - + .btn-group .btn:last-child { - .border-radius(0 @inputBorderRadius @inputBorderRadius 0); - } - } .add-on, .btn, .btn-group { margin-left: -1px; } - .add-on:last-child, - .btn:last-child, - .btn-group:last-child > .dropdown-toggle { - .border-radius(0 @inputBorderRadius @inputBorderRadius 0); - } } // Remove all border-radius for inputs with both prepend and append diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index 04da8fb0af..7e5645d6cd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -18,14 +18,24 @@ .controls-row img { max-width: none; } + +.thumbnail { + border-radius: 0px; +} + .thumbnail img { - max-width: 100% !important + max-width: 100% !important; + width: 100%; } #mapCanvas img { max-width: none !important; } +.btn-group .dropdown-backdrop { + display: none; +} + /* loading animation for iframes and content pages */ iframe, .content-column-body { background: center center url(../img/loader.gif) no-repeat; @@ -48,7 +58,7 @@ iframe, .content-column-body { transform: translate(-300px, 0) scale(4); font-size: 23px; direction: ltr; - cursor: pointer; + cursor: pointer; } @@ -67,4 +77,4 @@ iframe, .content-column-body { } .icon-chevron-down:before { content: "\e0c9"; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index 27971007ae..017599afdb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -309,10 +309,9 @@ text-decoration: none; } -.list-view-layout__remove-layout { - flex: 2; - text-align: right; - font-size: 20px; +.list-view-layout__remove { + position: relative; + cursor: pointer; } .list-view-add-layout { diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index bfcb3c9220..e29d9173a2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -197,8 +197,6 @@ h5{ left: 2px } -.umb-dashboard-control a{text-decoration: underline;} - .umb-dashboard-control iframe{ position: absolute; display: block; width: 99%; height: 99%; overflow: auto !important;} @@ -577,26 +575,11 @@ input[type=checkbox]:checked + .input-label--small { line-height: 20px; } -.umb-empty-state-text { - font-size: @fontSizeMedium; - line-height: 1.8em; - color: @grayMed; - text-align: center; -} -.umb-empty-state-text.-small { - font-size: @fontSizeSmall; -} - -.umb-empty-state-text.-large { - font-size: @fontSizeLarge; -} - -.umb-empty-state-text.-center { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%,-50%); - width: 80%; - max-width: 400px; +// Datepicker styles +.bootstrap-datetimepicker-widget, +.bootstrap-datetimepicker-widget td, +.bootstrap-datetimepicker-widget th, +.bootstrap-datetimepicker-widget td span { + border-radius: 0 !important; } diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index c79f4b6420..c8291f0c67 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -131,20 +131,18 @@ } // Actual tabs (as links) .nav-tabs > li > a { - -webkit-border-radius: @tabsBorderRadius; - border-radius: @tabsBorderRadius; color: @gray; padding-top: 5px; padding-bottom: 4px; line-height: @baseLineHeight; border: 1px solid transparent; - .border-radius(4px 4px 0 0); + &:hover { color: @black; } &:hover, &:focus { - border-color: @grayLighter @grayLighter #ddd; + border-color: @grayLighter @grayLighter #ddd; } } // Active state, and it's :hover/:focus to override normal :hover/:focus @@ -249,6 +247,14 @@ // DROPDOWNS // --------- +.dropdown-menu { + border-radius: 0; +} + +.dropdown-menu > li > a { + padding: 8px 20px; +} + .nav-tabs .dropdown-menu { .border-radius(0 0 6px 6px); // remove the top rounded corners here since there is a hard edge above the menu } 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 082e8d079d..b3cb034596 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -70,7 +70,7 @@ div.umb-codeeditor .umb-btn-toolbar { // // RTE // -------------------------------------------------- -.mce-tinymce{border: 1px solid @grayLight !important; } +.mce-tinymce{border: 1px solid @grayLight !important; border-radius: 0px !important;} .mce-panel{background: @grayLighter !important; border-color: @grayLight !important;} .mce-btn-group, .mce-btn{border: none !important; background: none !important;} .mce-ico{font-size: 12px !important; color: @blackLight !important;} @@ -122,18 +122,25 @@ ul.color-picker li a { // // Media picker // -------------------------------------------------- -.umb-mediapicker .add-link{ +.umb-mediapicker .add-link { display: inline-block; height: 120px; width: 120px; text-align: center; color: @grayLight; - border: 2px @grayLight dashed !important; + border: 2px @grayLight dashed; line-height: 120px; text-decoration: none; + + transition: all 150ms ease-in-out; + + &:hover { + color: @blue; + border-color: @blue; + } } -.umb-mediapicker .picked-image{ +.umb-mediapicker .picked-image { position: absolute; bottom: 10px; right: 10px; @@ -174,27 +181,30 @@ ul.color-picker li a { list-style-type: none; margin: 0; padding: 0; - display: block; + display: flex; + flex-direction: row; + flex-wrap: wrap; } -.umb-sortable-thumbnails li{ - display: inline-block; - border: 1px solid @grayLighter; - padding: 2px; - background: white; - margin: 5px; +.umb-sortable-thumbnails li { position: relative; - text-align: center; - vertical-align: top; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + flex-wrap: wrap; + padding: 2px; + margin: 5px; + background: white; + border: 1px solid #f8f8f8; } -.umb-sortable-thumbnails li:hover a{ +.umb-sortable-thumbnails li:hover a { display: block; } -.umb-sortable-thumbnails li img -{ +.umb-sortable-thumbnails li img { max-width:100%; max-height:100%; margin:auto; @@ -207,14 +217,15 @@ ul.color-picker li a { max-height: none !important; } -.umb-sortable-thumbnails .icon-holder .icon{ - font-size: 60px; - line-height: 70px; +.umb-sortable-thumbnails .icon-holder { + text-align: center; } -.umb-sortable-thumbnails .icon-holder * -{ - color: @grayLight; - display: block + +.umb-sortable-thumbnails .icon-holder .icon{ + font-size: 40px; + line-height: 50px; + color: @gray; + display: block; } @@ -233,6 +244,10 @@ ul.color-picker li a { left: 0; } + .umb-cropper img { + max-width: initial; + } + .umb-cropper .overlay, .umb-cropper-gravity .overlay { top: 0; left: 0; @@ -560,18 +575,21 @@ ul.color-picker li a { background: #efefef; float: left; margin-right: 30px; - margin-bottom: 30px + margin-bottom: 30px; } + .umb-fileupload ul { list-style: none; vertical-align: middle; - margin-bottom: 0px + margin-bottom: 0px; } + .umb-fileupload label { vertical-align: middle; padding-left: 7px; - font-weight: normal + font-weight: normal; } + .umb-fileupload .preview-file { color: #666; height: 45px; @@ -579,10 +597,12 @@ ul.color-picker li a { text-align: center; text-transform: uppercase; font-size: 10px; - padding-top: 27px + padding-top: 27px; } + .umb-fileupload input { - font-size: 12px + font-size: 12px; + line-height: 1; } // diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index 4e2e3e77e8..33f4b6373d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -29,13 +29,22 @@ min-width: 100%; width: auto; } -.umb-tree li.current > div, .umb-tree div.selected { +.umb-tree li.current > div, +.umb-tree div.selected { background: @blue; } -.umb-tree li.current > div a.umb-options i, .umb-tree div.selected i { +.umb-tree li.current > div a.umb-options i, +.umb-tree div.selected i { background: #fff; border-color: @blue; + transition: opacity 120ms ease; } + +.umb-tree li.current > div a.umb-options:hover i, +.umb-tree div.selected i { + opacity: .7; +} + .umb-tree li.current > div a, .umb-tree li.current > div i.icon, .umb-tree li.current > div ins { @@ -43,13 +52,20 @@ background: @blue; border-color: @blue; } + .umb-tree li.root > div { - padding-left: 20px; + padding: 0; } - .umb-tree li.root > div h5 { - margin-top: 10px; + margin: 0; + width: 100%; +} + +.umb-tree li.root > div h5 > a { + display: block; + padding: 20px 0 20px 20px; + box-sizing: border-box; } .umb-tree * { @@ -68,9 +84,9 @@ } .umb-tree a { - vertical-align: middle; - display: inline-block; cursor:pointer; + text-decoration: none; + outline: none; } .umb-tree a:hover { @@ -78,10 +94,13 @@ } .umb-tree div { - vertical-align: middle; padding: 5px 0 5px 0; position: relative; overflow: hidden; + display: flex; + flex-wrap: nowrap; + align-items: center; + } .umb-tree a.noSpr { @@ -95,7 +114,8 @@ visibility: visible; } -.umb-tree li.root > div a, .umb-tree li.root h5, .umb-tree-header { +.umb-tree li.root > div a, +.umb-tree li.root h5, .umb-tree-header { text-transform: uppercase; color: #b3b3b3; font-weight: bold; @@ -103,14 +123,18 @@ } .umb-tree ins { - vertical-align: middle; margin: -4px 0 0 -16px; width: 16px; height: 16px; - display: inline-block; visibility: hidden; text-decoration: none; font-size: 12px; + + transition: opacity 120ms ease; +} + +.umb-tree ins:hover { + opacity: .7; } .umb-tree li:hover ins { @@ -118,12 +142,27 @@ cursor: pointer } +.umb-tree li div { + padding: 0; +} + +.umb-tree li > div a:not(.umb-options) { + padding: 6px 0; + width: 100%; + display: flex; +} + .umb-tree .icon { vertical-align: middle; - margin: 1px 13px 1px 0px; + margin: 0 13px 0 0; color: #21201C; font-size: 20px; } + +.umb-tree-icon { + cursor: pointer; +} + .umb-tree i.noSpr { display: inline-block; margin-top: 1px; @@ -166,8 +205,8 @@ a.umb-options { text-align: center; position: absolute; right: 10px; - top: 3px; - padding: 0 3px 3px 5px; + top: 2px; + padding: 0 5px 5px 5px; border: 1px solid transparent; } @@ -180,6 +219,10 @@ a.umb-options i { margin: 0 2px 0 0; } +a.umb-options i:last-child { + margin: 0; +} + a.umb-options:hover { background: @btnBackgroundHighlight; border: 1px solid @grayLight; @@ -187,7 +230,10 @@ a.umb-options:hover { } li.root > div > a.umb-options { - top: 13px; + top: 18px; + + display: flex; + padding: 10px 5px; } .hide-options a.umb-options{display: none !important} @@ -275,6 +321,7 @@ div.is-container:before{ font-size: 8px; padding-left: 13px; padding-top: 8px; + pointer-events: none; } div.locked:before{ @@ -306,19 +353,16 @@ div.locked:before{ } .umb-actions li.sep { - margin-top: 4px; display: block; border-top: 1px solid #efefef; } -.umb-actions li.sep a { - margin-top: 4px; -} + .umb-actions a { white-space: nowrap; display: block; font-size: 14px; color: @black; - padding: 4px 25px 4px 20px; + padding: 8px 25px 8px 20px; text-decoration: none; cursor: pointer; } @@ -360,7 +404,6 @@ div.locked:before{ } .umb-actions-child li .menu-label { font-size: 12px; - margin-bottom: 10px; color: #000; margin-left: 20px; } @@ -416,9 +459,13 @@ div.locked:before{ // ------------------------ .umb-tree li div.l{ -width:100%; -height:1px; -overflow:hidden; + width:100%; + height:1px; + overflow:hidden; + + position: absolute; + left: 0; + bottom: 0; } .umb-tree li div.l div { diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 58303a7f37..970317fca9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -17,7 +17,7 @@ @grayMed: #999; @grayLight: #d9d9d9; @grayLighter: #f8f8f8; -@white: #fff; +@white: #fff; // Accent colors @@ -86,9 +86,11 @@ @paddingSmall: 2px 10px; // 26px @paddingMini: 0 6px; // 22px -@baseBorderRadius: 2px; -@borderRadiusLarge: 6px; -@borderRadiusSmall: 3px; + +// Disabled this to keep consistency throughout the backoffice UI. Untill a better solution is thought up, this will do. +@baseBorderRadius: 0px; // 2px; +@borderRadiusLarge: 0px; // 6px; +@borderRadiusSmall: 0px; // 3px; // Tables diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html index 452743d19f..3136bc28e7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html @@ -7,7 +7,9 @@ class="umb-dashboard" val-form-manager> - + -
+

{{property.caption}}

diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/compositions/compositions.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/compositions/compositions.html index 1e06166970..2cdcef5ee6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/compositions/compositions.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/compositions/compositions.html @@ -14,14 +14,21 @@ Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists.
-
- This content type is used in a composition, and therefore cannot be composed itself. -
+ + There are no content types available to use as a composition. + + + This content type is used in a composition, and therefore cannot be composed itself. +
    -
  • +
  • -
    +
  • -
+ \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js index 5b2dbbb26d..d8a15f841b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js @@ -28,7 +28,7 @@ { active: false, id: 2, - label: "Re-use", + label: "Reuse", alias: "Reuse", userConfigured: [] } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html index 9d1735f174..0ddf13b0ea 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html @@ -28,8 +28,11 @@
{{key}}
    -
  • - +
  • + {{ systemDataType.name }} @@ -49,8 +52,11 @@
    {{key}}
      -
    • - +
    • + {{ dataType.name }} diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html index a332b2c77d..304df7dd22 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html @@ -11,8 +11,11 @@
        -
      • - +
      • + {{ availableItem.name }} diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html index ecd8943a63..37bc70bd77 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html @@ -30,7 +30,11 @@
      -
      There are no parameters for this macro
      + + There are no parameters for this macro + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index 48ac33125c..4ad796ee11 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -5,6 +5,7 @@ angular.module("umbraco") var dialogOptions = $scope.model; + $scope.disableFolderSelect = dialogOptions.disableFolderSelect; $scope.onlyImages = dialogOptions.onlyImages; $scope.showDetails = dialogOptions.showDetails; $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; @@ -71,6 +72,10 @@ angular.module("umbraco") $scope.gotoFolder = function(folder) { + if(!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + if(!folder){ folder = {id: -1, name: "Media", icon: "icon-folder"}; } @@ -123,9 +128,14 @@ angular.module("umbraco") }; $scope.clickHandler = function(image, event, index) { - if (image.isFolder) { - $scope.gotoFolder(image); + if ($scope.disableFolderSelect) { + $scope.gotoFolder(image); + } else { + eventsService.emit("dialogs.mediaPicker.select", image); + selectImage(image); + } + } else { eventsService.emit("dialogs.mediaPicker.select", image); @@ -142,39 +152,33 @@ angular.module("umbraco") }; + $scope.clickItemName = function(item) { + if(item.isFolder) { + $scope.gotoFolder(item); + } + }; + function selectImage(image) { - if ($scope.model.selectedImages.length > 0) { + if(image.selected) { - var selectImage = false; + for(var i = 0; $scope.model.selectedImages.length > i; i++) { - for (var i = 0; i < $scope.model.selectedImages.length; i++) { - - var selectedImage = $scope.model.selectedImages[i]; - - if (image.key === selectedImage.key) { + var imageInSelection = $scope.model.selectedImages[i]; + if(image.key === imageInSelection.key) { image.selected = false; $scope.model.selectedImages.splice(i, 1); - selectImage = false; - } else { - selectImage = true; } - - } - - if (selectImage) { - - if(!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - - image.selected = true; - $scope.model.selectedImages.push(image); } } else { - $scope.model.selectedImages.push(image); + + if(!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + image.selected = true; + $scope.model.selectedImages.push(image); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html index 68752df784..b4d65167e7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html @@ -70,7 +70,8 @@ + on-click="clickHandler" + on-click-name="clickItemName"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index 76fc8509ce..2e530235d3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -267,12 +267,23 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", function multiSelectItem(item) { - var i = $scope.model.selection.indexOf(item); + var found = false; + var foundIndex = 0; - if (i < 0) { - $scope.model.selection.push(item); + if($scope.model.selection.length > 0) { + for(i = 0; $scope.model.selection.length > i; i++) { + var selectedItem = $scope.model.selection[i]; + if(selectedItem.id === item.id) { + found = true; + foundIndex = i; + } + } + } + + if(found) { + $scope.model.selection.splice(foundIndex, 1); } else { - $scope.model.selection.splice(i, 1); + $scope.model.selection.push(item); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index c88dda456c..3e87bccde6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -1,6 +1,6 @@ -
      - + @@ -8,11 +8,11 @@
    • -
    +
- +
-
- - \ No newline at end of file + + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html index 003031f834..0e3f1d0a4c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html @@ -10,7 +10,7 @@ - + {{label}} {{label}} @@ -18,7 +18,7 @@ - +
@@ -147,7 +147,7 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html index d2851a2492..df235fb254 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html @@ -10,8 +10,8 @@ This checks the data integrity for the xml structures for content, media and members that are stored in the cmsContentXml table. This does not check the data integrity of the xml cache file, only the xml structures stored in the database used to create the xml cache file.

-
- {{value.label}} ... +
+ {{value.label}} ... Checking... Ok Error @@ -28,4 +28,4 @@ -
\ No newline at end of file +
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/media/desktopmediauploader.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/media/desktopmediauploader.html index 00ff5db3c0..59a50f44fb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/media/desktopmediauploader.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/media/desktopmediauploader.html @@ -1,14 +1,14 @@

Desktop Media Uploader

- +

Desktop Media Uploader is a small desktop application that you can install on your computer which allows you to easily upload media items directly to the media section.

The badge below will auto configure itself based upon whether you already have Desktop Media Uploader installed or not.

Just click the Install Now / Upgrade Now / Launch Now link to perform that action.

-

Download Desktop Media Uploader now.

+

Download Desktop Media Uploader now.

- This application requires Adobe® AIR™ to be installed for Mac OS or Windows. + This application requires Adobe® AIR™ to be installed for Mac OS or Windows.

@@ -35,4 +35,4 @@ swfobject.embedSWF("/umbraco/dashboard/swfs/airinstallbadge.swf", "dmu-badge", "215", "180", "9.0.115", "/umbraco/dashboard/swfs/expressinstall.swf", flashvars, params, attributes); // ]]> - --> \ No newline at end of file + --> diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/media/mediadashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/media/mediadashboardintro.html index d97b77ca88..0af91b4544 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/media/mediadashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/media/mediadashboardintro.html @@ -10,5 +10,4 @@
  • Drag the files and folders you wish to upload directly into the Desktop Media Uploader application
  • Click Upload to start uploading
  • -

    For a more thorough guide on how to use the Desktop Media Uploader, checkout this video.

    - \ No newline at end of file +

    For a more thorough guide on how to use the Desktop Media Uploader, checkout this video.

    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html index 98d15dd472..d42d3ebf7c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html @@ -1,10 +1,10 @@

    Start here

    - +

    Get started with Members right now

    Use the tool below to search for an existing member.

    More about members

    \ No newline at end of file +
  • Learn about how to protect pages of your site from this Wiki entry
  • + diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index facb38e577..16bb80c0d1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -5,10 +5,10 @@

    Find out more:

    diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index f0dff7e2f3..f2205a1d8e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -130,10 +130,10 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig $scope.save = function() { - $scope.page.saveButtonState = "busy"; - if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { + $scope.page.saveButtonState = "busy"; + dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create) .then(function(data) { diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html index 1c80fa44c1..fc53982b90 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html @@ -3,12 +3,11 @@
    - + - +
    -
    Allow as root
    - Allow editors to create content if this type in the root of the content tree +
    +
    @@ -17,8 +18,8 @@
    -
    Allowed child node types
    - Allow content of these types to be created underneath content this type +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/media/edit.html b/src/Umbraco.Web.UI.Client/src/views/media/edit.html index 5bcd4cb750..8929d143b7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/edit.html @@ -3,11 +3,10 @@ - + - + +
    -
    Allow as root
    - Allow editors to create content if this type in the root of the content tree +
    +
    @@ -17,8 +18,8 @@
    -
    Allowed child node types
    - Allow content of these types to be created underneath content this type +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/member/edit.html b/src/Umbraco.Web.UI.Client/src/views/member/edit.html index 80a9d39756..51b937cd85 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/member/edit.html @@ -3,11 +3,10 @@ - + - + +
      -
    • +
    • - - - Choose... + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html index 8d44b008a1..b2373f2d12 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html @@ -1,3 +1,3 @@
      -
      +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js index fe413e0159..693bd49ec6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js @@ -44,6 +44,7 @@ function dateTimePickerController($scope, notificationsService, assetsService, a $scope.datePickerForm.datepicker.$setValidity("pickerError", true); $scope.hasDatetimePickerValue = true; $scope.datetimePickerValue = e.date.format($scope.model.config.format); + $scope.model.value = $scope.datetimePickerValue; } else { $scope.hasDatetimePickerValue = false; @@ -96,11 +97,11 @@ function dateTimePickerController($scope, notificationsService, assetsService, a }); if ($scope.hasDatetimePickerValue) { - //assign value to plugin/picker - var dateVal = $scope.model.value ? moment($scope.model.value, $scope.model.config.format) : moment(); + var dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment(); + element.datetimepicker("setValue", dateVal); - $scope.datetimePickerValue = moment($scope.model.value).format($scope.model.config.format); + $scope.datetimePickerValue = dateVal.format($scope.model.config.format); } element.find("input").bind("blur", function() { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html index 898afe88a3..e6d49e5c6c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html @@ -2,7 +2,7 @@
    - - - Required Invalid email -
    +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 47a4f90187..862d3bab51 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -149,31 +149,28 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag }; angular.module("umbraco") .controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController) - .run(function(mediaHelper, umbRequestHelper){ + .run(function(mediaHelper, umbRequestHelper, assetsService){ if (mediaHelper && mediaHelper.registerFileResolver) { - - //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource - // they contain different data structures so if we need to query against it we need to be aware of this. - mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){ - if (thumbnail) { - - if (mediaHelper.detectIfImageByExtension(property.value)) { - - var thumbnailUrl = umbRequestHelper.getApiUrl( - "imagesApiBaseUrl", - "GetBigThumbnail", - [{ originalImagePath: property.value }]); - - return thumbnailUrl; - } - else { - return null; - } - + assetsService.load(["lib/moment/moment-with-locales.js"]).then( + function () { + //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource + // they contain different data structures so if we need to query against it we need to be aware of this. + mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){ + if (thumbnail) { + if (mediaHelper.detectIfImageByExtension(property.value)) { + //get default big thumbnail from image processor + var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500"; + return thumbnailUrl; + } + else { + return null; + } + } + else { + return property.value; + } + }); } - else { - return property.value; - } - }); + ); } - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index e200b2726c..c213378b23 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -1,19 +1,19 @@ 
    - -
    + +
    - +
    @@ -23,9 +23,9 @@
    • {{file.file.name}}
    • -
    +
    -
    \ No newline at end of file +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html index ff82be99b2..2108340d5c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html @@ -2,8 +2,9 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js index 24ba8edaf6..abe2b41bd1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js @@ -24,6 +24,8 @@ }; vm.addLayout = addLayout; + vm.showPrompt = showPrompt; + vm.hidePrompt = hidePrompt; vm.removeLayout = removeLayout; vm.openIconPicker = openIconPicker; @@ -48,6 +50,14 @@ } + function showPrompt(layout) { + layout.deletePrompt = true; + } + + function hidePrompt(layout) { + layout.deletePrompt = false; + } + function removeLayout($index, layout) { $scope.model.value.splice($index, 1); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.html index 2d74be6640..c71f3306cd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.html @@ -19,18 +19,26 @@
    - + {{ layout.name }} (system layout)
    - +
    - +
    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html index ccf18b0236..a3e5f07817 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -4,12 +4,19 @@ ng-if="entityType !== 'media'"> + + Sorry, we can not find what you are looking for + +
    @@ -31,15 +38,15 @@ + on-click="vm.selectFolder" + on-click-name="vm.goToItem"> + on-click="vm.selectItem" + on-click-name="vm.goToItem"> -
    + + Sorry, we can not find what you are looking for + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js index e4f7cb3c8b..0d326a1d22 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js @@ -22,12 +22,13 @@ vm.dragEnter = dragEnter; vm.dragLeave = dragLeave; - vm.onFilesQueue = onFilesQueue; + vm.onFilesQueue = onFilesQueue; vm.onUploadComplete = onUploadComplete; + vm.hoverMediaItemDetails = hoverMediaItemDetails; vm.selectItem = selectItem; vm.selectFolder = selectFolder; - vm.clickItem = clickItem; + vm.goToItem = goToItem; function activate() { vm.itemsWithoutFolders = filterOutFolders($scope.items); @@ -85,16 +86,16 @@ } - function selectItem(selectedItem, $event, index) { - listViewHelper.selectHandler(selectedItem, index, vm.itemsWithoutFolders, $scope.selection, $event); + function selectItem(item, $event, $index) { + listViewHelper.selectHandler(item, $index, vm.itemsWithoutFolders, $scope.selection, $event); } - function selectFolder(selectedItem, $event, index) { - listViewHelper.selectHandler(selectedItem, index, $scope.folders, $scope.selection, $event); + function selectFolder(folder, $event, $index) { + listViewHelper.selectHandler(folder, $index, $scope.folders, $scope.selection, $event); } - function clickItem(item) { - $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); + function goToItem(item, $event, $index) { + $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); } activate(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html index 6f52b14af1..5a234ea69d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html @@ -13,12 +13,13 @@ files-uploaded="vm.onUploadComplete" accept="{{vm.acceptedFileTypes}}" max-file-size="{{vm.maxFileSize}}" - hide-dropzone="{{!vm.activeDrag && items.length > 0 }}" + hide-dropzone="{{!vm.activeDrag && items.length > 0 || !items && options.filter }}" compact="{{ items.length > 0 }}" files-queued="vm.onFilesQueue"> - + + Sorry, we can not find what you are looking for + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 63db542ee7..0d42428334 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -155,22 +155,13 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state with simple values */ - $scope.reloadView = function (id) { + $scope.reloadView = function(id) { - $scope.viewLoaded = false; + $scope.viewLoaded = false; - listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - if($scope.entityType === 'media') { - - mediaResource.getChildFolders($scope.contentId) - .then(function(folders) { - $scope.folders = folders; - }); - - } - - getListResultsCallback(id, $scope.options).then(function (data) { + getListResultsCallback(id, $scope.options).then(function(data) { $scope.actionInProgress = false; @@ -178,11 +169,23 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie //update all values for display if ($scope.listViewResultSet.items) { - _.each($scope.listViewResultSet.items, function (e, index) { + _.each($scope.listViewResultSet.items, function(e, index) { setPropertyValues(e); }); } + if ($scope.entityType === 'media') { + + mediaResource.getChildFolders($scope.contentId) + .then(function(folders) { + $scope.folders = folders; + $scope.viewLoaded = true; + }); + + } else { + $scope.viewLoaded = true; + } + //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last // available page and then re-load again @@ -193,39 +196,37 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.reloadView(id); } - $scope.viewLoaded = true; - }); }; - $scope.$watch(function() { - return $scope.options.filter; - }, _.debounce(function(newVal, oldVal) { + var searchListView = _.debounce(function(){ $scope.$apply(function() { - if (newVal !== null && newVal !== undefined && newVal !== oldVal) { - $scope.options.pageNumber = 1; - $scope.actionInProgress = true; - $scope.reloadView($scope.contentId); - } + makeSearch(); }); - }, 1000)); + }, 500); - $scope.filterResults = function (ev) { + $scope.forceSearch = function (ev) { //13: enter - switch (ev.keyCode) { case 13: - $scope.options.pageNumber = 1; - $scope.actionInProgress = true; - $scope.reloadView($scope.contentId); + makeSearch(); break; } }; - $scope.enterSearch = function ($event) { - $($event.target).next().focus(); + $scope.enterSearch = function() { + $scope.viewLoaded = false; + searchListView(); }; + function makeSearch() { + if ($scope.options.filter !== null && $scope.options.filter !== undefined) { + $scope.options.pageNumber = 1; + //$scope.actionInProgress = true; + $scope.reloadView($scope.contentId); + } + } + $scope.isAnythingSelected = function() { if ($scope.selection.length === 0) { return false; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 879471e6bc..e1873907a1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -61,11 +61,20 @@ - +
    - +
    @@ -144,6 +153,8 @@ on-get-content="reloadView"> + + + +Required \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index 8b9babfdfa..c55fc350fa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -2,6 +2,8 @@ + Required diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 03f50a4e65..3ba030b780 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -136,12 +136,12 @@ False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - ..\packages\ImageProcessor.2.3.0.0\lib\net45\ImageProcessor.dll + + ..\packages\ImageProcessor.2.3.2.0\lib\net45\ImageProcessor.dll True - - ..\packages\ImageProcessor.Web.4.4.0.0\lib\net45\ImageProcessor.Web.dll + + ..\packages\ImageProcessor.Web.4.5.0.0\lib\net45\ImageProcessor.Web.dll True @@ -334,8 +334,9 @@ umbraco.providers - - ..\packages\UrlRewritingNet.UrlRewriter.2.0.60829.1\lib\UrlRewritingNet.UrlRewriter.dll + + ..\packages\UrlRewritingNet.UrlRewriter.2.0.7\lib\UrlRewritingNet.UrlRewriter.dll + True @@ -1679,7 +1680,7 @@ - + @@ -1995,7 +1996,6 @@ UserControl - UserControl diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 8731d206db..184112d998 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -1,819 +1,963 @@ - - - - The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files - - - Angi domene - Revisjoner - Bla gjennom - Kopier - CREATE - Opprett pakke - Slett - Deaktiver - Tøm papirkurv - Eksporter dokumenttype - Importer documenttype - Importer pakke - Rediger i Canvas - Lukk Umbraco - Flytt - Varsling - Offentlig tilgang - Publiser - Oppdater noder - Republiser hele siten - Rettigheter - Reverser - Send til publisering - Send til oversetting - Sorter - Send til publisering - Oversett - Oppdater - Avpubliser - - - Legg til domene - Domene - Domene '%0%' er nå opprettet og tilknyttet siden - Domenet '%0%' er nå slettet - Domenet '%0%' er allerede tilknyttet - Gyldige domenenavn er: "eksempel.no", "www.eksempel.no", "eksempel.no:8080" eller "https://www.eksempel.no/".<br/><br/>Stier med ett nivå støttes, f.eks. "eksempel.com/no". Imidlertid bør det unngås. Bruk heller språkinnstillingen over. - Domenet '%0%' er nå oppdatert - eller rediger eksisterende domener - Ingen tilgang. - Fjern - Ugyldig node. - Ugyldig domeneformat. - Domene er allerede tilknyttet. - Språk - Arv - Språk - Sett språk for underordnede noder eller arv språk fra overordnet.<br/>Vil også gjelde denne noden, med mindre et underordnet domene også gjelder. - Domener - - - Viser for - - - Fet - Reduser innrykk - Sett inn skjemafelt - Sett inn grafisk overskrift - Rediger HTML - Øk innrykk - Kursiv - Midtstill - Juster tekst venstre - Juster tekst høyre - Sett inn lenke - Sett inn lokal lenke (anker) - Punktmerking - Nummerering - Sett inn makro - Sett inn bilde - Rediger relasjoner - Lagre - Lagre og publiser - Lagre og send til publisering - Forhåndsvis - Velg formattering - Vis stiler - Sett inn tabell - Forhåndsvisning er deaktivert siden det ikke er angitt noen mal - - - Om siden - Alternativ lenke - (hvordan du ville beskrevet bildet over telefon) - Alternative lenker - Klikk for å redigere denne noden - Opprettet av - Opprettet den - Dokumenttype - Redigerer - Utløpsdato - Denne noden er endret siden siste publisering - Denne noden er enda ikke publisert - Sist publisert - Mediatype - Medlemsgruppe - Rolle - Medlemstype - Ingen dato valgt - Sidetittel - Egenskaper - Dette dokumentet er publisert, men ikke synlig ettersom den overliggende siden '%0%' ikke er publisert - Publisert - Publiseringsstatus - Publiseringsdato - Fjern dato - Sorteringsrekkefølgen er oppdatert - Trekk og slipp nodene eller klikk på kolonneoverskriftene for å sortere. Du kan velge flere noder ved å holde shift eller control tastene mens du velger. - Statistikk - Tittel (valgfri) - Type - Avpubliser - Sist endret - Fjern fil - Lenke til dokument - Link til media - Intern feil: dokumentet er publisert men finnes ikke i hurtigbuffer - - - Hvor ønsker du å oprette den nye %0% - Opprett under - Velg en type og skriv en tittel - - - Til ditt nettsted - - Skjul - Hvis Umbraco ikke starter, kan det skyldes at pop-up vinduer ikke er tillatt - er åpnet i nytt vindu - Omstart - Besøk - Velkommen - - - Navn på lokal link - Rediger domener - Lukk dette vinduet - Er du sikker på at du vil slette - Er du sikker på at du vil deaktivere - Vennligst kryss av i denne boksen for å bekrefte sletting av %0% element(er) - Er du sikker på at du vil forlate Umbraco? - Er du sikker? - Klipp ut - Rediger ordboksnøkkel - Rediger språk - Sett inn lokal link - Sett inn spesialtegn - Sett inn grafisk overskrift - Sett inn bilde - Sett inn lenke - Sett inn makro - Sett inn tabell - Sist redigert - Lenke - Intern link: - Ved lokal link, sett inn "#" foran link - Åpne i nytt vindu? - Makroinnstillinger - Denne makroen har ingen egenskaper du kan endre - Lim inn - Endre rettigheter for - Innholdet i papirkurven blir nå slettet. Vennligst ikke lukk dette vinduet mens denne operasjonen foregår - Papirkurven er nå tom - Når elementer blir slettet fra papirkurven vil de være slettet for alltid - <a target='_blank' href='http://regexlib.com'>regexlib.com</a> tjenesten opplever for tiden problemer som vi ikke har kontroll over. Vi beklager denne ubeleiligheten. - Søk etter et regulært uttrykk for å legge inn validering til et felt. Eksempel: 'email, 'zip-code' 'url' - Fjern makro - Obligatorisk - Nettstedet er indeksert - Hurtigbufferen er blitt oppdatert. Alt publisert innhold er nå à jour. Alt upublisert innhold er fortsatt ikke publisert. - Hurtigbufferen for siden vil bli oppdatert. Alt publisert innhold vil bli oppdatert, mens upublisert innhold vil forbli upublisert. - Antall kolonner - Antall rader - <strong>Sett en plassholder-ID</strong><br/>Ved å sette en ID på plassholderen kan du legge inn innhold i denne malen fra underliggende maler, ved å referere denne ID'en ved hjelp av et <code>&lt;asp:content /&gt;</code> element. - <strong>Velg en plassholder ID</strong> fra listen under. Du kan bare velge ID'er fra den gjeldende malens overordnede mal. - Klikk på bildet for å se det i full størrelse - Velg punkt - Se buffret node - - - Rediger de forskjellige språkversjonene for ordbokelementet '<em>%0%</em>' under.<br/>Du kan legge til flere språk under 'språk' i menyen til venstre. - Språk - - - Tillatte underordnede noder - Opprett - Slett arkfane - Beskrivelse - Ny arkfane - Arkfane - Miniatyrbilde - - - Legg til forhåndsverdi - Database datatype - Kontrollelement GUID - Kontrollelement - Knapper - Aktiver avanserte instillinger for - Aktiver kontektsmeny - Maksimum standard størrelse på innsatte bilder - Beslektede stilark - Vis etikett - Bredde og høyde - - - Dine data har blitt lagret, men før du kan publisere denne siden må du rette noen feil: - Den gjeldende Membership Provider støtter ikke endring av passord. (EnablePasswordRetrieval må være satt til sann) - %0% finnes allerede - Det var feil i dokumentet: - Det var feil i skjemaet: - Passordet bør være minst %0% tegn og inneholde minst %1% numeriske tegn - %0% må være et heltall - %0% under %1% er obligatorisk - %0% er obligatorisk - %0% under %1% er ikke i et korrekt format - %0% er ikke i et korrekt format - - - NB! Selv om CodeMirror er aktivert i konfigurasjon er det deaktivert i Internet Explorer pga. ustabilitet. - Fyll ut både alias og navn på den nye egenskapstypen! - Det er et problem med lese/skrive rettighetene til en fil eller mappe - Tittel mangler - Type mangler - Du er i ferd med å gjøre bildet større enn originalen. Det vil forringe kvaliteten på bildet, ønsker du å fortsette? - Feil i python-skriptet - Python-skriptet ble ikke lagret fordi det inneholder en eller flere feil - Startnode er slettet. Kontakt din administrator - Du må markere innhold før du kan endre stil - Det er ingen aktive stiler eller formateringer på denne siden - Sett markøren til venstre i de 2 cellene du ønsker å slå sammen - Du kan ikke dele en celle som allerede er delt. - Feil i XSLT kode - XSLT ble ikke lagret på grunn av feil i koden - Filtypen er deaktivert av administrator - - - Om - Handling - Legg til - Alias - Er du sikker? - Ramme - eller - Avbryt - Cellemargin - Velg - Lukk - Lukk vindu - Kommentar - Bekreft - Behold proposjoner - Fortsett - Kopier - Opprett - Database - Dato - Standard - Slett - Slettet - Sletter... - Design - Dimensjoner - Ned - Last ned - Rediger - Endret - Elementer - E-post - Feil - Finn - Høyde - Hjelp - Ikon - Importer - Indre margin - Sett inn - Installer - Justering - Språk - Layout - Laster - Låst - Logg inn - Logg ut - Logg ut - Makro - Flytt - Navn - Ny - Neste - Nei - av - OK - Åpne - eller - Passord - Sti - Plassholder ID - Ett øyeblikk... - Forrige - Egenskaper - E-post som innholdet i skjemaet skal sendes til - Papirkurv - Gjenværende - Gi nytt navn - Forny - Prøv igjen - Rettigheter - Søk - Server - Vis - Hvilken side skal vises etter at skjemaet er sendt - Størrelse - Sorter - Type - Søk... - Opp - Oppdater - Oppgrader - Last opp - Url - Bruker - Brukernavn - Verdi - Visning - Velkommen... - Bredde - Ja - Mappe - - - Bakgrunnsfarge - Fet - Tekstfarge - Skrifttype - Tekst - - - Side - - - Installasjonsprogrammet kan ikke koble til databasen - Kunne ikke lagre Web.Config-filen. Vennligst endre databasens tilkoblingsstreng manuelt. - Din database er funnet og identifisert som - Databasekonfigurasjon - Klikk <strong>installer</strong>-knappen for å installere Umbraco %0% databasen - Umbraco %0% har nå blitt kopiert til din database. Trykk <strong>Neste</strong> for å fortsette. - <p>Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.</p><p>For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.</p><p>Klikk <strong>prøv på nytt</strong> når du er ferdig.<br /> <a href="http://our.umbraco.org/documentation/Using-Umbraco/Config-files/webconfig7" target="_blank">Mer informasjon om redigering av web.config her.</a></p> - For å fullføre dette steget, må du vite en del informasjon om din database server ("tilkoblingsstreng").<br/> Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator. - <p> Trykk på knappen <strong>oppgrader</strong> for å oppgradere databasen din til Umbraco %0%</p> <p> Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå! </p> - Databasen din har blitt oppgradert til den siste utgaven, %0%.<br/>Trykk <strong>Neste</strong> for å fortsette. - Databasen din er av nyeste versjon! Klikk <strong>neste</strong> for å fortsette konfigurasjonsveiviseren - <strong>Passordet til standardbrukeren må endres! - <strong>Standardbrukeren har blitt deaktivert eller har ingen tilgang til Umbraco!</strong></p><p>Ingen videre handling er nødvendig. Klikk <b>neste</b> for å fortsette. - <strong>Passordet til standardbrukeren har blitt forandret etter installasjonen!</strong></p><p>Ingen videre handling er nødvendig. Klikk <strong>Neste</strong> for å fortsette. - Passordet er blitt endret! - <p> Umbraco skaper en standard bruker med login <strong> ( "admin") </ strong> og passord <strong> ( "default") </ strong>. Det er <strong> viktig </ strong> at passordet er endret til noe unikt. </ p> <p> Dette trinnet vil sjekke standard brukerens passord og foreslår hvis det må skiftes </ p> - Få en god start med våre introduksjonsvideoer - Ved å klikke på Neste-knappen (eller endre UmbracoConfigurationStatus i Web.config), godtar du lisensen for denne programvaren som angitt i boksen nedenfor. Legg merke til at denne Umbraco distribusjon består av to ulike lisenser, åpen kilde MIT lisens for rammen og Umbraco frivareverktøy lisens som dekker brukergrensesnittet. - Ikke installert. - Berørte filer og mapper - Mer informasjon om å sette opp rettigheter for Umbraco her - Du må gi ASP.NET brukeren rettigheter til å endre de følgende filer og mapper - <strong>Rettighetene er nesten perfekt satt opp!</strong><br/><br/> Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut. - Hvordan løse problemet - Klikk her for å lese tekstversjonen - Se vår <strong>innføringsvideo</strong> om å sette opp rettigheter for Umbraco eller les tekstversjonen. - <strong>Rettighetsinnstillingene kan være et problem!</strong><br/><br/> Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut. - <strong>Rettighetsinstillingene er ikke klargjort for Umbraco!</strong><br/><br/> For å kunne kjøre Umbraco, må du oppdatere rettighetsinnstillingene dine. - <strong>Rettighetsinnstillingene er perfekt!</strong><br/><br/>Du er klar for å kjøre Umbraco og installere pakker! - Løser mappeproblem - Følg denne linken for mer informasjon om problemer med ASP.NET og oppretting av mapper - Konfigurerer mappetillatelser - Umbraco trenger skrive/endre tilgang til enkelte mapper for å kunne lagre filer som bilder og PDF-dokumenter. Den lagrer også midlertidig data (aka: hurtiglager) for å øke ytelsen på websiden din. - Jeg ønsker å starte fra bunnen. - Din website er helt tom for øyeblikket. Dette er perfekt hvis du vil begynne helt forfra og lage dine egne dokumenttyper og maler. (<a href="http://Umbraco.tv/documentation/videos/for-site-builders/foundation/document-types">lær hvordan</a>) Du kan fortsatt velge å installere Runway senere. Vennligst gå til Utvikler-seksjonen og velg Pakker. - Du har akkurat satt opp en ren Umbraco plattform. Hva vil du gjøre nå? - Runway er installert - Du har nå fundamentet på plass. Velg hvilke moduler du ønsker å installer på toppen av det.<br/> Dette er vår liste av anbefalte moduler- Kryss av de du ønsker å installere, eller se den<a href="#" onclick="toggleModules(); return false;" id="toggleModuleList">fulle listen av moduler</a> - Bare anbefalt for erfarne brukere - Jeg vil starte med en enkel webside - <p> "Runway" er en enkel webside som utstyrer deg med noen grunnleggende dokumenttyper og maler. Veiviseren kan sette opp Runway for deg automatisk, men du kan enkelt endre, utvide eller slette den. Runway er ikke nødvendig, og du kan enkelt bruke Umbraco uten den. Imidlertidig tilbyr Runway et enkelt fundament basert på de beste metodene for å hjelpe deg i gang fortere enn noensinne. Hvis du velger å installere Runway, kan du også velge blant grunnleggende byggeklosser kalt Runway Moduler for å forøke dine Runway-sider. </p> <small> <em>Sider inkludert i Runway:</em> Hjemmeside, Komme-i-gang, Installere moduler.<br /> <em>Valgfrie Moduler:</em> Toppnavigasjon, Sidekart, Kontakt, Galleri. </small> - Hva er Runway - Steg 1/5 Godta lisens - Steg 2/5 Database konfigurasjon - Steg 3/5: Valider filrettigheter - Steg 4/5: Skjekk Umbraco sikkerheten - Steg 5/5: Umbraco er klar for deg til å starte! - Tusen takk for at du valgte Umbraco! - <h3>Se ditt nye nettsted</h3> Du har installert Runway, hvorfor ikke se hvordan ditt nettsted ser ut. - <h3>Mer hjelp og info</h3> Få hjelp fra vårt prisbelønte samfunn, bla gjennom dokumentasjonen eller se noen gratis videoer på hvordan man bygger et enkelt nettsted, hvordan bruke pakker og en rask guide til Umbraco terminologi - Umbraco %0% er installert og klar til bruk - For å fullføre installasjonen, må du manuelt endre <strong>web.config</strong> filen, og oppdatere AppSetting-nøkkelen <strong>UmbracoConfigurationStatus</strong> til verdien <strong>'%0%'</strong> - Du kan <strong>starte øyeblikkelig</strong> ved å klikke på "Start Umbraco" knappen nedenfor. <br/>Hvis du er <strong>ny på Umbraco</strong>, kan du finne mange ressurser på våre komme-i-gang sider. - <h3>Start Umbraco</h3> For å administrere din webside, åpne Umbraco og begynn å legge til innhold, oppdatere maler og stilark eller utvide funksjonaliteten - Tilkobling til databasen mislyktes. - Umbraco Versjon 3 - Umbraco Versjon 4 - Pass på - Denne veiviseren vil hjelpe deg gjennom prosessen med å konfigurere <strong>Umbraco %0%</strong> for en ny installasjon eller oppgradering fra versjon 3.0. <br/><br/> Trykk <strong>"neste"</strong> for å starte veiviseren. - - - Språkkode - Språk - - - Du har vært inaktiv og vil logges ut automatisk om - Forny innlogging for å lagre - - - <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.org</a></p> - Velkommen til Umbraco, skriv inn ditt brukernavn og passord i feltene under: - - - Skrivebord - Seksjoner - Innhold - - - Velg side over... - %0% er nå kopiert til %1% - Kopier til - %0% er nå flyttet til %1% - Flytt til - har blitt valgt som rot til ditt nye innhold, klikk 'ok' nedenfor. - Ingen node er valgt, vennligst velg en node i listen over før du klikker 'fortsett' - Gjeldende nodes type tillates ikke under valgt node - Gjeldende node kan ikke legges under en underordnet node - Handlingen tillates ikke. Du mangler tilgang til en eller flere underordnede noder. - Relater kopierte elementer til original(e) - - - Rediger dine varsler for %0% - -Hei %0% - -Dette er en automatisk mail for å informere om at handlingen '%1%' -er utført på siden '%2%' -av brukeren '%3%' - -Gå til http://%4%/Umbraco/default.aspx?section=content&id=%5% for å redigere. - -Ha en fin dag! - -Vennlig hilsen Umbraco roboten - - <p>Hei %0%</p> - - <p>Dette er en automatisk mail for å informere om at handlingen '%1%' - er blitt utført på siden <a href="http://%4%/actions/preview.aspx?id=%5%"><strong>'%2%'</strong></a> - av brukeren <strong>'%3%'</strong> - </p> - <div style="margin: 8px 0; padding: 8px; display: block;"> - <br /> - <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/Umbraco/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REDIGER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; - <br /> - </div> - <p> - <h3>Rettelser:</h3> - <table style="width: 100%;"> - %6% - </table> - </p> - - <div style="margin: 8px 0; padding: 8px; display: block;"> - <br /> - <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/Umbraco/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REDIGER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; - <br /> - </div> - - <p>Ha en fin dag!<br /><br /> - Vennlig hilsen Umbraco roboten - </p> - [%0%] Varsling om %1% utført på %2% - Varsling - - - Klikke browse og velg pakke fra lokal disk. Umbraco-pakker har vanligvis endelsen ".umb" eller ".zip". - Utvikler - Demonstrasjon - Dokumentasjon - Metadata - Pakkenavn - Pakken inneholder ingen elementer - Denne pakkefilen inneholder ingen elementer å avinstallere.<br/><br/>Du kan trygt fjerne pakken fra systemet ved å klikke "avinstaller pakke" nedenfor. - Ingen oppdateringer tilgjengelig - Alternativer for pakke - Lesmeg for pakke - Pakkebrønn - Bekreft avinstallering - Pakken ble avinstallert - Pakken ble vellykket avinstallert - Avinstaller pakke - Du kan velge bort elementer du ikke vil slette på dette tidspunkt, nedenfor. Når du klikker "bekreft avinstallering" vil alle elementer som er krysset av bli slettet.<br/> <span style="color:red;font-weight:bold;">Advarsel:</span> alle dokumenter, media, etc. som som er avhengig av elementene du sletter, vil slutte å virke, noe som kan føre til ustabilitet, så avinstaller med forsiktighet. Hvis du er i tvil, kontakt pakkeutvikleren. - Last ned oppdatering fra pakkeregisteret - Oppgrader pakke - Oppgraderingsinstrukser - Det er en oppdatering tilgjengelig for denne pakken. Du kan laste den ned direkte fra pakkebrønnen. - Pakkeversjon - Se pakkens nettsted - - - Lim inn med full formattering (Anbefales ikke) - Teksten du er i ferd med å lime inn, inneholder spesialtegn eller formattering. Dette kan skyldes at du kopierer fra f.eks. Microsoft Word. Umbraco kan fjerne denne spesialformatteringen automatisk slik at innholdet er mer velegnet for visning på en webside. - Lim inn som ren tekst, dvs. fjern al formattering - Lim inn og fjern uegnet formatering (anbefalt) - - - Avansert: Beskytt ved å velge hvilke brukergrupper som har tilgang til siden - Om du ønsker å kontrollere tilgang til siden ved å bruke rolle-basert autentisering,<br /> ved å bruke Umbraco's medlems-grupper - Du må opprette en medlemsgruppe før du kan bruke <br /> rollebasert autentikasjon. - Feilside - Brukt når personer logger på, men ikke har tilgang - Hvordan vil du beskytte siden din? - %0% er nå beskyttet - Beskyttelse fjernet fra %0% - Innloggingsside - Velg siden som har loginformularet - Fjern beskyttelse - Velg sidene som inneholder login-skjema og feilmelding ved feil innolgging. - Velg rollene som har tilgang til denne siden - Sett brukernavn og passord for denne siden - Enkelt: Beskytt ved hjelp av brukernavn og passord - Om du ønsker å bruke enkel autentisering via ett enkelt brukernavn og passord - - - %0% kunne ikke publiseres fordi et tredjepartstillegg avbrøt handlingen. - Inkluder upubliserte undersider - Publiserer - vennligst vent... - %0% av %1% sider har blitt publisert... - %0% er nå publisert - %0% og alle undersider er nå publisert - Publiser alle undersider - Klikk <em>ok</em> for å publisere <strong>%0%</strong> og dermed gjøre innholdet synlig for alle.<br/><br />Du kan publisere denne siden og alle dens undersider ved å krysse av <em>Publiser alle undersider</em> nedenfor. - %0% ble ikke publisert. Ett eller flere felter ble ikke godkjent av validering. - %0% kan ikke publiseres fordi en overordnet side ikke er publisert. - - - Legg til ekstern lenke - Legg til intern lenke - Legg til - Tittel - Intern side - Url - Flytt ned - Flytt opp - Åpne i nytt vindu - Fjern lenke - - - Gjeldende versjon - Dette viser forskjellene mellom den gjeldende og den valgte versjonen<br /><del>Rød</del> tekst vil ikke bli vist i den valgte versjonen. , <ins>grønn betyr lagt til</ins> - Dokumentet er tilbakeført til en tidligere versjon - Dette viser den valgte versjonen som HTML, bruk avviksvisningen hvis du ønsker å se forksjellene mellom to versjoner samtidig. - Tilbakefør til - Velg versjon - Vis - - - Rediger scriptfilen - - - Concierge - Innhold - Courier - Utvikler - Umbraco konfigurasjonsveiviser - Mediaarkiv - Medlemmer - Nyhetsbrev - Innstillinger - Statistikk - Oversettelse - Brukere - - - Standardmal - Ordboksnøkkel - For å importere en dokumenttype, finn ".udt" filen på datamaskinen din ved å klikke "Utforsk" knappen og klikk "Importer" (du vil bli spurt om bekreftelse i det neste skjermbildet) - Ny tittel på arkfane - Nodetype - Type - Stilark - Stilark-egenskap - Arkfane - Tittel på arkfane - Arkfaner - Hovedinnholdstype aktivert - Denne dokumenttypen bruker - som hoveddokumenttype. Arkfaner fra hoveddokumenttyper vises ikke og kan kun endres på hoveddokumenttypen selv. - - - Sortering ferdig. - Dra elementene opp eller ned for å arrangere dem. Du kan også klikke kolonneoverskriftene for å sortere alt på en gang. - Vennligst vent. Elementene blir sortert, dette kan ta litt tid.<br/> <br/> Ikke lukk dette vinduet under sortering - - - Publisering ble avbrutt av et tredjepartstillegg - Egenskaptypen finnes allerede - Egenskapstype opprettet - Navn: %0% <br /> DataType: %1% - Egenskapstype slettet - Innholdstype lagret - Du har opprettet en arkfane - Arkfane slettet - Arkfane med id: %0% slettet - Stilarket ble ikke lagret - Stilarket ble lagret - Stilark lagret uten feil - Datatype lagret - Ordbokelement lagret - Publiseringen feilet fordi den overliggende siden ikke er publisert - Innhold publisert - og er nå synlig for besøkende - Innhold lagret - Husk å publisere for å gjøre endringene synlig for besøkende - Sendt for godkjenning - Endringer har blitt sendt til godkjenning - Medlem lagret - Stilarksegenskap lagret - Stilark lagret - Mal lagret - Feil ved lagring av bruker (sjekk loggen) - Bruker lagret - Filen ble ikke lagret - Filen kunne ikke lagres. Vennligst sjekk filrettigheter - Filen ble lagret - Filen ble lagret uten feil - Språk lagret - Python-skriptet ble ikke lagret - Python-skriptet kunne ikke lagres fordi det inneholder en eller flere feil - Python-skriptet er lagret! - Ingen feil i python-skriptet! - Malen ble ikke lagret - Vennligst forviss deg om at du ikke har to maler med samme alias - Malen ble lagret - Malen ble lagret uten feil! - XSLT-koden ble ikke lagret - XSLT-koden inneholdt en feil - XSLT-koden ble ikke lagret, sjekk filrettigheter - XSLT lagret - Ingen feil i XSLT! - Media lagret - Brukertypen lagret - Innhold avpublisert - Delmal lagret - Delmal lagret uten feil - Delmal ble ikke lagret! - En feil oppsto ved lagring av delmal - - - Bruk CSS syntaks f.eks: h1, .redHeader, .blueText - Rediger stilark - Rediger egenskap for stilark - Navn for å identifisere stilarksegenskapen i rik-tekst editoren - Forhåndsvis - Stiler - - - Rediger mal - Sett inn innholdsområde - Sett inn plassholder for innholdsområde - Sett inn ordbokselement - Sett inn makro - Sett inn Umbraco sidefelt - Hovedmal - Hurtigguide til Umbraco sine maltagger - Mal - - - Alternativt felt - Alternativ tekst - Store/små bokstaver - Felt som skal settes inn - Konverter linjeskift - Erstatter et linjeskift med htmltaggen &lt;br&gt; - Ja, kun dato - Formatter som dato - HTML koding - Formater spesialtegn med tilsvarende HTML-tegn. - Denne teksten vil settes inn etter verdien av feltet - Denne teksten vil settes inn før verdien av feltet - Små bokstaver - Ingen - Sett inn etter felt - Sett inn før felt - Rekursivt - Fjern paragraftagger - Fjerner eventuelle &lt;P&gt; rundt teksten - Store bokstaver - URL koding - Dersom innholdet av feltene skal sendes til en URL skal spesialtegn formatteres - Denne teksten vil benyttes dersom feltene over er tomme - Dette feltet vil benyttes dersom feltet over er tomt - Ja, med klokkeslett. Dato/tid separator: - Egendefinerte felt - Standardfelter - - - Oppgaver satt til deg - Listen nedenfor viser oversettelsesoppgaver <strong>som du er tildelt</strong>. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". <br/> For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen. - Lukk oppgave - Oversettelses detaljer - Last ned all oversettelsesoppgaver som XML - Last ned XML - Last ned XML DTD - Felt - Inkluder undersider - - Hei %0% - - Dette er en automatisk mail for å informere deg om at dokumentet '%1%' - har blitt anmodet oversatt til '%5%' av %2%. - - Gå til http://%3%/Umbraco/translation/default.aspx?id=%4% for å redigere. - - Ha en fin dag! - - Vennlig hilsen Umbraco Robot. - - [%0%] Oversettingsoppgave for %1% - Ingen oversettelses-bruker funnet. Vennligst opprett en oversettelses-bruker før du begynner å sende innhold til oversetting - Oppgaver opprettet av deg - Listen under viser sider <strong>opprettet av deg</strong>. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen. - Siden '%0%' har blitt sendt til oversetting - Send til oversetting - Tildelt av - Oppgave åpnet - Antall ord - Oversett til - Oversetting fullført. - Du kan forhåndsvise sidene du nettopp har oversatt ved å klikke nedenfor. Hvis den originale siden finnes, vil du få en sammenligning av sidene. - Oversetting mislykkes, XML filen kan være korrupt - Alternativer for oversetting - Oversetter - Last opp XML med oversettelse - - - Hurtigbufferleser - Papirkurv - Opprettede pakker - Datatyper - Ordbok - Installerte pakker - Installer utseende - Installer startpakke - Språk - Installer lokal pakke - Makroer - Mediatyper - Medlemmer - Medlemsgrupper - Roller - Medlemstyper - Dokumenttyper - Pakker - Pakker - Python Filer - Installer fra pakkeregister - Installer Runway - Runway moduler - Skriptfiler - Skript - Stiler - Maler - XSLT Filer - - - Ny oppdatering er klar - %0% er klar, klikk her for å laste ned - Ingen forbindelse til server - Kunne ikke sjekke etter ny oppdatering. Se trace for mere info. - - - Administrator - Kategorifelt - Bytt passord - Du kan endre passordet til Umbraco ved å fylle ut skjemaet under og klikke "Bytt passord" knappen. - Innholdskanal - Beskrivelsesfelt - Deaktiver bruker - Dokumenttype - Redaktør - Utdragsfelt - Språk - Login - Øverste nivå i Media - Moduler - Deaktiver tilgang til Umbraco - Passord - Passordet er endret - Bekreft nytt passord - Nytt passord - Nytt passord kan ikke være blankt - Nytt og bekreftet passord må være like - Nytt og bekreftet passord må være like - Overskriv tillatelser på undernoder - Du redigerer for øyeblikket tillatelser for sidene: - Velg sider for å redigere deres tillatelser - Søk i alle undersider - Startnode - Brukernavn - Brukertillatelser - Brukertype - Brukertyper - Forfatter - Nytt passord - Bekreft nytt passord - Gjeldende passord - Feil passord - + + + + The Umbraco community + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + + + Angi domene + Revisjoner + Bla gjennom + Skift dokumenttype + Kopier + Opprett + Opprett pakke + Slett + Deaktiver + Tøm papirkurv + Eksporter dokumenttype + Importer dokumenttype + Importer pakke + Rediger i Canvas + Logg av + Flytt + Varslinger + Offentlig tilgang + Publiser + Avpubliser + Oppdater noder + Republiser hele siten + Gjenopprett + Rettigheter + Reverser + Send til publisering + Send til oversetting + Sorter + Send til publisering + Oversett + Oppdater + Standard verdi + + + Ingen tilgang. + Legg til domene + Fjern + Ugyldig node. + Ugyldig domeneformat. + Domene er allerede tilknyttet. + Språk + Domene + Domene '%0%' er nå opprettet og tilknyttet siden + Domenet '%0%' er nå slettet + Domenet '%0%' er allerede tilknyttet + Domenet '%0%' er nå oppdatert + eller rediger eksisterende domener +
    Stier med ett nivå støttes, f.eks. "eksempel.com/no". Imidlertid bør det unngås. Bruk heller språkinnstillingen over.]]>
    + Arv + Språk + Vil også gjelde denne noden, med mindre et underordnet domene også gjelder.]]> + Domener + + + Viser for + + + Velg + Velg gjeldende mappe + Gjør noe annet + Fet + Reduser innrykk + Sett inn skjemafelt + Sett inn grafisk overskrift + Rediger HTML + Øk innrykk + Kursiv + Midtstill + Juster tekst venstre + Juster tekst høyre + Sett inn lenke + Sett inn lokal lenke (anker) + Punktmerking + Nummerering + Sett inn makro + Sett inn bilde + Rediger relasjoner + Tilbake til listen + Lagre + Lagre og publiser + Lagre og send til publisering + Forhåndsvis + Forhåndsvisning er deaktivert siden det ikke er angitt noen mal + Velg formattering + Vis stiler + Sett inn tabell + + + For å endre det valge innholdets dokumenttype, velger du først en ny dokumenttype som er gyldig på gjeldende plassering. + Kontroller deretter at alle egenskaper blir overført riktig til den nye dokumenttypen og klikk på Lagre. + Innholdet har blitt republisert. + Nåværende egenskap + Nåværende type + Du kan ikke endre dokumenttype, ettersom det ikke er andre gyldige dokumenttyper på denne plasseringen. + Dokumenttype endret + Overfør egenskaper + Overfør til egenskap + Ny mal + Ny type + ingen + Innhold + Velg ny dokumenttype + Dokumenttypen på det valgte innhold ble endret til [new type], og følgende egenskaper ble overført: + til + Overføringen av egenskaper kunne ikke fullføres da en eller flere egenskaper er satt til å bli overført mer enn en gang. + Kun andre dokumenttyper som er gyldige for denne plasseringen vises. + + + Publisert + Om siden + Alias + (hvordan du ville beskrevet bildet over telefon) + Alternative lenker + Klikk for å redigere denne noden + Opprettet av + Opprinnelig forfatter + Oppdatert av + Opprettet den + Tidspunkt for opprettelse + Dokumenttype + Redigerer + Utløpsdato + Denne noden er endret siden siste publisering + Denne noden er enda ikke publisert + Sist publisert + Det er ingen elementer å vise i listen. + Mediatype + Link til media + Medlemsgruppe + Rolle + Medlemstype + Ingen dato valgt + Sidetittel + Egenskaper + Dette dokumentet er publisert, men ikke synlig ettersom den overliggende siden '%0%' ikke er publisert + Intern feil: dokumentet er publisert men finnes ikke i hurtigbuffer + Publisert + Publiseringsstatus + Publiseringsdato + Dato for avpublisering + Fjern dato + Sorteringsrekkefølgen er oppdatert + Trekk og slipp nodene eller klikk på kolonneoverskriftene for å sortere. Du kan velge flere noder ved å holde shift eller control tastene mens du velger. + Statistikk + Tittel (valgfri) + Alternativ tekst (valgfri) + Type + Avpubliser + Sist endret + Tidspunkt for siste endring + Fjern fil + Lenke til dokument + Medlem av gruppe(ne) + Ikke medlem av gruppe(ne) + Undersider + Åpne i vindu + + + Klikk for å laste opp + Slipp filene her... + + + Opprett et nytt medlem + Alle medlemmer + + + Hvor ønsker du å oprette den nye %0% + Opprett under + Velg en type og skriv en tittel + "dokumenttyper".]]> + "mediatyper".]]> + + + Til ditt nettsted + - Skjul + Hvis Umbraco ikke starter, kan det skyldes at pop-up vinduer ikke er tillatt + er åpnet i nytt vindu + Omstart + Besøk + Velkommen + + + Navn på lokal link + Rediger domener + Lukk dette vinduet + Er du sikker på at du vil slette + Er du sikker på at du vil deaktivere + Vennligst kryss av i denne boksen for å bekrefte sletting av %0% element(er) + Er du sikker på at du vil forlate Umbraco? + Er du sikker? + Klipp ut + Rediger ordboksnøkkel + Rediger språk + Sett inn lokal link + Sett inn spesialtegn + Sett inn grafisk overskrift + Sett inn bilde + Sett inn lenke + Sett inn makro + Sett inn tabell + Sist redigert + Lenke + Intern link: + Ved lokal link, sett inn "#" foran link + Åpne i nytt vindu? + Makroinnstillinger + Denne makroen har ingen egenskaper du kan endre + Lim inn + Endre rettigheter for + Innholdet i papirkurven blir nå slettet. Vennligst ikke lukk dette vinduet mens denne operasjonen foregår + Papirkurven er nå tom + Når elementer blir slettet fra papirkurven vil de være slettet for alltid + regexlib.com tjenesten opplever for tiden problemer som vi ikke har kontroll over. Vi beklager denne ubeleiligheten.]]> + Søk etter et regulært uttrykk for å legge inn validering til et felt. Eksempel: 'email, 'zip-code' 'url' + Fjern makro + Obligatorisk + Nettstedet er indeksert + Hurtigbufferen er blitt oppdatert. Alt publisert innhold er nå à jour. Alt upublisert innhold er fortsatt ikke publisert. + Hurtigbufferen for siden vil bli oppdatert. Alt publisert innhold vil bli oppdatert, mens upublisert innhold vil forbli upublisert. + Antall kolonner + Antall rader + Sett en plassholder-ID
    Ved å sette en ID på plassholderen kan du legge inn innhold i denne malen fra underliggende maler, ved å referere denne ID'en ved hjelp av et <asp:content /> element.]]>
    + Velg en plassholder ID fra listen under. Du kan bare velge ID'er fra den gjeldende malens overordnede mal.]]> + Klikk på bildet for å se det i full størrelse + Velg punkt + Se buffret node + + + %0%' under.
    Du kan legge til flere språk under 'språk' i menyen til venstre.]]>
    + Språk + + + Skriv inn ditt brukernavn + Skriv inn ditt passord + Navngi %0%... + Skriv inn navn... + Søk... + Filtrer... + Skriv inn nøkkelord (trykk på Enter etter hvert nøkkelord)... + + + Tillat på rotnivå + Kun dokumenttyper med denne innstillingen aktivert kan opprettes på rotnivå under Innhold og Mediearkiv + Tillatte underordnede noder + Sammensetting av dokumenttyper + Opprett + Slett arkfane + Beskrivelse + Ny arkfane + Arkfane + Miniatyrbilde + Aktiver listevisning + Viser undersider i en søkbar liste, undersider vises ikke i innholdstreet + Gjeldende listevisning + Den aktive listevisningsdatatypen + Opprett brukerdefinert listevisning + Fjern brukerdefinert listevisning + + + Legg til forhåndsverdi + Database datatype + Kontrollelement GUID + Kontrollelement + Knapper + Aktiver avanserte instillinger for + Aktiver kontektsmeny + Maksimum standard størrelse på innsatte bilder + Beslektede stilark + Vis etikett + Bredde og høyde + + + Dine data har blitt lagret, men før du kan publisere denne siden må du rette noen feil: + Den gjeldende Membership Provider støtter ikke endring av passord. (EnablePasswordRetrieval må være satt til sann) + %0% finnes allerede + Det var feil i dokumentet: + Det var feil i skjemaet: + Passordet bør være minst %0% tegn og inneholde minst %1% numeriske tegn + %0% må være et heltall + %0% under %1% er obligatorisk + %0% er obligatorisk + %0% under %1% er ikke i et korrekt format + %0% er ikke i et korrekt format + + + Filtypen er deaktivert av administrator + NB! Selv om CodeMirror er aktivert i konfigurasjon er det deaktivert i Internet Explorer pga. ustabilitet. + Fyll ut både alias og navn på den nye egenskapstypen! + Det er et problem med lese/skrive rettighetene til en fil eller mappe + Tittel mangler + Type mangler + Du er i ferd med å gjøre bildet større enn originalen. Det vil forringe kvaliteten på bildet, ønsker du å fortsette? + Feil i python-skriptet + Python-skriptet ble ikke lagret fordi det inneholder en eller flere feil + Startnode er slettet. Kontakt din administrator + Du må markere innhold før du kan endre stil + Det er ingen aktive stiler eller formateringer på denne siden + Sett markøren til venstre i de 2 cellene du ønsker å slå sammen + Du kan ikke dele en celle som allerede er delt. + Feil i XSLT kode + XSLT ble ikke lagret på grunn av feil i koden + Det er et problem dem datatypen som brukes til denne egenskapen. Kontroller innstillingene og prøv igjen. + + + Om + Handling + Muligheter + Legg til + Alias + Er du sikker? + Ramme + av + Avbryt + Cellemargin + Velg + Lukk + Lukk vindu + Kommentar + Bekreft + Behold proposjoner + Fortsett + Kopier + Opprett + Database + Dato + Standard + Slett + Slettet + Sletter... + Design + Dimensjoner + Ned + Last ned + Rediger + Endret + Elementer + E-post + Feil + Finn + Høyde + Hjelp + Ikon + Importer + Indre margin + Sett inn + Installer + Justering + Språk + Layout + Laster + Låst + Logg inn + Logg ut + Logg ut + Makro + Flytt + Mer + Navn + Ny + Neste + Nei + av + OK + Åpne + eller + Passord + Sti + Plassholder ID + Ett øyeblikk... + Forrige + Egenskaper + E-post som innholdet i skjemaet skal sendes til + Papirkurv + Gjenværende + Gi nytt navn + Forny + Påkrevd + Prøv igjen + Rettigheter + Søk + Server + Vis + Hvilken side skal vises etter at skjemaet er sendt + Størrelse + Sorter + Submit + Type + Søk... + Opp + Oppdater + Oppgrader + Last opp + Url + Bruker + Brukernavn + Verdi + Visning + Velkommen... + Bredde + Ja + Mappe + Søkeresultater + + + Bakgrunnsfarge + Fet + Tekstfarge + Skrifttype + Tekst + + + Side + + + Installasjonsprogrammet kan ikke koble til databasen + Kunne ikke lagre Web.Config-filen. Vennligst endre databasens tilkoblingsstreng manuelt. + Din database er funnet og identifisert som + Databasekonfigurasjon + installer-knappen for å installere Umbraco %0% databasen]]> + Neste for å fortsette.]]> + Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.

    For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.

    Klikk prøv på nytt når du er ferdig.
    Mer informasjon om redigering av web.config her.

    ]]>
    + Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator.]]> + Trykk på knappen oppgrader for å oppgradere databasen din til Umbraco %0%

    Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå!

    ]]>
    + Trykk Neste for å fortsette.]]> + neste for å fortsette konfigurasjonsveiviseren]]> + Passordet til standardbrukeren må endres!]]> + Standardbrukeren har blitt deaktivert eller har ingen tilgang til Umbraco!

    Ingen videre handling er nødvendig. Klikk neste for å fortsette.]]> + Passordet til standardbrukeren har blitt forandret etter installasjonen!

    Ingen videre handling er nødvendig. Klikk Neste for å fortsette.]]> + Passordet er blitt endret! + Umbraco skaper en standard bruker med login ( "admin") og passord ( "default") . Det er viktig at passordet er endret til noe unikt.

    Dette trinnet vil sjekke standard brukerens passord og foreslår hvis det må skiftes ]]> + Få en god start med våre introduksjonsvideoer + Ved å klikke på Neste-knappen (eller endre UmbracoConfigurationStatus i Web.config), godtar du lisensen for denne programvaren som angitt i boksen nedenfor. Legg merke til at denne Umbraco distribusjon består av to ulike lisenser, åpen kilde MIT lisens for rammen og Umbraco frivareverktøy lisens som dekker brukergrensesnittet. + Ikke installert. + Berørte filer og mapper + Mer informasjon om å sette opp rettigheter for Umbraco her + Du må gi ASP.NET brukeren rettigheter til å endre de følgende filer og mapper + Rettighetene er nesten perfekt satt opp!

    Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]> + Hvordan løse problemet + Klikk her for å lese tekstversjonen + innføringsvideo
    om å sette opp rettigheter for Umbraco eller les tekstversjonen.]]> + Rettighetsinnstillingene kan være et problem!


    Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]> + Rettighetsinstillingene er ikke klargjort for Umbraco!

    For å kunne kjøre Umbraco, må du oppdatere rettighetsinnstillingene dine.]]>
    + Rettighetsinnstillingene er perfekt!

    Du er klar for å kjøre Umbraco og installere pakker!]]>
    + Løser mappeproblem + Følg denne linken for mer informasjon om problemer med ASP.NET og oppretting av mapper + Konfigurerer mappetillatelser + + Jeg ønsker å starte fra bunnen. + lær hvordan) Du kan fortsatt velge å installere Runway senere. Vennligst gå til Utvikler-seksjonen og velg Pakker.]]> + Du har akkurat satt opp en ren Umbraco plattform. Hva vil du gjøre nå? + Runway er installert + Dette er vår liste av anbefalte moduler- Kryss av de du ønsker å installere, eller se denfulle listen av moduler ]]> + Bare anbefalt for erfarne brukere + Jeg vil starte med en enkel webside + "Runway" er en enkel webside som utstyrer deg med noen grunnleggende dokumenttyper og maler. Veiviseren kan sette opp Runway for deg automatisk, men du kan enkelt endre, utvide eller slette den. Runway er ikke nødvendig, og du kan enkelt bruke Umbraco uten den. Imidlertidig tilbyr Runway et enkelt fundament basert på de beste metodene for å hjelpe deg i gang fortere enn noensinne. Hvis du velger å installere Runway, kan du også velge blant grunnleggende byggeklosser kalt Runway Moduler for å forøke dine Runway-sider.

    Sider inkludert i Runway: Hjemmeside, Komme-i-gang, Installere moduler.
    Valgfrie Moduler: Toppnavigasjon, Sidekart, Kontakt, Galleri.
    ]]>
    + Hva er Runway + Steg 1/5 Godta lisens + Steg 2/5 Database konfigurasjon + Steg 3/5: Valider filrettigheter + Steg 4/5: Skjekk Umbraco sikkerheten + Steg 5/5: Umbraco er klar for deg til å starte! + Tusen takk for at du valgte Umbraco! + Se ditt nye nettsted Du har installert Runway, hvorfor ikke se hvordan ditt nettsted ser ut.]]> + Mer hjelp og info Få hjelp fra vårt prisbelønte samfunn, bla gjennom dokumentasjonen eller se noen gratis videoer på hvordan man bygger et enkelt nettsted, hvordan bruke pakker og en rask guide til Umbraco terminologi]]> + Umbraco %0% er installert og klar til bruk + web.config filen, og oppdatere AppSetting-nøkkelen UmbracoConfigurationStatus til verdien '%0%']]> + starte øyeblikkelig ved å klikke på "Start Umbraco" knappen nedenfor.
    Hvis du er ny på Umbraco, kan du finne mange ressurser på våre komme-i-gang sider.]]>
    + Start Umbraco For å administrere din webside, åpne Umbraco og begynn å legge til innhold, oppdatere maler og stilark eller utvide funksjonaliteten]]> + Tilkobling til databasen mislyktes. + Umbraco Versjon 3 + Umbraco Versjon 4 + Se + Umbraco %0% for en ny installasjon eller oppgradering fra versjon 3.0.

    Trykk "neste" for å starte veiviseren.]]>
    + + + Språkkode + Språk + + + Du har vært inaktiv og vil logges ut automatisk om + Forny innlogging for å lagre + + + Da er det søndag! + Smil, det er mandag! + Hurra, det er tirsdag! + For en herlig onsdag! + Gledelig torsdag! + Endelig fredag! + Gledelig lørdag + Logg på nedenfor + Din sesjon er utløpt + © 2001 - %0%
    umbraco.com

    ]]>
    + + + Skrivebord + Seksjoner + Innhold + + + Velg side over... + %0% er nå kopiert til %1% + Kopier til + %0% er nå flyttet til %1% + Flytt til + har blitt valgt som rot til ditt nye innhold, klikk 'ok' nedenfor. + Ingen node er valgt, vennligst velg en node i listen over før du klikker 'fortsett' + Gjeldende nodes type tillates ikke under valgt node + Gjeldende node kan ikke legges under en underordnet node + Denne noden kan ikke ligge på rotnivå + Handlingen tillates ikke. Du mangler tilgang til en eller flere underordnede noder. + Relater kopierte elementer til original(e) + + + Rediger dine varsler for %0% + + Hei %0%

    + +

    Dette er en automatisk mail for å informere om at handlingen '%1%' + er blitt utført på siden '%2%' + av brukeren '%3%' +

    + +

    +

    Rettelser:

    + + %6% +
    +

    + + + +

    Ha en fin dag!

    + Vennlig hilsen Umbraco roboten +

    ]]>
    + [%0%] Varsling om %1% utført på %2% + Varslinger + + + Umbraco-pakker har vanligvis endelsen ".umb" eller ".zip".]]> + Utvikler + Demonstrasjon + Dokumentasjon + Metadata + Pakkenavn + Pakken inneholder ingen elementer +
    Du kan trygt fjerne pakken fra systemet ved å klikke "avinstaller pakke" nedenfor.]]>
    + Ingen oppdateringer tilgjengelig + Alternativer for pakke + Lesmeg for pakke + Pakkebrønn + Bekreft avinstallering + Pakken ble avinstallert + Pakken ble vellykket avinstallert + Avinstaller pakke + Advarsel: alle dokumenter, media, etc. som som er avhengig av elementene du sletter, vil slutte å virke, noe som kan føre til ustabilitet, så avinstaller med forsiktighet. Hvis du er i tvil, kontakt pakkeutvikleren.]]> + Last ned oppdatering fra pakkeregisteret + Oppgrader pakke + Oppgraderingsinstrukser + Det er en oppdatering tilgjengelig for denne pakken. Du kan laste den ned direkte fra pakkebrønnen. + Pakkeversjon + Pakkeversjonshistorie + Se pakkens nettsted + + + Lim inn med full formattering (Anbefales ikke) + Teksten du er i ferd med å lime inn, inneholder spesialtegn eller formattering. Dette kan skyldes at du kopierer fra f.eks. Microsoft Word. Umbraco kan fjerne denne spesialformatteringen automatisk slik at innholdet er mer velegnet for visning på en webside. + Lim inn som ren tekst, dvs. fjern al formattering + Lim inn og fjern uegnet formatering (anbefalt) + + + Avansert: Beskytt ved å velge hvilke brukergrupper som har tilgang til siden + ved å bruke Umbraco's medlems-grupper]]> + rollebasert autentikasjon.]]> + Feilside + Brukt når personer logger på, men ikke har tilgang + Hvordan vil du beskytte siden din? + %0% er nå beskyttet + Beskyttelse fjernet fra %0% + Innloggingsside + Velg siden som har loginformularet + Fjern beskyttelse + Velg sidene som inneholder login-skjema og feilmelding ved feil innolgging. + Velg rollene som har tilgang til denne siden + Sett brukernavn og passord for denne siden + Enkelt: Beskytt ved hjelp av brukernavn og passord + Om du ønsker å bruke enkel autentisering via ett enkelt brukernavn og passord + + + %0% kunne ikke publiseres fordi den har planlagt utgivelsesdato. + %0% ble ikke publisert. Ett eller flere felter ble ikke godkjent av validering. + %0% kunne ikke publiseres fordi et tredjepartstillegg avbrøt handlingen. + %0% kan ikke publiseres fordi en overordnet side ikke er publisert. + Inkluder upubliserte undersider + Publiserer - vennligst vent... + %0% av %1% sider har blitt publisert... + %0% er nå publisert + %0% og alle undersider er nå publisert + Publiser alle undersider + ok for å publisere %0% og dermed gjøre innholdet synlig for alle.

    Du kan publisere denne siden og alle dens undersider ved å krysse av Publiser alle undersider nedenfor.]]>
    + + + Du har ikke konfigurert noen godkjente farger + + + skriv inn ekstern lenke + velg en intern side + Tittel + Lenke + Åpne i nytt vindu + Skriv inn en tekst + Skriv inn en lenke + + + Nullstill + + + Gjeldende versjon + Rød tekst vil ikke bli vist i den valgte versjonen. , grønn betyr lagt til]]> + Dokumentet er tilbakeført til en tidligere versjon + Dette viser den valgte versjonen som HTML, bruk avviksvisningen hvis du ønsker å se forksjellene mellom to versjoner samtidig. + Tilbakefør til + Velg versjon + Vis + + + Rediger scriptfilen + + + Concierge + Innhold + Courier + Utvikler + Umbraco konfigurasjonsveiviser + Mediaarkiv + Medlemmer + Nyhetsbrev + Innstillinger + Statistikk + Oversettelse + Brukere + Hjelp + Skjemaer + Analytics + + + gå til + Hjelpeemner for + Videokapitler for + De beste Umbraco opplæringsvideoer + + + Standardmal + Ordboksnøkkel + For å importere en dokumenttype, finn ".udt" filen på datamaskinen din ved å klikke "Utforsk" knappen og klikk "Importer" (du vil bli spurt om bekreftelse i det neste skjermbildet) + Ny tittel på arkfane + Nodetype + Type + Stilark + Script + Stilark-egenskap + Arkfane + Tittel på arkfane + Arkfaner + Hovedinnholdstype aktivert + Denne dokumenttypen bruker + som hoveddokumenttype. Arkfaner fra hoveddokumenttyper vises ikke og kan kun endres på hoveddokumenttypen selv. + Ingen egenskaper definert i denne arkfanen. Klikk på "legg til ny egenskap" lenken i toppen for å opprette en ny egenskap. + Hovedinnholdstype + Opprett tilhørende mal + + + Sortering ferdig. + Dra elementene opp eller ned for å arrangere dem. Du kan også klikke kolonneoverskriftene for å sortere alt på en gang. +
    Ikke lukk dette vinduet under sortering]]>
    + + + En feil oppsto + Utilstrekkelige brukertillatelser, kunne ikke fullføre operasjonen + Avbrutt + Handlingen ble avbrutt av et tredjepartstillegg + Publisering ble avbrutt av et tredjepartstillegg + Egenskaptypen finnes allerede + Egenskapstype opprettet + DataType: %1%]]> + Egenskapstype slettet + Innholdstype lagret + Du har opprettet en arkfane + Arkfane slettet + Arkfane med id: %0% slettet + Stilarket ble ikke lagret + Stilarket ble lagret + Stilark lagret uten feil + Datatype lagret + Ordbokelement lagret + Publiseringen feilet fordi den overliggende siden ikke er publisert + Innhold publisert + og er nå synlig for besøkende + Innhold lagret + Husk å publisere for å gjøre endringene synlig for besøkende + Sendt for godkjenning + Endringer har blitt sendt til godkjenning + Media lagret + Media lagret uten feil + Medlem lagret + Stilarksegenskap lagret + Stilark lagret + Mal lagret + Feil ved lagring av bruker (sjekk loggen) + Bruker lagret + Brukertypen lagret + Filen ble ikke lagret + Filen kunne ikke lagres. Vennligst sjekk filrettigheter + Filen ble lagret + Filen ble lagret uten feil + Språk lagret + Python-skriptet ble ikke lagret + Python-skriptet kunne ikke lagres fordi det inneholder en eller flere feil + Python-skriptet er lagret! + Ingen feil i python-skriptet! + Malen ble ikke lagret + Vennligst forviss deg om at du ikke har to maler med samme alias + Malen ble lagret + Malen ble lagret uten feil! + XSLT-koden ble ikke lagret + XSLT-koden inneholdt en feil + XSLT-koden ble ikke lagret, sjekk filrettigheter + XSLT lagret + Ingen feil i XSLT! + Innhold avpublisert + Delmal lagret + Delmal lagret uten feil + Delmal ble ikke lagret! + En feil oppsto ved lagring av delmal + Script visning lagret + Script visning lagret uten feil! + Script visning ikke lagret + En feil oppsto under lagring av filen. + En feil oppsto under lagring av filen. + + + Bruk CSS syntaks f.eks: h1, .redHeader, .blueText + Rediger stilark + Rediger egenskap for stilark + Navn for å identifisere stilarksegenskapen i rik-tekst editoren + Forhåndsvis + Stiler + + + Rediger mal + Sett inn innholdsområde + Sett inn plassholder for innholdsområde + Sett inn ordbokselement + Sett inn makro + Sett inn Umbraco sidefelt + Hovedmal + Hurtigguide til Umbraco sine maltagger + Mal + + + Sett inn element + Velg ett oppsett for denne seksjonen + nedenfor og legg til det første elementet]]> + Klikk for å bygge inn + Klikk for å sette inn et bilde + Bildetekst... + Skriv her... + Rutenettoppsett + Et oppsett er det overordnede arbeidsområdet til ditt rutenett - du vil typisk kun behøve ét eller to + Legg til rutenettoppsett + Juster oppsettet ved at justere kolonnebredder og legg til ytterligere seksjoner + Radkonfigurasjoner + Rader er forhåndsdefinerte celler arrangert vannrett + Legg til radkonfigurasjon + Juster raden ved å sette celle bredder og legge til flere celler + Kolonner + Totale antallet kolonner i rutenettet + Innstillinger + Konfigurer hvilke innstillinger brukeren kan endre + Stiler + Konfigurer hvilke stiler redaktørene kan endre + Innstillingene lagres kun når json-konfigurasjonen er gyldig + Tillatt alle editorer + Tillat alle radkonfigurasjoner + + + Alternativt felt + Alternativ tekst + Store/små bokstaver + Encoding + Felt som skal settes inn + Konverter linjeskift + Erstatter et linjeskift med htmltaggen <br> + Egendefinerte felt + Ja, kun dato + Formatter som dato + HTML koding + Formater spesialtegn med tilsvarende HTML-tegn. + Denne teksten vil settes inn etter verdien av feltet + Denne teksten vil settes inn før verdien av feltet + Små bokstaver + Ingen + Sett inn etter felt + Sett inn før felt + Rekursivt + Fjern paragraftagger + Fjerner eventuelle <P> rundt teksten + Standardfelter + Store bokstaver + URL koding + Dersom innholdet av feltene skal sendes til en URL skal spesialtegn formatteres + Denne teksten vil benyttes dersom feltene over er tomme + Dette feltet vil benyttes dersom feltet over er tomt + Ja, med klokkeslett. Dato/tid separator: + + + Oppgaver satt til deg + som du er tildelt. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML".
    For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]>
    + Lukk oppgave + Oversettelses detaljer + Last ned all oversettelsesoppgaver som XML + Last ned XML + Last ned XML DTD + Felt + Inkluder undersider + + [%0%] Oversettingsoppgave for %1% + Ingen oversettelses-bruker funnet. Vennligst opprett en oversettelses-bruker før du begynner å sende innhold til oversetting + Oppgaver opprettet av deg + opprettet av deg. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]> + Siden '%0%' har blitt sendt til oversetting + Send til oversetting + Tildelt av + Oppgave åpnet + Antall ord + Oversett til + Oversetting fullført. + Du kan forhåndsvise sidene du nettopp har oversatt ved å klikke nedenfor. Hvis den originale siden finnes, vil du få en sammenligning av sidene. + Oversetting mislykkes, XML filen kan være korrupt + Alternativer for oversetting + Oversetter + Last opp XML med oversettelse + + + Hurtigbufferleser + Papirkurv + Opprettede pakker + Datatyper + Ordbok + Installerte pakker + Installer utseende + Installer startpakke + Språk + Installer lokal pakke + Makroer + Mediatyper + Medlemmer + Medlemsgrupper + Roller + Medlemstyper + Dokumenttyper + Pakker + Pakker + Python Filer + Installer fra pakkeregister + Installer Runway + Runway moduler + Skriptfiler + Skript + Stiler + Maler + XSLT Filer + Analytics + + + Ny oppdatering er klar + %0% er klar, klikk her for å laste ned + Ingen forbindelse til server + Kunne ikke sjekke etter ny oppdatering. Se trace for mere info. + + + Administrator + Kategorifelt + Bytt passord + Nytt passord + Bekreft nytt passord + Du kan endre passordet til Umbraco ved å fylle ut skjemaet under og klikke "Bytt passord" knappen. + Innholdskanal + Beskrivelsesfelt + Deaktiver bruker + Dokumenttype + Redaktør + Utdragsfelt + Språk + Brukernavn + Øverste nivå i Media + Moduler + Deaktiver tilgang til Umbraco + Passord + Nullstill passord + Passordet er endret + Bekreft nytt passord + Nytt passord + Nytt passord kan ikke være blankt + Gjeldende passord + Feil passord + Nytt og bekreftet passord må være like + Nytt og bekreftet passord må være like + Overskriv tillatelser på undernoder + Du redigerer for øyeblikket tillatelser for sidene: + Velg sider for å redigere deres tillatelser + Søk i alle undersider + Startnode + Navn + Brukertillatelser + Brukertype + Brukertyper + Forfatter + Oversetter + Endre + Din profil + Din historikk + Sesjonen utløper om +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config index a0c0655dbe..99e72e8216 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.config @@ -10,7 +10,7 @@ NOTES: * Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config * A new version will invalidate both client and server cache and create new persisted files --> - + - - - + + + diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 4d1b0eb349..4db3b51c40 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -34,9 +34,9 @@ - - - + + + diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index e235107f13..f5d0ff53b6 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -5,8 +5,8 @@ - - + + @@ -32,5 +32,5 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx b/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx index 6f0bd7c008..2a10d4d344 100644 --- a/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx +++ b/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx @@ -22,6 +22,11 @@ var parser = document.createElement('a'); parser.href = parts[1]; + // This next line may seem redundant but is required to get around a bug in IE + // that doesn't set the parser.hostname or parser.protocol correctly for relative URLs. + // See https://gist.github.com/jlong/2428561#gistcomment-1461205 + parser.href = parser.href; + // => "http:" if (!parser.protocol || (parser.protocol.toLowerCase() !== "http:" && parser.protocol.toLowerCase() !== "https:")) { throw "invalid protocol"; diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index fdbbe3c581..d72f7c4193 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -918,6 +918,17 @@ To manage your website, simply open the Umbraco back office and start adding con Allow all row configurations + + Allow as root + Allow editors to create content of this type in the root of the content tree + Yes - allow content of this type in the root + + Allowed child node types + Allow content of the specified types to be created underneath content of this type + + Choose child node + + Alternative field Alternative Text diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/no.xml b/src/Umbraco.Web.UI/umbraco/config/lang/no.xml deleted file mode 100644 index 184112d998..0000000000 --- a/src/Umbraco.Web.UI/umbraco/config/lang/no.xml +++ /dev/null @@ -1,963 +0,0 @@ - - - - The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files - - - Angi domene - Revisjoner - Bla gjennom - Skift dokumenttype - Kopier - Opprett - Opprett pakke - Slett - Deaktiver - Tøm papirkurv - Eksporter dokumenttype - Importer dokumenttype - Importer pakke - Rediger i Canvas - Logg av - Flytt - Varslinger - Offentlig tilgang - Publiser - Avpubliser - Oppdater noder - Republiser hele siten - Gjenopprett - Rettigheter - Reverser - Send til publisering - Send til oversetting - Sorter - Send til publisering - Oversett - Oppdater - Standard verdi - - - Ingen tilgang. - Legg til domene - Fjern - Ugyldig node. - Ugyldig domeneformat. - Domene er allerede tilknyttet. - Språk - Domene - Domene '%0%' er nå opprettet og tilknyttet siden - Domenet '%0%' er nå slettet - Domenet '%0%' er allerede tilknyttet - Domenet '%0%' er nå oppdatert - eller rediger eksisterende domener -
    Stier med ett nivå støttes, f.eks. "eksempel.com/no". Imidlertid bør det unngås. Bruk heller språkinnstillingen over.]]>
    - Arv - Språk - Vil også gjelde denne noden, med mindre et underordnet domene også gjelder.]]> - Domener - - - Viser for - - - Velg - Velg gjeldende mappe - Gjør noe annet - Fet - Reduser innrykk - Sett inn skjemafelt - Sett inn grafisk overskrift - Rediger HTML - Øk innrykk - Kursiv - Midtstill - Juster tekst venstre - Juster tekst høyre - Sett inn lenke - Sett inn lokal lenke (anker) - Punktmerking - Nummerering - Sett inn makro - Sett inn bilde - Rediger relasjoner - Tilbake til listen - Lagre - Lagre og publiser - Lagre og send til publisering - Forhåndsvis - Forhåndsvisning er deaktivert siden det ikke er angitt noen mal - Velg formattering - Vis stiler - Sett inn tabell - - - For å endre det valge innholdets dokumenttype, velger du først en ny dokumenttype som er gyldig på gjeldende plassering. - Kontroller deretter at alle egenskaper blir overført riktig til den nye dokumenttypen og klikk på Lagre. - Innholdet har blitt republisert. - Nåværende egenskap - Nåværende type - Du kan ikke endre dokumenttype, ettersom det ikke er andre gyldige dokumenttyper på denne plasseringen. - Dokumenttype endret - Overfør egenskaper - Overfør til egenskap - Ny mal - Ny type - ingen - Innhold - Velg ny dokumenttype - Dokumenttypen på det valgte innhold ble endret til [new type], og følgende egenskaper ble overført: - til - Overføringen av egenskaper kunne ikke fullføres da en eller flere egenskaper er satt til å bli overført mer enn en gang. - Kun andre dokumenttyper som er gyldige for denne plasseringen vises. - - - Publisert - Om siden - Alias - (hvordan du ville beskrevet bildet over telefon) - Alternative lenker - Klikk for å redigere denne noden - Opprettet av - Opprinnelig forfatter - Oppdatert av - Opprettet den - Tidspunkt for opprettelse - Dokumenttype - Redigerer - Utløpsdato - Denne noden er endret siden siste publisering - Denne noden er enda ikke publisert - Sist publisert - Det er ingen elementer å vise i listen. - Mediatype - Link til media - Medlemsgruppe - Rolle - Medlemstype - Ingen dato valgt - Sidetittel - Egenskaper - Dette dokumentet er publisert, men ikke synlig ettersom den overliggende siden '%0%' ikke er publisert - Intern feil: dokumentet er publisert men finnes ikke i hurtigbuffer - Publisert - Publiseringsstatus - Publiseringsdato - Dato for avpublisering - Fjern dato - Sorteringsrekkefølgen er oppdatert - Trekk og slipp nodene eller klikk på kolonneoverskriftene for å sortere. Du kan velge flere noder ved å holde shift eller control tastene mens du velger. - Statistikk - Tittel (valgfri) - Alternativ tekst (valgfri) - Type - Avpubliser - Sist endret - Tidspunkt for siste endring - Fjern fil - Lenke til dokument - Medlem av gruppe(ne) - Ikke medlem av gruppe(ne) - Undersider - Åpne i vindu - - - Klikk for å laste opp - Slipp filene her... - - - Opprett et nytt medlem - Alle medlemmer - - - Hvor ønsker du å oprette den nye %0% - Opprett under - Velg en type og skriv en tittel - "dokumenttyper".]]> - "mediatyper".]]> - - - Til ditt nettsted - - Skjul - Hvis Umbraco ikke starter, kan det skyldes at pop-up vinduer ikke er tillatt - er åpnet i nytt vindu - Omstart - Besøk - Velkommen - - - Navn på lokal link - Rediger domener - Lukk dette vinduet - Er du sikker på at du vil slette - Er du sikker på at du vil deaktivere - Vennligst kryss av i denne boksen for å bekrefte sletting av %0% element(er) - Er du sikker på at du vil forlate Umbraco? - Er du sikker? - Klipp ut - Rediger ordboksnøkkel - Rediger språk - Sett inn lokal link - Sett inn spesialtegn - Sett inn grafisk overskrift - Sett inn bilde - Sett inn lenke - Sett inn makro - Sett inn tabell - Sist redigert - Lenke - Intern link: - Ved lokal link, sett inn "#" foran link - Åpne i nytt vindu? - Makroinnstillinger - Denne makroen har ingen egenskaper du kan endre - Lim inn - Endre rettigheter for - Innholdet i papirkurven blir nå slettet. Vennligst ikke lukk dette vinduet mens denne operasjonen foregår - Papirkurven er nå tom - Når elementer blir slettet fra papirkurven vil de være slettet for alltid - regexlib.com tjenesten opplever for tiden problemer som vi ikke har kontroll over. Vi beklager denne ubeleiligheten.]]> - Søk etter et regulært uttrykk for å legge inn validering til et felt. Eksempel: 'email, 'zip-code' 'url' - Fjern makro - Obligatorisk - Nettstedet er indeksert - Hurtigbufferen er blitt oppdatert. Alt publisert innhold er nå à jour. Alt upublisert innhold er fortsatt ikke publisert. - Hurtigbufferen for siden vil bli oppdatert. Alt publisert innhold vil bli oppdatert, mens upublisert innhold vil forbli upublisert. - Antall kolonner - Antall rader - Sett en plassholder-ID
    Ved å sette en ID på plassholderen kan du legge inn innhold i denne malen fra underliggende maler, ved å referere denne ID'en ved hjelp av et <asp:content /> element.]]>
    - Velg en plassholder ID fra listen under. Du kan bare velge ID'er fra den gjeldende malens overordnede mal.]]> - Klikk på bildet for å se det i full størrelse - Velg punkt - Se buffret node - - - %0%' under.
    Du kan legge til flere språk under 'språk' i menyen til venstre.]]>
    - Språk - - - Skriv inn ditt brukernavn - Skriv inn ditt passord - Navngi %0%... - Skriv inn navn... - Søk... - Filtrer... - Skriv inn nøkkelord (trykk på Enter etter hvert nøkkelord)... - - - Tillat på rotnivå - Kun dokumenttyper med denne innstillingen aktivert kan opprettes på rotnivå under Innhold og Mediearkiv - Tillatte underordnede noder - Sammensetting av dokumenttyper - Opprett - Slett arkfane - Beskrivelse - Ny arkfane - Arkfane - Miniatyrbilde - Aktiver listevisning - Viser undersider i en søkbar liste, undersider vises ikke i innholdstreet - Gjeldende listevisning - Den aktive listevisningsdatatypen - Opprett brukerdefinert listevisning - Fjern brukerdefinert listevisning - - - Legg til forhåndsverdi - Database datatype - Kontrollelement GUID - Kontrollelement - Knapper - Aktiver avanserte instillinger for - Aktiver kontektsmeny - Maksimum standard størrelse på innsatte bilder - Beslektede stilark - Vis etikett - Bredde og høyde - - - Dine data har blitt lagret, men før du kan publisere denne siden må du rette noen feil: - Den gjeldende Membership Provider støtter ikke endring av passord. (EnablePasswordRetrieval må være satt til sann) - %0% finnes allerede - Det var feil i dokumentet: - Det var feil i skjemaet: - Passordet bør være minst %0% tegn og inneholde minst %1% numeriske tegn - %0% må være et heltall - %0% under %1% er obligatorisk - %0% er obligatorisk - %0% under %1% er ikke i et korrekt format - %0% er ikke i et korrekt format - - - Filtypen er deaktivert av administrator - NB! Selv om CodeMirror er aktivert i konfigurasjon er det deaktivert i Internet Explorer pga. ustabilitet. - Fyll ut både alias og navn på den nye egenskapstypen! - Det er et problem med lese/skrive rettighetene til en fil eller mappe - Tittel mangler - Type mangler - Du er i ferd med å gjøre bildet større enn originalen. Det vil forringe kvaliteten på bildet, ønsker du å fortsette? - Feil i python-skriptet - Python-skriptet ble ikke lagret fordi det inneholder en eller flere feil - Startnode er slettet. Kontakt din administrator - Du må markere innhold før du kan endre stil - Det er ingen aktive stiler eller formateringer på denne siden - Sett markøren til venstre i de 2 cellene du ønsker å slå sammen - Du kan ikke dele en celle som allerede er delt. - Feil i XSLT kode - XSLT ble ikke lagret på grunn av feil i koden - Det er et problem dem datatypen som brukes til denne egenskapen. Kontroller innstillingene og prøv igjen. - - - Om - Handling - Muligheter - Legg til - Alias - Er du sikker? - Ramme - av - Avbryt - Cellemargin - Velg - Lukk - Lukk vindu - Kommentar - Bekreft - Behold proposjoner - Fortsett - Kopier - Opprett - Database - Dato - Standard - Slett - Slettet - Sletter... - Design - Dimensjoner - Ned - Last ned - Rediger - Endret - Elementer - E-post - Feil - Finn - Høyde - Hjelp - Ikon - Importer - Indre margin - Sett inn - Installer - Justering - Språk - Layout - Laster - Låst - Logg inn - Logg ut - Logg ut - Makro - Flytt - Mer - Navn - Ny - Neste - Nei - av - OK - Åpne - eller - Passord - Sti - Plassholder ID - Ett øyeblikk... - Forrige - Egenskaper - E-post som innholdet i skjemaet skal sendes til - Papirkurv - Gjenværende - Gi nytt navn - Forny - Påkrevd - Prøv igjen - Rettigheter - Søk - Server - Vis - Hvilken side skal vises etter at skjemaet er sendt - Størrelse - Sorter - Submit - Type - Søk... - Opp - Oppdater - Oppgrader - Last opp - Url - Bruker - Brukernavn - Verdi - Visning - Velkommen... - Bredde - Ja - Mappe - Søkeresultater - - - Bakgrunnsfarge - Fet - Tekstfarge - Skrifttype - Tekst - - - Side - - - Installasjonsprogrammet kan ikke koble til databasen - Kunne ikke lagre Web.Config-filen. Vennligst endre databasens tilkoblingsstreng manuelt. - Din database er funnet og identifisert som - Databasekonfigurasjon - installer-knappen for å installere Umbraco %0% databasen]]> - Neste for å fortsette.]]> - Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.

    For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.

    Klikk prøv på nytt når du er ferdig.
    Mer informasjon om redigering av web.config her.

    ]]>
    - Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator.]]> - Trykk på knappen oppgrader for å oppgradere databasen din til Umbraco %0%

    Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå!

    ]]>
    - Trykk Neste for å fortsette.]]> - neste for å fortsette konfigurasjonsveiviseren]]> - Passordet til standardbrukeren må endres!]]> - Standardbrukeren har blitt deaktivert eller har ingen tilgang til Umbraco!

    Ingen videre handling er nødvendig. Klikk neste for å fortsette.]]> - Passordet til standardbrukeren har blitt forandret etter installasjonen!

    Ingen videre handling er nødvendig. Klikk Neste for å fortsette.]]> - Passordet er blitt endret! - Umbraco skaper en standard bruker med login ( "admin") og passord ( "default") . Det er viktig at passordet er endret til noe unikt.

    Dette trinnet vil sjekke standard brukerens passord og foreslår hvis det må skiftes ]]> - Få en god start med våre introduksjonsvideoer - Ved å klikke på Neste-knappen (eller endre UmbracoConfigurationStatus i Web.config), godtar du lisensen for denne programvaren som angitt i boksen nedenfor. Legg merke til at denne Umbraco distribusjon består av to ulike lisenser, åpen kilde MIT lisens for rammen og Umbraco frivareverktøy lisens som dekker brukergrensesnittet. - Ikke installert. - Berørte filer og mapper - Mer informasjon om å sette opp rettigheter for Umbraco her - Du må gi ASP.NET brukeren rettigheter til å endre de følgende filer og mapper - Rettighetene er nesten perfekt satt opp!

    Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]> - Hvordan løse problemet - Klikk her for å lese tekstversjonen - innføringsvideo
    om å sette opp rettigheter for Umbraco eller les tekstversjonen.]]> - Rettighetsinnstillingene kan være et problem!


    Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]> - Rettighetsinstillingene er ikke klargjort for Umbraco!

    For å kunne kjøre Umbraco, må du oppdatere rettighetsinnstillingene dine.]]>
    - Rettighetsinnstillingene er perfekt!

    Du er klar for å kjøre Umbraco og installere pakker!]]>
    - Løser mappeproblem - Følg denne linken for mer informasjon om problemer med ASP.NET og oppretting av mapper - Konfigurerer mappetillatelser - - Jeg ønsker å starte fra bunnen. - lær hvordan) Du kan fortsatt velge å installere Runway senere. Vennligst gå til Utvikler-seksjonen og velg Pakker.]]> - Du har akkurat satt opp en ren Umbraco plattform. Hva vil du gjøre nå? - Runway er installert - Dette er vår liste av anbefalte moduler- Kryss av de du ønsker å installere, eller se denfulle listen av moduler ]]> - Bare anbefalt for erfarne brukere - Jeg vil starte med en enkel webside - "Runway" er en enkel webside som utstyrer deg med noen grunnleggende dokumenttyper og maler. Veiviseren kan sette opp Runway for deg automatisk, men du kan enkelt endre, utvide eller slette den. Runway er ikke nødvendig, og du kan enkelt bruke Umbraco uten den. Imidlertidig tilbyr Runway et enkelt fundament basert på de beste metodene for å hjelpe deg i gang fortere enn noensinne. Hvis du velger å installere Runway, kan du også velge blant grunnleggende byggeklosser kalt Runway Moduler for å forøke dine Runway-sider.

    Sider inkludert i Runway: Hjemmeside, Komme-i-gang, Installere moduler.
    Valgfrie Moduler: Toppnavigasjon, Sidekart, Kontakt, Galleri.
    ]]>
    - Hva er Runway - Steg 1/5 Godta lisens - Steg 2/5 Database konfigurasjon - Steg 3/5: Valider filrettigheter - Steg 4/5: Skjekk Umbraco sikkerheten - Steg 5/5: Umbraco er klar for deg til å starte! - Tusen takk for at du valgte Umbraco! - Se ditt nye nettsted Du har installert Runway, hvorfor ikke se hvordan ditt nettsted ser ut.]]> - Mer hjelp og info Få hjelp fra vårt prisbelønte samfunn, bla gjennom dokumentasjonen eller se noen gratis videoer på hvordan man bygger et enkelt nettsted, hvordan bruke pakker og en rask guide til Umbraco terminologi]]> - Umbraco %0% er installert og klar til bruk - web.config filen, og oppdatere AppSetting-nøkkelen UmbracoConfigurationStatus til verdien '%0%']]> - starte øyeblikkelig ved å klikke på "Start Umbraco" knappen nedenfor.
    Hvis du er ny på Umbraco, kan du finne mange ressurser på våre komme-i-gang sider.]]>
    - Start Umbraco For å administrere din webside, åpne Umbraco og begynn å legge til innhold, oppdatere maler og stilark eller utvide funksjonaliteten]]> - Tilkobling til databasen mislyktes. - Umbraco Versjon 3 - Umbraco Versjon 4 - Se - Umbraco %0% for en ny installasjon eller oppgradering fra versjon 3.0.

    Trykk "neste" for å starte veiviseren.]]>
    - - - Språkkode - Språk - - - Du har vært inaktiv og vil logges ut automatisk om - Forny innlogging for å lagre - - - Da er det søndag! - Smil, det er mandag! - Hurra, det er tirsdag! - For en herlig onsdag! - Gledelig torsdag! - Endelig fredag! - Gledelig lørdag - Logg på nedenfor - Din sesjon er utløpt - © 2001 - %0%
    umbraco.com

    ]]>
    - - - Skrivebord - Seksjoner - Innhold - - - Velg side over... - %0% er nå kopiert til %1% - Kopier til - %0% er nå flyttet til %1% - Flytt til - har blitt valgt som rot til ditt nye innhold, klikk 'ok' nedenfor. - Ingen node er valgt, vennligst velg en node i listen over før du klikker 'fortsett' - Gjeldende nodes type tillates ikke under valgt node - Gjeldende node kan ikke legges under en underordnet node - Denne noden kan ikke ligge på rotnivå - Handlingen tillates ikke. Du mangler tilgang til en eller flere underordnede noder. - Relater kopierte elementer til original(e) - - - Rediger dine varsler for %0% - - Hei %0%

    - -

    Dette er en automatisk mail for å informere om at handlingen '%1%' - er blitt utført på siden '%2%' - av brukeren '%3%' -

    - -

    -

    Rettelser:

    - - %6% -
    -

    - - - -

    Ha en fin dag!

    - Vennlig hilsen Umbraco roboten -

    ]]>
    - [%0%] Varsling om %1% utført på %2% - Varslinger - - - Umbraco-pakker har vanligvis endelsen ".umb" eller ".zip".]]> - Utvikler - Demonstrasjon - Dokumentasjon - Metadata - Pakkenavn - Pakken inneholder ingen elementer -
    Du kan trygt fjerne pakken fra systemet ved å klikke "avinstaller pakke" nedenfor.]]>
    - Ingen oppdateringer tilgjengelig - Alternativer for pakke - Lesmeg for pakke - Pakkebrønn - Bekreft avinstallering - Pakken ble avinstallert - Pakken ble vellykket avinstallert - Avinstaller pakke - Advarsel: alle dokumenter, media, etc. som som er avhengig av elementene du sletter, vil slutte å virke, noe som kan føre til ustabilitet, så avinstaller med forsiktighet. Hvis du er i tvil, kontakt pakkeutvikleren.]]> - Last ned oppdatering fra pakkeregisteret - Oppgrader pakke - Oppgraderingsinstrukser - Det er en oppdatering tilgjengelig for denne pakken. Du kan laste den ned direkte fra pakkebrønnen. - Pakkeversjon - Pakkeversjonshistorie - Se pakkens nettsted - - - Lim inn med full formattering (Anbefales ikke) - Teksten du er i ferd med å lime inn, inneholder spesialtegn eller formattering. Dette kan skyldes at du kopierer fra f.eks. Microsoft Word. Umbraco kan fjerne denne spesialformatteringen automatisk slik at innholdet er mer velegnet for visning på en webside. - Lim inn som ren tekst, dvs. fjern al formattering - Lim inn og fjern uegnet formatering (anbefalt) - - - Avansert: Beskytt ved å velge hvilke brukergrupper som har tilgang til siden - ved å bruke Umbraco's medlems-grupper]]> - rollebasert autentikasjon.]]> - Feilside - Brukt når personer logger på, men ikke har tilgang - Hvordan vil du beskytte siden din? - %0% er nå beskyttet - Beskyttelse fjernet fra %0% - Innloggingsside - Velg siden som har loginformularet - Fjern beskyttelse - Velg sidene som inneholder login-skjema og feilmelding ved feil innolgging. - Velg rollene som har tilgang til denne siden - Sett brukernavn og passord for denne siden - Enkelt: Beskytt ved hjelp av brukernavn og passord - Om du ønsker å bruke enkel autentisering via ett enkelt brukernavn og passord - - - %0% kunne ikke publiseres fordi den har planlagt utgivelsesdato. - %0% ble ikke publisert. Ett eller flere felter ble ikke godkjent av validering. - %0% kunne ikke publiseres fordi et tredjepartstillegg avbrøt handlingen. - %0% kan ikke publiseres fordi en overordnet side ikke er publisert. - Inkluder upubliserte undersider - Publiserer - vennligst vent... - %0% av %1% sider har blitt publisert... - %0% er nå publisert - %0% og alle undersider er nå publisert - Publiser alle undersider - ok for å publisere %0% og dermed gjøre innholdet synlig for alle.

    Du kan publisere denne siden og alle dens undersider ved å krysse av Publiser alle undersider nedenfor.]]>
    - - - Du har ikke konfigurert noen godkjente farger - - - skriv inn ekstern lenke - velg en intern side - Tittel - Lenke - Åpne i nytt vindu - Skriv inn en tekst - Skriv inn en lenke - - - Nullstill - - - Gjeldende versjon - Rød tekst vil ikke bli vist i den valgte versjonen. , grønn betyr lagt til]]> - Dokumentet er tilbakeført til en tidligere versjon - Dette viser den valgte versjonen som HTML, bruk avviksvisningen hvis du ønsker å se forksjellene mellom to versjoner samtidig. - Tilbakefør til - Velg versjon - Vis - - - Rediger scriptfilen - - - Concierge - Innhold - Courier - Utvikler - Umbraco konfigurasjonsveiviser - Mediaarkiv - Medlemmer - Nyhetsbrev - Innstillinger - Statistikk - Oversettelse - Brukere - Hjelp - Skjemaer - Analytics - - - gå til - Hjelpeemner for - Videokapitler for - De beste Umbraco opplæringsvideoer - - - Standardmal - Ordboksnøkkel - For å importere en dokumenttype, finn ".udt" filen på datamaskinen din ved å klikke "Utforsk" knappen og klikk "Importer" (du vil bli spurt om bekreftelse i det neste skjermbildet) - Ny tittel på arkfane - Nodetype - Type - Stilark - Script - Stilark-egenskap - Arkfane - Tittel på arkfane - Arkfaner - Hovedinnholdstype aktivert - Denne dokumenttypen bruker - som hoveddokumenttype. Arkfaner fra hoveddokumenttyper vises ikke og kan kun endres på hoveddokumenttypen selv. - Ingen egenskaper definert i denne arkfanen. Klikk på "legg til ny egenskap" lenken i toppen for å opprette en ny egenskap. - Hovedinnholdstype - Opprett tilhørende mal - - - Sortering ferdig. - Dra elementene opp eller ned for å arrangere dem. Du kan også klikke kolonneoverskriftene for å sortere alt på en gang. -
    Ikke lukk dette vinduet under sortering]]>
    - - - En feil oppsto - Utilstrekkelige brukertillatelser, kunne ikke fullføre operasjonen - Avbrutt - Handlingen ble avbrutt av et tredjepartstillegg - Publisering ble avbrutt av et tredjepartstillegg - Egenskaptypen finnes allerede - Egenskapstype opprettet - DataType: %1%]]> - Egenskapstype slettet - Innholdstype lagret - Du har opprettet en arkfane - Arkfane slettet - Arkfane med id: %0% slettet - Stilarket ble ikke lagret - Stilarket ble lagret - Stilark lagret uten feil - Datatype lagret - Ordbokelement lagret - Publiseringen feilet fordi den overliggende siden ikke er publisert - Innhold publisert - og er nå synlig for besøkende - Innhold lagret - Husk å publisere for å gjøre endringene synlig for besøkende - Sendt for godkjenning - Endringer har blitt sendt til godkjenning - Media lagret - Media lagret uten feil - Medlem lagret - Stilarksegenskap lagret - Stilark lagret - Mal lagret - Feil ved lagring av bruker (sjekk loggen) - Bruker lagret - Brukertypen lagret - Filen ble ikke lagret - Filen kunne ikke lagres. Vennligst sjekk filrettigheter - Filen ble lagret - Filen ble lagret uten feil - Språk lagret - Python-skriptet ble ikke lagret - Python-skriptet kunne ikke lagres fordi det inneholder en eller flere feil - Python-skriptet er lagret! - Ingen feil i python-skriptet! - Malen ble ikke lagret - Vennligst forviss deg om at du ikke har to maler med samme alias - Malen ble lagret - Malen ble lagret uten feil! - XSLT-koden ble ikke lagret - XSLT-koden inneholdt en feil - XSLT-koden ble ikke lagret, sjekk filrettigheter - XSLT lagret - Ingen feil i XSLT! - Innhold avpublisert - Delmal lagret - Delmal lagret uten feil - Delmal ble ikke lagret! - En feil oppsto ved lagring av delmal - Script visning lagret - Script visning lagret uten feil! - Script visning ikke lagret - En feil oppsto under lagring av filen. - En feil oppsto under lagring av filen. - - - Bruk CSS syntaks f.eks: h1, .redHeader, .blueText - Rediger stilark - Rediger egenskap for stilark - Navn for å identifisere stilarksegenskapen i rik-tekst editoren - Forhåndsvis - Stiler - - - Rediger mal - Sett inn innholdsområde - Sett inn plassholder for innholdsområde - Sett inn ordbokselement - Sett inn makro - Sett inn Umbraco sidefelt - Hovedmal - Hurtigguide til Umbraco sine maltagger - Mal - - - Sett inn element - Velg ett oppsett for denne seksjonen - nedenfor og legg til det første elementet]]> - Klikk for å bygge inn - Klikk for å sette inn et bilde - Bildetekst... - Skriv her... - Rutenettoppsett - Et oppsett er det overordnede arbeidsområdet til ditt rutenett - du vil typisk kun behøve ét eller to - Legg til rutenettoppsett - Juster oppsettet ved at justere kolonnebredder og legg til ytterligere seksjoner - Radkonfigurasjoner - Rader er forhåndsdefinerte celler arrangert vannrett - Legg til radkonfigurasjon - Juster raden ved å sette celle bredder og legge til flere celler - Kolonner - Totale antallet kolonner i rutenettet - Innstillinger - Konfigurer hvilke innstillinger brukeren kan endre - Stiler - Konfigurer hvilke stiler redaktørene kan endre - Innstillingene lagres kun når json-konfigurasjonen er gyldig - Tillatt alle editorer - Tillat alle radkonfigurasjoner - - - Alternativt felt - Alternativ tekst - Store/små bokstaver - Encoding - Felt som skal settes inn - Konverter linjeskift - Erstatter et linjeskift med htmltaggen <br> - Egendefinerte felt - Ja, kun dato - Formatter som dato - HTML koding - Formater spesialtegn med tilsvarende HTML-tegn. - Denne teksten vil settes inn etter verdien av feltet - Denne teksten vil settes inn før verdien av feltet - Små bokstaver - Ingen - Sett inn etter felt - Sett inn før felt - Rekursivt - Fjern paragraftagger - Fjerner eventuelle <P> rundt teksten - Standardfelter - Store bokstaver - URL koding - Dersom innholdet av feltene skal sendes til en URL skal spesialtegn formatteres - Denne teksten vil benyttes dersom feltene over er tomme - Dette feltet vil benyttes dersom feltet over er tomt - Ja, med klokkeslett. Dato/tid separator: - - - Oppgaver satt til deg - som du er tildelt. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML".
    For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]>
    - Lukk oppgave - Oversettelses detaljer - Last ned all oversettelsesoppgaver som XML - Last ned XML - Last ned XML DTD - Felt - Inkluder undersider - - [%0%] Oversettingsoppgave for %1% - Ingen oversettelses-bruker funnet. Vennligst opprett en oversettelses-bruker før du begynner å sende innhold til oversetting - Oppgaver opprettet av deg - opprettet av deg. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]> - Siden '%0%' har blitt sendt til oversetting - Send til oversetting - Tildelt av - Oppgave åpnet - Antall ord - Oversett til - Oversetting fullført. - Du kan forhåndsvise sidene du nettopp har oversatt ved å klikke nedenfor. Hvis den originale siden finnes, vil du få en sammenligning av sidene. - Oversetting mislykkes, XML filen kan være korrupt - Alternativer for oversetting - Oversetter - Last opp XML med oversettelse - - - Hurtigbufferleser - Papirkurv - Opprettede pakker - Datatyper - Ordbok - Installerte pakker - Installer utseende - Installer startpakke - Språk - Installer lokal pakke - Makroer - Mediatyper - Medlemmer - Medlemsgrupper - Roller - Medlemstyper - Dokumenttyper - Pakker - Pakker - Python Filer - Installer fra pakkeregister - Installer Runway - Runway moduler - Skriptfiler - Skript - Stiler - Maler - XSLT Filer - Analytics - - - Ny oppdatering er klar - %0% er klar, klikk her for å laste ned - Ingen forbindelse til server - Kunne ikke sjekke etter ny oppdatering. Se trace for mere info. - - - Administrator - Kategorifelt - Bytt passord - Nytt passord - Bekreft nytt passord - Du kan endre passordet til Umbraco ved å fylle ut skjemaet under og klikke "Bytt passord" knappen. - Innholdskanal - Beskrivelsesfelt - Deaktiver bruker - Dokumenttype - Redaktør - Utdragsfelt - Språk - Brukernavn - Øverste nivå i Media - Moduler - Deaktiver tilgang til Umbraco - Passord - Nullstill passord - Passordet er endret - Bekreft nytt passord - Nytt passord - Nytt passord kan ikke være blankt - Gjeldende passord - Feil passord - Nytt og bekreftet passord må være like - Nytt og bekreftet passord må være like - Overskriv tillatelser på undernoder - Du redigerer for øyeblikket tillatelser for sidene: - Velg sider for å redigere deres tillatelser - Søk i alle undersider - Startnode - Navn - Brukertillatelser - Brukertype - Brukertyper - Forfatter - Oversetter - Endre - Din profil - Din historikk - Sesjonen utløper om - -
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/create/media.ascx b/src/Umbraco.Web.UI/umbraco/create/media.ascx deleted file mode 100644 index 7531907c67..0000000000 --- a/src/Umbraco.Web.UI/umbraco/create/media.ascx +++ /dev/null @@ -1,17 +0,0 @@ -<%@ Control Language="c#" AutoEventWireup="True" Codebehind="media.ascx.cs" Inherits="umbraco.cms.presentation.create.controls.media" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %> - -<%=umbraco.ui.Text("name")%>: *
    - - -
    -<%=umbraco.ui.Text("choose")%> <%=umbraco.ui.Text("media")%> <%=umbraco.ui.Text("type")%>:
    -
    -
    - - -