diff --git a/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_0_0/Models/ContentTypeDto80.cs b/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_0_0/Models/ContentTypeDto80.cs new file mode 100644 index 0000000000..bc41e5e32a --- /dev/null +++ b/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_0_0/Models/ContentTypeDto80.cs @@ -0,0 +1,63 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models +{ + + /// + /// Snapshot of the as it was at version 8.0 + /// + /// + /// This is required during migrations the schema of this table changed and running SQL against the new table would result in errors + /// + [TableName(TableName)] + [PrimaryKey("pk")] + [ExplicitColumns] + internal class ContentTypeDto80 + { + public const string TableName = Constants.DatabaseSchema.Tables.ContentType; + + [Column("pk")] + [PrimaryKeyColumn(IdentitySeed = 535)] + public int PrimaryKey { get; set; } + + [Column("nodeId")] + [ForeignKey(typeof(NodeDto))] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsContentType")] + public int NodeId { get; set; } + + [Column("alias")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Alias { get; set; } + + [Column("icon")] + [Index(IndexTypes.NonClustered)] + [NullSetting(NullSetting = NullSettings.Null)] + public string Icon { get; set; } + + [Column("thumbnail")] + [Constraint(Default = "folder.png")] + public string Thumbnail { get; set; } + + [Column("description")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(1500)] + public string Description { get; set; } + + [Column("isContainer")] + [Constraint(Default = "0")] + public bool IsContainer { get; set; } + + [Column("allowAtRoot")] + [Constraint(Default = "0")] + public bool AllowAtRoot { get; set; } + + [Column("variations")] + [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] + public byte Variations { get; set; } + + [ResultColumn] + public NodeDto NodeDto { get; set; } + } +} diff --git a/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_0_0/Models/PropertyDataDto80.cs b/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_0_0/Models/PropertyDataDto80.cs new file mode 100644 index 0000000000..f0a36b223d --- /dev/null +++ b/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_0_0/Models/PropertyDataDto80.cs @@ -0,0 +1,142 @@ +using NPoco; +using System; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models +{ + /// + /// Snapshot of the as it was at version 8.0 + /// + /// + /// This is required during migrations the schema of this table changed and running SQL against the new table would result in errors + /// + [TableName(TableName)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class PropertyDataDto80 + { + public const string TableName = Constants.DatabaseSchema.Tables.PropertyData; + public const int VarcharLength = 512; + public const int SegmentLength = 256; + + private decimal? _decimalValue; + + // pk, not used at the moment (never updating) + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("versionId")] + [ForeignKey(typeof(ContentVersionDto))] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_VersionId", ForColumns = "versionId,propertyTypeId,languageId,segment")] + public int VersionId { get; set; } + + [Column("propertyTypeId")] + [ForeignKey(typeof(PropertyTypeDto80))] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_PropertyTypeId")] + public int PropertyTypeId { get; set; } + + [Column("languageId")] + [ForeignKey(typeof(LanguageDto))] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_LanguageId")] + [NullSetting(NullSetting = NullSettings.Null)] + public int? LanguageId { get; set; } + + [Column("segment")] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Segment")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(SegmentLength)] + public string Segment { get; set; } + + [Column("intValue")] + [NullSetting(NullSetting = NullSettings.Null)] + public int? IntegerValue { get; set; } + + [Column("decimalValue")] + [NullSetting(NullSetting = NullSettings.Null)] + public decimal? DecimalValue + { + get => _decimalValue; + set => _decimalValue = value?.Normalize(); + } + + [Column("dateValue")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? DateValue { get; set; } + + [Column("varcharValue")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(VarcharLength)] + public string VarcharValue { get; set; } + + [Column("textValue")] + [NullSetting(NullSetting = NullSettings.Null)] + [SpecialDbType(SpecialDbTypes.NTEXT)] + public string TextValue { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "PropertyTypeId")] + public PropertyTypeDto80 PropertyTypeDto { get; set; } + + [Ignore] + public object Value + { + get + { + if (IntegerValue.HasValue) + return IntegerValue.Value; + + if (DecimalValue.HasValue) + return DecimalValue.Value; + + if (DateValue.HasValue) + return DateValue.Value; + + if (!string.IsNullOrEmpty(VarcharValue)) + return VarcharValue; + + if (!string.IsNullOrEmpty(TextValue)) + return TextValue; + + return null; + } + } + + public PropertyDataDto80 Clone(int versionId) + { + return new PropertyDataDto80 + { + VersionId = versionId, + PropertyTypeId = PropertyTypeId, + LanguageId = LanguageId, + Segment = Segment, + IntegerValue = IntegerValue, + DecimalValue = DecimalValue, + DateValue = DateValue, + VarcharValue = VarcharValue, + TextValue = TextValue, + PropertyTypeDto = PropertyTypeDto + }; + } + + protected bool Equals(PropertyDataDto other) + { + return Id == other.Id; + } + + public override bool Equals(object other) + { + return + !ReferenceEquals(null, other) // other is not null + && (ReferenceEquals(this, other) // and either ref-equals, or same id + || other is PropertyDataDto pdata && pdata.Id == Id); + } + + public override int GetHashCode() + { + // ReSharper disable once NonReadonlyMemberInGetHashCode + return Id; + } + } +} diff --git a/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_0_0/Models/PropertyTypeDto80.cs b/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_0_0/Models/PropertyTypeDto80.cs new file mode 100644 index 0000000000..6e2bd7371a --- /dev/null +++ b/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_0_0/Models/PropertyTypeDto80.cs @@ -0,0 +1,80 @@ +using NPoco; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models +{ + /// + /// Snapshot of the as it was at version 8.0 + /// + /// + /// This is required during migrations before 8.6 since the schema has changed and running SQL against the new table would result in errors + /// + [TableName(Constants.DatabaseSchema.Tables.PropertyType)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class PropertyTypeDto80 + { + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = 50)] + public int Id { get; set; } + + [Column("dataTypeId")] + [ForeignKey(typeof(DataTypeDto), Column = "nodeId")] + public int DataTypeId { get; set; } + + [Column("contentTypeId")] + [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] + public int ContentTypeId { get; set; } + + [Column("propertyTypeGroupId")] + [NullSetting(NullSetting = NullSettings.Null)] + [ForeignKey(typeof(PropertyTypeGroupDto))] + public int? PropertyTypeGroupId { get; set; } + + [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyTypeAlias")] + [Column("Alias")] + public string Alias { get; set; } + + [Column("Name")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Name { get; set; } + + [Column("sortOrder")] + [Constraint(Default = "0")] + public int SortOrder { get; set; } + + [Column("mandatory")] + [Constraint(Default = "0")] + public bool Mandatory { get; set; } + + [Column("validationRegExp")] + [NullSetting(NullSetting = NullSettings.Null)] + public string ValidationRegExp { get; set; } + + [Column("Description")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(2000)] + public string Description { get; set; } + + [Column("variations")] + [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] + public byte Variations { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "DataTypeId")] + public DataTypeDto DataTypeDto { get; set; } + + [Column("UniqueID")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.NewGuid)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyTypeUniqueID")] + public Guid UniqueId { get; set; } + } +} diff --git a/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_7_0/MissingDictionaryIndex.cs b/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_7_0/MissingDictionaryIndex.cs new file mode 100644 index 0000000000..fa5116c990 --- /dev/null +++ b/src/Umbraco.Abstractions/Migrations/Upgrade/V_8_7_0/MissingDictionaryIndex.cs @@ -0,0 +1,33 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 +{ + public class MissingDictionaryIndex : MigrationBase + { + public MissingDictionaryIndex(IMigrationContext context) + : base(context) + { + + } + + /// + /// Adds an index to the foreign key column parent on DictionaryDto's table + /// if it doesn't already exist + /// + public override void Migrate() + { + var indexName = "IX_" + DictionaryDto.TableName + "_Parent"; + + if (!IndexExists(indexName)) + { + Create + .Index(indexName) + .OnTable(DictionaryDto.TableName) + .OnColumn("parent") + .Ascending() + .WithOptions().NonClustered() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Abstractions/Udi.cs b/src/Umbraco.Abstractions/Udi.cs index 80e58b2f86..8a0ce83811 100644 --- a/src/Umbraco.Abstractions/Udi.cs +++ b/src/Umbraco.Abstractions/Udi.cs @@ -11,8 +11,8 @@ namespace Umbraco.Core /// An Udi can be fully qualified or "closed" eg umb://document/{guid} or "open" eg umb://document. [TypeConverter(typeof(UdiTypeConverter))] public abstract class Udi : IComparable - { - public Uri UriValue { get; } + { + public Uri UriValue { get; } /// /// Initializes a new instance of the Udi class. @@ -35,7 +35,7 @@ namespace Umbraco.Core UriValue = uriValue; } - + /// /// Gets the entity type part of the identifier. @@ -54,7 +54,7 @@ namespace Umbraco.Core return UriValue.AbsoluteUri; } - + /// /// Creates a root Udi for an entity type. @@ -114,7 +114,7 @@ namespace Umbraco.Core if (udiType == UdiType.GuidUdi) return new GuidUdi(uri); - if (udiType == UdiType.GuidUdi) + if (udiType == UdiType.StringUdi) return new StringUdi(uri); throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri)); @@ -166,6 +166,6 @@ namespace Umbraco.Core return (udi1 == udi2) == false; } - + } } diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs b/src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs index bf07e4d08f..f4c6150073 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; @@ -84,10 +85,18 @@ namespace Umbraco.Core.Migrations protected void ReplaceColumn(string tableName, string currentName, string newName) { - AddColumn(tableName, newName, out var sqls); - Execute.Sql($"UPDATE {SqlSyntax.GetQuotedTableName(tableName)} SET {SqlSyntax.GetQuotedColumnName(newName)}={SqlSyntax.GetQuotedColumnName(currentName)}").Do(); - foreach (var sql in sqls) Execute.Sql(sql).Do(); - Delete.Column(currentName).FromTable(tableName).Do(); + if (DatabaseType.IsSqlCe()) + { + AddColumn(tableName, newName, out var sqls); + Execute.Sql($"UPDATE {SqlSyntax.GetQuotedTableName(tableName)} SET {SqlSyntax.GetQuotedColumnName(newName)}={SqlSyntax.GetQuotedColumnName(currentName)}").Do(); + foreach (var sql in sqls) Execute.Sql(sql).Do(); + Delete.Column(currentName).FromTable(tableName).Do(); + } + else + { + Execute.Sql(SqlSyntax.FormatColumnRename(tableName, currentName, newName)).Do(); + AlterColumn(tableName, newName); + } } protected bool TableExists(string tableName) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs index 29006c8811..4872e0019d 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs @@ -12,6 +12,7 @@ namespace Umbraco.Core.Migrations.Upgrade.Common { // remove those that may already have keys Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.KeyValue).Do(); + Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.PropertyData).Do(); // re-create *all* keys and indexes foreach (var x in DatabaseSchemaCreator.OrderedTables) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 67dfe346d6..b8a5755212 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -193,6 +193,9 @@ namespace Umbraco.Core.Migrations.Upgrade To("{EE288A91-531B-4995-8179-1D62D9AA3E2E}"); To("{2AB29964-02A1-474D-BD6B-72148D2A53A2}"); + + To("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}"); + //FINAL } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs index 309f8acbc3..66f9114370 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs @@ -75,8 +75,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 var labelPropertyTypes = Database.Fetch(Sql() .Select(x => x.Id, x => x.Alias) .From() - .Where(x => x.DataTypeId == Constants.DataTypes.LabelString) - ); + .Where(x => x.DataTypeId == Constants.DataTypes.LabelString)); var intPropertyAliases = new[] { Constants.Conventions.Media.Width, Constants.Conventions.Media.Height, Constants.Conventions.Member.FailedPasswordAttempts }; var bigintPropertyAliases = new[] { Constants.Conventions.Media.Bytes }; @@ -101,16 +100,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 foreach (var value in values) Database.Execute(Sql() .Update(u => u - .Set(x => x.IntegerValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (int?) null : int.Parse(value.VarcharValue, NumberStyles.Any, CultureInfo.InvariantCulture)) - .Set(x => x.TextValue, null)) + .Set(x => x.IntegerValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (int?)null : int.Parse(value.VarcharValue, NumberStyles.Any, CultureInfo.InvariantCulture)) + .Set(x => x.TextValue, null) + .Set(x => x.VarcharValue, null)) .Where(x => x.Id == value.Id)); values = Database.Fetch(Sql().Select(x => x.Id, x => x.VarcharValue).From().WhereIn(x => x.PropertyTypeId, dtPropertyTypes)); foreach (var value in values) Database.Execute(Sql() .Update(u => u - .Set(x => x.DateValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (DateTime?) null : DateTime.Parse(value.VarcharValue, CultureInfo.InvariantCulture, DateTimeStyles.None)) - .Set(x => x.TextValue, null)) + .Set(x => x.DateValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (DateTime?)null : DateTime.Parse(value.VarcharValue, CultureInfo.InvariantCulture, DateTimeStyles.None)) + .Set(x => x.TextValue, null) + .Set(x => x.VarcharValue, null)) .Where(x => x.Id == value.Id)); // anything that's custom... ppl will have to figure it out manually, there isn't much we can do about it diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs index 84dd393b0d..eabbd34b08 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs @@ -4,8 +4,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using NPoco; +using Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 @@ -47,14 +49,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 } } - var propertyTypes = Database.Fetch(Sql().Select().From()); + var propertyTypes = Database.Fetch(Sql().Select().From()); foreach (var dto in propertyTypes) { dto.Variations = GetNewValue(dto.Variations); Database.Update(dto); } - var contentTypes = Database.Fetch(Sql().Select().From()); + var contentTypes = Database.Fetch(Sql().Select().From()); foreach (var dto in contentTypes) { dto.Variations = GetNewValue(dto.Variations); @@ -62,57 +64,11 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 } } - // we *need* to use this private DTO here, which does *not* have extra properties, which would kill the migration + // we *need* to use these private DTOs here, which does *not* have extra properties, which would kill the migration - [TableName(TableName)] - [PrimaryKey("pk")] - [ExplicitColumns] - private class ContentTypeDto - { - public const string TableName = Constants.DatabaseSchema.Tables.ContentType; + - [Column("pk")] - [PrimaryKeyColumn(IdentitySeed = 535)] - public int PrimaryKey { get; set; } - - [Column("nodeId")] - [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsContentType")] - public int NodeId { get; set; } - - [Column("alias")] - [NullSetting(NullSetting = NullSettings.Null)] - public string Alias { get; set; } - - [Column("icon")] - [Index(IndexTypes.NonClustered)] - [NullSetting(NullSetting = NullSettings.Null)] - public string Icon { get; set; } - - [Column("thumbnail")] - [Constraint(Default = "folder.png")] - public string Thumbnail { get; set; } - - [Column("description")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(1500)] - public string Description { get; set; } - - [Column("isContainer")] - [Constraint(Default = "0")] - public bool IsContainer { get; set; } - - [Column("allowAtRoot")] - [Constraint(Default = "0")] - public bool AllowAtRoot { get; set; } - - [Column("variations")] - [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] - public byte Variations { get; set; } - - [ResultColumn] - public NodeDto NodeDto { get; set; } - } + } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs index 343557e3f5..b82d1eab8b 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs @@ -2,8 +2,7 @@ using System.Linq; using System.Runtime.Serialization; using Newtonsoft.Json; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -34,11 +33,11 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 } var sqlPropertyTpes = Sql() - .Select() - .From() - .Where(x => dataTypeIds.Contains(x.DataTypeId)); + .Select() + .From() + .Where(x => dataTypeIds.Contains(x.DataTypeId)); - var propertyTypeIds = Database.Fetch(sqlPropertyTpes).Select(x => x.Id).ToList(); + var propertyTypeIds = Database.Fetch(sqlPropertyTpes).Select(x => x.Id).ToList(); if (propertyTypeIds.Count == 0) return; diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs index b60923fcba..2b27bdafe8 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { @@ -17,14 +18,24 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 AddColumn("id", out var sqls); - // SQLCE does not support UPDATE...FROM - var temp2 = Database.Fetch($@"SELECT v.versionId, v.id + if (Database.DatabaseType.IsSqlCe()) + { + // SQLCE does not support UPDATE...FROM + var versions = Database.Fetch($@"SELECT v.versionId, v.id FROM cmsContentVersion v JOIN umbracoNode n on v.contentId=n.id WHERE n.nodeObjectType='{Constants.ObjectTypes.Media}'"); - foreach (var t in temp2) - Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id={t.id} WHERE versionId='{t.versionId}'").Do(); - + foreach (var t in versions) + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id={t.id} WHERE versionId='{t.versionId}'").Do(); + } + else + { + Database.Execute($@"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id=v.id +FROM {Constants.DatabaseSchema.Tables.MediaVersion} m +JOIN cmsContentVersion v on m.versionId = v.versionId +JOIN umbracoNode n on v.contentId=n.id +WHERE n.nodeObjectType='{Constants.ObjectTypes.Media}'"); + } foreach (var sql in sqls) Execute.Sql(sql).Do(); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs index 8c60d30680..adc451ef6a 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Umbraco.Core.Migrations.Install; @@ -19,6 +20,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { MigratePropertyData(); + CreatePropertyDataIndexes(); MigrateContentAndPropertyTypes(); MigrateContent(); MigrateVersions(); @@ -74,10 +76,20 @@ HAVING COUNT(v2.id) <> 1").Any()) { Alter.Table(PreTables.PropertyData).AddColumn("versionId2").AsInt32().Nullable().Do(); - // SQLCE does not support UPDATE...FROM - var temp = Database.Fetch($"SELECT id, versionId FROM {PreTables.ContentVersion}"); - foreach (var t in temp) - Database.Execute($"UPDATE {PreTables.PropertyData} SET versionId2=@v2 WHERE versionId=@v1", new { v1 = t.versionId, v2 = t.id }); + if (Database.DatabaseType.IsSqlCe()) + { + // SQLCE does not support UPDATE...FROM + var versions = Database.Fetch($"SELECT id, versionId FROM {PreTables.ContentVersion}"); + foreach (var t in versions) + Database.Execute($"UPDATE {PreTables.PropertyData} SET versionId2=@v2 WHERE versionId=@v1", new { v1 = t.versionId, v2 = t.id }); + } + else + { + Database.Execute($@"UPDATE {PreTables.PropertyData} SET versionId2={PreTables.ContentVersion}.id +FROM {PreTables.ContentVersion} +INNER JOIN {PreTables.PropertyData} ON {PreTables.ContentVersion}.versionId = {PreTables.PropertyData}.versionId"); + } + Delete.Column("versionId").FromTable(PreTables.PropertyData).Do(); ReplaceColumn(PreTables.PropertyData, "versionId2", "versionId"); } @@ -90,6 +102,22 @@ HAVING COUNT(v2.id) <> 1").Any()) Rename.Table(PreTables.PropertyData).To(Constants.DatabaseSchema.Tables.PropertyData).Do(); } + private void CreatePropertyDataIndexes() + { + // Creates a temporary index on umbracoPropertyData to speed up other migrations which update property values. + // It will be removed in CreateKeysAndIndexes before the normal indexes for the table are created + var tableDefinition = Persistence.DatabaseModelDefinitions.DefinitionFactory.GetTableDefinition(typeof(PropertyDataDto), SqlSyntax); + Execute.Sql(SqlSyntax.FormatPrimaryKey(tableDefinition)).Do(); + Create.Index("IX_umbracoPropertyData_Temp").OnTable(PropertyDataDto.TableName) + .WithOptions().Unique() + .WithOptions().NonClustered() + .OnColumn("versionId").Ascending() + .OnColumn("propertyTypeId").Ascending() + .OnColumn("languageId").Ascending() + .OnColumn("segment").Ascending() + .Do(); + } + private void MigrateContentAndPropertyTypes() { if (!ColumnExists(PreTables.ContentType, "variations")) @@ -153,22 +181,40 @@ HAVING COUNT(v2.id) <> 1").Any()) ReplaceColumn(PreTables.ContentVersion, "ContentId", "nodeId"); // populate contentVersion text, current and userId columns for documents - // SQLCE does not support UPDATE...FROM - var temp1 = Database.Fetch($"SELECT versionId, text, newest, documentUser FROM {PreTables.Document}"); - foreach (var t in temp1) - Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=@current, userId=@userId WHERE versionId=@versionId", - new { text = t.text, current = t.newest, userId=t.documentUser, versionId=t.versionId }); + if (Database.DatabaseType.IsSqlCe()) + { + // SQLCE does not support UPDATE...FROM + var documents = Database.Fetch($"SELECT versionId, text, published, newest, documentUser FROM {PreTables.Document}"); + foreach (var t in documents) + Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=@current, userId=@userId WHERE versionId=@versionId", + new { text = t.text, current = t.newest && !t.published, userId = t.documentUser, versionId = t.versionId }); + } + else + { + Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=d.text, {SqlSyntax.GetQuotedColumnName("current")}=(d.newest & ~d.published), userId=d.documentUser +FROM {PreTables.ContentVersion} v INNER JOIN {PreTables.Document} d ON d.versionId = v.versionId"); + } // populate contentVersion text and current columns for non-documents, userId is default - // SQLCE does not support UPDATE...FROM - var temp2 = Database.Fetch($@"SELECT cver.versionId, n.text + if (Database.DatabaseType.IsSqlCe()) + { + // SQLCE does not support UPDATE...FROM + var otherContent = Database.Fetch($@"SELECT cver.versionId, n.text FROM {PreTables.ContentVersion} cver JOIN {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.Node)} n ON cver.nodeId=n.id WHERE cver.versionId NOT IN (SELECT versionId FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)})"); - foreach (var t in temp2) - Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=1, userId=0 WHERE versionId=@versionId", - new { text = t.text, versionId=t.versionId }); + foreach (var t in otherContent) + Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=1, userId=0 WHERE versionId=@versionId", + new { text = t.text, versionId = t.versionId }); + } + else + { + Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=n.text, {SqlSyntax.GetQuotedColumnName("current")}=1, userId=0 +FROM {PreTables.ContentVersion} cver +JOIN {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.Node)} n ON cver.nodeId=n.id +WHERE cver.versionId NOT IN (SELECT versionId FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)})"); + } // create table Create.Table(withoutKeysAndIndexes: true).Do(); @@ -179,36 +225,42 @@ SELECT cver.id, doc.templateId, doc.published FROM {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver JOIN {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId"); + // need to add extra rows for where published=newest // 'cos INSERT above has inserted the 'published' document version // and v8 always has a 'edited' document version too - var temp3 = Database.Fetch($@"SELECT doc.nodeId, doc.updateDate, doc.documentUser, doc.text, doc.templateId, cver.id versionId + Database.Execute($@" +INSERT INTO {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} (nodeId, versionId, versionDate, userId, {SqlSyntax.GetQuotedColumnName("current")}, text) +SELECT doc.nodeId, NEWID(), doc.updateDate, doc.documentUser, 1, doc.text FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId WHERE doc.newest=1 AND doc.published=1"); - var getIdentity = "@@@@IDENTITY"; - foreach (var t in temp3) - { - Database.Execute($@"INSERT INTO {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} (nodeId, versionId, versionDate, userId, {SqlSyntax.GetQuotedColumnName("current")}, text) -VALUES (@nodeId, @versionId, @versionDate, @userId, 1, @text)", new { nodeId=t.nodeId, versionId=Guid.NewGuid(), versionDate=t.updateDate, userId=t.documentUser, text=t.text }); - var id = Database.ExecuteScalar("SELECT " + getIdentity); - Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} SET {SqlSyntax.GetQuotedColumnName("current")}=0 WHERE nodeId=@0 AND id<>@1", (int) t.nodeId, id); - Database.Execute($@"INSERT INTO {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DocumentVersion)} (id, templateId, published) -VALUES (@id, @templateId, 0)", new { id=id, templateId=t.templateId }); - var versionId = (int) t.versionId; - var pdatas = Database.Fetch(Sql().Select().From().Where(x => x.VersionId == versionId)); - foreach (var pdata in pdatas) - { - pdata.VersionId = id; - Database.Insert(pdata); - } - } + Database.Execute($@" +INSERT INTO {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DocumentVersion)} (id, templateId, published) +SELECT cverNew.id, doc.templateId, 0 +FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc +JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cverNew ON doc.nodeId = cverNew.nodeId +WHERE doc.newest=1 AND doc.published=1 AND cverNew.{SqlSyntax.GetQuotedColumnName("current")} = 1"); + + Database.Execute($@" +INSERT INTO {SqlSyntax.GetQuotedTableName(PropertyDataDto.TableName)} (propertytypeid,languageId,segment,textValue,varcharValue,decimalValue,intValue,dateValue,versionId) +SELECT propertytypeid,languageId,segment,textValue,varcharValue,decimalValue,intValue,dateValue,cverNew.id +FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc +JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId +JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cverNew ON doc.nodeId = cverNew.nodeId +JOIN {SqlSyntax.GetQuotedTableName(PropertyDataDto.TableName)} pd ON pd.versionId=cver.id +WHERE doc.newest=1 AND doc.published=1 AND cverNew.{SqlSyntax.GetQuotedColumnName("current")} = 1"); + // reduce document to 1 row per content Database.Execute($@"DELETE FROM {PreTables.Document} WHERE versionId NOT IN (SELECT (versionId) FROM {PreTables.ContentVersion} WHERE {SqlSyntax.GetQuotedColumnName("current")} = 1) AND (published<>1 OR newest<>1)"); + // ensure that documents with a published version are marked as published + Database.Execute($@"UPDATE {PreTables.Document} SET published=1 WHERE nodeId IN ( +SELECT nodeId FROM {PreTables.ContentVersion} cv INNER JOIN {Constants.DatabaseSchema.Tables.DocumentVersion} dv ON dv.id = cv.id WHERE dv.published=1)"); + // drop some document columns Delete.Column("text").FromTable(PreTables.Document).Do(); Delete.Column("templateId").FromTable(PreTables.Document).Do(); @@ -223,7 +275,7 @@ WHERE versionId NOT IN (SELECT (versionId) FROM {PreTables.ContentVersion} WHERE if (!ColumnExists(PreTables.Document, "edited")) { AddColumn(PreTables.Document, "edited", out var sqls); - Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=0"); + Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=~published"); foreach (var sql in sqls) Database.Execute(sql); } @@ -240,11 +292,15 @@ JOIN {Constants.DatabaseSchema.Tables.PropertyData} v1 ON cv1.id=v1.versionId JOIN {PreTables.ContentVersion} cv2 ON n.id=cv2.nodeId JOIN {Constants.DatabaseSchema.Tables.DocumentVersion} dv ON cv2.id=dv.id AND dv.published=1 JOIN {Constants.DatabaseSchema.Tables.PropertyData} v2 ON cv2.id=v2.versionId -WHERE v1.propertyTypeId=v2.propertyTypeId AND v1.languageId=v2.languageId AND v1.segment=v2.segment"); +WHERE v1.propertyTypeId=v2.propertyTypeId +AND (v1.languageId=v2.languageId OR (v1.languageId IS NULL AND v2.languageId IS NULL)) +AND (v1.segment=v2.segment OR (v1.segment IS NULL AND v2.segment IS NULL))"); + var updatedIds = new HashSet(); foreach (var t in temp) if (t.intValue1 != t.intValue2 || t.decimalValue1 != t.decimalValue2 || t.dateValue1 != t.dateValue2 || t.varcharValue1 != t.varcharValue2 || t.textValue1 != t.textValue2) - Database.Execute("UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=1 WHERE nodeId=@nodeIdd", new { t.id }); + if (updatedIds.Add((int)t.id)) + Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=1 WHERE nodeId=@nodeId", new { nodeId = t.id }); // drop more columns Delete.Column("versionId").FromTable(PreTables.ContentVersion).Do(); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs index b68aa23d78..052b72ca26 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Migrations.PostMigrations; +using Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Services; @@ -27,15 +28,15 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); var sqlPropertyData = Sql() - .Select(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto))) - .From() - .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) - .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) + .Select(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto))) + .From() + .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) + .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) .Where(x => x.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce || x.EditorAlias == Constants.PropertyEditors.Aliases.Grid); - var properties = Database.Fetch(sqlPropertyData); + var properties = Database.Fetch(sqlPropertyData); var exceptions = new List(); foreach (var property in properties) @@ -43,6 +44,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 var value = property.TextValue; if (string.IsNullOrWhiteSpace(value)) continue; + + bool propertyChanged = false; if (property.PropertyTypeDto.DataTypeDto.EditorAlias == Constants.PropertyEditors.Aliases.Grid) { try @@ -55,7 +58,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 var controlValue = control["value"]; if (controlValue?.Type == JTokenType.String) { - control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value()); + control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value(), out var controlChanged); + propertyChanged |= controlChanged; } } @@ -76,10 +80,11 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 } else { - property.TextValue = UpdateMediaUrls(mediaLinkPattern, value); + property.TextValue = UpdateMediaUrls(mediaLinkPattern, value, out propertyChanged); } - Database.Update(property); + if (propertyChanged) + Database.Update(property); } @@ -91,10 +96,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 Context.AddPostMigration(); } - private string UpdateMediaUrls(Regex mediaLinkPattern, string value) + private string UpdateMediaUrls(Regex mediaLinkPattern, string value, out bool changed) { - return mediaLinkPattern.Replace(value, match => + bool matched = false; + + var result = mediaLinkPattern.Replace(value, match => { + matched = true; + // match groups: // - 1 = from the beginning of the a tag until href attribute value begins // - 2 = the href attribute value excluding the querystring (if present) @@ -106,6 +115,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 ? match.Value : $"{match.Groups[1].Value}/{{localLink:{media.GetUdi()}}}{match.Groups[3].Value}"; }); + + changed = matched; + + return result; } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs index 75de01dd7f..c744409c2f 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs @@ -4,21 +4,30 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 { public class MissingContentVersionsIndexes : MigrationBase { + private const string IndexName = "IX_" + ContentVersionDto.TableName + "_NodeId"; + public MissingContentVersionsIndexes(IMigrationContext context) : base(context) { } public override void Migrate() { - Create - .Index("IX_" + ContentVersionDto.TableName + "_NodeId") - .OnTable(ContentVersionDto.TableName) - .OnColumn("nodeId") - .Ascending() - .OnColumn("current") - .Ascending() - .WithOptions().NonClustered() - .Do(); + // We must check before we create an index because if we are upgrading from v7 we force re-create all + // indexes in the whole DB and then this would throw + + if (!IndexExists(IndexName)) + { + Create + .Index(IndexName) + .OnTable(ContentVersionDto.TableName) + .OnColumn("nodeId") + .Ascending() + .OnColumn("current") + .Ascending() + .WithOptions().NonClustered() + .Do(); + } + } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/DictionaryDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/DictionaryDto.cs index 655474b217..d357e9adbc 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/DictionaryDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/DictionaryDto.cs @@ -5,11 +5,12 @@ using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Persistence.Dtos { - [TableName(Constants.DatabaseSchema.Tables.DictionaryEntry)] + [TableName(TableName)] [PrimaryKey("pk")] [ExplicitColumns] internal class DictionaryDto { + public const string TableName = Constants.DatabaseSchema.Tables.DictionaryEntry; [Column("pk")] [PrimaryKeyColumn] public int PrimaryKey { get; set; } @@ -21,6 +22,7 @@ namespace Umbraco.Core.Persistence.Dtos [Column("parent")] [NullSetting(NullSetting = NullSettings.Null)] [ForeignKey(typeof(DictionaryDto), Column = "id")] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Parent")] public Guid? Parent { get; set; } [Column("key")] diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index ee9441befd..1889e4b032 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -15221,9 +15221,9 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "tinymce": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.2.tgz", - "integrity": "sha512-ZRoTGG4GAsOI73QPSNkabO7nkoYw9H6cglRB44W2mMkxSiqxYi8WJlgkUphk0fDqo6ZD6r3E+NSP4UHxF2lySg==" + "version": "4.9.7", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.7.tgz", + "integrity": "sha512-cj0HvUuniTuIjOAJdRt5BUfeQqM5yHjbA2NOub9HUHXlCrT9OwD9WBPU6tGlaPC2l2I4eGoOnT8llosZRdQU5Q==" }, "tmp": { "version": "0.0.33", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index beff2b45d1..508409b4ea 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -42,7 +42,7 @@ "npm": "6.13.6", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", - "tinymce": "4.9.2", + "tinymce": "4.9.7", "typeahead.js": "0.11.1", "underscore": "1.9.1" }, diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 78a2111fc5..0ed1b12000 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -130,6 +130,7 @@ view: "default", content: labels.doctypeChangeWarning, submitButtonLabelKey: "general_continue", + submitButtonStyle: "warning", closeButtonLabelKey: "general_cancel", submit: function () { openDocTypeEditor(documentType); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js index 5dc8903e65..6fec20b256 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js @@ -58,9 +58,9 @@ /** Called when the component has linked all elements, this is when the form controller is available */ function postLink() { //set the content to dirty if the header changes - unsubscribe.push($scope.$watch("contentHeaderForm.$dirty", - function(newValue, oldValue) { - if (newValue === true) { + unsubscribe.push($scope.$watch("vm.editor.content.name", + function (newValue, oldValue) { + if (newValue !== oldValue) { vm.editor.content.isDirty = true; } })); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index 6f195dcc52..3e2e7e362e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -14,7 +14,8 @@ function treeSearchBox(localizationService, searchService, $q) { section: "@", datatypeKey: "@", hideSearchCallback: "=", - searchCallback: "=" + searchCallback: "=", + autoFocus: "=" }, restrict: "E", // restrict to an element replace: true, // replace the html element with the template diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index cbb90cb22b..d2b91a3707 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -308,6 +308,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s var src = imgUrl + "?width=" + newSize.width + "&height=" + newSize.height; editor.dom.setAttrib(imageDomElement, 'data-mce-src', src); } + + editor.execCommand("mceAutoResize", false, null, null); } } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 112f94572d..c25941514e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -672,7 +672,8 @@ } .imagecropper .umb-sortable-thumbnails li .crop-name, - .imagecropper .umb-sortable-thumbnails li .crop-size { + .imagecropper .umb-sortable-thumbnails li .crop-size, + .imagecropper .umb-sortable-thumbnails li .crop-annotation { display: block; text-align: left; font-size: 13px; @@ -684,17 +685,28 @@ margin: 10px 0 5px; } - .imagecropper .umb-sortable-thumbnails li .crop-size { + .imagecropper .umb-sortable-thumbnails li .crop-size, + .imagecropper .umb-sortable-thumbnails li .crop-annotation { font-size: 10px; font-style: italic; margin: 0 0 5px; } + .imagecropper .umb-sortable-thumbnails li .crop-annotation { + color: @gray-6; + } + .btn-crop-delete { display: block; text-align: left; } + .imagecropper .cropList-container { + h5 { + margin-left: 10px; + margin-top: 0; + } + } // // folder-browser // -------------------------------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js index 471d23ae84..5bfee22c4e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js @@ -16,7 +16,7 @@ function IconPickerController($scope, iconHelper, localizationService) { vm.close = close; vm.colors = [ - { name: "Black", value: "color-black" }, + { name: "Black", value: "color-black", default: true }, { name: "Blue Grey", value: "color-blue-grey" }, { name: "Grey", value: "color-grey" }, { name: "Brown", value: "color-brown" }, @@ -49,7 +49,7 @@ function IconPickerController($scope, iconHelper, localizationService) { }); // set a default color if nothing is passed in - vm.color = $scope.model.color ? findColor($scope.model.color) : vm.colors[0]; + vm.color = $scope.model.color ? findColor($scope.model.color) : vm.colors.find(x => x.default); // if an icon is passed in - preselect it vm.icon = $scope.model.icon ? $scope.model.icon : undefined; @@ -71,12 +71,13 @@ function IconPickerController($scope, iconHelper, localizationService) { } function findColor(value) { - return _.findWhere(vm.colors, {value: value}); + return vm.colors.find(x => x.value === value); } - function selectColor(color, $index, $event) { - $scope.model.color = color.value; - vm.color = color; + function selectColor(color) { + let newColor = (color || vm.colors.find(x => x.default)); + $scope.model.color = newColor.value; + vm.color = newColor; } function close() { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 4419805194..c189131646 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Editors.MediaPickerController", - function ($scope, $timeout, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService) { + function ($scope, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService, umbSessionStorage) { var vm = this; @@ -37,6 +37,11 @@ angular.module("umbraco") $scope.cropSize = dialogOptions.cropSize; $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); $scope.lockedFolder = true; + $scope.allowMediaEdit = dialogOptions.allowMediaEdit ? dialogOptions.allowMediaEdit : false; + + $scope.filterOptions = { + excludeSubFolders: umbSessionStorage.get("mediaPickerExcludeSubFolders") || false + }; var userStartNodes = []; @@ -392,6 +397,7 @@ angular.module("umbraco") } function toggle() { + umbSessionStorage.set("mediaPickerExcludeSubFolders", $scope.filterOptions.excludeSubFolders); // Make sure to activate the changeSearch function everytime the toggle is clicked changeSearch(); } @@ -404,7 +410,7 @@ angular.module("umbraco") function searchMedia() { vm.loading = true; - entityResource.getPagedDescendants($scope.currentFolder.id, "Media", vm.searchOptions) + entityResource.getPagedDescendants($scope.filterOptions.excludeSubFolders ? $scope.currentFolder.id : $scope.startNodeId, "Media", vm.searchOptions) .then(function (data) { // update image data to work with image grid angular.forEach(data.items, function (mediaItem) { 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 82612d5e19..e1a89061b6 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 @@ -32,10 +32,10 @@
+ label-key="general_excludeFromSubFolders">
@@ -112,9 +112,8 @@ disable-folder-select={{disableFolderSelect}} only-images={{onlyImages}} only-folders={{onlyFolders}} - include-sub-folders={{showChilds}} - current-folder-id="{{currentFolder.id}}" - allow-open-folder="!disableFolderSelect"> + include-sub-folders={{!filterOptions.excludeSubFolders}} + current-folder-id="{{currentFolder.id}}"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 0faee7bf90..3ec6a3f3cb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -64,6 +64,8 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", var currentNode = $scope.model.currentNode; + var previouslyFocusedElement = null; + function initDialogTree() { vm.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler); // TODO: Also deal with unexpanding!! @@ -487,6 +489,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", } function openMiniListView(node) { + previouslyFocusedElement = document.activeElement; vm.miniListView = node; } @@ -650,6 +653,12 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", function closeMiniListView() { vm.miniListView = undefined; + if (previouslyFocusedElement) { + $timeout(function () { + previouslyFocusedElement.focus(); + previouslyFocusedElement = null; + }); + } } function listViewItemsLoaded(items) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html index ef042205aa..0c10d94136 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html @@ -37,7 +37,8 @@ search-from-name="{{vm.searchInfo.searchFromName}}" show-search="{{vm.searchInfo.showSearch}}" datatype-key="{{vm.searchInfo.dataTypeKey}}" - section="{{vm.section}}"> + section="{{vm.section}}" + auto-focus="true"> diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-box.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-box.html index 6bb2d120c8..217489f14c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-box.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-box.html @@ -5,9 +5,9 @@ ng-model="term" class="umb-search-field search-query -full-width-input" placeholder="{{searchPlaceholderText}}" - focus-when="{{showSearch}}"> + umb-auto-focus="{{autoFocus ? 'true' : 'false'}}">

