diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index ae2c45e329..72733c5800 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -72,7 +72,6 @@ - diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 46703a0c39..783bb8ae5c 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -17,7 +17,7 @@ - + @@ -33,7 +33,6 @@ - diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index f8ec871700..dfb9925840 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -21,6 +21,8 @@ + + diff --git a/build/NuSpecs/tools/uninstall.core.ps1 b/build/NuSpecs/tools/uninstall.core.ps1 deleted file mode 100644 index 3daa6a1ba7..0000000000 --- a/build/NuSpecs/tools/uninstall.core.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -Write-Host "installPath:" "${installPath}" -Write-Host "toolsPath:" "${toolsPath}" - -Write-Host " " - -if ($project) { - - # Create paths and list them - $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName - Write-Host "projectPath:" "${projectPath}" - $backupPath = Join-Path $projectPath "App_Data\NuGetBackup" - Write-Host "backupPath:" "${backupPath}" - $umbracoBinFolder = Join-Path $projectPath "bin" - Write-Host "umbracoBinFolder:" "${umbracoBinFolder}" - - # Remove backups - Write-Host "removing backups:" "${backupPath}" - if(Test-Path $backupPath) { Remove-Item -Recurse -Force $backupPath -Confirm:$false } - - # Delete files Umbraco ships with - - Write-Host "removing dlls:" "${umbracoBinFolder}" - if(Test-Path $umbracoBinFolder\businesslogic.dll) { Remove-Item $umbracoBinFolder\businesslogic.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\cms.dll) { Remove-Item $umbracoBinFolder\cms.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\controls.dll) { Remove-Item $umbracoBinFolder\controls.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\interfaces.dll) { Remove-Item $umbracoBinFolder\interfaces.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\log4net.dll) { Remove-Item $umbracoBinFolder\log4net.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll) { Remove-Item $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\SQLCE4Umbraco.dll) { Remove-Item $umbracoBinFolder\SQLCE4Umbraco.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\TidyNet.dll) { Remove-Item $umbracoBinFolder\TidyNet.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\umbraco.dll) { Remove-Item $umbracoBinFolder\umbraco.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Core.dll) { Remove-Item $umbracoBinFolder\Umbraco.Core.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\umbraco.DataLayer.dll) { Remove-Item $umbracoBinFolder\umbraco.DataLayer.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\umbraco.editorControls.dll) { Remove-Item $umbracoBinFolder\umbraco.editorControls.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\umbraco.MacroEngines.dll) { Remove-Item $umbracoBinFolder\umbraco.MacroEngines.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\umbraco.providers.dll) { Remove-Item $umbracoBinFolder\umbraco.providers.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Web.UI.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.UI.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\UmbracoExamine.dll) { Remove-Item $umbracoBinFolder\UmbracoExamine.dll -Force -Confirm:$false } - - $amd64Folder = Join-Path $umbracoBinFolder "amd64" - if(Test-Path $amd64Folder) { Remove-Item $amd64Folder -Force -Recurse -Confirm:$false } - $x86Folder = Join-Path $umbracoBinFolder "x86" - if(Test-Path $x86Folder) { Remove-Item $x86Folder -Force -Recurse -Confirm:$false } -} \ No newline at end of file diff --git a/build/NuSpecs/tools/uninstall.ps1 b/build/NuSpecs/tools/uninstall.ps1 deleted file mode 100644 index 4f7dd35384..0000000000 --- a/build/NuSpecs/tools/uninstall.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -Write-Host "installPath:" "${installPath}" -Write-Host "toolsPath:" "${toolsPath}" - -Write-Host " " - -if ($project) { - - # Create paths and list them - $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName - Write-Host "projectPath:" "${projectPath}" - $backupPath = Join-Path $projectPath "App_Data\NuGetBackup" - Write-Host "backupPath:" "${backupPath}" - $appBrowsers = Join-Path $projectPath "App_Browsers" - Write-Host "appBrowsers:" "${appBrowsers}" - $appData = Join-Path $projectPath "App_Data" - Write-Host "appData:" "${appData}" - - # Remove backups - Write-Host "removing backups:" "${backupPath}" - if(Test-Path $backupPath) { Remove-Item -Recurse -Force $backupPath -Confirm:$false } - - # Remove app_data files - Write-Host "removing app_data files:" "${appData}" - if(Test-Path $appData\packages) { Remove-Item $appData\packages -Recurse -Force -Confirm:$false } - - Write-Host "removing app_browsers:" "${appBrowsers}" - if(Test-Path $appBrowsers\Form.browser) { Remove-Item $appBrowsers\Form.browser -Force -Confirm:$false } - if(Test-Path $appBrowsers\w3cvalidator.browser) { Remove-Item $appBrowsers\w3cvalidator.browser -Force -Confirm:$false } - - # Remove umbraco and umbraco_files - $umbracoFolder = Join-Path $projectPath "Umbraco" - Write-Host "removing umbraco folder:" "${umbracoFolder}" - if(Test-Path $umbracoFolder) { Remove-Item $umbracoFolder -Recurse -Force -Confirm:$false } - $umbracoClientFolder = Join-Path $projectPath "Umbraco_Client" - Write-Host "removing umbraco client folder:" "${umbracoClientFolder}" - if(Test-Path $umbracoClientFolder) { Remove-Item $umbracoClientFolder -Recurse -Force -Confirm:$false } -} diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs index 4430d1d74e..9772aaf5bd 100644 --- a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs +++ b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs @@ -5,8 +5,10 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Linq.Expressions; +using System.Text; using System.Web.Services.Description; using Umbraco.Core.Cache; +using Umbraco.Core.Logging; namespace Umbraco.Core.Dynamics { @@ -20,6 +22,20 @@ namespace Umbraco.Core.Dynamics /// private static readonly ConcurrentDictionary, MethodInfo[]> MethodCache = new ConcurrentDictionary, MethodInfo[]>(); + private static IEnumerable GetTypes(Assembly a) + { + try + { + return TypeFinder.GetTypesWithFormattedException(a); + } + catch (ReflectionTypeLoadException ex) + { + // is this going to flood the log? + LogHelper.Error(typeof (ExtensionMethodFinder), "Failed to get types.", ex); + return Enumerable.Empty(); + } + } + /// /// Returns the enumerable of all extension method info's in the app domain = USE SPARINGLY!!! /// @@ -36,7 +52,7 @@ namespace Umbraco.Core.Dynamics // assemblies that contain extension methods .Where(a => a.IsDefined(typeof (ExtensionAttribute), false)) // types that contain extension methods - .SelectMany(a => a.GetTypes() + .SelectMany(a => GetTypes(a) .Where(t => t.IsDefined(typeof (ExtensionAttribute), false) && t.IsSealed && t.IsGenericType == false && t.IsNested == false)) // actual extension methods .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public) @@ -45,9 +61,9 @@ namespace Umbraco.Core.Dynamics .Concat(typeof (Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)) //If we don't do this then we'll be scanning all assemblies each time! .ToArray(), - + //only cache for 5 minutes - timeout: TimeSpan.FromMinutes(5), + timeout: TimeSpan.FromMinutes(5), //each time this is accessed it will be for 5 minutes longer isSliding:true); @@ -57,7 +73,7 @@ namespace Umbraco.Core.Dynamics /// Returns all extension methods found matching the definition /// /// - /// The runtime cache is used to temporarily cache all extension methods found in the app domain so that + /// The runtime cache is used to temporarily cache all extension methods found in the app domain so that /// while we search for individual extension methods, the process will be reasonably 'quick'. We then statically /// cache the MethodInfo's that we are looking for and then the runtime cache will expire and give back all that memory. /// @@ -78,7 +94,7 @@ namespace Umbraco.Core.Dynamics { var candidates = GetAllExtensionMethodsInAppDomain(runtimeCache); - // filter by name + // filter by name var filtr1 = candidates.Where(m => m.Name == name); // filter by args count @@ -102,7 +118,7 @@ namespace Umbraco.Core.Dynamics return filtr3.ToArray(); }); - + } private static MethodInfo DetermineMethodFromParams(IEnumerable methods, Type genericType, IEnumerable args) @@ -123,12 +139,12 @@ namespace Umbraco.Core.Dynamics types = method.GetParameters().Select(pi => pi.ParameterType).Skip(1) }); - //This type comparer will check + //This type comparer will check var typeComparer = new DelegateEqualityComparer( - //Checks if the argument type passed in can be assigned from the parameter type in the method. For + //Checks if the argument type passed in can be assigned from the parameter type in the method. For // example, if the argument type is HtmlHelper but the method parameter type is HtmlHelper then // it will match because the argument is assignable to that parameter type and will be able to execute - TypeHelper.IsTypeAssignableFrom, + TypeHelper.IsTypeAssignableFrom, //This will not ever execute but if it does we need to get the hash code of the string because the hash // code of a type is random type => type.FullName.GetHashCode()); @@ -159,7 +175,7 @@ namespace Umbraco.Core.Dynamics .ToArray(); var methods = GetAllExtensionMethods(runtimeCache, thisType, name, args.Length).ToArray(); - + return DetermineMethodFromParams(methods, genericType, args); } } diff --git a/src/Umbraco.Core/Events/MigrationEventArgs.cs b/src/Umbraco.Core/Events/MigrationEventArgs.cs index 7eed61484b..008e50d2ee 100644 --- a/src/Umbraco.Core/Events/MigrationEventArgs.cs +++ b/src/Umbraco.Core/Events/MigrationEventArgs.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using Semver; +using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.Migrations; namespace Umbraco.Core.Events @@ -13,23 +14,49 @@ namespace Umbraco.Core.Events /// /// /// + /// /// /// - public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel) - : base(eventObject, canCancel) - { - ConfiguredSemVersion = configuredVersion; - TargetSemVersion = targetVersion; - } + public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel) + : this(eventObject, null, configuredVersion, targetVersion, productName, canCancel) + { } - [Obsolete("Use constructor accepting UmbracoVersion instances instead")] + /// + /// Constructor accepting multiple migrations that are used in the migration runner + /// + /// + /// + /// + /// + [Obsolete("Use constructor accepting a product name instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel) + : this(eventObject, null, configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName, canCancel) + { } + + [Obsolete("Use constructor accepting SemVersion instances and a product name instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public MigrationEventArgs(IList eventObject, Version configuredVersion, Version targetVersion, bool canCancel) - : base(eventObject, canCancel) - { - ConfiguredSemVersion = new SemVersion(configuredVersion); - TargetSemVersion = new SemVersion(targetVersion); - } + : this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), GlobalSettings.UmbracoMigrationName, canCancel) + { } + + /// + /// Constructor accepting multiple migrations that are used in the migration runner + /// + /// + /// + /// + /// + /// + /// + internal MigrationEventArgs(IList eventObject, MigrationContext migrationContext, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel) + : base(eventObject, canCancel) + { + MigrationContext = migrationContext; + ConfiguredSemVersion = configuredVersion; + TargetSemVersion = targetVersion; + ProductName = productName; + } /// /// Constructor accepting multiple migrations that are used in the migration runner @@ -39,12 +66,15 @@ namespace Umbraco.Core.Events /// /// /// + [Obsolete("Use constructor accepting a product name instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] internal MigrationEventArgs(IList eventObject, MigrationContext migrationContext, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel) : base(eventObject, canCancel) { MigrationContext = migrationContext; ConfiguredSemVersion = configuredVersion; TargetSemVersion = targetVersion; + ProductName = GlobalSettings.UmbracoMigrationName; } /// @@ -53,21 +83,28 @@ namespace Umbraco.Core.Events /// /// /// - public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion) - : base(eventObject) - { - ConfiguredSemVersion = configuredVersion; - TargetSemVersion = targetVersion; - } + /// + public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion, string productName) + : this(eventObject, null, configuredVersion, targetVersion, productName, false) + { } - [Obsolete("Use constructor accepting UmbracoVersion instances instead")] + /// + /// Constructor accepting multiple migrations that are used in the migration runner + /// + /// + /// + /// + [Obsolete("Use constructor accepting a product name instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion) + : this(eventObject, null, configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName, false) + { } + + [Obsolete("Use constructor accepting SemVersion instances and a product name instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public MigrationEventArgs(IList eventObject, Version configuredVersion, Version targetVersion) - : base(eventObject) - { - ConfiguredSemVersion = new SemVersion(configuredVersion); - TargetSemVersion = new SemVersion(targetVersion); - } + : this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), GlobalSettings.UmbracoMigrationName, false) + { } /// /// Returns all migrations that were used in the migration runner @@ -95,6 +132,8 @@ namespace Umbraco.Core.Events public SemVersion TargetSemVersion { get; private set; } + public string ProductName { get; private set; } + internal MigrationContext MigrationContext { get; private set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 7218a2421d..a0305d2cfb 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -487,11 +487,8 @@ namespace Umbraco.Core.Models var oldPropertyGroup = PropertyGroups.FirstOrDefault(x => x.PropertyTypes.Any(y => y.Alias == propertyTypeAlias)); - // reset PropertyGroupId, which will be re-evaluated when the content type - // is saved - what is important is group.PropertyTypes - see code in - // ContentTypeBaseRepository.PersistUpdatedBaseContentType - propertyType.PropertyGroupId = new Lazy(() => default(int)); - propertyType.ResetDirtyProperties(); // PropertyGroupId must not be dirty + // set new group + propertyType.PropertyGroupId = newPropertyGroup == null ? null : new Lazy(() => newPropertyGroup.Id, false); // remove from old group, if any - add to new group, if any if (oldPropertyGroup != null) @@ -540,7 +537,7 @@ namespace Umbraco.Core.Models // re-assign the group's properties to no group foreach (var property in group.PropertyTypes) { - property.PropertyGroupId = new Lazy(() => 0); + property.PropertyGroupId = null; _propertyTypes.Add(property); } diff --git a/src/Umbraco.Core/Models/IMediaType.cs b/src/Umbraco.Core/Models/IMediaType.cs index 3934d7a40f..29e4b665ba 100644 --- a/src/Umbraco.Core/Models/IMediaType.cs +++ b/src/Umbraco.Core/Models/IMediaType.cs @@ -7,6 +7,12 @@ namespace Umbraco.Core.Models /// + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset + /// + /// + /// + IMediaType DeepCloneWithResetIdentities(string newAlias); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs index c8e2915afd..8596cce910 100644 --- a/src/Umbraco.Core/Models/MediaType.cs +++ b/src/Umbraco.Core/Models/MediaType.cs @@ -38,5 +38,30 @@ namespace Umbraco.Core.Models : base(parent, alias) { } + + /// + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset + /// + /// + public IMediaType DeepCloneWithResetIdentities(string alias) + { + var clone = (MediaType)DeepClone(); + clone.Alias = alias; + clone.Key = Guid.Empty; + foreach (var propertyGroup in clone.PropertyGroups) + { + propertyGroup.ResetIdentity(); + propertyGroup.ResetDirtyProperties(false); + } + foreach (var propertyType in clone.PropertyTypes) + { + propertyType.ResetIdentity(); + propertyType.ResetDirtyProperties(false); + } + + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 7f22b65c8c..0649801a0c 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -221,8 +221,9 @@ namespace Umbraco.Core.Models } /// - /// Gets or Sets the PropertyGroup's Id for which this PropertyType belongs + /// Gets or sets the identifier of the PropertyGroup this PropertyType belongs to. /// + /// For generic properties, the value is null. [DataMember] internal Lazy PropertyGroupId { diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 5f30c08ce7..d05960b08f 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Web.Caching; -using System.Web.UI; using Umbraco.Core.Cache; namespace Umbraco.Core.Models.PublishedContent @@ -16,6 +13,7 @@ namespace Umbraco.Core.Models.PublishedContent public class PublishedContentType { private readonly PublishedPropertyType[] _propertyTypes; + private readonly HashSet _compositionAliases; // fast alias-to-index xref containing both the raw alias and its lowercase version private readonly Dictionary _indexes = new Dictionary(); @@ -27,6 +25,7 @@ namespace Umbraco.Core.Models.PublishedContent { Id = contentType.Id; Alias = contentType.Alias; + _compositionAliases = new HashSet(contentType.CompositionAliases(), StringComparer.InvariantCultureIgnoreCase); _propertyTypes = contentType.CompositionPropertyTypes .Select(x => new PublishedPropertyType(this, x)) .ToArray(); @@ -34,10 +33,11 @@ namespace Umbraco.Core.Models.PublishedContent } // internal so it can be used for unit tests - internal PublishedContentType(int id, string alias, IEnumerable propertyTypes) + internal PublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) { Id = id; Alias = alias; + _compositionAliases = new HashSet(compositionAliases, StringComparer.InvariantCultureIgnoreCase); _propertyTypes = propertyTypes.ToArray(); foreach (var propertyType in _propertyTypes) propertyType.ContentType = this; @@ -45,8 +45,8 @@ namespace Umbraco.Core.Models.PublishedContent } // create detached content type - ie does not match anything in the DB - internal PublishedContentType(string alias, IEnumerable propertyTypes) - : this (0, alias, propertyTypes) + internal PublishedContentType(string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) + : this(0, alias, compositionAliases, propertyTypes) { } private void InitializeIndexes() @@ -63,6 +63,7 @@ namespace Umbraco.Core.Models.PublishedContent public int Id { get; private set; } public string Alias { get; private set; } + public HashSet CompositionAliases { get { return _compositionAliases; } } #endregion @@ -113,10 +114,25 @@ namespace Umbraco.Core.Models.PublishedContent internal static void ClearContentType(int id) { Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id); - // requires a predicate because the key does not contain the ID - // faster than key strings comparisons anyway + + // we don't support "get all" at the moment - so, cheating + var all = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItemsByKeySearch("PublishedContentType_").ToArray(); + + // the one we want to clear + var clr = all.FirstOrDefault(x => x.Id == id); + if (clr == null) return; + + // those that have that one in their composition aliases + // note: CompositionAliases contains all recursive aliases + var oth = all.Where(x => x.CompositionAliases.InvariantContains(clr.Alias)).Select(x => x.Id); + + // merge ids + var ids = oth.Concat(new[] { clr.Id }).ToArray(); + + // clear them all at once + // we don't support "clear many at once" at the moment - so, cheating ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => value.Id == id); + (key, value) => ids.Contains(value.Id)); } internal static void ClearDataType(int id) diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index 93ad99a5c1..eebbc34eda 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -164,7 +164,7 @@ namespace Umbraco.Core.Persistence.Factories Name = typeDto.Name, SortOrder = typeDto.SortOrder, ValidationRegExp = typeDto.ValidationRegExp, - PropertyGroupId = new Lazy(() => default(int)), + PropertyGroupId = null, CreateDate = dto.CreateDate, UpdateDate = dto.CreateDate }; diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index 55a7194511..db609c7d2a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Text; @@ -29,6 +30,9 @@ namespace Umbraco.Core.Persistence.Migrations private readonly IMigration[] _migrations; public MigrationRunner(IMigrationResolver resolver, IMigrationEntryService migrationEntryService, ILogger logger, SemVersion currentVersion, SemVersion targetVersion, string productName, params IMigration[] migrations) + [EditorBrowsable(EditorBrowsableState.Never)] + [EditorBrowsable(EditorBrowsableState.Never)] + [EditorBrowsable(EditorBrowsableState.Never)] { if (resolver == null) throw new ArgumentNullException("resolver"); if (migrationEntryService == null) throw new ArgumentNullException("migrationEntryService"); @@ -67,7 +71,7 @@ namespace Umbraco.Core.Persistence.Migrations : OrderedDowngradeMigrations(foundMigrations).ToList(); - if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _currentVersion, _targetVersion, true), this)) + if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _currentVersion, _targetVersion, _productName, true), this)) { _logger.Warn("Migration was cancelled by an event"); return false; @@ -96,7 +100,7 @@ namespace Umbraco.Core.Persistence.Migrations throw; } - Migrated.RaiseEvent(new MigrationEventArgs(migrations, migrationContext, _currentVersion, _targetVersion, false), this); + Migrated.RaiseEvent(new MigrationEventArgs(migrations, migrationContext, _currentVersion, _targetVersion, _productName, false), this); return true; } diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index f9c291c66f..05d9646b18 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -133,6 +133,34 @@ namespace Umbraco.Core.Persistence.Querying right = Visit(b.Right); } } + else if (operand == "=") + { + // deal with (x == true|false) - most common + var constRight = b.Right as ConstantExpression; + if (constRight != null && constRight.Type == typeof (bool)) + return ((bool) constRight.Value) ? VisitNotNot(b.Left) : VisitNot(b.Left); + right = Visit(b.Right); + + // deal with (true|false == x) - why not + var constLeft = b.Left as ConstantExpression; + if (constLeft != null && constLeft.Type == typeof (bool)) + return ((bool) constLeft.Value) ? VisitNotNot(b.Right) : VisitNot(b.Right); + left = Visit(b.Left); + } + else if (operand == "<>") + { + // deal with (x != true|false) - most common + var constRight = b.Right as ConstantExpression; + if (constRight != null && constRight.Type == typeof(bool)) + return ((bool) constRight.Value) ? VisitNot(b.Left) : VisitNotNot(b.Left); + right = Visit(b.Right); + + // deal with (true|false != x) - why not + var constLeft = b.Left as ConstantExpression; + if (constLeft != null && constLeft.Type == typeof(bool)) + return ((bool) constLeft.Value) ? VisitNot(b.Right) : VisitNotNot(b.Right); + left = Visit(b.Left); + } else { left = Visit(b.Left); @@ -157,7 +185,7 @@ namespace Umbraco.Core.Persistence.Querying case "COALESCE": return string.Format("{0}({1},{2})", operand, left, right); default: - return left + " " + operand + " " + right; + return "(" + left + " " + operand + " " + right + ")"; } } @@ -236,25 +264,47 @@ namespace Umbraco.Core.Persistence.Querying switch (u.NodeType) { case ExpressionType.Not: - var o = Visit(u.Operand); - - //use a Not equal operator instead of <> since we don't know that <> works in all sql servers - - switch (u.Operand.NodeType) - { - case ExpressionType.MemberAccess: - //In this case it wil be a false property , i.e. x => !Trashed - SqlParameters.Add(true); - return string.Format("NOT ({0} = @0)", o); - default: - //In this case it could be anything else, such as: x => !x.Path.StartsWith("-20") - return string.Format("NOT ({0})", o); - } + return VisitNot(u.Operand); default: return Visit(u.Operand); } } + private string VisitNot(Expression exp) + { + var o = Visit(exp); + + // use a "NOT (...)" syntax instead of "<>" since we don't know whether "<>" works in all sql servers + // also, x.StartsWith(...) translates to "x LIKE '...%'" which we cannot "<>" and have to "NOT (...") + + switch (exp.NodeType) + { + case ExpressionType.MemberAccess: + // false property , i.e. x => !Trashed + SqlParameters.Add(true); + return string.Format("NOT ({0} = @{1})", o, SqlParameters.Count - 1); + default: + // could be anything else, such as: x => !x.Path.StartsWith("-20") + return "NOT (" + o + ")"; + } + } + + private string VisitNotNot(Expression exp) + { + var o = Visit(exp); + + switch (exp.NodeType) + { + case ExpressionType.MemberAccess: + // true property, i.e. x => Trashed + SqlParameters.Add(true); + return string.Format("({0} = @{1})", o, SqlParameters.Count - 1); + default: + // could be anything else, such as: x => x.Path.StartsWith("-20") + return o; + } + } + protected virtual string VisitNewArray(NewArrayExpression na) { diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 93a25d2b57..5055f60ab9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -915,7 +915,7 @@ AND umbracoNode.id <> @id", //check for default templates bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault); int? templateId = ct.dtTemplateId; - if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && templateId.HasValue) + if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && isDefaultTemplate.Value && templateId.HasValue) { currDefaultTemplate = templateId.Value; } @@ -1213,5 +1213,19 @@ AND umbracoNode.id <> @id", { return PerformExists(id); } + + public string GetUniqueAlias(string alias) + { + // alias is unique accross ALL content types! + var aliasColumn = SqlSyntax.GetQuotedColumnName("alias"); + var aliases = Database.Fetch(@"SELECT cmsContentType." + aliasColumn + @" FROM cmsContentType +INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id +WHERE cmsContentType." + aliasColumn + @" LIKE @pattern", + new { pattern = alias + "%", objectType = NodeObjectTypeId }); + var i = 1; + string test; + while (aliases.Contains(test = alias + i)) i++; + return test; + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index 49520387cd..61d83645b3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -32,5 +32,13 @@ namespace Umbraco.Core.Persistence.Repositories /// /// IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes); + + /// + /// Derives a unique alias from an existing alias. + /// + /// The original alias. + /// The original alias with a number appended to it, so that it is unique. + /// /// Unique accross all content, media and member types. + string GetUniqueAlias(string alias); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs index 1cec8005c9..7f2f76e541 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs @@ -15,5 +15,13 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable GetByQuery(IQuery query); IEnumerable> Move(IMediaType toMove, EntityContainer container); + + /// + /// Derives a unique alias from an existing alias. + /// + /// The original alias. + /// The original alias with a number appended to it, so that it is unique. + /// Unique accross all content, media and member types. + string GetUniqueAlias(string alias); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index b151f84c08..c18a5f8477 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -28,6 +28,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax GuidColumnDefinition = "char(36)"; DefaultValueFormat = "DEFAULT {0}"; + + InitColumnTypeMap(); } public override IEnumerable GetTablesInSchema(Database db) diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index b8064474bb..524663c9c5 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -103,10 +103,18 @@ namespace Umbraco.Core.Security var backOfficeIdentity = http.User.Identity as UmbracoBackOfficeIdentity; if (backOfficeIdentity != null) return backOfficeIdentity; + //Check if there's more than one identity assigned and see if it's a UmbracoBackOfficeIdentity and use that + var claimsPrincipal = http.User as ClaimsPrincipal; + if (claimsPrincipal != null) + { + backOfficeIdentity = claimsPrincipal.Identities.OfType().FirstOrDefault(); + if (backOfficeIdentity != null) return backOfficeIdentity; + } + //Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd and has the back office session var claimsIdentity = http.User.Identity as ClaimsIdentity; if (claimsIdentity != null && claimsIdentity.IsAuthenticated) - { + { try { return UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity); diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index bf9c56abec..90a01b8c62 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -794,7 +794,7 @@ namespace Umbraco.Core.Services //TODO: This needs to change, if we are deleting a content type, we should just delete the data, // this method will recursively go lookup every content item, check if any of it's descendants are - // of a different type, move them to the recycle bin, then permanently delete the content items. + // of a different type, move them to the recycle bin, then permanently delete the content items. // The main problem with this is that for every content item being deleted, events are raised... // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. @@ -1067,6 +1067,92 @@ namespace Umbraco.Core.Services new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); } + public Attempt> CopyMediaType(IMediaType toCopy, int containerId) + { + var evtMsgs = EventMessagesFactory.Get(); + + IMediaType copy; + var uow = UowProvider.GetUnitOfWork(); + using (var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) + using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) + { + try + { + if (containerId > 0) + { + var container = containerRepository.Get(containerId); + if (container == null) + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); + } + var alias = repository.GetUniqueAlias(toCopy.Alias); + copy = toCopy.DeepCloneWithResetIdentities(alias); + copy.Name = copy.Name + " (copy)"; // might not be unique + + // if it has a parent, and the parent is a content type, unplug composition + // all other compositions remain in place in the copied content type + if (copy.ParentId > 0) + { + var parent = repository.Get(copy.ParentId); + if (parent != null) + copy.RemoveContentType(parent.Alias); + } + + copy.ParentId = containerId; + repository.AddOrUpdate(copy); + } + catch (DataOperationException ex) + { + return Attempt.Fail(new OperationStatus(null, ex.Operation, evtMsgs)); + } + uow.Commit(); + } + + return Attempt.Succeed(new OperationStatus(copy, MoveOperationStatusType.Success, evtMsgs)); + } + + public Attempt> CopyContentType(IContentType toCopy, int containerId) + { + var evtMsgs = EventMessagesFactory.Get(); + + IContentType copy; + var uow = UowProvider.GetUnitOfWork(); + using (var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) + using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) + { + try + { + if (containerId > 0) + { + var container = containerRepository.Get(containerId); + if (container == null) + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); + } + var alias = repository.GetUniqueAlias(toCopy.Alias); + copy = toCopy.DeepCloneWithResetIdentities(alias); + copy.Name = copy.Name + " (copy)"; // might not be unique + + // if it has a parent, and the parent is a content type, unplug composition + // all other compositions remain in place in the copied content type + if (copy.ParentId > 0) + { + var parent = repository.Get(copy.ParentId); + if (parent != null) + copy.RemoveContentType(parent.Alias); + } + + copy.ParentId = containerId; + repository.AddOrUpdate(copy); + } + catch (DataOperationException ex) + { + return Attempt.Fail(new OperationStatus(null, ex.Operation, evtMsgs)); + } + uow.Commit(); + } + + return Attempt.Succeed(new OperationStatus(copy, MoveOperationStatusType.Success, evtMsgs)); + } + /// /// Saves a single object /// diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index f450bf52e0..3ef0a2a0cf 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -285,11 +285,15 @@ namespace Umbraco.Core.Services structure.Add(new XElement("MediaType", allowedType.Alias)); } - var genericProperties = new XElement("GenericProperties"); + var genericProperties = new XElement("GenericProperties"); // actually, all of them foreach (var propertyType in mediaType.PropertyTypes) { var definition = dataTypeService.GetDataTypeDefinitionById(propertyType.DataTypeDefinitionId); - var propertyGroup = mediaType.PropertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value); + + var propertyGroup = propertyType.PropertyGroupId == null // true generic property + ? null + : mediaType.PropertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value); + var genericProperty = new XElement("GenericProperty", new XElement("Name", propertyType.Name), new XElement("Alias", propertyType.Alias), @@ -399,14 +403,14 @@ namespace Umbraco.Core.Services structure.Add(new XElement("DocumentType", allowedType.Alias)); } - var genericProperties = new XElement("GenericProperties"); + var genericProperties = new XElement("GenericProperties"); // actually, all of them foreach (var propertyType in contentType.PropertyTypes) { var definition = dataTypeService.GetDataTypeDefinitionById(propertyType.DataTypeDefinitionId); - var propertyGroup = propertyType.PropertyGroupId == null - ? null - : contentType.PropertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value); + var propertyGroup = propertyType.PropertyGroupId == null // true generic property + ? null + : contentType.PropertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value); var genericProperty = new XElement("GenericProperty", new XElement("Name", propertyType.Name), diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index cd905a5ccc..2dcdf01291 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -291,5 +291,7 @@ namespace Umbraco.Core.Services Attempt> MoveMediaType(IMediaType toMove, int containerId); Attempt> MoveContentType(IContentType toMove, int containerId); + Attempt> CopyMediaType(IMediaType toCopy, int containerId); + Attempt> CopyContentType(IContentType toCopy, int containerId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs index e99a491939..f970cf225b 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/TypeFinder.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core /// /// A utility class to find all classes of a certain type by reflection in the current bin folder - /// of the web application. + /// of the web application. /// public static class TypeFinder { @@ -34,7 +34,7 @@ namespace Umbraco.Core /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder /// /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been + /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been /// loaded in the CLR, not all assemblies. /// See these threads: /// http://issues.umbraco.org/issue/U5-198 @@ -136,7 +136,7 @@ namespace Umbraco.Core }); /// - /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan + /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are /// cached for perforance reasons. /// @@ -190,6 +190,7 @@ namespace Umbraco.Core /// /// /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match + /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" /// internal static readonly string[] KnownAssemblyExclusionFilter = new[] { @@ -214,7 +215,7 @@ namespace Umbraco.Core "RouteDebugger,", "SqlCE4Umbraco,", "umbraco.datalayer,", - "umbraco.interfaces,", + "umbraco.interfaces,", //"umbraco.providers,", //"Umbraco.Web.UI,", "umbraco.webservices", @@ -230,9 +231,9 @@ namespace Umbraco.Core "AutoMapper,", "AutoMapper.", "AzureDirectory,", - "itextsharp,", + "itextsharp,", "UrlRewritingNet.", - "HtmlAgilityPack,", + "HtmlAgilityPack,", "MiniProfiler,", "Moq,", "nunit.framework,", @@ -371,7 +372,7 @@ namespace Umbraco.Core var assemblyList = assemblies.ToArray(); - //find all assembly references that are referencing the attribute type's assembly since we + //find all assembly references that are referencing the attribute type's assembly since we //should only be scanning those assemblies because any other assembly will definitely not //contain a class that has this attribute. var referencedAssemblies = TypeHelper.GetReferencedAssemblies(attributeType, assemblyList); @@ -418,7 +419,7 @@ namespace Umbraco.Core foreach (var subTypesInAssembly in allAttributeTypes.GroupBy(x => x.Assembly)) { - //So that we are not scanning too much, we need to group the sub types: + //So that we are not scanning too much, we need to group the sub types: // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class // * We should not search for sub types if the type is static since you cannot inherit from them. @@ -489,7 +490,7 @@ namespace Umbraco.Core /// /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly - /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type + /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type /// deriving from the base type. /// /// @@ -513,7 +514,7 @@ namespace Umbraco.Core var assemblyList = assemblies.ToArray(); - //find all assembly references that are referencing the current type's assembly since we + //find all assembly references that are referencing the current type's assembly since we //should only be scanning those assemblies because any other assembly will definitely not //contain sub type's of the one we're currently looking for var referencedAssemblies = TypeHelper.GetReferencedAssemblies(assignTypeFrom, assemblyList); @@ -560,7 +561,7 @@ namespace Umbraco.Core foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly)) { - //So that we are not scanning too much, we need to group the sub types: + //So that we are not scanning too much, we need to group the sub types: // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class // * We should not search for sub types if the type is static since you cannot inherit from them. @@ -602,39 +603,69 @@ namespace Umbraco.Core return foundAssignableTypes; } - private static IEnumerable GetTypesWithFormattedException(Assembly a) + internal static IEnumerable GetTypesWithFormattedException(Assembly a) { //if the assembly is dynamic, do not try to scan it if (a.IsDynamic) return Enumerable.Empty(); + var getAll = a.GetCustomAttribute() == null; + try { //we need to detect if an assembly is partially trusted, if so we cannot go interrogating all of it's types //only its exported types, otherwise we'll get exceptions. - if (a.GetCustomAttribute() == null) - { - return a.GetTypes(); - } - else - { - return a.GetExportedTypes(); - } + return getAll ? a.GetTypes() : a.GetExportedTypes(); } - catch (ReflectionTypeLoadException ex) + catch (TypeLoadException ex) // GetExportedTypes *can* throw TypeLoadException! { var sb = new StringBuilder(); - sb.AppendLine("Could not load types from assembly " + a.FullName + ", errors:"); - foreach (var loaderException in ex.LoaderExceptions.WhereNotNull()) - { - sb.AppendLine("Exception: " + loaderException); - } - throw new ReflectionTypeLoadException(ex.Types, ex.LoaderExceptions, sb.ToString()); + AppendCouldNotLoad(sb, a, getAll); + AppendLoaderException(sb, ex); + + // rethrow as ReflectionTypeLoadException (for consistency) with new message + throw new ReflectionTypeLoadException(new Type[0], new Exception[] { ex }, sb.ToString()); + } + catch (ReflectionTypeLoadException rex) // GetTypes throws ReflectionTypeLoadException + { + var sb = new StringBuilder(); + AppendCouldNotLoad(sb, a, getAll); + foreach (var loaderException in rex.LoaderExceptions.WhereNotNull()) + AppendLoaderException(sb, loaderException); + + // rethrow with new message + throw new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString()); } } + private static void AppendCouldNotLoad(StringBuilder sb, Assembly a, bool getAll) + { + sb.Append("Could not load "); + sb.Append(getAll ? "all" : "exported"); + sb.Append(" types from \""); + sb.Append(a.FullName); + sb.AppendLine("\" due to LoaderExceptions, skipping:"); + } + + private static void AppendLoaderException(StringBuilder sb, Exception loaderException) + { + sb.Append(". "); + sb.Append(loaderException.GetType().FullName); + + var tloadex = loaderException as TypeLoadException; + if (tloadex != null) + { + sb.Append(" on "); + sb.Append(tloadex.TypeName); + } + + sb.Append(": "); + sb.Append(loaderException.Message); + sb.AppendLine(); + } + #endregion - + public static Type GetTypeByName(string typeName) { diff --git a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs new file mode 100644 index 0000000000..dd35f9ad3c --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Moq; +using NUnit.Framework; +using Semver; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Profiling; +using Umbraco.Core.Services; +using Umbraco.Web.Strategies.Migrations; + +namespace Umbraco.Tests.Persistence.Migrations +{ + [TestFixture] + public class MigrationStartupHandlerTests + { + [Test] + public void Executes_For_Any_Product_Name_When_Not_Specified() + { + var changed1 = new Args { CountExecuted = 0 }; + var testHandler1 = new TestMigrationHandler(changed1); + testHandler1.OnApplicationStarting(Mock.Of(), new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of()))); + + var conn = new Mock(); + conn.Setup(x => x.BeginTransaction(It.IsAny())).Returns(Mock.Of()); + var db = new Mock(conn.Object); + + var runner1 = new MigrationRunner(Mock.Of(), Mock.Of(), new SemVersion(1), new SemVersion(2), "Test1", + new IMigration[] { Mock.Of() }); + var result1 = runner1.Execute(db.Object, DatabaseProviders.SqlServerCE, false); + Assert.AreEqual(1, changed1.CountExecuted); + } + + [Test] + public void Executes_Only_For_Specified_Product_Name() + { + var changed1 = new Args { CountExecuted = 0}; + var testHandler1 = new TestMigrationHandler("Test1", changed1); + testHandler1.OnApplicationStarting(Mock.Of(), new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of()))); + var changed2 = new Args { CountExecuted = 0 }; + var testHandler2 = new TestMigrationHandler("Test2", changed2); + testHandler2.OnApplicationStarting(Mock.Of(), new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of()))); + + var conn = new Mock(); + conn.Setup(x => x.BeginTransaction(It.IsAny())).Returns(Mock.Of()); + var db = new Mock(conn.Object); + + var runner1 = new MigrationRunner(Mock.Of(), Mock.Of(), new SemVersion(1), new SemVersion(2), "Test1", + new IMigration[] { Mock.Of()}); + var result1 = runner1.Execute(db.Object, DatabaseProviders.SqlServerCE, false); + Assert.AreEqual(1, changed1.CountExecuted); + Assert.AreEqual(0, changed2.CountExecuted); + + var runner2 = new MigrationRunner(Mock.Of(), Mock.Of(), new SemVersion(1), new SemVersion(2), "Test2", + new IMigration[] { Mock.Of() }); + var result2 = runner2.Execute(db.Object, DatabaseProviders.SqlServerCE, false); + Assert.AreEqual(1, changed1.CountExecuted); + Assert.AreEqual(1, changed2.CountExecuted); + } + + public class Args + { + public int CountExecuted { get; set; } + } + + public class TestMigrationHandler : MigrationStartupHander + { + private readonly string _prodName; + private readonly Args _changed; + + public TestMigrationHandler(Args changed) + { + _changed = changed; + } + + public TestMigrationHandler(string prodName, Args changed) + { + _prodName = prodName; + _changed = changed; + } + + protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) + { + _changed.CountExecuted++; + } + + public override string[] TargetProductNames + { + get { return _prodName.IsNullOrWhiteSpace() ? new string[] {} : new[] {_prodName}; } + } + } + } +} diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs index caad5a7162..99b3a3cdce 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]") .InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]") .InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")); + .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")); var sql = new Sql(); sql.Select("*") @@ -56,8 +56,8 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]") .InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]") .InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")) - .Where("[umbracoNode].[id] = @0", 1050); + .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")) + .Where("([umbracoNode].[id] = @0)", 1050); var sql = new Sql(); sql.Select("*") @@ -94,9 +94,9 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]") .InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]") .InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")) - .Where("[umbracoNode].[id] = @0", 1050) - .Where("[cmsContentVersion].[VersionId] = @0", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")) + .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")) + .Where("([umbracoNode].[id] = @0)", 1050) + .Where("([cmsContentVersion].[VersionId] = @0)", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")) .OrderBy("[cmsContentVersion].[VersionDate] DESC"); var sql = new Sql(); @@ -133,8 +133,8 @@ namespace Umbraco.Tests.Persistence.Querying expected.Select("*"); expected.From("[cmsPropertyData]"); expected.InnerJoin("[cmsPropertyType]").On("[cmsPropertyData].[propertytypeid] = [cmsPropertyType].[id]"); - expected.Where("[cmsPropertyData].[contentNodeId] = @0", 1050); - expected.Where("[cmsPropertyData].[versionId] = @0", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")); + expected.Where("([cmsPropertyData].[contentNodeId] = @0)", 1050); + expected.Where("([cmsPropertyData].[versionId] = @0)", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")); var sql = new Sql(); sql.Select("*") diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs index b56b5f2816..bf2d0693ae 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs @@ -24,8 +24,8 @@ namespace Umbraco.Tests.Persistence.Querying .On("[cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId]") .InnerJoin("[umbracoNode]") .On("[cmsContentType].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb")) - .Where("[cmsDocumentType].[IsDefault] = @0", true); + .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb")) + .Where("([cmsDocumentType].[IsDefault] = @0)", true); var sql = new Sql(); sql.Select("*") @@ -61,9 +61,9 @@ namespace Umbraco.Tests.Persistence.Querying .On("[cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId]") .InnerJoin("[umbracoNode]") .On("[cmsContentType].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb")) + .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb")) .Where("[cmsDocumentType].[IsDefault] = @0", true) - .Where("[umbracoNode].[id] = @0", 1050); + .Where("([umbracoNode].[id] = @0)", 1050); var sql = new Sql(); sql.Select("*") @@ -117,7 +117,7 @@ namespace Umbraco.Tests.Persistence.Querying var sqlSyntaxProvider = new SqlCeSyntaxProvider(); expected.Select("*") .From("[cmsContentTypeAllowedContentType]") - .Where("[cmsContentTypeAllowedContentType].[Id] = @0", 1050); + .Where("([cmsContentTypeAllowedContentType].[Id] = @0)", 1050); var sql = new Sql(); sql.Select("*") @@ -144,7 +144,7 @@ namespace Umbraco.Tests.Persistence.Querying .From("[cmsPropertyTypeGroup]") .RightJoin("[cmsPropertyType]").On("[cmsPropertyTypeGroup].[id] = [cmsPropertyType].[propertyTypeGroupId]") .InnerJoin("[cmsDataType]").On("[cmsPropertyType].[dataTypeId] = [cmsDataType].[nodeId]") - .Where("[cmsPropertyType].[contentTypeId] = @0", 1050); + .Where("([cmsPropertyType].[contentTypeId] = @0)", 1050); var sql = new Sql(); sql.Select("*") diff --git a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs index 92ad7e893b..55805282a4 100644 --- a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.Persistence.Querying expected.Select("*") .From("[cmsDataType]") .InnerJoin("[umbracoNode]").On("[cmsDataType].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("30a2a501-1978-4ddb-a57b-f7efed43ba3c")); + .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("30a2a501-1978-4ddb-a57b-f7efed43ba3c")); var sql = new Sql(); sql.Select("*") diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index b0cf87acac..9100a77eb0 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -56,7 +56,7 @@ namespace Umbraco.Tests.Persistence.Querying Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); - Assert.AreEqual("[umbracoNode].[parentID] = @0", result); + Assert.AreEqual("([umbracoNode].[parentID] = @0)", result); Assert.AreEqual(-1, modelToSqlExpressionHelper.GetSqlParameters()[0]); } @@ -70,7 +70,7 @@ namespace Umbraco.Tests.Persistence.Querying Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); - Assert.AreEqual("[umbracoUser].[userLogin] = @0", result); + Assert.AreEqual("([umbracoUser].[userLogin] = @0)", result); Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); } diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs index 89343826f3..5d4501eb15 100644 --- a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests.Persistence.Querying .From("[cmsContentVersion]") .InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]") .InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("b796f64c-1f99-4ffb-b886-4bf4bc011a9c")); + .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("b796f64c-1f99-4ffb-b886-4bf4bc011a9c")); var sql = new Sql(); sql.Select("*") diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs index 871fba78b9..6d6ec4ed2c 100644 --- a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.Persistence.Querying expected.Select("*") .From("[cmsContentType]") .InnerJoin("[umbracoNode]").On("[cmsContentType].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("4ea4382b-2f5a-4c2b-9587-ae9b3cf3602e")); + .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("4ea4382b-2f5a-4c2b-9587-ae9b3cf3602e")); var sql = new Sql(); sql.Select("*") diff --git a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs index 672b115415..930d24a491 100644 --- a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs @@ -35,7 +35,7 @@ namespace Umbraco.Tests.Persistence.Querying var sql = new Sql("SELECT *").From(SqlSyntax) .Where(SqlSyntax, x => x.Path.StartsWith(content.Path) && x.NodeId != content.NodeId); - Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[path]) LIKE upper(@0) AND [umbracoNode].[id] <> @1)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((upper([umbracoNode].[path]) LIKE upper(@0) AND ([umbracoNode].[id] <> @1)))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(2, sql.Arguments.Length); Assert.AreEqual(content.Path + "%", sql.Arguments[0]); Assert.AreEqual(content.NodeId, sql.Arguments[1]); @@ -48,7 +48,19 @@ namespace Umbraco.Tests.Persistence.Querying var sql = new Sql("SELECT *").From(SqlSyntax) .Where(SqlSyntax, x => x.Level == level && !x.Path.StartsWith("-20")); - Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[level] = @0 AND NOT (upper([umbracoNode].[path]) LIKE upper(@1)))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[level] = @0) AND NOT (upper([umbracoNode].[path]) LIKE upper(@1))))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(2, sql.Arguments.Length); + Assert.AreEqual(level, sql.Arguments[0]); + Assert.AreEqual("-20%", sql.Arguments[1]); + } + + [Test] + public void Where_Clause_With_EqualsFalse_Starts_With() + { + var level = 1; + var sql = new Sql("SELECT *").From().Where(x => x.Level == level && x.Path.StartsWith("-20") == false); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[level] = @0) AND NOT (upper([umbracoNode].[path]) LIKE upper(@1))))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(2, sql.Arguments.Length); Assert.AreEqual(level, sql.Arguments[0]); Assert.AreEqual("-20%", sql.Arguments[1]); @@ -76,6 +88,16 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(true, sql.Arguments[0]); } + [Test] + public void Where_Clause_With_EqualsFalse_Boolean() + { + var sql = new Sql("SELECT *").From().Where(x => x.Trashed == false); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(1, sql.Arguments.Length); + Assert.AreEqual(true, sql.Arguments[0]); + } + [Test] public void Where_Clause_With_Boolean() { @@ -93,7 +115,7 @@ namespace Umbraco.Tests.Persistence.Querying var sql = new Sql("SELECT *").From(SqlSyntax) .Where(SqlSyntax, x => x.Text.ToUpper() == "hello".ToUpper()); - Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) = upper(@0))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((upper([umbracoNode].[text]) = upper(@0)))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); Assert.AreEqual("hello", sql.Arguments[0]); } @@ -104,7 +126,7 @@ namespace Umbraco.Tests.Persistence.Querying var sql = new Sql("SELECT *").From(SqlSyntax) .Where(SqlSyntax, x => x.Text == 1.ToString()); - Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[text] = @0)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (([umbracoNode].[text] = @0))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); Assert.AreEqual("1", sql.Arguments[0]); } @@ -126,7 +148,7 @@ namespace Umbraco.Tests.Persistence.Querying var sql = new Sql("SELECT *").From(SqlSyntax) .Where(SqlSyntax, x => x.NodeId == 2); - Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[id] = @0)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (([umbracoNode].[id] = @0))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); Assert.AreEqual(2, sql.Arguments[0]); } @@ -137,7 +159,7 @@ namespace Umbraco.Tests.Persistence.Querying var sql = new Sql("SELECT *").From(SqlSyntax) .Where(SqlSyntax, x => x.NodeId != 2 && x.NodeId != 3); - Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[id] <> @0 AND [umbracoNode].[id] <> @1)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[id] <> @0) AND ([umbracoNode].[id] <> @1)))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(2, sql.Arguments.Length); Assert.AreEqual(2, sql.Arguments[0]); Assert.AreEqual(3, sql.Arguments[1]); @@ -149,7 +171,7 @@ namespace Umbraco.Tests.Persistence.Querying var sql = new Sql("SELECT *").From(SqlSyntax) .Where(SqlSyntax, x => x.Text == "hello" || x.NodeId == 3); - Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[text] = @0 OR [umbracoNode].[id] = @1)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[text] = @0) OR ([umbracoNode].[id] = @1)))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(2, sql.Arguments.Length); Assert.AreEqual("hello", sql.Arguments[0]); Assert.AreEqual(3, sql.Arguments[1]); @@ -220,7 +242,7 @@ namespace Umbraco.Tests.Persistence.Querying public void Can_Use_Where_Predicate() { var expected = new Sql(); - expected.Select("*").From("[cmsContent]").Where("[cmsContent].[nodeId] = @0", 1045); + expected.Select("*").From("[cmsContent]").Where("([cmsContent].[nodeId] = @0)", 1045); var sql = new Sql(); sql.Select("*").From(SqlSyntax).Where(SqlSyntax, x => x.NodeId == 1045); @@ -236,8 +258,8 @@ namespace Umbraco.Tests.Persistence.Querying var expected = new Sql(); expected.Select("*") .From("[cmsContent]") - .Where("[cmsContent].[nodeId] = @0", 1045) - .Where("[cmsContent].[contentType] = @0", 1050); + .Where("([cmsContent].[nodeId] = @0)", 1045) + .Where("([cmsContent].[contentType] = @0)", 1050); var sql = new Sql(); sql.Select("*") diff --git a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs index a4778fd47d..62582ace64 100644 --- a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs @@ -56,7 +56,7 @@ namespace Umbraco.Tests.Persistence.Querying var result = translator.Translate(); var strResult = result.SQL; - string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([umbracoNode].[parentID] = @0)"; + string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (([umbracoNode].[parentID] = @0))"; // Assert Assert.That(strResult, Is.Not.Empty); @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Persistence.Querying var result = translator.Translate(); var strResult = result.SQL; - string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([cmsContentType].[alias] = @0)"; + string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (([cmsContentType].[alias] = @0))"; // Assert Assert.That(strResult, Is.Not.Empty); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 444c50eba1..2c1404683f 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -91,7 +91,7 @@ namespace Umbraco.Tests.Persistence.Repositories templateRepo.AddOrUpdate(template); } unitOfWork.Commit(); - + var contentType = MockedContentTypes.CreateSimpleContentType(); contentType.AllowedTemplates = new[] {templates[0], templates[1]}; contentType.SetDefaultTemplate(templates[0]); @@ -128,7 +128,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(contentType); unitOfWork.Commit(); - //create a + //create a var contentType2 = (IContentType)new ContentType(contentType, "hello") { Name = "Blahasdfsadf" @@ -294,7 +294,9 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { // Act - var contentType = (IContentType)MockedContentTypes.CreateSimpleContentType("test", "Test", propertyGroupName: "testGroup"); + var contentType = (IContentType)MockedContentTypes.CreateSimpleContentType2("test", "Test", propertyGroupName: "testGroup"); + + Assert.AreEqual(4, contentType.PropertyTypes.Count()); // there is NO mapping from display to contentType, but only from save // to contentType, so if we want to test, let's to it properly! @@ -302,12 +304,18 @@ namespace Umbraco.Tests.Persistence.Repositories var save = MapToContentTypeSave(display); var mapped = Mapper.Map(save); + Assert.AreEqual(4, mapped.PropertyTypes.Count()); + repository.AddOrUpdate(mapped); unitOfWork.Commit(); + Assert.AreEqual(4, mapped.PropertyTypes.Count()); + //re-get contentType = repository.Get(mapped.Id); + Assert.AreEqual(4, contentType.PropertyTypes.Count()); + // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(contentType.PropertyGroups.All(x => x.HasIdentity), Is.True); @@ -317,7 +325,11 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(contentType.PropertyGroups.ElementAt(0).Name == "testGroup", Is.True); var groupId = contentType.PropertyGroups.ElementAt(0).Id; - Assert.That(contentType.PropertyTypes.All(x => x.PropertyGroupId.Value == groupId), Is.True); + + var propertyTypes = contentType.PropertyTypes.ToArray(); + Assert.AreEqual("gen", propertyTypes[0].Alias); // just to be sure + Assert.IsNull(propertyTypes[0].PropertyGroupId); + Assert.IsTrue(propertyTypes.Skip(1).All((x => x.PropertyGroupId.Value == groupId))); } } @@ -354,7 +366,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "subtitle"), Is.True); } - + } // this is for tests only because it makes no sense at all to have such a @@ -492,16 +504,16 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) - { + { var ctMain = MockedContentTypes.CreateSimpleContentType(); var ctChild1 = MockedContentTypes.CreateSimpleContentType("child1", "Child 1", ctMain, true); var ctChild2 = MockedContentTypes.CreateSimpleContentType("child2", "Child 2", ctChild1, true); - + repository.AddOrUpdate(ctMain); repository.AddOrUpdate(ctChild1); - repository.AddOrUpdate(ctChild2); + repository.AddOrUpdate(ctChild2); unitOfWork.Commit(); - + // Act var resolvedParent = repository.Get(ctMain.Id); @@ -529,7 +541,7 @@ namespace Umbraco.Tests.Persistence.Repositories var child3 = MockedContentTypes.CreateSimpleContentType("zyx", "zyx", contentType, randomizeAliases: true); repository.AddOrUpdate(child3); var child2 = MockedContentTypes.CreateSimpleContentType("a123", "a123", contentType, randomizeAliases: true); - repository.AddOrUpdate(child2); + repository.AddOrUpdate(child2); unitOfWork.Commit(); // Act @@ -541,7 +553,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("aabc", contentTypes.ElementAt(1).Name); Assert.AreEqual("zyx", contentTypes.ElementAt(2).Name); } - + } [Test] @@ -672,7 +684,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var contentType = repository.Get(NodeDto.NodeIdSeed + 1); - // Act + // Act contentType.PropertyGroups["Meta"].PropertyTypes.Remove("description"); repository.AddOrUpdate(contentType); unitOfWork.Commit(); @@ -842,7 +854,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(5)); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "metaAuthor"), Is.True); } - + } [Test] diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index 0857eb77c6..b5ae4a5ed5 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs @@ -9,11 +9,12 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations.Syntax.Create.Index; using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Persistence.SyntaxProvider { [TestFixture] - public class SqlCeSyntaxProviderTests + public class SqlCeSyntaxProviderTests : BaseUsingSqlCeSyntax { [Test] @@ -35,7 +36,7 @@ namespace Umbraco.Tests.Persistence.SyntaxProvider FROM [cmsContentXml] INNER JOIN [umbracoNode] ON [cmsContentXml].[nodeId] = [umbracoNode].[id] -WHERE ([umbracoNode].[nodeObjectType] = @0)) x)".Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "), +WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "), sqlOutput.SQL.Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " ")); Assert.AreEqual(1, sqlOutput.Arguments.Length); diff --git a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs index 414221bd5b..44a329c655 100644 --- a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs @@ -75,8 +75,8 @@ namespace Umbraco.Tests.Plugins var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies); Assert.AreEqual(originalTypesFound.Count(), typesFound.Count()); - Assert.AreEqual(8, typesFound.Count()); - Assert.AreEqual(8, originalTypesFound.Count()); + Assert.AreEqual(9, typesFound.Count()); + Assert.AreEqual(9, originalTypesFound.Count()); } [Test] diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index c79da17251..4ee572ae98 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -234,9 +234,9 @@ namespace Umbraco.Tests.PublishedContent new PublishedPropertyType("prop1", 1, "?"), }; - var contentType1 = new PublishedContentType(1, "ContentType1", props); - var contentType2 = new PublishedContentType(2, "ContentType2", props); - var contentType2s = new PublishedContentType(3, "ContentType2Sub", props); + var contentType1 = new PublishedContentType(1, "ContentType1", Enumerable.Empty(), props); + var contentType2 = new PublishedContentType(2, "ContentType2", Enumerable.Empty(), props); + var contentType2s = new PublishedContentType(3, "ContentType2Sub", Enumerable.Empty(), props); cache.Add(new SolidPublishedContent(contentType1) { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs index 4abfc6e18d..82115d670e 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs @@ -355,7 +355,11 @@ namespace Umbraco.Tests.PublishedContent private static readonly PublishedPropertyType Default = new PublishedPropertyType("*", 0, "?"); public AutoPublishedContentType(int id, string alias, IEnumerable propertyTypes) - : base(id, alias, propertyTypes) + : base(id, alias, Enumerable.Empty(), propertyTypes) + { } + + public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) + : base(id, alias, compositionAliases, propertyTypes) { } public override PublishedPropertyType GetPropertyType(string alias) diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 2aa72091de..3967afce79 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -50,7 +50,8 @@ namespace Umbraco.Tests.PublishedContent new PublishedPropertyType("content", 0, Constants.PropertyEditors.TinyMCEAlias), new PublishedPropertyType("testRecursive", 0, "?"), }; - var type = new AutoPublishedContentType(0, "anything", propertyTypes); + var compositionAliases = new[] {"MyCompositionAlias"}; + var type = new AutoPublishedContentType(0, "anything", compositionAliases, propertyTypes); PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; } @@ -468,6 +469,16 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNull(doc.FirstChild()); } + [Test] + public void IsComposedOf() + { + var doc = GetNode(1173); + + var isComposedOf = doc.IsComposedOf("MyCompositionAlias"); + + Assert.IsTrue(isComposedOf); + } + [Test] public void HasProperty() { @@ -475,8 +486,7 @@ namespace Umbraco.Tests.PublishedContent var hasProp = doc.HasProperty(Constants.Conventions.Content.UrlAlias); - Assert.AreEqual(true, (bool)hasProp); - + Assert.IsTrue(hasProp); } [Test] diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index 786053e680..e3585d0554 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -164,6 +164,24 @@ namespace Umbraco.Tests.TestHelpers.Entities return contentType; } + public static ContentType CreateSimpleContentType2(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") + { + var contentType = CreateSimpleContentType(alias, name, parent, randomizeAliases, propertyGroupName); + + var propertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = RandomAlias("gen", randomizeAliases), + Name = "Gen", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeDefinitionId = -88 + }; + contentType.AddPropertyType(propertyType); + + return contentType; + } + public static ContentType CreateSimpleContentType(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") { var contentType = parent == null ? new ContentType(-1) : new ContentType(parent, alias); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 65600854f3..11cabc6d6a 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -176,6 +176,7 @@ + @@ -246,6 +247,7 @@ + @@ -691,7 +693,7 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" /Y /F /E /I /C /D - + + + + + + +

Controller example

+
+	(function () {
+		"use strict";
+
+		function Controller() {
+
+            var vm = this;
+
+            vm.id = 1;
+            vm.name = "My Parent element";
+            vm.icon = "icon-document";
+            vm.selectedChildren = [];
+            vm.availableChildren = [
+                {
+                    id: 1,
+                    alias: "item1",
+                    name: "Item 1",
+                    icon: "icon-document"
+                },
+                {
+                    id: 2,
+                    alias: "item2",
+                    name: "Item 2",
+                    icon: "icon-document"
+                }
+            ];
+
+            vm.addChild = addChild;
+            vm.removeChild = removeChild;
+
+            function addChild($event) {
+                vm.overlay = {
+                    view: "itempicker",
+                    title: "Choose child",
+                    availableItems: vm.availableChildren,
+                    selectedItems: vm.selectedChildren,
+                    event: $event,
+                    show: true,
+                    submit: function(model) {
+
+                        // add selected child
+                        vm.selectedChildren.push(model.selectedItem);
+
+                        // close overlay
+                        vm.overlay.show = false;
+                        vm.overlay = null;
+                    }
+                };
+            }
+
+            function removeChild($index) {
+                vm.selectedChildren.splice($index, 1);
+            }
+
+        }
+
+		angular.module("umbraco").controller("My.Controller", Controller);
+
+	})();
+
+ +@param {array} selectedChildren (binding): Array of selected children. +@param {array} availableChildren (binding: Array of items available for selection. +@param {string} parentName (binding): The parent name. +@param {string} parentIcon (binding): The parent icon. +@param {number} parentId (binding): The parent id. +@param {callback} onRemove (binding): Callback when the remove button is clicked on an item. +

The callback returns:

+
    +
  • child: The selected item.
  • +
  • $index: The selected item index.
  • +
+@param {callback} onAdd (binding): Callback when the add button is clicked. +

The callback returns:

+
    +
  • $event: The select event.
  • +
+**/ + (function() { 'use strict'; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js index df3e431620..1dcccda481 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js @@ -1,3 +1,69 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbConfirmAction +@restrict E +@scope + +@description +

Use this directive to toggle a confirmation prompt for an action. +The prompt consists of a checkmark and a cross to confirm or cancel the action. +The prompt can be opened in four direction up, down, left or right.

+ +

Markup example

+
+    
+ +
+ + + +
+ +
+
+ +

Controller example

+
+    (function () {
+
+        "use strict";
+
+        function Controller() {
+
+            var vm = this;
+            vm.promptIsVisible = false;
+
+            vm.confirmAction = confirmAction;
+            vm.showPrompt = showPrompt;
+            vm.hidePrompt = hidePrompt;
+
+            function confirmAction() {
+                // confirm logic here
+            }
+
+            function showPrompt() {
+                vm.promptIsVisible = true;
+            }
+
+            function hidePrompt() {
+                vm.promptIsVisible = false;
+            }
+
+        }
+
+        angular.module("umbraco").controller("My.Controller", Controller);
+    })();
+
+ +@param {string} direction The direction the prompt opens ("up", "down", "left", "right"). +@param {callback} onConfirm Callback when the checkmark is clicked. +@param {callback} onCancel Callback when the cross is clicked. +**/ + (function() { 'use strict'; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js index d993880e8c..3994770c8e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js @@ -1,3 +1,108 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbContentGrid +@restrict E +@scope + +@description +Use this directive to generate a list of content items presented as a flexbox grid. + +

Markup example

+
+    
+ + + + +
+
+ +

Controller example

+
+    (function () {
+        "use strict";
+
+        function Controller() {
+
+            var vm = this;
+            vm.contentItems = [
+                {
+                    "name": "Cape",
+                    "published": true,
+                    "icon": "icon-document",
+                    "updateDate": "15-02-2016",
+                    "owner": "Mr. Batman",
+                    "selected": false
+                },
+                {
+                    "name": "Utility Belt",
+                    "published": true,
+                    "icon": "icon-document",
+                    "updateDate": "15-02-2016",
+                    "owner": "Mr. Batman",
+                    "selected": false
+                },
+                {
+                    "name": "Cave",
+                    "published": true,
+                    "icon": "icon-document",
+                    "updateDate": "15-02-2016",
+                    "owner": "Mr. Batman",
+                    "selected": false
+                }
+            ];
+            vm.includeProperties = [
+                {
+                  "alias": "updateDate",
+                  "header": "Last edited"
+                },
+                {
+                  "alias": "owner",
+                  "header": "Created by"
+                }
+            ];
+
+            vm.clickItem = clickItem;
+            vm.selectItem = selectItem;
+
+
+            function clickItem(item, $event, $index){
+                // do magic here
+            }
+
+            function selectItem(item, $event, $index) {
+                // set item.selected = true; to select the item
+                // do magic here
+            }
+
+        }
+
+        angular.module("umbraco").controller("My.Controller", Controller);
+    })();
+
+ +@param {array} content (binding): Array of content items. +@param {array=} contentProperties (binding): Array of content item properties to include in the item. If left empty the item will only show the item icon and name. +@param {callback=} onClick (binding): Callback method to handle click events on the content item. +

The callback returns:

+
    +
  • item: The clicked item
  • +
  • $event: The select event
  • +
  • $index: The item index
  • +
+@param {callback=} onClickName (binding): Callback method to handle click events on the checkmark icon. +

The callback returns:

+
    +
  • item: The selected item
  • +
  • $event: The select event
  • +
  • $index: The item index
  • +
+**/ + (function() { 'use strict'; 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 index 6f5cbd2166..b37895637b 100644 --- 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 @@ -1,3 +1,29 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbEmptyState +@restrict E +@scope + +@description +Use this directive to show an empty state message. + +

Markup example

+
+    
+ + + // Empty state content + + +
+
+ +@param {string=} size Set the size of the text ("small", "large"). +@param {string=} position Set the position of the text ("center"). +**/ + (function() { 'use strict'; 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 9f18f91e26..812038b51a 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 @@ -1,3 +1,83 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbFolderGrid +@restrict E +@scope + +@description +Use this directive to generate a list of folders presented as a flexbox grid. + +

Markup example

+
+    
+ + +
+
+ +

Controller example

+
+    (function () {
+        "use strict";
+
+        function Controller(myService) {
+
+            var vm = this;
+            vm.folders = [
+                {
+                    "name": "Folder 1",
+                    "icon": "icon-folder",
+                    "selected": false
+                },
+                {
+                    "name": "Folder 2",
+                    "icon": "icon-folder",
+                    "selected": false
+                }
+
+            ];
+
+            vm.clickFolder = clickFolder;
+            vm.selectFolder = selectFolder;
+
+            myService.getFolders().then(function(folders){
+                vm.folders = folders;
+            });
+
+            function clickFolder(folder){
+                // Execute when clicking folder name/link
+            }
+
+            function selectFolder(folder, event, index) {
+                // Execute when clicking folder
+                // set folder.selected = true; to show checkmark icon
+            }
+
+        }
+
+        angular.module("umbraco").controller("My.Controller", Controller);
+    })();
+
+ +@param {array} folders (binding): Array of folders +@param {callback=} onClick (binding): Callback method to handle click events on the folder. +

The callback returns:

+
    +
  • folder: The selected folder
  • +
+@param {callback=} onSelect (binding): Callback method to handle click events on the checkmark icon. +

The callback returns:

+
    +
  • folder: The selected folder
  • +
  • $event: The select event
  • +
  • $index: The folder index
  • +
+**/ + (function() { 'use strict'; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js index 127bf38f49..1bbfb850d9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js @@ -1,3 +1,112 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbkeyboardShortcutsOverview +@restrict E +@scope + +@description + +

Use this directive to show an overview of keyboard shortcuts in an editor. +The directive will render an overview trigger wich shows how the overview is opened. +When this combination is hit an overview is opened with shortcuts based on the model sent to the directive.

+ +

Markup example

+
+    
+ + + + +
+
+ +

Controller example

+
+    (function () {
+
+        "use strict";
+
+        function Controller() {
+
+            var vm = this;
+
+            vm.keyboardShortcutsOverview = [
+                {
+                    "name": "Sections",
+                    "shortcuts": [
+                        {
+                            "description": "Navigate sections",
+                            "keys": [
+                                {"key": "1"},
+                                {"key": "4"}
+                            ],
+                            "keyRange": true
+                        }
+                    ]
+                },
+                {
+                    "name": "Design",
+                    "shortcuts": [
+                        {
+                            "description": "Add tab",
+                            "keys": [
+                                {"key": "alt"},
+                                {"key": "shift"},
+                                {"key": "t"}
+                            ]
+                        }
+                    ]
+                }
+            ];
+
+        }
+
+        angular.module("umbraco").controller("My.Controller", Controller);
+    })();
+
+ +

Model description

+
    +
  • + name + (string) - + Sets the shortcut section name. +
  • +
  • + shortcuts + (array) - + Array of available shortcuts in the section. +
  • +
      +
    • + description + (string) - + Short description of the shortcut. +
    • +
    • + keys + (array) - + Array of keys in the shortcut. +
    • +
        +
      • + key + (string) - + The invidual key in the shortcut. +
      • +
      +
    • + keyRange + (boolean) - + Set to true to show a key range. It combines the shortcut keys with "-" instead of "+". +
    • +
    +
+ +@param {object} model keyboard shortcut model. See description and example above. +**/ + (function() { 'use strict'; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js index efa95f1fbd..0671770796 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js @@ -1,3 +1,50 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbLoadIndicator +@restrict E + +@description +Use this directive to generate a loading indicator. + +

Markup example

+
+    
+ + + + +
+

{{content}}

+
+ +
+
+ +

Controller example

+
+    (function () {
+        "use strict";
+
+        function Controller(myService) {
+
+            var vm = this;
+
+            vm.content = "";
+            vm.loading = true;
+
+            myService.getContent().then(function(content){
+                vm.content = content;
+                vm.loading = false;
+            });
+
+        }
+½
+        angular.module("umbraco").controller("My.Controller", Controller);
+    })();
+
+**/ + (function() { 'use strict'; 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 204fa7bb86..cc5a1eb2b1 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 @@ -1,11 +1,48 @@ /** -* @ngdoc directive -* @name umbraco.directives.directive:umbContentName -* @restrict E -* @function -* @description -* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. +@ngdoc directive +@name umbraco.directives.directive:umbLockedField +@restrict E +@scope + +@description +Use this directive to render a value with a lock next to it. When the lock is clicked the value gets unlocked and can be edited. + +

Markup example

+
+	
+ + + + +
+
+ +

Controller example

+
+	(function () {
+		"use strict";
+
+		function Controller() {
+
+			var vm = this;
+			vm.value = "My locked text";
+
+        }
+
+		angular.module("umbraco").controller("My.Controller", Controller);
+
+	})();
+
+ +@param {string} ngModel (binding): The locked text. +@param {boolean=} locked (binding): true by default. Set to false to unlock the text. +@param {string=} placeholderText (binding): If ngModel is empty this text will be shown. +@param {string=} regexValidation (binding): Set a regex expression for validation of the field. +@param {string=} serverValidationField (attribute): Set a server validation field. **/ + (function() { 'use strict'; 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 eed0cffadb..2e95b0c96b 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 @@ -1,3 +1,87 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbMediaGrid +@restrict E +@scope + +@description +Use this directive to generate a thumbnail grid of media items. + +

Markup example

+
+    
+ + + + +
+
+ +

Controller example

+
+    (function () {
+        "use strict";
+
+        function Controller() {
+
+            var vm = this;
+            vm.mediaItems = [];
+
+            vm.clickItem = clickItem;
+            vm.clickItemName = clickItemName;
+
+            myService.getMediaItems().then(function (mediaItems) {
+                vm.mediaItems = mediaItems;
+            });
+
+            function clickItem(item, $event, $index){
+                // do magic here
+            }
+
+            function clickItemName(item, $event, $index) {
+                // set item.selected = true; to select the item
+                // do magic here
+            }
+
+        }
+
+        angular.module("umbraco").controller("My.Controller", Controller);
+    })();
+
+ +@param {array} items (binding): Array of media items. +@param {callback=} onDetailsHover (binding): Callback method when the details icon is hovered. +

The callback returns:

+
    +
  • item: The hovered item
  • +
  • $event: The hover event
  • +
  • hover: Boolean to tell if the item is hovered or not
  • +
+@param {callback=} onClick (binding): Callback method to handle click events on the media item. +

The callback returns:

+
    +
  • item: The clicked item
  • +
  • $event: The click event
  • +
  • $index: The item index
  • +
+@param {callback=} onClickName (binding): Callback method to handle click events on the media item name. +

The callback returns:

+
    +
  • item: The clicked item
  • +
  • $event: The click event
  • +
  • $index: The item index
  • +
+@param {string=} filterBy (binding): String to filter media items by +@param {string=} itemMaxWidth (attribute): Sets a max width on the media item thumbnails. +@param {string=} itemMaxHeight (attribute): Sets a max height on the media item thumbnails. +@param {string=} itemMinWidth (attribute): Sets a min width on the media item thumbnails. +@param {string=} itemMinHeight (attribute): Sets a min height on the media item thumbnails. + +**/ + (function() { 'use strict'; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js index 18dd616f75..fe753171e1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js @@ -1,3 +1,88 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbPagination +@restrict E +@scope + +@description +Use this directive to generate a pagination. + +

Markup example

+
+    
+ + + + +
+
+ +

Controller example

+
+    (function () {
+        "use strict";
+
+        function Controller() {
+
+            var vm = this;
+
+            vm.pagination = {
+                pageNumber: 1,
+                totalPages: 10
+            }
+
+            vm.nextPage = nextPage;
+            vm.prevPage = prevPage;
+            vm.goToPage = goToPage;
+
+            function nextPage(pageNumber) {
+                // do magic here
+                console.log(pageNumber);
+                alert("nextpage");
+            }
+
+            function prevPage(pageNumber) {
+                // do magic here
+                console.log(pageNumber);
+                alert("prevpage");
+            }
+
+            function goToPage(pageNumber) {
+                // do magic here
+                console.log(pageNumber);
+                alert("go to");
+            }
+
+        }
+
+        angular.module("umbraco").controller("My.Controller", Controller);
+    })();
+
+ +@param {number} pageNumber (binding): Current page number. +@param {number} totalPages (binding): The total number of pages. +@param {callback} onNext (binding): Callback method to go to the next page. +

The callback returns:

+
    +
  • pageNumber: The page number
  • +
+@param {callback=} onPrev (binding): Callback method to go to the previous page. +

The callback returns:

+
    +
  • pageNumber: The page number
  • +
+@param {callback=} onGoToPage (binding): Callback method to go to a specific page. +

The callback returns:

+
    +
  • pageNumber: The page number
  • +
+**/ + (function() { 'use strict'; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js index 727dc5ec94..cee705b8e8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js @@ -1,3 +1,41 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbStickyBar +@restrict A + +@description +Use this directive make an element sticky and follow the page when scrolling. + +

Markup example

+
+    
+ +
+
+ +
+
+ +

CSS example

+
+    .my-sticky-bar {
+        padding: 15px 0;
+        background: #000000;
+        position: relative;
+        top: 0;
+    }
+
+    .my-sticky-bar.-umb-sticky-bar {
+        top: 100px;
+    }
+
+ +@param {string} scrollableContainer Set the class (".element") or the id ("#element") of the scrollable container element. +**/ + (function() { 'use strict'; 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 176e1681b8..d3bda41d43 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 @@ -1,3 +1,71 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbTooltip +@restrict E +@scope + +@description +Use this directive to render a tooltip. + +

Markup example

+
+    
+ +
+ Hover me +
+ + + // tooltip content here + + +
+
+ +

Controller example

+
+    (function () {
+        "use strict";
+
+        function Controller() {
+
+            var vm = this;
+            vm.tooltip = {
+                show: false,
+                event: null
+            };
+
+            vm.mouseOver = mouseOver;
+            vm.mouseLeave = mouseLeave;
+
+            function mouseOver($event) {
+                vm.tooltip = {
+                    show: true,
+                    event: $event
+                };
+            }
+
+            function mouseLeave() {
+                vm.tooltip = {
+                    show: false,
+                    event: null
+                };
+            }
+
+        }
+
+        angular.module("umbraco").controller("My.Controller", Controller);
+
+    })();
+
+ +@param {string} event Set the $event from the target element to position the tooltip relative to the mouse cursor. +**/ + (function() { 'use strict'; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index d33a8edb48..e0678c1065 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -1,6 +1,6 @@ /** * @ngdoc directive -* @name umbraco.directives.directive:umbContentName +* @name umbraco.directives.directive:umbFileDropzone * @restrict E * @function * @description diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbsinglefileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbsinglefileupload.directive.js index b8123b9202..df4f68950e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbsinglefileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbsinglefileupload.directive.js @@ -1,6 +1,6 @@ /** * @ngdoc directive -* @name umbraco.directives.directive:umbFileUpload +* @name umbraco.directives.directive:umbSingleFileUpload * @function * @restrict A * @scope diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 5aa56a80af..2cf7127707 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -509,6 +509,16 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to check permission for item ' + id); }, + getPermissions: function (nodeIds) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetPermissions"), + nodeIds), + 'Failed to get permissions'); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#save 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 55cc2974b7..4c7b6f916e 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 @@ -29,7 +29,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { filterContentTypes: filterContentTypes, filterPropertyTypes: filterPropertyTypes }; - + return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( @@ -201,9 +201,9 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * .then(function() { * alert("node was moved"); * }, function(err){ - * alert("node didnt move:" + err.data.Message); + * alert("node didnt move:" + err.data.Message); * }); - * + * * @param {Object} args arguments object * @param {Int} args.idd the ID of the node to move * @param {Int} args.parentId the ID of the parent node to move to @@ -230,6 +230,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to move content'); }, + copy: function(args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCopy"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to copy content'); + }, + createContainer: function(parentId, name) { return umbRequestHelper.resourcePromise( @@ -237,7 +257,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to create a folder under parent id ' + parentId); } - + }; } angular.module('umbraco.resources').factory('contentTypeResource', contentTypeResource); 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 516b9eb5d1..117edef77f 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 @@ -53,7 +53,7 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * .then(function(array) { * $scope.type = type; * }); - * + * * @param {Int} mediaId id of the media item to retrive allowed child types for * @returns {Promise} resourcePromise object. * @@ -145,9 +145,9 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * .then(function() { * alert("node was moved"); * }, function(err){ - * alert("node didnt move:" + err.data.Message); + * alert("node didnt move:" + err.data.Message); * }); - * + * * @param {Object} args arguments object * @param {Int} args.idd the ID of the node to move * @param {Int} args.parentId the ID of the parent node to move to @@ -174,6 +174,26 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to move content'); }, + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostCopy"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to copy content'); + }, + createContainer: function(parentId, name) { return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js index ea4bee718f..73b6394b0a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js @@ -16,7 +16,7 @@ function treeResource($q, $http, umbRequestHelper) { /** internal method to get the tree menu url */ function getTreeMenuUrl(node) { if (!node.menuUrl) { - throw "No menuUrl property found on the tree node, cannot load menu"; + return null; } return node.menuUrl; } @@ -26,10 +26,16 @@ function treeResource($q, $http, umbRequestHelper) { /** Loads in the data to display the nodes menu */ loadMenu: function (node) { - - return umbRequestHelper.resourcePromise( - $http.get(getTreeMenuUrl(node)), - "Failed to retrieve data for a node's menu " + node.id); + var treeMenuUrl = getTreeMenuUrl(node); + if (treeMenuUrl !== undefined && treeMenuUrl !== null && treeMenuUrl.length > 0) { + return umbRequestHelper.resourcePromise( + $http.get(getTreeMenuUrl(node)), + "Failed to retrieve data for a node's menu " + node.id); + } else { + return $q.reject({ + errorMsg: "No tree menu url defined for node " + node.id + }); + } }, /** Loads in the data to display the nodes for an application */ 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 13936a6733..b1464e5c9d 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 @@ -55,7 +55,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var deferred = $q.defer(); - if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage })) { + if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage, action: args.action })) { args.scope.busy = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index 4b5521b8db..9dfa440a0e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -50,7 +50,7 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati } //the first thing any form must do is broadcast the formSubmitting event - args.scope.$broadcast("formSubmitting", { scope: args.scope }); + args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); //then check if the form is valid if (!args.skipValidation) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js index a9fb9eed84..f0b7f4dc55 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js @@ -1,270 +1,323 @@ // This service was based on OpenJS library available in BSD License // http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php -angular.module('umbraco.services') -.factory('keyboardService', ['$window', '$timeout', function ($window, $timeout) { - var keyboardManagerService = {}; - var defaultOpt = { - 'type': 'keydown', - 'propagate': false, - 'inputDisabled': false, - 'target': $window.document, - 'keyCode': false - }; - var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; +function keyboardService($window, $timeout) { + + var keyboardManagerService = {}; + + var defaultOpt = { + 'type': 'keydown', + 'propagate': false, + 'inputDisabled': false, + 'target': $window.document, + 'keyCode': false + }; - // Store all keyboard combination shortcuts - keyboardManagerService.keyboardEvent = {}; + // Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken + var shift_nums = { + "`": "~", + "1": "!", + "2": "@", + "3": "#", + "4": "$", + "5": "%", + "6": "^", + "7": "&", + "8": "*", + "9": "(", + "0": ")", + "-": "_", + "=": "+", + ";": ":", + "'": "\"", + ",": "<", + ".": ">", + "/": "?", + "\\": "|" + }; + // Special Keys - and their codes + var special_keys = { + 'esc': 27, + 'escape': 27, + 'tab': 9, + 'space': 32, + 'return': 13, + 'enter': 13, + 'backspace': 8, - // Add a new keyboard combination shortcut - keyboardManagerService.bind = function (label, callback, opt) { + 'scrolllock': 145, + 'scroll_lock': 145, + 'scroll': 145, + 'capslock': 20, + 'caps_lock': 20, + 'caps': 20, + 'numlock': 144, + 'num_lock': 144, + 'num': 144, - //replace ctrl key with meta key - if(isMac && label !== "ctrl+space"){ - label = label.replace("ctrl","meta"); - } + 'pause': 19, + 'break': 19, - var fct, elt, code, k; - // Initialize opt object - opt = angular.extend({}, defaultOpt, opt); - label = label.toLowerCase(); - elt = opt.target; - if(typeof opt.target === 'string'){ - elt = document.getElementById(opt.target); - } + 'insert': 45, + 'home': 36, + 'delete': 46, + 'end': 35, + 'pageup': 33, + 'page_up': 33, + 'pu': 33, - fct = function (e) { - e = e || $window.event; + 'pagedown': 34, + 'page_down': 34, + 'pd': 34, - // Disable event handler when focus input and textarea - if (opt['inputDisabled']) { - var elt; - if (e.target){ - elt = e.target; - }else if (e.srcElement){ - elt = e.srcElement; - } + 'left': 37, + 'up': 38, + 'right': 39, + 'down': 40, - if (elt.nodeType === 3){elt = elt.parentNode;} - if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA'){return;} - } + 'f1': 112, + 'f2': 113, + 'f3': 114, + 'f4': 115, + 'f5': 116, + 'f6': 117, + 'f7': 118, + 'f8': 119, + 'f9': 120, + 'f10': 121, + 'f11': 122, + 'f12': 123 + }; - // Find out which key is pressed - if (e.keyCode){ - code = e.keyCode; - }else if (e.which){ - code = e.which; - } + var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; - var character = String.fromCharCode(code).toLowerCase(); + // The event handler for bound element events + function eventHandler(e) { + e = e || $window.event; - if (code === 188){character = ",";} // If the user presses , when the type is onkeydown - if (code === 190){character = ".";} // If the user presses , when the type is onkeydown + var code, k; - var keys = label.split("+"); - // Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked - var kp = 0; - // Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken - var shift_nums = { - "`":"~", - "1":"!", - "2":"@", - "3":"#", - "4":"$", - "5":"%", - "6":"^", - "7":"&", - "8":"*", - "9":"(", - "0":")", - "-":"_", - "=":"+", - ";":":", - "'":"\"", - ",":"<", - ".":">", - "/":"?", - "\\":"|" - }; - // Special Keys - and their codes - var special_keys = { - 'esc':27, - 'escape':27, - 'tab':9, - 'space':32, - 'return':13, - 'enter':13, - 'backspace':8, + // Find out which key is pressed + if (e.keyCode) + { + code = e.keyCode; + } + else if (e.which) { + code = e.which; + } - 'scrolllock':145, - 'scroll_lock':145, - 'scroll':145, - 'capslock':20, - 'caps_lock':20, - 'caps':20, - 'numlock':144, - 'num_lock':144, - 'num':144, + var character = String.fromCharCode(code).toLowerCase(); - 'pause':19, - 'break':19, + if (code === 188){character = ",";} // If the user presses , when the type is onkeydown + if (code === 190){character = ".";} // If the user presses , when the type is onkeydown - 'insert':45, - 'home':36, - 'delete':46, - 'end':35, + var propagate = true; - 'pageup':33, - 'page_up':33, - 'pu':33, + //Now we need to determine which shortcut this event is for, we'll do this by iterating over each + //registered shortcut to find the match. We use Find here so that the loop exits as soon + //as we've found the one we're looking for + _.find(_.keys(keyboardManagerService.keyboardEvent), function(key) { - 'pagedown':34, - 'page_down':34, - 'pd':34, + var shortcutLabel = key; + var shortcutVal = keyboardManagerService.keyboardEvent[key]; - 'left':37, - 'up':38, - 'right':39, - 'down':40, + // Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked + var kp = 0; - 'f1':112, - 'f2':113, - 'f3':114, - 'f4':115, - 'f5':116, - 'f6':117, - 'f7':118, - 'f8':119, - 'f9':120, - 'f10':121, - 'f11':122, - 'f12':123 - }; - // Some modifiers key - var modifiers = { - shift: { - wanted: false, - pressed: e.shiftKey ? true : false - }, - ctrl : { - wanted: false, - pressed: e.ctrlKey ? true : false - }, - alt : { - wanted: false, - pressed: e.altKey ? true : false - }, - meta : { //Meta is Mac specific - wanted: false, - pressed: e.metaKey ? true : false - } - }; - // Foreach keys in label (split on +) - var l = keys.length; - for (var i = 0; i < l; i++) { + // Some modifiers key + var modifiers = { + shift: { + wanted: false, + pressed: e.shiftKey ? true : false + }, + ctrl: { + wanted: false, + pressed: e.ctrlKey ? true : false + }, + alt: { + wanted: false, + pressed: e.altKey ? true : false + }, + meta: { //Meta is Mac specific + wanted: false, + pressed: e.metaKey ? true : false + } + }; - var k=keys[i]; - switch (k) { - case 'ctrl': - case 'control': - kp++; - modifiers.ctrl.wanted = true; - break; - case 'shift': - case 'alt': - case 'meta': - kp++; - modifiers[k].wanted = true; - break; - } + var keys = shortcutLabel.split("+"); + var opt = shortcutVal.opt; + var callback = shortcutVal.callback; - if (k.length > 1) { // If it is a special key - if(special_keys[k] === code){ - kp++; - } + // Foreach keys in label (split on +) + var l = keys.length; + for (var i = 0; i < l; i++) { - } else if (opt['keyCode']) { // If a specific key is set into the config - if (opt['keyCode'] === code) { - kp++; - } + var k = keys[i]; + switch (k) { + case 'ctrl': + case 'control': + kp++; + modifiers.ctrl.wanted = true; + break; + case 'shift': + case 'alt': + case 'meta': + kp++; + modifiers[k].wanted = true; + break; + } - } else { // The special keys did not match - if(character === k) { - kp++; - }else { - if(shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase - character = shift_nums[character]; - if(character === k){ - kp++; - } - } - } - } + if (k.length > 1) { // If it is a special key + if (special_keys[k] === code) { + kp++; + } + } + else if (opt['keyCode']) { // If a specific key is set into the config + if (opt['keyCode'] === code) { + kp++; + } + } + else { // The special keys did not match + if (character === k) { + kp++; + } + else { + if (shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase + character = shift_nums[character]; + if (character === k) { + kp++; + } + } + } + } - } //for end + } //for end - if(kp === keys.length && - modifiers.ctrl.pressed === modifiers.ctrl.wanted && - modifiers.shift.pressed === modifiers.shift.wanted && - modifiers.alt.pressed === modifiers.alt.wanted && - modifiers.meta.pressed === modifiers.meta.wanted) { - $timeout(function() { - callback(e); - }, 1); + if (kp === keys.length && + modifiers.ctrl.pressed === modifiers.ctrl.wanted && + modifiers.shift.pressed === modifiers.shift.wanted && + modifiers.alt.pressed === modifiers.alt.wanted && + modifiers.meta.pressed === modifiers.meta.wanted) { - if(!opt['propagate']) { // Stop the event - // e.cancelBubble is supported by IE - this will kill the bubbling process. - e.cancelBubble = true; - e.returnValue = false; + //found the right callback! - // e.stopPropagation works in Firefox. - if (e.stopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - return false; - } - } - }; - // Store shortcut - keyboardManagerService.keyboardEvent[label] = { - 'callback': fct, - 'target': elt, - 'event': opt['type'] - }; + // Disable event handler when focus input and textarea + if (opt['inputDisabled']) { + var elt; + if (e.target) { + elt = e.target; + } else if (e.srcElement) { + elt = e.srcElement; + } - //Attach the function with the event - if(elt.addEventListener){ - elt.addEventListener(opt['type'], fct, false); - }else if(elt.attachEvent){ - elt.attachEvent('on' + opt['type'], fct); - }else{ - elt['on' + opt['type']] = fct; - } - }; - // Remove the shortcut - just specify the shortcut and I will remove the binding - keyboardManagerService.unbind = function (label) { - label = label.toLowerCase(); - var binding = keyboardManagerService.keyboardEvent[label]; - delete(keyboardManagerService.keyboardEvent[label]); + if (elt.nodeType === 3) { elt = elt.parentNode; } + if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA') { + //This exits the Find loop + return true; + } + } - if(!binding){return;} + $timeout(function () { + callback(e); + }, 1); - var type = binding['event'], + if (!opt['propagate']) { // Stop the event + propagate = false; + } + + //This exits the Find loop + return true; + } + + //we haven't found one so continue looking + return false; + + }); + + // Stop the event if required + if (!propagate) { + // e.cancelBubble is supported by IE - this will kill the bubbling process. + e.cancelBubble = true; + e.returnValue = false; + + // e.stopPropagation works in Firefox. + if (e.stopPropagation) { + e.stopPropagation(); + e.preventDefault(); + } + return false; + } + } + + // Store all keyboard combination shortcuts + keyboardManagerService.keyboardEvent = {}; + + // Add a new keyboard combination shortcut + keyboardManagerService.bind = function (label, callback, opt) { + + //replace ctrl key with meta key + if(isMac && label !== "ctrl+space"){ + label = label.replace("ctrl","meta"); + } + + var elt; + // Initialize opt object + opt = angular.extend({}, defaultOpt, opt); + label = label.toLowerCase(); + elt = opt.target; + if(typeof opt.target === 'string'){ + elt = document.getElementById(opt.target); + } + + //Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding + // and raising events for now reason. So here we'll check if the event is already registered for the element + var boundValues = _.values(keyboardManagerService.keyboardEvent); + var found = _.find(boundValues, function (i) { + return i.target === elt && i.event === opt['type']; + }); + + // Store shortcut + keyboardManagerService.keyboardEvent[label] = { + 'callback': callback, + 'target': elt, + 'opt': opt['type'] + }; + + if (!found) { + //Attach the function with the event + if (elt.addEventListener) { + elt.addEventListener(opt['type'], eventHandler, false); + } else if (elt.attachEvent) { + elt.attachEvent('on' + opt['type'], eventHandler); + } else { + elt['on' + opt['type']] = eventHandler; + } + } + + }; + // Remove the shortcut - just specify the shortcut and I will remove the binding + keyboardManagerService.unbind = function (label) { + label = label.toLowerCase(); + var binding = keyboardManagerService.keyboardEvent[label]; + delete(keyboardManagerService.keyboardEvent[label]); + + if(!binding){return;} + + var type = binding['event'], elt = binding['target'], callback = binding['callback']; - if(elt.detachEvent){ - elt.detachEvent('on' + type, callback); - }else if(elt.removeEventListener){ - elt.removeEventListener(type, callback, false); - }else{ - elt['on'+type] = false; - } - }; - // + if(elt.detachEvent){ + elt.detachEvent('on' + type, callback); + }else if(elt.removeEventListener){ + elt.removeEventListener(type, callback, false); + }else{ + elt['on'+type] = false; + } + }; + // - return keyboardManagerService; -}]); \ No newline at end of file + return keyboardManagerService; +} angular.module('umbraco.services').factory('keyboardService', ['$window', '$timeout', keyboardService]); \ No newline at end of file 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 428b9f4323..e211218ce1 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,288 +1,319 @@ -(function() { - 'use strict'; +(function () { + 'use strict'; - function listViewHelper(localStorageService) { + function listViewHelper(localStorageService) { - var firstSelectedIndex = 0; - var localStorageKey = "umblistViewLayout"; + var firstSelectedIndex = 0; + var localStorageKey = "umblistViewLayout"; - function getLayout(nodeId, availableLayouts) { + function getLayout(nodeId, availableLayouts) { - var storedLayouts = []; + var storedLayouts = []; - if(localStorageService.get(localStorageKey)) { - storedLayouts = localStorageService.get(localStorageKey); - } - - if (storedLayouts && storedLayouts.length > 0) { - for (var i = 0; storedLayouts.length > i; i++) { - var layout = storedLayouts[i]; - if (layout.nodeId === nodeId) { - return setLayout(nodeId, layout, availableLayouts); - } - } - - } - - return getFirstAllowedLayout(availableLayouts); - - } - - function setLayout(nodeId, selectedLayout, availableLayouts) { - - var activeLayout = {}; - var layoutFound = false; - - for (var i = 0; availableLayouts.length > i; i++) { - var layout = availableLayouts[i]; - if (layout.path === selectedLayout.path) { - activeLayout = layout; - layout.active = true; - layoutFound = true; - } else { - layout.active = false; - } - } - - if(!layoutFound) { - activeLayout = getFirstAllowedLayout(availableLayouts); - } - - saveLayoutInLocalStorage(nodeId, activeLayout); - - return activeLayout; - - } - - function saveLayoutInLocalStorage(nodeId, selectedLayout) { - var layoutFound = false; - var storedLayouts = []; - - if(localStorageService.get(localStorageKey)) { - storedLayouts = localStorageService.get(localStorageKey); - } - - if(storedLayouts.length > 0) { - for(var i = 0; storedLayouts.length > i; i++) { - var layout = storedLayouts[i]; - if(layout.nodeId === nodeId) { - layout.path = selectedLayout.path; - layoutFound = true; - } - } - } - - if(!layoutFound) { - var storageObject = { - "nodeId": nodeId, - "path": selectedLayout.path - }; - storedLayouts.push(storageObject); - } - - localStorageService.set(localStorageKey, storedLayouts); - - } - - function getFirstAllowedLayout(layouts) { - - var firstAllowedLayout = {}; - - for (var i = 0; layouts.length > i; i++) { - var layout = layouts[i]; - if (layout.selected === true) { - firstAllowedLayout = layout; - break; + if (localStorageService.get(localStorageKey)) { + storedLayouts = localStorageService.get(localStorageKey); } - } - return firstAllowedLayout; - } + if (storedLayouts && storedLayouts.length > 0) { + for (var i = 0; storedLayouts.length > i; i++) { + var layout = storedLayouts[i]; + if (layout.nodeId === nodeId) { + return setLayout(nodeId, layout, availableLayouts); + } + } - function selectHandler(selectedItem, selectedIndex, items, selection, $event) { + } - var start = 0; - var end = 0; - var item = null; + return getFirstAllowedLayout(availableLayouts); - if ($event.shiftKey === true) { + } - if(selectedIndex > firstSelectedIndex) { + function setLayout(nodeId, selectedLayout, availableLayouts) { - start = firstSelectedIndex; - end = selectedIndex; + var activeLayout = {}; + var layoutFound = false; - for (; end >= start; start++) { - item = items[start]; - selectItem(item, selection); - } + for (var i = 0; availableLayouts.length > i; i++) { + var layout = availableLayouts[i]; + if (layout.path === selectedLayout.path) { + activeLayout = layout; + layout.active = true; + layoutFound = true; + } else { + layout.active = false; + } + } + + if (!layoutFound) { + activeLayout = getFirstAllowedLayout(availableLayouts); + } + + saveLayoutInLocalStorage(nodeId, activeLayout); + + return activeLayout; + + } + + function saveLayoutInLocalStorage(nodeId, selectedLayout) { + var layoutFound = false; + var storedLayouts = []; + + if (localStorageService.get(localStorageKey)) { + storedLayouts = localStorageService.get(localStorageKey); + } + + if (storedLayouts.length > 0) { + for (var i = 0; storedLayouts.length > i; i++) { + var layout = storedLayouts[i]; + if (layout.nodeId === nodeId) { + layout.path = selectedLayout.path; + layoutFound = true; + } + } + } + + if (!layoutFound) { + var storageObject = { + "nodeId": nodeId, + "path": selectedLayout.path + }; + storedLayouts.push(storageObject); + } + + localStorageService.set(localStorageKey, storedLayouts); + + } + + function getFirstAllowedLayout(layouts) { + + var firstAllowedLayout = {}; + + for (var i = 0; layouts.length > i; i++) { + var layout = layouts[i]; + if (layout.selected === true) { + firstAllowedLayout = layout; + break; + } + } + + return firstAllowedLayout; + } + + function selectHandler(selectedItem, selectedIndex, items, selection, $event) { + + var start = 0; + var end = 0; + var item = null; + + if ($event.shiftKey === true) { + + if (selectedIndex > firstSelectedIndex) { + + start = firstSelectedIndex; + end = selectedIndex; + + for (; end >= start; start++) { + item = items[start]; + selectItem(item, selection); + } + + } else { + + start = firstSelectedIndex; + end = selectedIndex; + + for (; end <= start; start--) { + item = items[start]; + selectItem(item, selection); + } + + } } else { - start = firstSelectedIndex; - end = selectedIndex; + if (selectedItem.selected) { + deselectItem(selectedItem, selection); + } else { + selectItem(selectedItem, selection); + } - for (; end <= start; start--) { - item = items[start]; - selectItem(item, selection); - } + firstSelectedIndex = selectedIndex; } - } else { + } - if(selectedItem.selected) { - deselectItem(selectedItem, selection); - } else { - selectItem(selectedItem, selection); + function selectItem(item, selection) { + var isSelected = false; + for (var i = 0; selection.length > i; i++) { + var selectedItem = selection[i]; + if (item.id === selectedItem.id) { + isSelected = true; + } + } + if (!isSelected) { + selection.push({ id: item.id }); + item.selected = true; + } + } + + function deselectItem(item, selection) { + for (var i = 0; selection.length > i; i++) { + var selectedItem = selection[i]; + if (item.id === selectedItem.id) { + selection.splice(i, 1); + item.selected = false; + } + } + } + + function clearSelection(items, folders, selection) { + + var i = 0; + + selection.length = 0; + + if (angular.isArray(items)) { + for (i = 0; items.length > i; i++) { + var item = items[i]; + item.selected = false; + } } - firstSelectedIndex = selectedIndex; - - } - - } - - function selectItem(item, selection) { - var isSelected = false; - for (var i = 0; selection.length > i; i++) { - var selectedItem = selection[i]; - if (item.id === selectedItem.id) { - isSelected = true; + if (angular.isArray(items)) { + for (i = 0; folders.length > i; i++) { + var folder = folders[i]; + folder.selected = false; + } } - } - if(!isSelected) { - selection.push({id: item.id}); - item.selected = true; - } - } + } - function deselectItem(item, selection) { - for (var i = 0; selection.length > i; i++) { - var selectedItem = selection[i]; - if (item.id === selectedItem.id) { - selection.splice(i, 1); - item.selected = false; + function selectAllItems(items, selection, $event) { + + var checkbox = $event.target; + var clearSelection = false; + + if (!angular.isArray(items)) { + return; } - } - } - function clearSelection(items, folders, selection) { + selection.length = 0; - var i = 0; + for (var i = 0; i < items.length; i++) { - selection.length = 0; + var item = items[i]; + + if (checkbox.checked) { + selection.push({ id: item.id }); + } else { + clearSelection = true; + } + + item.selected = checkbox.checked; - if(angular.isArray(items)) { - for(i = 0; items.length > i; i++) { - var item = items[i]; - item.selected = false; } - } - if(angular.isArray(items)) { - for(i = 0; folders.length > i; i++) { - var folder = folders[i]; - folder.selected = false; + if (clearSelection) { + selection.length = 0; } - } - } - function selectAllItems(items, selection, $event) { + } - var checkbox = $event.target; - var clearSelection = false; + function isSelectedAll(items, selection) { - if (!angular.isArray(items)) { - return; - } + var numberOfSelectedItem = 0; - selection.length = 0; + for (var itemIndex = 0; items.length > itemIndex; itemIndex++) { + var item = items[itemIndex]; - for (var i = 0; i < items.length; i++) { + for (var selectedIndex = 0; selection.length > selectedIndex; selectedIndex++) { + var selectedItem = selection[selectedIndex]; - var item = items[i]; + if (item.id === selectedItem.id) { + numberOfSelectedItem++; + } + } - if (checkbox.checked) { - selection.push({id: item.id}); - } else { - clearSelection = true; - } + } - item.selected = checkbox.checked; + if (numberOfSelectedItem === items.length) { + return true; + } - } + } - if (clearSelection) { - selection.length = 0; - } + function setSortingDirection(col, direction, options) { + return options.orderBy.toUpperCase() === col.toUpperCase() && options.orderDirection === direction; + } - } + function setSorting(field, allow, options) { + if (allow) { + options.orderBy = field; - function isSelectedAll(items, selection) { + if (options.orderDirection === "desc") { + options.orderDirection = "asc"; + } else { + options.orderDirection = "desc"; + } + } + } + + //This takes in a dictionary of Ids with Permissions and determines + // the intersect of all permissions to return an object representing the + // listview button permissions + function getButtonPermissions(unmergedPermissions, currentIdsWithPermissions) { + + if (currentIdsWithPermissions == null) { + currentIdsWithPermissions = {}; + } - var numberOfSelectedItem = 0; + //merge the newly retrieved permissions to the main dictionary + _.each(unmergedPermissions, function (value, key, list) { + currentIdsWithPermissions[key] = value; + }); - for(var itemIndex = 0; items.length > itemIndex; itemIndex++) { - var item = items[itemIndex]; + //get the intersect permissions + var arr = []; + _.each(currentIdsWithPermissions, function (value, key, list) { + arr.push(value); + }); - for(var selectedIndex = 0; selection.length > selectedIndex; selectedIndex++) { - var selectedItem = selection[selectedIndex]; + //we need to use 'apply' to call intersection with an array of arrays, + //see: http://stackoverflow.com/a/16229480/694494 + var intersectPermissions = _.intersection.apply(_, arr); - if(item.id === selectedItem.id) { - numberOfSelectedItem++; - } - } + return { + canCopy: _.contains(intersectPermissions, 'O'), //Magic Char = O + canCreate: _.contains(intersectPermissions, 'C'), //Magic Char = C + canDelete: _.contains(intersectPermissions, 'D'), //Magic Char = D + canMove: _.contains(intersectPermissions, 'M'), //Magic Char = M + canPublish: _.contains(intersectPermissions, 'U'), //Magic Char = U + canUnpublish: _.contains(intersectPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) + }; + } - } + var service = { + getLayout: getLayout, + getFirstAllowedLayout: getFirstAllowedLayout, + setLayout: setLayout, + saveLayoutInLocalStorage: saveLayoutInLocalStorage, + selectHandler: selectHandler, + selectItem: selectItem, + deselectItem: deselectItem, + clearSelection: clearSelection, + selectAllItems: selectAllItems, + isSelectedAll: isSelectedAll, + setSortingDirection: setSortingDirection, + setSorting: setSorting, + getButtonPermissions: getButtonPermissions + }; - if(numberOfSelectedItem === items.length) { - return true; - } + return service; - } + } - function setSortingDirection(col, direction, options) { - return options.orderBy.toUpperCase() === col.toUpperCase() && options.orderDirection === direction; - } - - - function setSorting(field, allow, options) { - if (allow) { - options.orderBy = field; - - if (options.orderDirection === "desc") { - options.orderDirection = "asc"; - } else { - options.orderDirection = "desc"; - } - } - } - - - - var service = { - getLayout: getLayout, - getFirstAllowedLayout: getFirstAllowedLayout, - setLayout: setLayout, - saveLayoutInLocalStorage: saveLayoutInLocalStorage, - selectHandler: selectHandler, - selectItem: selectItem, - deselectItem: deselectItem, - clearSelection: clearSelection, - selectAllItems: selectAllItems, - isSelectedAll: isSelectedAll, - setSortingDirection: setSortingDirection, - setSorting: setSorting - }; - - return service; - - } - - - angular.module('umbraco.services').factory('listViewHelper', listViewHelper); + angular.module('umbraco.services').factory('listViewHelper', listViewHelper); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 554790b6d0..9fbf2947af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -21,6 +21,7 @@ function packageHelper(assetsService, treeService, eventsService, $templateCache } angular.module('umbraco.services').factory('packageHelper', packageHelper); +//TODO: I believe this is obsolete function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, mediaHelper, umbRequestHelper) { return { /** sets the image's url, thumbnail and if its a folder */ @@ -319,7 +320,6 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me } }; } - angular.module("umbraco.services").factory("umbPhotoFolderHelper", umbPhotoFolderHelper); /** diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index a7335d8314..9d01cae4b9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -107,6 +107,7 @@ @import "components/umb-grid.less"; @import "components/umb-empty-state.less"; @import "components/umb-property-editor.less"; +@import "components/umb-iconpicker.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less new file mode 100644 index 0000000000..e0cdb7cac5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less @@ -0,0 +1,46 @@ +.umb-iconpicker { + display: flex; + flex-direction: row; + flex-wrap: wrap; + + margin: 0; +} + +.umb-iconpicker-item { + display: flex; + flex-direction: row; + flex: 0 0 14.28%; + justify-content: center; + align-items: center; + + margin-bottom: 0; + + overflow: hidden; +} + +.umb-iconpicker-item a { + display: flex; + justify-content: center; + align-items: center; + + width: 100%; + height: 100%; + + padding: 15px 0; + + text-decoration: none; +} + +.umb-iconpicker-item a:hover, +.umb-iconpicker-item a:focus{ + background: darken(@grayLighter, 1%); + outline: none; +} + +.umb-iconpicker-item a:active { + background: darken(@grayLighter, 4%); +} + +.umb-iconpicker-item i { + font-size: 30px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-keyboard-shortcuts-overview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-keyboard-shortcuts-overview.less index ab82eeec85..c603a3c7cd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-keyboard-shortcuts-overview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-keyboard-shortcuts-overview.less @@ -16,8 +16,23 @@ .umb-keyboard-shortcuts-overview__overlay-close { position: absolute; - top: 10px; - right: 10px; + top: 20px; + right: 20px; + font-size: 25px; + border-radius: 20px; + width: 35px; + height: 35px; + display: flex; + justify-content: center; + align-items: center; + background-color: transparent; + line-height: 1em; +} + +.umb-keyboard-shortcuts-overview__overlay-close:hover { + background-color: @blue; + color: #ffffff; + text-decoration: none; } .umb-keyboard-shortcuts-overview__overlay-content { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index 631d7f5730..e94aa81898 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -198,6 +198,7 @@ input.umb-table__input { padding: 6px 2px; text-align: left; + overflow:hidden; } .umb-table-cell > * { diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index eb569c7cdf..f9dedf5902 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -16,12 +16,12 @@ label small, .guiDialogTiny { font-size: 11px } -label.control-label { +label.control-label, .control-label { padding: 0 10px 0 0 !important; font-weight: bold; - } + .umb-status-label{ color: @gray !important; } @@ -164,7 +164,7 @@ textarea { // Identify controls by their labels label { - display: block; + display: inline-block; margin-bottom: 5px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 09f916c59f..00cdd4f9b1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -139,7 +139,7 @@ h5.-black { } /* LABELS*/ -.umb-control-group label.control-label { +.umb-control-group label.control-label, .umb-control-group .control-label { text-align: left; } .umb-control-group label.control-label > div > label { diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index 79f3797197..081a194b7e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -9,13 +9,41 @@ color: @white; position: absolute; z-index: 10000; - top: 0px; - left: 0px; - margin: 0 !Important; + top: 0; + left: 0; + margin: 0 !important; padding: 0; border-radius: 0; } + +@media only screen and (-webkit-min-device-pixel-ratio: 1.5), + only screen and (min-resolution: 144dpi) +{ + .login-overlay { + background-image: url(../img/application/logo@2x.png) !important; + } +} + + +@media only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and (min-resolution: 192dpi) +{ + .login-overlay { + background-image: url(../img/application/logo@2x.png) !important; + } +} + + +@media only screen and (-webkit-min-device-pixel-ratio: 3), + only screen and (min-resolution: 3dppx), + only screen and (min-resolution: 350dpi) +{ + .login-overlay { + background-image: url(../img/application/logo@3x.png) !important; + } +} + .login-overlay .umb-modalcolumn { background: none; border: none; @@ -62,7 +90,7 @@ } #hrOr hr { - margin: 0px; + margin: 0; border: none; background-color: @gray; height: 1px; 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 80647b5327..f274740020 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -81,6 +81,9 @@ div.umb-codeeditor .umb-btn-toolbar { font-size:16px !important; } +/* pre-value editor */ +.rte-editor-preval .control-group .controls > div > label .mce-ico { line-height: 20px; } + // // Color picker @@ -275,11 +278,12 @@ ul.color-picker li a { .umb-cropper{ position: relative; - padding: 10px; } .umb-cropper img, .umb-cropper-gravity img{ - position: absolute; + position: relative; + max-width: 100%; + height: auto; top: 0; left: 0; } @@ -293,19 +297,22 @@ ul.color-picker li a { left: 0; cursor: move; z-index: 6001; + position: absolute; } .umb-cropper .viewport{ overflow: hidden; position: relative; margin: auto; + max-width: 100%; + height: auto; } .umb-cropper-gravity .viewport{ overflow: hidden; position: relative; - width: 400px; - height: 300px + width: 100%; + height: 100%; } @@ -335,27 +342,45 @@ ul.color-picker li a { opacity: 0.8; } -.umb-cropper-gravity .overlay i{ +.umb-cropper-gravity .overlay i { font-size: 26px; line-height: 26px; opacity: 0.8 !important; } -.umb-cropper .crop-container{ +.umb-cropper .crop-container { text-align: center; } -.umb-cropper .crop-slider{ - vertical-align: middle; - padding: 10px 50px 10px 50px; +.umb-cropper .crop-slider { + padding: 10px; border-top: 1px solid @grayLighter; margin-top: 10px; + + display: flex; + align-items: center; + justify-content: center; + + flex-wrap: wrap; + + @media (min-width: 769px) { + padding: 10px 50px 10px 50px; + } } -.umb-cropper .crop-slider i{color: @gray;} -.umb-cropper .crop-slider input{ - margin-top: 7px; - width: 320px; +.umb-cropper .crop-slider i { + color: @gray; + flex: 0 0 25px; + padding: 0 5px; + box-sizing: border-box; +} + +.umb-cropper .crop-slider i:first-of-type { + text-align: right; +} + +.umb-cropper .crop-slider input { + flex: 0 1 auto; } .umb-cropper-gravity .viewport, .umb-cropper-gravity, .umb-cropper-imageholder { display: inline-block; @@ -372,55 +397,98 @@ ul.color-picker li a { } .gravity-container .viewport { - width: 494px; + max-width: 600px; + } + + .gravity-container .viewport:hover { + cursor: pointer; + } + + .imagecropper { + display: flex; + align-items: flex-start; + flex-direction: row; + + @media (max-width: 768px) { + flex-direction: column; + float: left; + max-width: 100%; + } + } + + .imagecropper .umb-cropper__container { + position: relative; + margin-bottom: 10px; + max-width: 100%; + border: 1px solid #f8f8f8; + + @media (min-width: 769px) { + width: 600px; + } + } + + .umb-close-cropper { + position: absolute; + top: 3px; + right: 3px; + cursor: pointer; + } + + .umb-close-cropper:hover { + opacity: .9; + background: @grayLighter; } .imagecropper .umb-sortable-thumbnails { - border-left: 2px solid #f8f8f8; - margin-left: 4px; - padding-left: 2px; - float: left; - width: 100px; + display: flex; + flex-direction: row; + flex-wrap: wrap; } .imagecropper .umb-sortable-thumbnails li { - display: block; - } + display: flex; + flex-direction: column; + justify-content: space-between; - .imagecropper .umb-sortable-thumbnails li.current a, .imagecropper .umb-sortable-thumbnails li.current a:hover { - background: #eeeeee; - text-decoration: none; - } - - .imagecropper .umb-sortable-thumbnails li:first-of-type { + padding: 8px; margin-top: 0; - padding-top: 0; } - .imagecropper .umb-sortable-thumbnails li .crop-name, .imagecropper .umb-sortable-thumbnails li .crop-size { + .imagecropper .umb-sortable-thumbnails li.current { + border-color: @grayLight; + background: @grayLighter; + color: @black; + cursor: pointer; + } + + .imagecropper .umb-sortable-thumbnails li:hover, + .imagecropper .umb-sortable-thumbnails li.current:hover { + border-color: @grayLight; + background: @grayLighter; + color: @black; + + cursor: pointer; + + opacity: .95; + } + + .imagecropper .umb-sortable-thumbnails li .crop-name, + .imagecropper .umb-sortable-thumbnails li .crop-size { display: block; text-align: left; - font-size: 11px; + font-size: 13px; + line-height: 1; + } + + .imagecropper .umb-sortable-thumbnails li .crop-name { + font-weight: bold; + margin: 10px 0 5px; } .imagecropper .umb-sortable-thumbnails li .crop-size { font-size: 10px; font-style: italic; - color: #666; - } - - .imagecropper .umb-sortable-thumbnails li a { - display: block; - padding: 5px; - } - - .imagecropper .umb-sortable-thumbnails li a:hover { - background: #f8f8f8; - text-decoration: none; - } - - .imagecropper .umb-sortable-thumbnails li a:hover .crop-text { - text-decoration: none; + margin: 0 0 5px; } .btn-crop-delete { @@ -428,20 +496,6 @@ ul.color-picker li a { text-align: left; } - .imagecropper .umb-sortable-thumbnails.many { - width: 210px; - } - - .imagecropper .umb-sortable-thumbnails.many li { - width: 85px; - float: left; - } - - .imagecropper .umb-sortable-thumbnails.many li:nth-child(2) { - margin-top: 0; - padding-top: 0; - } - // // folder-browser // -------------------------------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index f57feeb2ad..c2db58a99e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -19,7 +19,8 @@ ul.sections li { } ul.sections li [class^="icon-"]:before, -ul.sections li [class*=" icon-"]:before{ +ul.sections li [class*=" icon-"]:before, +ul.sections li img.icon-section { font-size: 30px; margin: 1px 0 0 0; opacity: 0.4; @@ -29,7 +30,8 @@ ul.sections li [class*=" icon-"]:before{ } ul.sections:hover li [class^="icon-"]:before, -ul.sections:hover li [class*=" icon-"]:before { +ul.sections:hover li [class*=" icon-"]:before, +ul.sections:hover li img.icon-section { opacity: 1 } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html index bcf7fe3247..260d8ff85c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html @@ -2,55 +2,48 @@