diff --git a/.github/BUILD.md b/.github/BUILD.md index ad33872423..5f962a8911 100644 --- a/.github/BUILD.md +++ b/.github/BUILD.md @@ -39,7 +39,7 @@ To build Umbraco, fire up PowerShell and move to Umbraco's repository root (the build/build.ps1 -If you only see a build.bat-file, you're probably on the wrong branch. If you switch to the correct branch (dev-v8) the file will appear and you can build it. +If you only see a build.bat-file, you're probably on the wrong branch. If you switch to the correct branch (v8/contrib) the file will appear and you can build it. You might run into [Powershell quirks](#powershell-quirks). diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt index 91d49d896c..fd1b2174e2 100644 --- a/build/NuSpecs/tools/ReadmeUpgrade.txt +++ b/build/NuSpecs/tools/ReadmeUpgrade.txt @@ -11,7 +11,7 @@ Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P Don't forget to build! -We've done our best to transform your configuration files but in case something is not quite right: we recommmend you look in source control for the previous version so you can find the original files before they were transformed. +We've done our best to transform your configuration files but in case something is not quite right: we recommend you look in source control for the previous version so you can find the original files before they were transformed. This NuGet package includes build targets that extend the creation of a deploy package, which is generated by Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 77ad7df0dc..3ebb632882 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -40,6 +40,9 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("loginBackgroundImage")] internal InnerTextConfigurationElement LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty); + [ConfigurationProperty("loginLogoImage")] + internal InnerTextConfigurationElement LoginLogoImage => GetOptionalTextElement("loginLogoImage", "assets/img/application/umbraco_logo_white.svg"); + string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress; bool IContentSection.DisableHtmlEmail => Notifications.DisableHtmlEmail; @@ -61,5 +64,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool IContentSection.ShowDeprecatedPropertyEditors => ShowDeprecatedPropertyEditors; string IContentSection.LoginBackgroundImage => LoginBackgroundImage; + + string IContentSection.LoginLogoImage => LoginLogoImage; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index 228b0923dc..0f52adf02e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -32,5 +32,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool ShowDeprecatedPropertyEditors { get; } string LoginBackgroundImage { get; } + + string LoginLogoImage { get; } } } diff --git a/src/Umbraco.Core/Constants-SqlTemplates.cs b/src/Umbraco.Core/Constants-SqlTemplates.cs index 984bc495b0..5a78b62f5b 100644 --- a/src/Umbraco.Core/Constants-SqlTemplates.cs +++ b/src/Umbraco.Core/Constants-SqlTemplates.cs @@ -14,7 +14,16 @@ public const string GetParentNode = "Umbraco.Core.VersionableRepository.GetParentNode"; public const string GetReservedId = "Umbraco.Core.VersionableRepository.GetReservedId"; } + public static class RelationRepository + { + public const string DeleteByParentAll = "Umbraco.Core.RelationRepository.DeleteByParent"; + public const string DeleteByParentIn = "Umbraco.Core.RelationRepository.DeleteByParentIn"; + } + public static class DataTypeRepository + { + public const string EnsureUniqueNodeName = "Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName"; + } } } } diff --git a/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs b/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs new file mode 100644 index 0000000000..6f672d17cd --- /dev/null +++ b/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Exceptions +{ + /// + /// An exception that is thrown if an unattended installation occurs. + /// + [Serializable] + public class UnattendedInstallException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public UnattendedInstallException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public UnattendedInstallException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception which is the cause of this exception. + /// + /// The message that describes the error. + /// The inner exception, or null. + public UnattendedInstallException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected UnattendedInstallException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index d5d8bbab6f..71faf42daf 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -112,6 +112,14 @@ namespace Umbraco.Core.Migrations.Install } } + internal bool IsUmbracoInstalled() + { + using (var scope = _scopeProvider.CreateScope(autoComplete: true)) + { + return scope.Database.IsUmbracoInstalled(_logger); + } + } + #endregion #region Configure Connection String @@ -391,15 +399,15 @@ namespace Umbraco.Core.Migrations.Install private DatabaseSchemaResult ValidateSchema(IScope scope) { if (_databaseFactory.Initialized == false) - return new DatabaseSchemaResult(_databaseFactory.SqlContext.SqlSyntax); + return new DatabaseSchemaResult(); if (_databaseSchemaValidationResult != null) return _databaseSchemaValidationResult; - var database = scope.Database; - var dbSchema = new DatabaseSchemaCreator(database, _logger); - _databaseSchemaValidationResult = dbSchema.ValidateSchema(); + _databaseSchemaValidationResult = scope.Database.ValidateSchema(_logger); + scope.Complete(); + return _databaseSchemaValidationResult; } @@ -438,11 +446,9 @@ namespace Umbraco.Core.Migrations.Install var schemaResult = ValidateSchema(); var hasInstalledVersion = schemaResult.DetermineHasInstalledVersion(); - //var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); - //var hasInstalledVersion = !installedSchemaVersion.Equals(new Version(0, 0, 0)); - //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing - if (string.IsNullOrEmpty(_globalSettings.ConfigurationStatus) && !hasInstalledVersion) + //If the determined version is "empty" its a new install - otherwise upgrade the existing + if (!hasInstalledVersion) { if (_runtime.Level == RuntimeLevel.Run) throw new Exception("Umbraco is already configured!"); diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index eab7afe308..e9580da74a 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -21,8 +21,13 @@ namespace Umbraco.Core.Migrations.Install public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger) { - _database = database; - _logger = logger; + _database = database ?? throw new ArgumentNullException(nameof(database)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + if (_database?.SqlContext?.SqlSyntax == null) + { + throw new InvalidOperationException("No SqlContext has been assigned to the database"); + } } private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax; @@ -143,7 +148,7 @@ namespace Umbraco.Core.Migrations.Install internal DatabaseSchemaResult ValidateSchema(IEnumerable orderedTables) { - var result = new DatabaseSchemaResult(SqlSyntax); + var result = new DatabaseSchemaResult(); result.IndexDefinitions.AddRange(SqlSyntax.GetDefinedIndexes(_database) .Select(x => new DbIndexDefinition(x))); @@ -445,14 +450,14 @@ namespace Umbraco.Core.Migrations.Install } //Execute the Create Table sql - var created = _database.Execute(new Sql(createSql)); - _logger.Info("Create Table {TableName} ({Created}): \n {Sql}", tableName, created, createSql); + _database.Execute(new Sql(createSql)); + _logger.Info("Create Table {TableName}: \n {Sql}", tableName, createSql); //If any statements exists for the primary key execute them here if (string.IsNullOrEmpty(createPrimaryKeySql) == false) { - var createdPk = _database.Execute(new Sql(createPrimaryKeySql)); - _logger.Info("Create Primary Key ({CreatedPk}):\n {Sql}", createdPk, createPrimaryKeySql); + _database.Execute(new Sql(createPrimaryKeySql)); + _logger.Info("Create Primary Key:\n {Sql}", createPrimaryKeySql); } if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) @@ -469,24 +474,24 @@ namespace Umbraco.Core.Migrations.Install //Loop through index statements and execute sql foreach (var sql in indexSql) { - var createdIndex = _database.Execute(new Sql(sql)); - _logger.Info("Create Index ({CreatedIndex}):\n {Sql}", createdIndex, sql); + _database.Execute(new Sql(sql)); + _logger.Info("Create Index:\n {Sql}", sql); } //Loop through foreignkey statements and execute sql foreach (var sql in foreignSql) { - var createdFk = _database.Execute(new Sql(sql)); - _logger.Info("Create Foreign Key ({CreatedFk}):\n {Sql}", createdFk, sql); + _database.Execute(new Sql(sql)); + _logger.Info("Create Foreign Key:\n {Sql}", sql); } if (overwrite) { - _logger.Info("Table {TableName} was recreated", tableName); + _logger.Info("Table {TableName} was recreated", tableName); } else { - _logger.Info("New table {TableName} was created", tableName); + _logger.Info("New table {TableName} was created", tableName); } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs index f21216fde3..c0d8eff311 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Migrations.Install { @@ -12,7 +12,7 @@ namespace Umbraco.Core.Migrations.Install /// internal class DatabaseSchemaResult { - public DatabaseSchemaResult(ISqlSyntaxProvider sqlSyntax) + public DatabaseSchemaResult() { Errors = new List>(); TableDefinitions = new List(); diff --git a/src/Umbraco.Core/Models/Range.cs b/src/Umbraco.Core/Models/Range.cs index 83afa11658..108d564665 100644 --- a/src/Umbraco.Core/Models/Range.cs +++ b/src/Umbraco.Core/Models/Range.cs @@ -1,52 +1,130 @@ using System; +using System.Globalization; + namespace Umbraco.Core.Models { - // The Range class. Adapted from http://stackoverflow.com/questions/5343006/is-there-a-c-sharp-type-for-representing-an-integer-range - /// Generic parameter. - public class Range where T : IComparable + /// + /// Represents a range with a minimum and maximum value. + /// + /// The type of the minimum and maximum values. + /// + public class Range : IEquatable> + where T : IComparable { - /// Minimum value of the range. + /// + /// Gets or sets the minimum value. + /// + /// + /// The minimum value. + /// public T Minimum { get; set; } - /// Maximum value of the range. + /// + /// Gets or sets the maximum value. + /// + /// + /// The maximum value. + /// public T Maximum { get; set; } - /// Presents the Range in readable format. - /// String representation of the Range - public override string ToString() - { - return string.Format("{0},{1}", this.Minimum, this.Maximum); - } + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() => this.ToString("{0},{1}", CultureInfo.InvariantCulture); - /// Determines if the range is valid. - /// True if range is valid, else false - public bool IsValid() - { - return this.Minimum.CompareTo(this.Maximum) <= 0; - } + /// + /// Returns a that represents this instance. + /// + /// A composite format string for a single value (minimum and maximum are equal). Use {0} for the minimum and {1} for the maximum value. + /// A composite format string for the range values. Use {0} for the minimum and {1} for the maximum value. + /// An object that supplies culture-specific formatting information. + /// + /// A that represents this instance. + /// + public string ToString(string format, string formatRange, IFormatProvider provider = null) => this.ToString(this.Minimum.CompareTo(this.Maximum) == 0 ? format : formatRange, provider); - /// Determines if the provided value is inside the range. - /// The value to test - /// True if the value is inside Range, else false - public bool ContainsValue(T value) - { - return (this.Minimum.CompareTo(value) <= 0) && (value.CompareTo(this.Maximum) <= 0); - } + /// + /// Returns a that represents this instance. + /// + /// A composite format string for the range values. Use {0} for the minimum and {1} for the maximum value. + /// An object that supplies culture-specific formatting information. + /// + /// A that represents this instance. + /// + public string ToString(string format, IFormatProvider provider = null) => string.Format(provider, format, this.Minimum, this.Maximum); - /// Determines if this Range is inside the bounds of another range. - /// The parent range to test on - /// True if range is inclusive, else false - public bool IsInsideRange(Range range) - { - return this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum); - } + /// + /// Determines whether this range is valid (the minimum value is lower than or equal to the maximum value). + /// + /// + /// true if this range is valid; otherwise, false. + /// + public bool IsValid() => this.Minimum.CompareTo(this.Maximum) <= 0; - /// Determines if another range is inside the bounds of this range. - /// The child range to test - /// True if range is inside, else false - public bool ContainsRange(Range range) - { - return this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum); - } + /// + /// Determines whether this range contains the specified value. + /// + /// The value. + /// + /// true if this range contains the specified value; otherwise, false. + /// + public bool ContainsValue(T value) => this.Minimum.CompareTo(value) <= 0 && value.CompareTo(this.Maximum) <= 0; + + /// + /// Determines whether this range is inside the specified range. + /// + /// The range. + /// + /// true if this range is inside the specified range; otherwise, false. + /// + public bool IsInsideRange(Range range) => this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum); + + /// + /// Determines whether this range contains the specified range. + /// + /// The range. + /// + /// true if this range contains the specified range; otherwise, false. + /// + public bool ContainsRange(Range range) => this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum); + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) => obj is Range other && this.Equals(other); + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// if the current object is equal to the parameter; otherwise, . + /// + public bool Equals(Range other) => other != null && this.Equals(other.Minimum, other.Maximum); + + /// + /// Determines whether the specified and values are equal to this instance values. + /// + /// The minimum value. + /// The maximum value. + /// + /// true if the specified and values are equal to this instance values; otherwise, false. + /// + public bool Equals(T minimum, T maximum) => this.Minimum.CompareTo(minimum) == 0 && this.Maximum.CompareTo(maximum) == 0; + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() => (this.Minimum, this.Maximum).GetHashCode(); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index a7b23d95cb..f879fd5224 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -890,7 +890,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.EnsureUniqueNodeName, tsql => tsql .Select(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name")) .From() - .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId"))); + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId")) + ); var sql = template.Sql(NodeObjectTypeId, parentId); var names = Database.Fetch(sql); @@ -900,28 +901,43 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected virtual int GetNewChildSortOrder(int parentId, int first) { - var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetSortOrder, tsql => - tsql.Select($"COALESCE(MAX(sortOrder),{first - 1})").From().Where(x => x.ParentId == SqlTemplate.Arg("parentId") && x.NodeObjectType == NodeObjectTypeId) + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetSortOrder, tsql => tsql + .Select("MAX(sortOrder)") + .From() + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId")) ); - return Database.ExecuteScalar(template.Sql(new { parentId })) + 1; + var sql = template.Sql(NodeObjectTypeId, parentId); + var sortOrder = Database.ExecuteScalar(sql); + + return (sortOrder + 1) ?? first; } protected virtual NodeDto GetParentNodeDto(int parentId) { - var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql => - tsql.Select().From().Where(x => x.NodeId == SqlTemplate.Arg("parentId")) + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql => tsql + .Select() + .From() + .Where(x => x.NodeId == SqlTemplate.Arg("parentId")) ); - return Database.Fetch(template.Sql(parentId)).First(); + var sql = template.Sql(parentId); + var nodeDto = Database.First(sql); + + return nodeDto; } protected virtual int GetReservedId(Guid uniqueId) { - var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql => - tsql.Select(x => x.NodeId).From().Where(x => x.UniqueId == SqlTemplate.Arg("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation) + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql => tsql + .Select(x => x.NodeId) + .From() + .Where(x => x.UniqueId == SqlTemplate.Arg("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation) ); - var id = Database.ExecuteScalar(template.Sql(new { uniqueId = uniqueId })); + + var sql = template.Sql(new { uniqueId }); + var id = Database.ExecuteScalar(sql); + return id ?? 0; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs index 9ccf6e9623..cba030e17a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -303,7 +303,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private string EnsureUniqueNodeName(string nodeName, int id = 0) { - var template = SqlContext.Templates.Get("Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName", tsql => tsql + var template = SqlContext.Templates.Get(Constants.SqlTemplates.DataTypeRepository.EnsureUniqueNodeName, tsql => tsql .Select(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name")) .From() .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType"))); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 56a6336f75..592e112be1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -283,20 +283,54 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return result; } - + public void DeleteByParent(int parentId, params string[] relationTypeAliases) { - var subQuery = Sql().Select(x => x.Id) - .From() - .InnerJoin().On(x => x.RelationType, x => x.Id) - .Where(x => x.ParentId == parentId); - - if (relationTypeAliases.Length > 0) + if (Database.DatabaseType.IsSqlCe()) { - subQuery.WhereIn(x => x.Alias, relationTypeAliases); - } + var subQuery = Sql().Select(x => x.Id) + .From() + .InnerJoin().On(x => x.RelationType, x => x.Id) + .Where(x => x.ParentId == parentId); - Database.Execute(Sql().Delete().WhereIn(x => x.Id, subQuery)); + if (relationTypeAliases.Length > 0) + { + subQuery.WhereIn(x => x.Alias, relationTypeAliases); + } + + Database.Execute(Sql().Delete().WhereIn(x => x.Id, subQuery)); + + } + else + { + if (relationTypeAliases.Length > 0) + { + var template = SqlContext.Templates.Get( + Constants.SqlTemplates.RelationRepository.DeleteByParentIn, + tsql => Sql().Delete() + .From() + .InnerJoin().On(x => x.RelationType, x => x.Id) + .Where(x => x.ParentId == SqlTemplate.Arg("parentId")) + .WhereIn(x => x.Alias, SqlTemplate.ArgIn("relationTypeAliases"))); + + var sql = template.Sql(parentId, relationTypeAliases); + + Database.Execute(sql); + } + else + { + var template = SqlContext.Templates.Get( + Constants.SqlTemplates.RelationRepository.DeleteByParentAll, + tsql => Sql().Delete() + .From() + .InnerJoin().On(x => x.RelationType, x => x.Id) + .Where(x => x.ParentId == SqlTemplate.Arg("parentId"))); + + var sql = template.Sql(parentId); + + Database.Execute(sql); + } + } } /// diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseExtensions.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseExtensions.cs index 249dd3dc73..1fcaca6fa1 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseExtensions.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseExtensions.cs @@ -1,4 +1,7 @@ using System; +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Install; namespace Umbraco.Core.Persistence { @@ -10,5 +13,63 @@ namespace Umbraco.Core.Persistence if (asDatabase == null) throw new Exception("oops: database."); return asDatabase; } + + /// + /// Returns true if the database contains the specified table + /// + /// + /// + /// + public static bool HasTable(this IUmbracoDatabase database, string tableName) + { + try + { + return database.SqlContext.SqlSyntax.GetTablesInSchema(database).Any(table => table.InvariantEquals(tableName)); + } + catch (Exception) + { + return false; // will occur if the database cannot connect + } + } + + /// + /// Returns true if the database contains no tables + /// + /// + /// + public static bool IsDatabaseEmpty(this IUmbracoDatabase database) + => database.SqlContext.SqlSyntax.GetTablesInSchema(database).Any() == false; + + /// + /// Returns the for the database + /// + /// + /// + /// + public static DatabaseSchemaResult ValidateSchema(this IUmbracoDatabase database, ILogger logger) + { + if (database is null) throw new ArgumentNullException(nameof(database)); + if (logger is null) throw new ArgumentNullException(nameof(logger)); + + var dbSchema = new DatabaseSchemaCreator(database, logger); + var databaseSchemaValidationResult = dbSchema.ValidateSchema(); + return databaseSchemaValidationResult; + } + + /// + /// Returns true if Umbraco database tables are detected to be installed + /// + /// + /// + /// + public static bool IsUmbracoInstalled(this IUmbracoDatabase database, ILogger logger) + { + if (database is null) throw new ArgumentNullException(nameof(database)); + if (logger is null) throw new ArgumentNullException(nameof(logger)); + + var databaseSchemaValidationResult = database.ValidateSchema(logger); + return databaseSchemaValidationResult.DetermineHasInstalledVersion(); + } + } } diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index b852aff2ff..81738151f3 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Threading; using System.Web; using System.Web.Hosting; using Umbraco.Core.Cache; @@ -10,6 +12,7 @@ using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; +using Umbraco.Core.Migrations.Install; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Sync; @@ -149,7 +152,7 @@ namespace Umbraco.Core.Runtime { MainDom = new MainDom(Logger, new MainDomSemaphoreLock(Logger)); } - + // create the composition composition = new Composition(register, typeLoader, ProfilingLogger, _state, configs); @@ -158,6 +161,9 @@ namespace Umbraco.Core.Runtime // run handlers RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); + // determines if unattended install is enabled and performs it if required + DoUnattendedInstall(databaseFactory); + // register runtime-level services // there should be none, really - this is here "just in case" Compose(composition); @@ -175,7 +181,7 @@ namespace Umbraco.Core.Runtime using (ProfilingLogger.DebugDuration("Scanning enable/disable composer attributes")) { enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); - } + } var composers = new Composers(composition, composerTypes, enableDisableAttributes, ProfilingLogger); composers.Compose(); @@ -225,6 +231,63 @@ namespace Umbraco.Core.Runtime return _factory; } + private void DoUnattendedInstall(IUmbracoDatabaseFactory databaseFactory) + { + // unattended install is not enabled + if (RuntimeOptions.InstallUnattended == false) return; + + var localVersion = UmbracoVersion.LocalVersion; // the local, files, version + var codeVersion = _state.SemanticVersion; // the executing code version + + // local version and code version is not equal, an unattended install cannot be performed + if (localVersion != codeVersion) return; + + // no connection string set + if (databaseFactory.Configured == false) return; + + var tries = 5; + var connect = false; + for (var i = 0;;) + { + connect = databaseFactory.CanConnect; + if (connect || ++i == tries) break; + Logger.Debug("Could not immediately connect to database, trying again."); + Thread.Sleep(1000); + } + + // could not connect to the database + if (connect == false) return; + + using (var database = databaseFactory.CreateDatabase()) + { + var hasUmbracoTables = database.IsUmbracoInstalled(Logger); + + // database has umbraco tables, assume Umbraco is already installed + if (hasUmbracoTables) return; + + // all conditions fulfilled, do the install + Logger.Info("Starting unattended install."); + + try + { + database.BeginTransaction(); + var creator = new DatabaseSchemaCreator(database, Logger); + creator.InitializeDatabaseSchema(); + database.CompleteTransaction(); + Logger.Info("Unattended install completed."); + } + catch (Exception ex) + { + Logger.Error(ex, "Error during unattended install."); + database.AbortTransaction(); + + throw new UnattendedInstallException( + "The database configuration failed with the following message: " + ex.Message + + "\n Please check log file for additional information (can be found in '/App_Data/Logs/')"); + } + } + } + protected virtual void ConfigureUnhandledException() { //take care of unhandled exceptions - there is nothing we can do to @@ -265,14 +328,14 @@ namespace Umbraco.Core.Runtime } } - // internal for tests - internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) + // internal/virtual for tests (i.e. hack, do not port to netcore) + internal virtual void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) { using (var timer = profilingLogger.DebugDuration("Determining runtime level.", "Determined.")) { try { - _state.DetermineRuntimeLevel(databaseFactory, profilingLogger); + _state.DetermineRuntimeLevel(databaseFactory); profilingLogger.Debug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason); diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 36c91386e8..f58b279a8d 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -7,12 +7,12 @@ using System.Linq; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; -using System.Web; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; +using MapperCollection = Umbraco.Core.Persistence.Mappers.MapperCollection; namespace Umbraco.Core.Runtime { @@ -28,31 +28,32 @@ namespace Umbraco.Core.Runtime private readonly UmbracoDatabaseFactory _dbFactory; private bool _errorDuringAcquiring; private object _locker = new object(); + private bool _hasTable = false; public SqlMainDomLock(ILogger logger) { // unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer _lockId = Guid.NewGuid().ToString(); _logger = logger; - + _dbFactory = new UmbracoDatabaseFactory( Constants.System.UmbracoConnectionName, _logger, - new Lazy(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty()))); + new Lazy(() => new MapperCollection(Enumerable.Empty()))); } public async Task AcquireLockAsync(int millisecondsTimeout) { if (!_dbFactory.Configured) { - // if we aren't configured, then we're in an install state, in which case we have no choice but to assume we can acquire + // if we aren't configured then we're in an install state, in which case we have no choice but to assume we can acquire return true; } if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider)) { throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server"); - } + } _sqlServerSyntax = sqlServerSyntaxProvider; @@ -65,12 +66,20 @@ namespace Umbraco.Core.Runtime try { db = _dbFactory.CreateDatabase(); + + _hasTable = db.HasTable(Constants.DatabaseSchema.Tables.KeyValue); + if (!_hasTable) + { + // the Db does not contain the required table, we must be in an install state we have no choice but to assume we can acquire + return true; + } + db.BeginTransaction(IsolationLevel.ReadCommitted); try { // wait to get a write lock - _sqlServerSyntax.WriteLock(db, TimeSpan.FromMilliseconds(millisecondsTimeout), Constants.Locks.MainDom); + _sqlServerSyntax.WriteLock(db, TimeSpan.FromMilliseconds(millisecondsTimeout), Constants.Locks.MainDom); } catch(SqlException ex) { @@ -110,7 +119,7 @@ namespace Umbraco.Core.Runtime db?.CompleteTransaction(); db?.Dispose(); } - + return await WaitForExistingAsync(tempId, millisecondsTimeout); } @@ -130,7 +139,7 @@ namespace Umbraco.Core.Runtime _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html - TaskScheduler.Default); + TaskScheduler.Default); } @@ -169,11 +178,24 @@ namespace Umbraco.Core.Runtime _logger.Debug("Task canceled, exiting loop"); return; } - IUmbracoDatabase db = null; + try { db = _dbFactory.CreateDatabase(); + + if (!_hasTable) + { + // re-check if its still false, we don't want to re-query once we know its there since this + // loop needs to use minimal resources + _hasTable = db.HasTable(Constants.DatabaseSchema.Tables.KeyValue); + if (!_hasTable) + { + // the Db does not contain the required table, we just keep looping since we can't query the db + continue; + } + } + db.BeginTransaction(IsolationLevel.ReadCommitted); // get a read lock _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); @@ -247,7 +269,7 @@ namespace Umbraco.Core.Runtime _logger.Error(ex, "An error occurred trying to acquire and waiting for existing SqlMainDomLock to shutdown"); return false; } - + }, _cancellationTokenSource.Token); } @@ -264,7 +286,7 @@ namespace Umbraco.Core.Runtime // get a read lock _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); - // the row + // the row var mainDomRows = db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId) @@ -332,7 +354,7 @@ namespace Umbraco.Core.Runtime try { transaction = db.GetTransaction(IsolationLevel.ReadCommitted); - + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); // so now we update the row with our appdomain id @@ -361,7 +383,7 @@ namespace Umbraco.Core.Runtime } /// - /// Inserts or updates the key/value row + /// Inserts or updates the key/value row /// private RecordPersistenceType InsertLockRecord(string id, IUmbracoDatabase db) { @@ -407,7 +429,7 @@ namespace Umbraco.Core.Runtime _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); - if (_dbFactory.Configured) + if (_dbFactory.Configured && _hasTable) { IUmbracoDatabase db = null; try diff --git a/src/Umbraco.Core/RuntimeOptions.cs b/src/Umbraco.Core/RuntimeOptions.cs index c0bae23446..0d64d36849 100644 --- a/src/Umbraco.Core/RuntimeOptions.cs +++ b/src/Umbraco.Core/RuntimeOptions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Configuration; using System.Runtime.CompilerServices; using Umbraco.Core.Cache; @@ -21,6 +22,7 @@ namespace Umbraco.Core private static List> _onEssentials; private static bool? _installMissingDatabase; private static bool? _installEmptyDatabase; + private static bool? _installUnattended; // reads a boolean appSetting private static bool BoolSetting(string key, bool missing) => ConfigurationManager.AppSettings[key]?.InvariantEquals("true") ?? missing; @@ -37,24 +39,31 @@ namespace Umbraco.Core /// public static bool InstallMissingDatabase { - get => _installEmptyDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallMissingDatabase", false); - set => _installEmptyDatabase = value; + get => _installMissingDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallMissingDatabase", false); + set => _installMissingDatabase = value; + } + + [Obsolete("This setting is no longer used and will be removed in future versions. If a database connection string is configured and the database is empty Umbraco will be installed during the installation sequence.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool InstallEmptyDatabase + { + get => _installEmptyDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallEmptyDatabase", true); + set => _installEmptyDatabase = value; } /// - /// Gets a value indicating whether the runtime should enter Install level when the database is empty. + /// Gets a value indicating whether unattended installs are enabled. /// /// /// By default, when a database connection string is configured and it is possible to connect to - /// the database, but the database is empty, the runtime enters the BootFailed level. If this options - /// is set to true, it enters the Install level instead. - /// It is then up to the implementor, that is setting this value, to take over the installation - /// sequence. + /// the database, but the database is empty, the runtime enters the Install level. + /// If this option is set to true an unattended install will be performed and the runtime enters + /// the Run level. /// - public static bool InstallEmptyDatabase + public static bool InstallUnattended { - get => _installMissingDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallEmptyDatabase", false); - set => _installMissingDatabase = value; + get => _installUnattended ?? BoolSetting("Umbraco.Core.RuntimeState.InstallUnattended", false); + set => _installUnattended = value; } /// diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 8a30a97e7b..4a10b48dd6 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -1,4 +1,7 @@ using System; +using System.Data.Common; +using System.Data.SqlClient; +using System.Data.SqlServerCe; using System.Threading; using System.Web; using Semver; @@ -123,16 +126,15 @@ namespace Umbraco.Core /// /// Determines the runtime level. /// - public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory) { var localVersion = UmbracoVersion.LocalVersion; // the local, files, version var codeVersion = SemanticVersion; // the executing code version - var connect = false; - + if (localVersion == null) { // there is no local version, we are not installed - logger.Debug("No local version, need to install Umbraco."); + _logger.Debug("No local version, need to install Umbraco."); Level = RuntimeLevel.Install; Reason = RuntimeLevelReason.InstallNoVersion; return; @@ -142,13 +144,13 @@ namespace Umbraco.Core { // there *is* a local version, but it does not match the code version // need to upgrade - logger.Debug("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion); + _logger.Debug("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion); Level = RuntimeLevel.Upgrade; Reason = RuntimeLevelReason.UpgradeOldVersion; } else if (localVersion > codeVersion) { - logger.Warn("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion); + _logger.Warn("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion); // in fact, this is bad enough that we want to throw Reason = RuntimeLevelReason.BootFailedCannotDowngrade; @@ -158,108 +160,139 @@ namespace Umbraco.Core { // local version *does* match code version, but the database is not configured // install - may happen with Deploy/Cloud/etc - logger.Debug("Database is not configured, need to install Umbraco."); + _logger.Debug("Database is not configured, need to install Umbraco."); Level = RuntimeLevel.Install; Reason = RuntimeLevelReason.InstallNoDatabase; return; } - // else, keep going, - // anything other than install wants a database - see if we can connect - // (since this is an already existing database, assume localdb is ready) - var tries = RuntimeOptions.InstallMissingDatabase ? 2 : 5; - for (var i = 0;;) + // Check the database state, whether we can connect or if it's in an upgrade or empty state, etc... + + switch (GetUmbracoDatabaseState(databaseFactory)) { - connect = databaseFactory.CanConnect; - if (connect || ++i == tries) break; - logger.Debug("Could not immediately connect to database, trying again."); - Thread.Sleep(1000); + case UmbracoDatabaseState.CannotConnect: + { + // cannot connect to configured database, this is bad, fail + _logger.Debug("Could not connect to database."); + + if (RuntimeOptions.InstallMissingDatabase) + { + // ok to install on a configured but missing database + Level = RuntimeLevel.Install; + Reason = RuntimeLevelReason.InstallMissingDatabase; + return; + } + + // else it is bad enough that we want to throw + Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase; + throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); + } + case UmbracoDatabaseState.NotInstalled: + { + // ok to install on an empty database + Level = RuntimeLevel.Install; + Reason = RuntimeLevelReason.InstallEmptyDatabase; + return; + } + case UmbracoDatabaseState.NeedsUpgrade: + { + // the db version does not match... but we do have a migration table + // so, at least one valid table, so we quite probably are installed & need to upgrade + + // although the files version matches the code version, the database version does not + // which means the local files have been upgraded but not the database - need to upgrade + _logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); + Level = RuntimeLevel.Upgrade; + Reason = RuntimeLevelReason.UpgradeMigrations; + } + break; + case UmbracoDatabaseState.Ok: + default: + { + // if we already know we want to upgrade, exit here + if (Level == RuntimeLevel.Upgrade) + return; + + // the database version matches the code & files version, all clear, can run + Level = RuntimeLevel.Run; + Reason = RuntimeLevelReason.Run; + } + break; } + } - if (connect == false) - { - // cannot connect to configured database, this is bad, fail - logger.Debug("Could not connect to database."); + private enum UmbracoDatabaseState + { + Ok, + CannotConnect, + NotInstalled, + NeedsUpgrade + } - if (RuntimeOptions.InstallMissingDatabase) - { - // ok to install on a configured but missing database - Level = RuntimeLevel.Install; - Reason = RuntimeLevelReason.InstallMissingDatabase; - return; - } - - // else it is bad enough that we want to throw - Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase; - throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); - } - - // if we already know we want to upgrade, - // still run EnsureUmbracoUpgradeState to get the states - // (v7 will just get a null state, that's ok) - - // else - // look for a matching migration entry - bypassing services entirely - they are not 'up' yet - bool noUpgrade; + private UmbracoDatabaseState GetUmbracoDatabaseState(IUmbracoDatabaseFactory databaseFactory) + { try { - noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); + if (!TryDbConnect(databaseFactory)) + { + return UmbracoDatabaseState.CannotConnect; + } + + // no scope, no service - just directly accessing the database + using (var database = databaseFactory.CreateDatabase()) + { + if (!database.IsUmbracoInstalled(_logger)) + { + return UmbracoDatabaseState.NotInstalled; + } + + if (DoesUmbracoRequireUpgrade(database)) + { + return UmbracoDatabaseState.NeedsUpgrade; + } + } + + return UmbracoDatabaseState.Ok; } catch (Exception e) { - // can connect to the database but cannot check the upgrade state... oops - logger.Warn(e, "Could not check the upgrade state."); - - if (RuntimeOptions.InstallEmptyDatabase) - { - // ok to install on an empty database - Level = RuntimeLevel.Install; - Reason = RuntimeLevelReason.InstallEmptyDatabase; - return; - } + // can connect to the database so cannot check the upgrade state... oops + _logger.Warn(e, "Could not check the upgrade state."); // else it is bad enough that we want to throw Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState; throw new BootFailedException("Could not check the upgrade state.", e); } - - // if we already know we want to upgrade, exit here - if (Level == RuntimeLevel.Upgrade) - return; - - if (noUpgrade) - { - // the database version matches the code & files version, all clear, can run - Level = RuntimeLevel.Run; - Reason = RuntimeLevelReason.Run; - return; - } - - // the db version does not match... but we do have a migration table - // so, at least one valid table, so we quite probably are installed & need to upgrade - - // although the files version matches the code version, the database version does not - // which means the local files have been upgraded but not the database - need to upgrade - logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); - Level = RuntimeLevel.Upgrade; - Reason = RuntimeLevelReason.UpgradeMigrations; } - protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + private bool TryDbConnect(IUmbracoDatabaseFactory databaseFactory) + { + // anything other than install wants a database - see if we can connect + // (since this is an already existing database, assume localdb is ready) + bool canConnect; + var tries = RuntimeOptions.InstallMissingDatabase ? 2 : 5; + for (var i = 0; ;) + { + canConnect = databaseFactory.CanConnect; + if (canConnect || ++i == tries) break; + _logger.Debug("Could not immediately connect to database, trying again."); + Thread.Sleep(1000); + } + + return canConnect; + } + + private bool DoesUmbracoRequireUpgrade(IUmbracoDatabase database) { var upgrader = new Upgrader(new UmbracoPlan()); var stateValueKey = upgrader.StateValueKey; - // no scope, no service - just directly accessing the database - using (var database = databaseFactory.CreateDatabase()) - { - CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); - FinalMigrationState = upgrader.Plan.FinalState; - } + CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); + FinalMigrationState = upgrader.Plan.FinalState; - logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? ""); + _logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? ""); - return CurrentMigrationState == FinalMigrationState; + return CurrentMigrationState != FinalMigrationState; } } } diff --git a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs index 37f1e5127f..06f1ae0c0e 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs @@ -37,7 +37,7 @@ namespace Umbraco.Core.Services /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot /// be looked up via the db, they need to be passed in. /// - /// Wether the composite content types should be applicable for an element type + /// Whether the composite content types should be applicable for an element type /// internal static ContentTypeAvailableCompositionsResults GetAvailableCompositeContentTypes(this IContentTypeService ctService, IContentTypeComposition source, diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 685e5a5165..37cebc99a1 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -129,6 +129,7 @@ --> + diff --git a/src/Umbraco.Tests/Models/RangeTests.cs b/src/Umbraco.Tests/Models/RangeTests.cs new file mode 100644 index 0000000000..3fb4e8708b --- /dev/null +++ b/src/Umbraco.Tests/Models/RangeTests.cs @@ -0,0 +1,179 @@ +using System.Globalization; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class RangeTests + { + [TestCase(0, 0, "0,0")] + [TestCase(0, 1, "0,1")] + [TestCase(1, 1, "1,1")] + public void RangeInt32_ToString(int minimum, int maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString()); + } + + [TestCase(0, 0.5, "0,0.5")] + [TestCase(0.5, 0.5, "0.5,0.5")] + [TestCase(0.5, 1, "0.5,1")] + public void RangeDouble_ToString(double minimum, double maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString()); + } + + [TestCase(0, 0, "0")] + [TestCase(0, 1, "0,1")] + [TestCase(1, 1, "1")] + public void RangeInt32_ToStringFormatRange(int minimum, int maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString("{0}", "{0},{1}", CultureInfo.InvariantCulture)); + } + + [TestCase(0, 0.5, "0,0.5")] + [TestCase(0.5, 0.5, "0.5")] + [TestCase(0.5, 1, "0.5,1")] + public void RangeDouble_ToStringFormatRange(double minimum, double maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString("{0}", "{0},{1}", CultureInfo.InvariantCulture)); + } + + [TestCase(0, 0, "0,0")] + [TestCase(0, 1, "0,1")] + [TestCase(1, 1, "1,1")] + public void RangeInt32_ToStringFormat(int minimum, int maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString("{0},{1}", CultureInfo.InvariantCulture)); + } + + [TestCase(0, 0.5, "0,0.5")] + [TestCase(0.5, 0.5, "0.5,0.5")] + [TestCase(0.5, 1, "0.5,1")] + public void RangeDouble_ToStringFormat(double minimum, double maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString("{0},{1}", CultureInfo.InvariantCulture)); + } + + [TestCase(0, 0, true)] + [TestCase(0, 1, true)] + [TestCase(-1, 1, true)] + [TestCase(1, 0, false)] + [TestCase(0, -1, false)] + public void RangeInt32_IsValid(int minimum, int maximum, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.IsValid()); + } + + [TestCase(0, 0, 0, true)] + [TestCase(0, 1, 0, true)] + [TestCase(0, 1, 1, true)] + [TestCase(-1, 1, 0, true)] + [TestCase(0, 0, 1, false)] + [TestCase(0, 0, -1, false)] + public void RangeInt32_ContainsValue(int minimum, int maximum, int value, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ContainsValue(value)); + } + + [TestCase(0, 0, 0, 0, true)] + [TestCase(0, 1, 0, 1, true)] + [TestCase(0, 1, 1, 1, false)] + [TestCase(-1, 1, 0, 0, false)] + [TestCase(0, 0, 1, 1, false)] + [TestCase(0, 0, -1, 1, true)] + public void RangeInt32_IsInsideRange(int minimum1, int maximum1, int minimum2, int maximum2, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum1, + Maximum = maximum1 + }.IsInsideRange(new Range() + { + Minimum = minimum2, + Maximum = maximum2 + })); + } + + [TestCase(0, 0, 0, 0, true)] + [TestCase(0, 1, 0, 1, true)] + [TestCase(0, 1, 1, 1, true)] + [TestCase(-1, 1, 0, 0, true)] + [TestCase(0, 0, 1, 1, false)] + [TestCase(0, 0, -1, 1, false)] + public void RangeInt32_ContainsRange(int minimum1, int maximum1, int minimum2, int maximum2, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum1, + Maximum = maximum1 + }.ContainsRange(new Range() + { + Minimum = minimum2, + Maximum = maximum2 + })); + } + + [TestCase(0, 0, 0, 0, true)] + [TestCase(0, 1, 0, 1, true)] + [TestCase(0, 1, 1, 1, false)] + [TestCase(0, 0, 1, 1, false)] + public void RangeInt32_Equals(int minimum1, int maximum1, int minimum2, int maximum2, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum1, + Maximum = maximum1 + }.Equals(new Range() + { + Minimum = minimum2, + Maximum = maximum2 + })); + } + + [TestCase(0, 0, 0, 0, true)] + [TestCase(0, 1, 0, 1, true)] + [TestCase(0, 1, 1, 1, false)] + [TestCase(0, 0, 1, 1, false)] + public void RangeInt32_EqualsValues(int minimum1, int maximum1, int minimum2, int maximum2, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum1, + Maximum = maximum1 + }.Equals(minimum2, maximum2)); + } + } +} diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index 5850153100..3a99fbb0de 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -82,14 +82,18 @@ namespace Umbraco.Tests.Runtimes protected override IRuntime GetRuntime() { - return Runtime = new TestRuntime(); + var logger = new DebugDiagnosticsLogger(); + return Runtime = new TestRuntime(logger, new MainDom(logger, new MainDomSemaphoreLock(logger))); } } // test runtime public class TestRuntime : CoreRuntime { - protected override ILogger GetLogger() => new DebugDiagnosticsLogger(); + public TestRuntime(ILogger logger, IMainDom mainDom) : base(logger, mainDom) + { + } + protected override IProfiler GetProfiler() => new TestProfiler(); // must override the database factory @@ -102,6 +106,13 @@ namespace Umbraco.Tests.Runtimes return mock.Object; } + internal override void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) + { + // mega hack to make it boot up without DB access. + var state = (RuntimeState)State; + state.Level = RuntimeLevel.Boot; + } + protected override Configs GetConfigs() { var configs = new Configs(); @@ -142,6 +153,8 @@ namespace Umbraco.Tests.Runtimes private IMainDom _mainDom; + + public override IFactory Boot(IRegister container) { var factory = base.Boot(container); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 93b4dd0766..b2ceabf905 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Runtimes coreRuntime.Compose(composition); // determine actual runtime level - runtimeState.DetermineRuntimeLevel(databaseFactory, logger); + runtimeState.DetermineRuntimeLevel(databaseFactory); Console.WriteLine(runtimeState.Level); // going to be Install BUT we want to force components to be there (nucache etc) runtimeState.Level = RuntimeLevel.Run; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4e2a83c6bb..2ac28aa7d7 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -143,6 +143,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index a5b94cd1e1..b5339b60c9 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -29,14 +29,17 @@ function dependencies() { "./node_modules/ace-builds/src-min-noconflict/snippets/javascript.js", "./node_modules/ace-builds/src-min-noconflict/snippets/css.js", "./node_modules/ace-builds/src-min-noconflict/snippets/json.js", + "./node_modules/ace-builds/src-min-noconflict/snippets/xml.js", "./node_modules/ace-builds/src-min-noconflict/theme-chrome.js", "./node_modules/ace-builds/src-min-noconflict/mode-razor.js", "./node_modules/ace-builds/src-min-noconflict/mode-javascript.js", "./node_modules/ace-builds/src-min-noconflict/mode-css.js", + "./node_modules/ace-builds/src-min-noconflict/mode-json.js", + "./node_modules/ace-builds/src-min-noconflict/mode-xml.js", "./node_modules/ace-builds/src-min-noconflict/worker-javascript.js", "./node_modules/ace-builds/src-min-noconflict/worker-css.js", - "./node_modules/ace-builds/src-min-noconflict/mode-json.js", - "./node_modules/ace-builds/src-min-noconflict/worker-json.js" + "./node_modules/ace-builds/src-min-noconflict/worker-json.js", + "./node_modules/ace-builds/src-min-noconflict/worker-xml.js" ], "base": "./node_modules/ace-builds" }, diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index dad7932de4..36eeb173d6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -57,6 +57,7 @@ vm.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin(); vm.externalLoginInfo = externalLoginInfo; vm.resetPasswordCodeInfo = resetPasswordCodeInfo; + vm.logoImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginLogoImage; vm.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage; vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js index 10fc44c2c6..070ffd4ddd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js @@ -126,7 +126,7 @@ // Load in ace library assetsService.load(['lib/ace-builds/src-min-noconflict/ace.js', 'lib/ace-builds/src-min-noconflict/ext-language_tools.js'], scope).then(function () { - if (angular.isUndefined(window.ace)) { + if (Utilities.isUndefined(window.ace)) { throw new Error('ui-ace need ace to work... (o rly?)'); } else { // init editor diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js index e45fb174b8..b8731c9c51 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js @@ -5,7 +5,7 @@ @scope @description -Added in Umbraco v. 8.8: Use this directive to render a color picker. +Added in Umbraco v. 8.10: Use this directive to render a color picker.

Markup example

diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js
index 9d7927f59a..d6eda76940 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js
@@ -30,15 +30,15 @@ Use this directive to generate color swatches to pick from.
         function link(scope, el, attr, ctrl) {
 
             // Set default to true if not defined
-            if (angular.isUndefined(scope.useColorClass)) {
+            if (Utilities.isUndefined(scope.useColorClass)) {
                 scope.useColorClass = false;
             }
 
             // Set default to "btn" if not defined
-            if (angular.isUndefined(scope.colorClassNamePrefix)) {
+            if (Utilities.isUndefined(scope.colorClassNamePrefix)) {
                 scope.colorClassNamePrefix = "btn";
             }
-            
+
             scope.setColor = function (color, $index, $event) {
                 if (scope.onSelect) {
                     // did the value change?
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js
index 87d976f6d9..0dee0866d2 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js
@@ -15,7 +15,7 @@ Simple icon
 
 Icon with additional attribute. It can be treated like any other dom element
 
-    
+    
 
@example **/ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js index 78dd00e64b..ad646748b7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js @@ -11,6 +11,7 @@ link: function (scope, element, attr, ctrl) { var formMgr = ctrl.length > 1 ? ctrl[1] : null; + const hiddenClass = 'ng-hide'; //We can either get the form submitted status by the parent directive valFormManager //or we can check upwards in the DOM for the css class... lets try both :) @@ -18,17 +19,17 @@ //reset the status. var submitted = element.closest(".show-validation").length > 0 || (formMgr && formMgr.showValidation); if (!submitted) { - element.hide(); + element[0].classList.add(hiddenClass); } var unsubscribe = []; unsubscribe.push(scope.$on("formSubmitting", function (ev, args) { - element.show(); + element[0].classList.remove(hiddenClass); })); unsubscribe.push(scope.$on("formSubmitted", function (ev, args) { - element.hide(); + element[0].classList.add(hiddenClass); })); //no isolate scope to listen to element destroy diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js index a519f81054..870e497541 100644 --- a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js +++ b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js @@ -13,7 +13,7 @@ */ angular.module("umbraco.filters").filter('umbCmsJoinArray', function () { return function join(array, separator, prop) { - return (!angular.isUndefined(prop) ? array.map(function (item) { + return (!Utilities.isUndefined(prop) ? array.map(function (item) { return item[prop]; }) : array).join(separator || ''); }; diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js index 93f0bddc96..bd597b21d0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js +++ b/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js @@ -17,7 +17,7 @@ return collection; } - if (angular.isUndefined(property)) { + if (Utilities.isUndefined(property)) { return collection; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index 936f69e738..e26ac26f7c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -12,7 +12,24 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { return { - + /** + * @ngdoc method + * @name umbraco.resources.authResource#get2FAProviders + * @methodOf umbraco.resources.authResource + * + * @description + * Logs the Umbraco backoffice user in if the credentials are good + * + * ##usage + *
+     * authResource.get2FAProviders()
+     *    .then(function(data) {
+     *        //Do stuff ...
+     *    });
+     * 
+ * @returns {Promise} resourcePromise object + * + */ get2FAProviders: function () { return umbRequestHelper.resourcePromise( @@ -23,6 +40,25 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { 'Could not retrive two factor provider info'); }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#get2FAProviders + * @methodOf umbraco.resources.authResource + * + * @description + * Generate the two-factor authentication code for the provider and send it to the user + * + * ##usage + *
+    * authResource.send2FACode(provider)
+    *    .then(function(data) {
+    *        //Do stuff ...
+    *    });
+    * 
+ * @param {string} provider Name of the provider + * @returns {Promise} resourcePromise object + * + */ send2FACode: function (provider) { return umbRequestHelper.resourcePromise( @@ -34,6 +70,26 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { 'Could not send code'); }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#get2FAProviders + * @methodOf umbraco.resources.authResource + * + * @description + * Verify the two-factor authentication code entered by the user against the provider + * + * ##usage + *
+    * authResource.verify2FACode(provider, code)
+    *    .then(function(data) {
+    *        //Do stuff ...
+    *    });
+    * 
+ * @param {string} provider Name of the provider + * @param {string} code The two-factor authentication code + * @returns {Promise} resourcePromise object + * + */ verify2FACode: function (provider, code) { return umbRequestHelper.resourcePromise( 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 97bebef062..01b6360d07 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -7,6 +7,25 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca return { + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getCount + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Gets the count of content types + * + * ##usage + *
+        * contentTypeResource.getCount()
+        *    .then(function(data) {
+        *        console.log(data);
+        *    });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ getCount: function () { return umbRequestHelper.resourcePromise( $http.get( @@ -16,6 +35,29 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve count'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getAvailableCompositeContentTypes + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Gets the compositions for a content type + * + * ##usage + *
+        * contentTypeResource.getAvailableCompositeContentTypes()
+        *    .then(function(data) {
+        *        console.log(data);
+        *    });
+        * 
+ * + * @param {Int} contentTypeId id of the content type to retrieve the list of the compositions + * @param {Array} filterContentTypes array of content types to filter out + * @param {Array} filterPropertyTypes array of property aliases to filter out. If specified any content types with the property aliases will be filtered out + * @param {Boolean} isElement whether the composite content types should be applicable for an element type + * @returns {Promise} resourcePromise object. + * + */ getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes, isElement) { if (!filterContentTypes) { filterContentTypes = []; @@ -86,6 +128,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca * $scope.type = type; * }); *
+ * * @param {Int} contentTypeId id of the content item to retrive allowed child types for * @returns {Promise} resourcePromise object. * @@ -110,6 +153,14 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca * @description * Returns a list of defined property type aliases * + * ##usage + *
+         * contentTypeResource.getAllPropertyTypeAliases()
+         *    .then(function(array) {
+         *       Do stuff...
+         *    });
+         * 
+ * * @returns {Promise} resourcePromise object. * */ @@ -123,6 +174,25 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve property type aliases'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getAllStandardFields + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a list of standard property type aliases + * + * ##usage + *
+        * contentTypeResource.getAllStandardFields()
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ getAllStandardFields: function () { return umbRequestHelper.resourcePromise( @@ -133,6 +203,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve standard fields'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getPropertyTypeScaffold + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns the property display for a given datatype id + * + * ##usage + *
+        * contentTypeResource.getPropertyTypeScaffold(1234)
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id the id of the datatype + * @returns {Promise} resourcePromise object. + * + */ getPropertyTypeScaffold: function (id) { return umbRequestHelper.resourcePromise( $http.get( @@ -143,6 +233,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve property type scaffold'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getById + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Get the content type with a given id + * + * ##usage + *
+        * contentTypeResource.getById("64058D0F-4911-4AB7-B3BA-000D89F00A26")
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {String} id the guid id of the content type + * @returns {Promise} resourcePromise object. + * + */ getById: function (id) { return umbRequestHelper.resourcePromise( @@ -154,6 +264,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve content type'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#deleteById + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Delete the content type of a given id + * + * ##usage + *
+        * contentTypeResource.deleteById(1234)
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id the id of the content type + * @returns {Promise} resourcePromise object. + * + */ deleteById: function (id) { return umbRequestHelper.resourcePromise( @@ -165,6 +295,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to delete content type'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#deleteContainerById + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Delete the content type container of a given id + * + * ##usage + *
+        * contentTypeResource.deleteContainerById(1234)
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id the id of the content type container + * @returns {Promise} resourcePromise object. + * + */ deleteContainerById: function (id) { return umbRequestHelper.resourcePromise( @@ -177,16 +327,16 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca }, /** - * @ngdoc method - * @name umbraco.resources.contentTypeResource#getAll - * @methodOf umbraco.resources.contentTypeResource - * - * @description - * Returns a list of all content types - * - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getAll + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a list of all content types + * + * @returns {Promise} resourcePromise object. + * + */ getAll: function () { return umbRequestHelper.resourcePromise( @@ -197,6 +347,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve all content types'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getScaffold + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns an empty content type for use as a scaffold when creating a new content type + * + * ##usage + *
+        * contentTypeResource.getScaffold(1234)
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id the parent id + * @returns {Promise} resourcePromise object. + * + */ getScaffold: function (parentId) { return umbRequestHelper.resourcePromise( @@ -240,14 +410,14 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca *
          * contentTypeResource.move({ parentId: 1244, id: 123 })
          *    .then(function() {
-         *        alert("node was moved");
+         *        alert("content type was moved");
          *    }, function(err){
-         *      alert("node didnt move:" + err.data.Message);
+         *      alert("content type 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 + * @param {Int} args.id the ID of the content type to move + * @param {Int} args.parentId the ID of the parent content type to move to * @returns {Promise} resourcePromise object. * */ @@ -271,6 +441,29 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to move content'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#copy + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Copied a content type underneath a new parentId + * + * ##usage + *
+         * contentTypeResource.copy({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("content type was copied");
+         *    }, function(err){
+         *      alert("content type didnt copy:" + err.data.Message);
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.id the ID of the content type to copy + * @param {Int} args.parentId the ID of the parent content type to copy to + * @returns {Promise} resourcePromise object. + * + */ copy: function (args) { if (!args) { throw "args cannot be null"; @@ -291,6 +484,27 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to copy content'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#createContainer + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Create a new content type container of a given name underneath a given parent item + * + * ##usage + *
+        * contentTypeResource.createContainer(1244,"testcontainer")
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} parentId the ID of the parent content type underneath which to create the container + * @param {String} name the name of the container + * @returns {Promise} resourcePromise object. + * + */ createContainer: function (parentId, name) { return umbRequestHelper.resourcePromise( @@ -299,6 +513,32 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#createCollection + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Create a collection of a content types + * + * ##usage + *
+        * contentTypeResource.createCollection(1244,"testcollectionname",true,"collectionItemName",true,"icon-name","icon-name")
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} parentId the ID of the parent content type underneath which to create the collection + * @param {String} collectionName the name of the collection + * @param {Boolean} collectionCreateTemplate true/false to specify whether to create a default template for the collection + * @param {String} collectionItemName the name of the collection item + * @param {Boolean} collectionItemCreateTemplate true/false to specify whether to create a default template for the collection item + * @param {String} collectionIcon the icon for the collection + * @param {String} collectionItemIcon the icon for the collection item + * @returns {Promise} resourcePromise object. + * + */ createCollection: function (parentId, collectionName, collectionCreateTemplate, collectionItemName, collectionItemCreateTemplate, collectionIcon, collectionItemIcon) { return umbRequestHelper.resourcePromise( @@ -307,6 +547,27 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#renameContainer + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Rename a container of a given id + * + * ##usage + *
+        * contentTypeResource.renameContainer( 1244,"testcontainer")
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} id the ID of the container to rename + * @param {String} name the new name of the container + * @returns {Promise} resourcePromise object. + * + */ renameContainer: function (id, name) { return umbRequestHelper.resourcePromise( @@ -318,6 +579,27 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#export + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Export a content type of a given id. + * + * ##usage + *
+        * contentTypeResource.export(1234){
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} id the ID of the container to rename + * @param {String} name the new name of the container + * @returns {Promise} resourcePromise object. + * + */ export: function (id) { if (!id) { throw "id cannot be null"; @@ -336,6 +618,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca }); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#import + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Import a content type from a file + * + * ##usage + *
+        * contentTypeResource.import("path to file"){
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {String} file path of the file to import + * @returns {Promise} resourcePromise object. + * + */ import: function (file) { if (!file) { throw "file cannot be null"; @@ -347,12 +649,52 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca ); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#createDefaultTemplate + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Create a default template for a content type with a given id + * + * ##usage + *
+        * contentTypeResource.createDefaultTemplate(1234){
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} id the id of the content type for which to create the default template + * @returns {Promise} resourcePromise object. + * + */ createDefaultTemplate: function (id) { return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateDefaultTemplate", { id: id })), 'Failed to create default template for content type with id ' + id); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#hasContentNodes + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns whether a content type has content nodes + * + * ##usage + *
+        * contentTypeResource.hasContentNodes(1234){
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} id the id of the content type + * @returns {Promise} resourcePromise object. + * + */ hasContentNodes: function (id) { return umbRequestHelper.resourcePromise( $http.get( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/elementtype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/elementtype.resource.js index acc0a94485..b097a65447 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/elementtype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/elementtype.resource.js @@ -7,6 +7,25 @@ function elementTypeResource($q, $http, umbRequestHelper) { return { + /** + * @ngdoc method + * @name umbraco.resources.elementTypeResource#getAll + * @methodOf umbraco.resources.elementTypeResource + * + * @description + * Gets a list of all element types + * + * ##usage + *
+        * elementTypeResource.getAll()
+        *    .then(function() {
+        *        alert('Found it!');
+        *    });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + **/ getAll: function () { return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js index ee3cd80c71..8352ca07e6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js @@ -1,18 +1,82 @@ + +/** +* @ngdoc service +* @name umbraco.resources.modelsBuilderManagementResource +* @description Resources to get information on modelsbuilder status and build models +**/ function modelsBuilderManagementResource($q, $http, umbRequestHelper) { return { + + /** + * @ngdoc method + * @name umbraco.resources.modelsBuilderManagementResource#getModelsOutOfDateStatus + * @methodOf umbraco.resources.modelsBuilderManagementResource + * + * @description + * Gets the status of modelsbuilder + * + * ##usage + *
+        * modelsBuilderManagementResource.getModelsOutOfDateStatus()
+        *  .then(function() {
+        *        Do stuff...*
+        * });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ getModelsOutOfDateStatus: function () { return umbRequestHelper.resourcePromise( $http.get(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "GetModelsOutOfDateStatus")), "Failed to get models out-of-date status"); }, + /** + * @ngdoc method + * @name umbraco.resources.modelsBuilderManagementResource#buildModels + * @methodOf umbraco.resources.modelsBuilderManagementResource + * + * @description + * Builds the models + * + * ##usage + *
+        * modelsBuilderManagementResource.buildModels()
+        *  .then(function() {
+        *        Do stuff...*
+        * });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ buildModels: function () { return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "BuildModels")), "Failed to build models"); }, + /** + * @ngdoc method + * @name umbraco.resources.modelsBuilderManagementResource#getDashboard + * @methodOf umbraco.resources.modelsBuilderManagementResource + * + * @description + * Gets the modelsbuilder dashboard + * + * ##usage + *
+        * modelsBuilderManagementResource.getDashboard()
+        *  .then(function() {
+        *        Do stuff...*
+        * });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ getDashboard: function () { return umbRequestHelper.resourcePromise( $http.get(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "GetDashboard")), diff --git a/src/Umbraco.Web.UI.Client/src/install.loader.js b/src/Umbraco.Web.UI.Client/src/install.loader.js index 997c8cbe84..8c17b8cd64 100644 --- a/src/Umbraco.Web.UI.Client/src/install.loader.js +++ b/src/Umbraco.Web.UI.Client/src/install.loader.js @@ -1,6 +1,6 @@ LazyLoad.js([ 'lib/jquery/jquery.min.js', - + 'lib/angular/angular.js', 'lib/angular-cookies/angular-cookies.js', 'lib/angular-touch/angular-touch.js', @@ -8,10 +8,14 @@ LazyLoad.js([ 'lib/angular-messages/angular-messages.js', 'lib/angular-aria/angular-aria.min.js', 'lib/underscore/underscore-min.js', - 'lib/angular-ui-sortable/sortable.js', + 'lib/angular-ui-sortable/sortable.js', + + 'js/utilities.js', + 'js/installer.app.js', 'js/umbraco.directives.js', 'js/umbraco.installer.js' + ], function () { jQuery(document).ready(function () { angular.bootstrap(document, ['umbraco']); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js index 687fce95ae..cbd2bb9242 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -10,7 +10,7 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll { name: 'Custom connection string', id: -1 } ]; - if (angular.isUndefined(installerService.status.current.model.dbType) || installerService.status.current.model.dbType === null) { + if (Utilities.isUndefined(installerService.status.current.model.dbType) || installerService.status.current.model.dbType === null) { installerService.status.current.model.dbType = 0; } diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index 9e91da4792..68160923c8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -171,13 +171,13 @@ body.umb-drawer-is-visible #mainwrapper{ } @media (min-width: 1101px) { - #contentwrapper, #speechbubble {left: 360px;} - .emptySection #contentwrapper {left:0px;} + #contentwrapper, #speechbubble { left: 360px; } + .emptySection #contentwrapper { left: 0 !important; } } //empty section modification -.emptySection #speechbubble {left: 0;} -.emptySection #navigation {display: none} +.emptySection #speechbubble { left: 0; } +.emptySection #navigation { display: none } .login-only #speechbubble { z-index: 10000; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less index 2a8137e5f9..ea08794c23 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less @@ -10,11 +10,12 @@ font-weight: bold; display: flex; align-items: center; - cursor: pointer; justify-content: space-between; color: @black; + width: 100%; - &:hover .umb-expansion-panel__expand { + &:hover .umb-expansion-panel__expand, + &:focus .umb-expansion-panel__expand { color: @gray-6; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less index 784598e84a..e1fc5573e5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -392,7 +392,7 @@ // EDITOR PLACEHOLDER // ------------------------- .umb-grid .umb-editor-placeholder { - min-height: 65px; + min-height: 110px; padding: 20px; padding-bottom: 30px; position: relative; @@ -400,7 +400,7 @@ border: 4px dashed @gray-8; text-align: center; text-align: -moz-center; - cursor: pointer; + width: 100%; } .umb-grid .umb-editor-placeholder i { @@ -413,6 +413,7 @@ .umb-grid .umb-editor-preview { position: relative; + width: 100%; .umb-editor-preview-overlay { cursor: pointer; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 05824ba425..29a19bbba9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -240,17 +240,17 @@ } .umb-nested-content__doctypepicker table td.icon-navigation, -.umb-nested-content__doctypepicker i.umb-nested-content__help-icon { +.umb-nested-content__doctypepicker .umb-nested-content__help-icon { vertical-align: middle; color: @gray-7; } .umb-nested-content__doctypepicker table td.icon-navigation:hover, -.umb-nested-content__doctypepicker i.umb-nested-content__help-icon:hover { +.umb-nested-content__doctypepicker .umb-nested-content__help-icon:hover { color: @gray-2; } -.umb-nested-content__doctypepicker i.umb-nested-content__help-icon { +.umb-nested-content__doctypepicker .umb-nested-content__help-action { margin-left: 10px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 90b2dbe37e..369a761f71 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -65,7 +65,10 @@ label.control-label, .control-label { .form-search small { color: @gray-8; } -.form-search .icon, .form-search .icon-search { + +.form-search .icon, +.form-search .icon-search, +.form-search .umb-icon { position: absolute; z-index: 1; top: 50%; diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index e45a4d46bb..6dab771c94 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -12,7 +12,10 @@ padding-right: 7px; } -.icon.handle{color: @gray-8;} +.umb-icon.handle, +.icon.handle { + color: @gray-8; +} // BASE CLASS diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index a036267c85..4beec7c295 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -361,7 +361,7 @@ border-radius: 3px; width: 50px; &:hover { - .icon { + .umb-icon { opacity: 0.8; } border-color: @ui-action-discreet-border-hover; @@ -397,7 +397,7 @@ border: 1px dashed @gray-8; } -.umb-panel-header-icon .icon { +.umb-panel-header-icon .umb-icon { font-size: 30px; color: @gray-7; transition: opacity 120ms; diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less index 7960aae2c9..2c8a43093f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/properties.less +++ b/src/Umbraco.Web.UI.Client/src/less/properties.less @@ -45,6 +45,7 @@ .date-wrapper-mini { display: flex; flex-direction: row; + align-items:center; } .date-wrapper-mini--checkbox{ @@ -54,7 +55,7 @@ .date-wrapper-mini__date { display: flex; - margin-left: 5px; + margin-left: 15px; margin-top: 5px; margin-bottom: 10px; @@ -65,6 +66,17 @@ border-top-right-radius: 0; border-bottom-right-radius: 0; } + + .flatpickr-label { + margin-right:5px; + display:flex; + align-items: center; + line-height: 1; + } + + .btn-reset { + font-size: 14px; + } } .date-wrapper-mini__date .flatpickr-input > a { diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index a21ce75c30..703082eb53 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -106,6 +106,13 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi .fail(function () { console.log("Could not connect to SignalR preview hub."); }); } + function fixExternalLinks(iframe) { + // Make sure external links don't open inside the iframe + Array.from(iframe.contentDocument.getElementsByTagName("a")) + .filter(a => a.hostname !== location.hostname && !a.target) + .forEach(a => a.target = "_top"); + } + var isInit = getParameterByName("init"); if (isInit === "true") { //do not continue, this is the first load of this new window, if this is passed in it means it's been @@ -432,6 +439,7 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.frameLoaded = true; configureSignalR(iframe); + fixExternalLinks(iframe); $scope.currentCultureIso = $location.search().culture || null; }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html index 04b5501846..d0c8b81a99 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html @@ -97,11 +97,11 @@ - + - +
- +
Visit umbraco.tv @@ -138,7 +138,7 @@ - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index 7368d2f39b..3a8c6d2196 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -44,7 +44,7 @@
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index a9803b70f9..4106f7d096 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -147,7 +147,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", }); } - if (!angular.isUndefined($scope.model.target.isMedia)) { + if (!Utilities.isUndefined($scope.model.target.isMedia)) { delete $scope.model.target.isMedia; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macroparameterpicker/macroparameterpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macroparameterpicker/macroparameterpicker.controller.js index 5b81cb947d..81e3e6bb34 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macroparameterpicker/macroparameterpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macroparameterpicker/macroparameterpicker.controller.js @@ -103,9 +103,6 @@ function pickParameterEditor(selectedParameterEditor) { - console.log("pickParameterEditor", selectedParameterEditor); - console.log("$scope.model", $scope.model); - $scope.model.parameter.editor = selectedParameterEditor.alias; $scope.model.parameter.dataTypeName = selectedParameterEditor.name; $scope.model.parameter.dataTypeIcon = selectedParameterEditor.icon; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html index a6e2062f22..df0c8e3cef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html @@ -19,15 +19,15 @@
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html index e009bd2f40..f6442c6e22 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html @@ -118,7 +118,7 @@