Search {{searchFromName}}

- \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html index da1e5c3aa7..d66e7f7f90 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html @@ -43,6 +43,7 @@ ng-model="search" ng-change="searchMiniListView(search, miniListView)" prevent-enter-submit + umb-auto-focus no-dirty-check> diff --git a/src/Umbraco.Web.UI.Client/src/views/content/rights.html b/src/Umbraco.Web.UI.Client/src/views/content/rights.html index 6ceec1db51..3395468cb0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/rights.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/rights.html @@ -6,8 +6,8 @@ -
- + -
Set permissions for {{ currentNode.name }}
-

+
Set permissions for {{ currentNode.name }}
+

{{value.alias}} {{value.width}}px x {{value.height}}px + User defined 
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 78075d9b7a..ae8925e911 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -661,7 +661,7 @@ Ikon Id Importer - Inkludér undermapper i søgning + Søg kun i denne mappe Info Indre margen Indsæt @@ -1086,6 +1086,7 @@ Mange hilsner fra Umbraco robotten Tilføj ny beskæring Acceptér Fortryd + Brugerdefineret Vælg en version at sammenligne med den nuværende version diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 93a97414c9..a85df5714b 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -674,7 +674,7 @@ Icon Id Import - Include subfolders in search + Search only this folder Info Inner margin Insert @@ -1318,6 +1318,7 @@ To manage your website, simply open the Umbraco back office and start adding con Add new crop Done Undo edits + User defined Select a version to compare with the current version diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 91fad336c7..d14fb03727 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -676,7 +676,7 @@ Icon Id Import - Include subfolders in search + Search only this folder Info Inner margin Insert @@ -1316,11 +1316,12 @@ To manage your website, simply open the Umbraco back office and start adding con Enter the link - Reset crop + Reset crop Save crop Add new crop - Done - Undo edits + Done + Undo edits + User defined Select a version to compare with the current version diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 698c771db9..5814a82095 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -241,6 +241,14 @@ By default you can call any content Id in the url and show the content with that id, for example: http://mysite.com/1092 or http://mysite.com/1092.aspx would render the content with id 1092. Setting this setting to true stops that behavior + @disableRedirectUrlTracking + When the URL changes for content, redirects are automatically created for redirect handling within the + request pipeline. Setting this setting to true stops the automatic creation of redirects. Note that this + does not stop the request pipeline from handling any previously created redirects. + @urlProviderMode + By default Umbraco automatically figures out if internal URLs should be rendered as relative or absolute, + depending on the current request and the configured domains. By setting this setting to "Relative" or + "Absolute" you can force Umbraco to always render URLs as either relative or absolute. @umbracoApplicationUrl The url of the Umbraco application. By default, Umbraco will figure it out from the first request. Configure it here if you need anything specific. Needs to be a complete url with scheme and umbraco diff --git a/src/Umbraco.Web/Editors/TemplateController.cs b/src/Umbraco.Web/Editors/TemplateController.cs index 92c4b53227..2250b16485 100644 --- a/src/Umbraco.Web/Editors/TemplateController.cs +++ b/src/Umbraco.Web/Editors/TemplateController.cs @@ -197,7 +197,9 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - var template = Services.FileService.CreateTemplateWithIdentity(display.Name, display.Alias, display.Content, master); + // we need to pass the template name as alias to keep the template file casing consistent with templates created with content + // - see comment in FileService.CreateTemplateForContentType for additional details + var template = Services.FileService.CreateTemplateWithIdentity(display.Name, display.Name, display.Content, master); Mapper.Map(template, display); } diff --git a/src/Umbraco.Web/FormlessPage.cs b/src/Umbraco.Web/FormlessPage.cs deleted file mode 100644 index 9789479fb5..0000000000 --- a/src/Umbraco.Web/FormlessPage.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Web.UI; - -namespace Umbraco.Web -{ - /// - /// A formless page for use with the rendering a control in a page via Server.Execute. - /// This ignores the check to check for a form control on the page. - /// - /// - /// UmbracoHelper currently uses this for rendering macros but could be used anywhere we want when rendering - /// a page with Server.Execute. - /// SD: I have a custom MVC engine that uses this in my own internal libs if we want to pull it out which is called ViewManager - /// and works really well for things like this. - /// - internal class FormlessPage : Page - { - public override void VerifyRenderingInServerForm(Control control) { } - - } -} diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs index 582a930b9f..40249a4750 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs @@ -86,7 +86,7 @@ namespace Umbraco.Web.Models.Mapping target.Icon = source.ContentType.Icon; target.Id = source.Id; target.IsBlueprint = source.Blueprint; - target.IsChildOfListView = DermineIsChildOfListView(source); + target.IsChildOfListView = DetermineIsChildOfListView(source); target.IsContainer = source.ContentType.IsContainer; target.IsElement = source.ContentType.IsElement; target.Key = source.Key; @@ -217,7 +217,7 @@ namespace Umbraco.Web.Models.Mapping return source.CultureInfos.TryGetValue(culture, out var name) && !name.Name.IsNullOrWhiteSpace() ? name.Name : $"({source.Name})"; } - private bool DermineIsChildOfListView(IContent source) + private bool DetermineIsChildOfListView(IContent source) { // map the IsChildOfListView (this is actually if it is a descendant of a list view!) var parent = _contentService.GetParent(source); diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs index bdd748883a..3298f3bd1e 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs @@ -62,7 +62,7 @@ namespace Umbraco.Web.Models.Mapping target.CreateDate = source.CreateDate; target.Icon = source.ContentType.Icon; target.Id = source.Id; - target.IsChildOfListView = DermineIsChildOfListView(source); + target.IsChildOfListView = DetermineIsChildOfListView(source); target.Key = source.Key; target.MediaLink = string.Join(",", source.GetUrls(_umbracoSettingsSection.Content, _logger, _propertyEditorCollection)); target.Name = source.Name; @@ -101,7 +101,7 @@ namespace Umbraco.Web.Models.Mapping target.VariesByCulture = source.ContentType.VariesByCulture(); } - private bool DermineIsChildOfListView(IMedia source) + private bool DetermineIsChildOfListView(IMedia source) { // map the IsChildOfListView (this is actually if it is a descendant of a list view!) var parent = _mediaService.GetParent(source); diff --git a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs index 1440abc13c..fe5b061d15 100644 --- a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs @@ -114,6 +114,19 @@ namespace Umbraco.Web.Security return SignInStatus.LockedOut; } + // We need to verify that the user belongs to one or more groups that define content and media start nodes. + // To do so we have to create the user claims identity and validate the calculated start nodes. + var userIdentity = await CreateUserIdentityAsync(user); + if (userIdentity is UmbracoBackOfficeIdentity backOfficeIdentity) + { + if (backOfficeIdentity.StartContentNodes.Length == 0 || backOfficeIdentity.StartMediaNodes.Length == 0) + { + _logger.WriteCore(TraceEventType.Information, 0, + $"Login attempt failed for username {userName} from IP address {_request.RemoteIpAddress}, no content and/or media start nodes could be found for any of the user's groups", null, null); + return SignInStatus.Failure; + } + } + await UserManager.ResetAccessFailedCountAsync(user.Id); return await SignInOrTwoFactor(user, isPersistent); } diff --git a/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs index 49248bb564..74333cf533 100644 --- a/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs +++ b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs @@ -13,14 +13,10 @@ namespace Umbraco.Web.Templates this._getMediaUrl = getMediaUrl; } + private readonly IUmbracoContextAccessor _umbracoContextAccessor; public HtmlImageSourceParser(IUmbracoContextAccessor umbracoContextAccessor) { - if (umbracoContextAccessor?.UmbracoContext?.UrlProvider == null) - { - return; - } - - _getMediaUrl = (guid) => umbracoContextAccessor.UmbracoContext.UrlProvider.GetMediaUrl(guid); + _umbracoContextAccessor = umbracoContextAccessor; } private static readonly Regex ResolveImgPattern = new Regex(@"(]*src="")([^""\?]*)((?:\?[^""]*)?""[^>]*data-udi="")([^""]*)(""[^>]*>)", @@ -29,7 +25,7 @@ namespace Umbraco.Web.Templates private static readonly Regex DataUdiAttributeRegex = new Regex(@"data-udi=\\?(?:""|')(?umb://[A-z0-9\-]+/[A-z0-9]+)\\?(?:""|')", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - private readonly Func _getMediaUrl; + private Func _getMediaUrl; /// /// Parses out media UDIs from an html string based on 'data-udi' html attributes @@ -57,12 +53,8 @@ namespace Umbraco.Web.Templates /// Umbraco image tags are identified by their data-udi attributes public string EnsureImageSources(string text) { - // no point in doing any processing if we don't have - // a function to retrieve Urls - if (_getMediaUrl == null) - { - return text; - } + if(_getMediaUrl == null) + _getMediaUrl = (guid) => _umbracoContextAccessor.UmbracoContext.UrlProvider.GetMediaUrl(guid); return ResolveImgPattern.Replace(text, match => { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 9d76d6bd39..1437104b69 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -644,9 +644,6 @@ - - ASPXCodeBehind -