From 8bfb8e2b727bce2c8da75a1a593c958930a73de3 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 21 Mar 2018 14:40:59 +0100 Subject: [PATCH] Port v7@2aa0dfb2c5 - WIP --- .../Migrations/Install/DatabaseDataCreator.cs | 4 +- .../Install/DatabaseSchemaCreator.cs | 131 ++++++++---------- .../Install/DatabaseSchemaResult.cs | 12 ++ .../Migrations/MigrationBase_Extra.cs | 8 ++ .../Migrations/Upgrade/UmbracoPlan.cs | 84 ++++++----- .../AddIndexToPropertyTypeAliasColumn.cs | 27 ++++ .../V_7_8_0/AddInstructionCountColumn.cs | 20 +++ .../Upgrade/V_7_8_0/AddMediaVersionTable.cs | 65 +++++++++ .../Upgrade/V_7_8_0/AddTourDataUserColumn.cs | 21 +++ .../Upgrade/V_7_8_0/AddUserLoginTable.cs | 22 +++ .../V_7_9_0/AddIsSensitiveMemberTypeColumn.cs | 20 +++ .../Upgrade/V_7_9_0/AddUmbracoAuditTable.cs | 22 +++ .../Upgrade/V_7_9_0/AddUmbracoConsentTable.cs | 22 +++ .../V_7_9_0/CreateSensitiveDataUserGroup.cs | 27 ++++ .../Upgrade/V_8_0_0/DataTypeMigration.cs | 2 +- .../Upgrade/V_8_0_0/VariantsMigration.cs | 2 +- src/Umbraco.Core/Models/AuditEntry.cs | 94 +++++++++++++ src/Umbraco.Core/Models/AuditItem.cs | 19 ++- src/Umbraco.Core/Models/Consent.cs | 102 ++++++++++++++ src/Umbraco.Core/Models/ConsentExtensions.cs | 18 +++ src/Umbraco.Core/Models/ConsentState.cs | 38 +++++ src/Umbraco.Core/Models/ContentBase.cs | 43 ++++-- src/Umbraco.Core/Models/ContentType.cs | 8 +- src/Umbraco.Core/Models/IAuditEntry.cs | 60 ++++++++ src/Umbraco.Core/Models/IAuditItem.cs | 12 ++ src/Umbraco.Core/Models/IConsent.cs | 55 ++++++++ src/Umbraco.Core/Models/IMemberType.cs | 14 ++ src/Umbraco.Core/Models/Member.cs | 14 +- src/Umbraco.Core/Models/MemberType.cs | 45 ++++-- .../Models/MemberTypePropertyProfileAccess.cs | 4 +- src/Umbraco.Core/Models/UmbracoObjectTypes.cs | 4 +- src/Umbraco.Core/Models/UserExtensions.cs | 13 +- .../Persistence/Constants-DatabaseSchema.cs | 1 + src/Umbraco.Core/Persistence/Dtos/MediaDto.cs | 21 +++ .../Persistence/Dtos/MediaVersionDto.cs | 25 ++++ .../Factories/AuditEntryFactory.cs | 52 +++++++ .../Persistence/Factories/ConsentFactory.cs | 65 +++++++++ .../Factories/ContentBaseFactory.cs | 48 ++++++- .../Factories/ContentTypeFactory.cs | 3 +- .../Factories/MemberTypeReadOnlyFactory.cs | 10 +- .../Persistence/Factories/UserFactory.cs | 4 +- .../Persistence/Mappers/AuditEntryMapper.cs | 40 ++++++ .../Persistence/Mappers/AuditMapper.cs | 32 +++++ .../Persistence/Mappers/ConsentMapper.cs | 39 ++++++ src/Umbraco.Core/Umbraco.Core.csproj | 23 +++ .../Migrations/AdvancedMigrationTests.cs | 4 +- 46 files changed, 1240 insertions(+), 159 deletions(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs create mode 100644 src/Umbraco.Core/Models/AuditEntry.cs create mode 100644 src/Umbraco.Core/Models/Consent.cs create mode 100644 src/Umbraco.Core/Models/ConsentExtensions.cs create mode 100644 src/Umbraco.Core/Models/ConsentState.cs create mode 100644 src/Umbraco.Core/Models/IAuditEntry.cs create mode 100644 src/Umbraco.Core/Models/IAuditItem.cs create mode 100644 src/Umbraco.Core/Models/IConsent.cs create mode 100644 src/Umbraco.Core/Persistence/Dtos/MediaDto.cs create mode 100644 src/Umbraco.Core/Persistence/Dtos/MediaVersionDto.cs create mode 100644 src/Umbraco.Core/Persistence/Factories/AuditEntryFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Factories/ConsentFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs create mode 100644 src/Umbraco.Core/Persistence/Mappers/AuditMapper.cs create mode 100644 src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 1371486ce5..f12533a5de 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -153,11 +153,13 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); } private void CreateUser2UserGroupData() { - _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = Constants.Security.SuperId }); + _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = Constants.Security.SuperId }); // add super to admins + _database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = Constants.Security.SuperId }); // add super to sensitive data } private void CreateUserGroup2AppData() diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index dfd173681b..cc9c0f3893 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -28,71 +28,60 @@ namespace Umbraco.Core.Migrations.Install private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax; // all tables, in order - public static readonly Dictionary OrderedTables = new Dictionary + public static readonly List OrderedTables = new List { - {0, typeof (NodeDto)}, - {1, typeof (ContentTypeDto)}, - {2, typeof (TemplateDto)}, - {3, typeof (ContentDto)}, - {4, typeof (ContentVersionDto)}, - {5, typeof (DocumentDto)}, - {6, typeof (ContentTypeTemplateDto)}, - {7, typeof (DataTypeDto)}, - //removed: {8, typeof (DataTypePreValueDto)}, - {9, typeof (DictionaryDto)}, - - {10, typeof (LanguageDto)}, - {11, typeof (LanguageTextDto)}, - {12, typeof (DomainDto)}, - {13, typeof (LogDto)}, - {14, typeof (MacroDto)}, - {15, typeof (MacroPropertyDto)}, - {16, typeof (MemberTypeDto)}, - {17, typeof (MemberDto)}, - {18, typeof (Member2MemberGroupDto)}, - {19, typeof (ContentXmlDto)}, - - {20, typeof (PreviewXmlDto)}, - {21, typeof (PropertyTypeGroupDto)}, - {22, typeof (PropertyTypeDto)}, - {23, typeof (PropertyDataDto)}, - {24, typeof (RelationTypeDto)}, - {25, typeof (RelationDto)}, - //removed: {26... - //removed: {27... - {28, typeof (TagDto)}, - {29, typeof (TagRelationshipDto)}, - - //removed: {30... - //removed in 7.6: {31, typeof (UserTypeDto)}, - {32, typeof (UserDto)}, - {33, typeof (TaskTypeDto)}, - {34, typeof (TaskDto)}, - {35, typeof (ContentType2ContentTypeDto)}, - {36, typeof (ContentTypeAllowedContentTypeDto)}, - //removed in 7.6: {37, typeof (User2AppDto)}, - {38, typeof (User2NodeNotifyDto)}, - //removed in 7.6: {39, typeof (User2NodePermissionDto)}, - - {40, typeof (ServerRegistrationDto)}, - {41, typeof (AccessDto)}, - {42, typeof (AccessRuleDto)}, - {43, typeof (CacheInstructionDto)}, - {44, typeof (ExternalLoginDto)}, - //removed: {45, typeof (MigrationDto)}, - //removed: {46, typeof (UmbracoDeployChecksumDto)}, - //removed: {47, typeof (UmbracoDeployDependencyDto)}, - {48, typeof (RedirectUrlDto) }, - {49, typeof (LockDto) }, - - {50, typeof (UserGroupDto) }, - {51, typeof (User2UserGroupDto) }, - {52, typeof (UserGroup2NodePermissionDto) }, - {53, typeof (UserGroup2AppDto) }, - {54, typeof (UserStartNodeDto) }, - {55, typeof (ContentNuDto) }, - {56, typeof (DocumentVersionDto) }, - {57, typeof (KeyValueDto) } + typeof (NodeDto), + typeof (ContentTypeDto), + typeof (TemplateDto), + typeof (ContentDto), + typeof (ContentVersionDto), + typeof (MediaVersionDto), + typeof (DocumentDto), + typeof (ContentTypeTemplateDto), + typeof (DataTypeDto), + typeof (DictionaryDto), + typeof (LanguageDto), + typeof (LanguageTextDto), + typeof (DomainDto), + typeof (LogDto), + typeof (MacroDto), + typeof (MacroPropertyDto), + typeof (MemberTypeDto), + typeof (MemberDto), + typeof (Member2MemberGroupDto), + typeof (ContentXmlDto), + typeof (PreviewXmlDto), + typeof (PropertyTypeGroupDto), + typeof (PropertyTypeDto), + typeof (PropertyDataDto), + typeof (RelationTypeDto), + typeof (RelationDto), + typeof (TagDto), + typeof (TagRelationshipDto), + typeof (UserDto), + typeof (TaskTypeDto), + typeof (TaskDto), + typeof (ContentType2ContentTypeDto), + typeof (ContentTypeAllowedContentTypeDto), + typeof (User2NodeNotifyDto), + typeof (ServerRegistrationDto), + typeof (AccessDto), + typeof (AccessRuleDto), + typeof (CacheInstructionDto), + typeof (ExternalLoginDto), + typeof (RedirectUrlDto), + typeof (LockDto), + typeof (UserGroupDto), + typeof (User2UserGroupDto), + typeof (UserGroup2NodePermissionDto), + typeof (UserGroup2AppDto), + typeof (UserStartNodeDto), + typeof (ContentNuDto), + typeof (DocumentVersionDto), + typeof (KeyValueDto), + typeof (UserLoginDto), + typeof (ConsentDto), + typeof (AuditEntryDto) }; /// @@ -102,11 +91,10 @@ namespace Umbraco.Core.Migrations.Install { _logger.Info("Start UninstallDatabaseSchema"); - foreach (var item in OrderedTables.OrderByDescending(x => x.Key)) + foreach (var table in OrderedTables.AsEnumerable().Reverse()) { - var tableNameAttribute = item.Value.FirstAttribute(); - - var tableName = tableNameAttribute == null ? item.Value.Name : tableNameAttribute.Value; + var tableNameAttribute = table.FirstAttribute(); + var tableName = tableNameAttribute == null ? table.Name : tableNameAttribute.Value; _logger.Info("Uninstall" + tableName); @@ -135,8 +123,8 @@ namespace Umbraco.Core.Migrations.Install if (e.Cancel == false) { var dataCreation = new DatabaseDataCreator(_database, _logger); - foreach (var item in OrderedTables.OrderBy(x => x.Key)) - CreateTable(false, item.Value, dataCreation); + foreach (var table in OrderedTables) + CreateTable(false, table, dataCreation); } FireAfterCreation(e); @@ -160,8 +148,7 @@ namespace Umbraco.Core.Migrations.Install }).ToArray(); result.TableDefinitions.AddRange(OrderedTables - .OrderBy(x => x.Key) - .Select(x => DefinitionFactory.GetTableDefinition(x.Value, SqlSyntax))); + .Select(x => DefinitionFactory.GetTableDefinition(x, SqlSyntax))); ValidateDbTables(result); ValidateDbColumns(result); diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs index 41184c4471..0ec27cf0b1 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs @@ -136,6 +136,18 @@ namespace Umbraco.Core.Migrations.Install return new Version(7, 6, 0); } + //if the error is for cmsMedia it must be the previous version to 7.8 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoMedia")))) + { + return new Version(7, 7, 0); + } + + //if the error is for isSensitive column it must be the previous version to 7.9 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMemberType,isSensitive")))) + { + return new Version(7, 8, 0); + } + return UmbracoVersion.Current; } diff --git a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs index 91342de867..0f871abb5d 100644 --- a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs +++ b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs @@ -11,6 +11,14 @@ namespace Umbraco.Core.Migrations { // provides extra methods for migrations + protected void AddColumn(string columnName) + { + var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + var column = table.Columns.First(x => x.Name == columnName); + var createSql = SqlSyntax.Format(column); + Database.Execute(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(table.Name), createSql)); + } + protected void AddColumn(string tableName, string columnName) { //if (ColumnExists(tableName, columnName)) diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 16ec3d0a30..3f676c78bf 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -7,6 +7,8 @@ using Umbraco.Core.Migrations.Upgrade.V_7_5_0; using Umbraco.Core.Migrations.Upgrade.V_7_5_5; using Umbraco.Core.Migrations.Upgrade.V_7_6_0; using Umbraco.Core.Migrations.Upgrade.V_7_7_0; +using Umbraco.Core.Migrations.Upgrade.V_7_8_0; +using Umbraco.Core.Migrations.Upgrade.V_7_9_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; namespace Umbraco.Core.Migrations.Upgrade @@ -36,8 +38,8 @@ namespace Umbraco.Core.Migrations.Upgrade if (!SemVersion.TryParse(ConfigurationManager.AppSettings["umbracoConfigurationStatus"], out var currentVersion)) throw new InvalidOperationException("Could not get current version from web.config umbracoConfigurationStatus appSetting."); - // must be at least 7.8.0 - fixme adjust when releasing - if (currentVersion < new SemVersion(7, 8)) + // must be at least 7.?.? - fixme adjust when releasing + if (currentVersion < new SemVersion(7, 999)) throw new InvalidOperationException($"Version {currentVersion} cannot be upgraded to {UmbracoVersion.SemanticVersion}."); // cannot go back in time @@ -60,12 +62,6 @@ namespace Umbraco.Core.Migrations.Upgrade private void DefinePlan() { - // INSTALL - // - // when installing, the source state is empty, and the target state should be the final state. - - Add(string.Empty, "{CA7DB949-3EF4-403D-8464-F9BA36A52E87}"); - // UPGRADE FROM 7 // // when 8.0.0 is released, on the first upgrade, the state is automatically @@ -74,45 +70,38 @@ namespace Umbraco.Core.Migrations.Upgrade // then, as more v7 and v8 versions are released, new chains needs to be defined to // support the upgrades (new v7 may backport some migrations and require their own // upgrade paths, etc). + // fixme adjust when releasing - From("{init-7.8.0}") - .Chain("{7C447271-CA3F-4A6A-A913-5D77015655CB}") // add more lock objects - .Chain("{CBFF58A2-7B50-4F75-8E98-249920DB0F37}") - .Chain("{3D18920C-E84D-405C-A06A-B7CEE52FE5DD}") - .Chain("{FB0A5429-587E-4BD0-8A67-20F0E7E62FF7}") - .Chain("{F0C42457-6A3B-4912-A7EA-F27ED85A2092}") - .Chain("{8640C9E4-A1C0-4C59-99BB-609B4E604981}") - .Chain("{DD1B99AF-8106-4E00-BAC7-A43003EA07F8}") - .Chain("{9DF05B77-11D1-475C-A00A-B656AF7E0908}") - .Chain("{CA7DB949-3EF4-403D-8464-F9BA36A52E87}");; + From("{init-7.8.0}"); + Chain("{7C447271-CA3F-4A6A-A913-5D77015655CB}"); // add more lock objects + Chain("{CBFF58A2-7B50-4F75-8E98-249920DB0F37}"); + Chain("{3D18920C-E84D-405C-A06A-B7CEE52FE5DD}"); + Chain("{FB0A5429-587E-4BD0-8A67-20F0E7E62FF7}"); + Chain("{F0C42457-6A3B-4912-A7EA-F27ED85A2092}"); + Chain("{8640C9E4-A1C0-4C59-99BB-609B4E604981}"); + Chain("{DD1B99AF-8106-4E00-BAC7-A43003EA07F8}"); + Chain("{9DF05B77-11D1-475C-A00A-B656AF7E0908}"); + Chain("{CA7DB949-3EF4-403D-8464-F9BA36A52E87}"); // 7.8.1 = same as 7.8.0 - From("{init-7.8.1}") - .Chain("{init-7.8.0}"); + From("{init-7.8.1}"); + Chain("{init-7.8.0}"); // 7.9.0 = requires its own chain - From("{init-7.9.0}") - // chain... - .Chain("{82C4BA1D-7720-46B1-BBD7-07F3F73800E6}"); + From("{init-7.9.0}"); + // chain... + Chain("{82C4BA1D-7720-46B1-BBD7-07F3F73800E6}"); + // UPGRADE 8 // // starting from the original 8.0.0 final state, chain migrations to upgrade version 8, // defining new final states as more migrations are added to the chain. - - //From("") - // .Chain("") - // .Chain(""); - - // WIP 8 // // before v8 is released, some sites may exist, and these "pre-8" versions require their - // own upgrade plan. in other words, this is the plan for sites that were on v8 before + // own upgrade plan. in other words, this is also the plan for sites that were on v8 before // v8 was released - // fixme - this is essentially for ZpqrtBnk website - // need to determine which version it is and where it should resume running migrations - // 8.0.0 From("{init-origin}"); Chain("{98347B5E-65BF-4DD7-BB43-A09CB7AF4FCA}"); @@ -129,8 +118,6 @@ namespace Umbraco.Core.Migrations.Upgrade Chain("{44484C32-EEB3-4A12-B1CB-11E02CE22AB2}"); // 7.6.0 - //Chain("{858B4039-070C-4928-BBEC-DDE8303352DA}"); - //Chain("{64F587C1-0B28-4D78-B4CC-26B7D87F69C1}"); Chain("{3586E4E9-2922-49EB-8E2A-A530CE6DBDE0}"); Chain("{D4A5674F-654D-4CC7-85E5-CFDBC533A318}"); Chain("{7F828EDD-6622-4A8D-AD80-EEAF46C11680}"); @@ -156,6 +143,33 @@ namespace Umbraco.Core.Migrations.Upgrade Chain("{139F26D7-7E08-48E3-81D9-E50A21A72F67}"); Chain("{CC1B1201-1328-443C-954A-E0BBB8CCC1B5}"); Chain("{CA7DB949-3EF4-403D-8464-F9BA36A52E87}"); + + // at this point of the chain, people started to work on v8, so whenever we + // merge stuff from v7, we have to chain the migrations here so they also + // run for v8. + + // mergin from 7.8.0 + Chain("{FDCB727A-EFB6-49F3-89E4-A346503AB849}"); + Chain("{2A796A08-4FE4-4783-A1A5-B8A6C8AA4A92}"); + Chain("{1A46A98B-2AAB-4C8E-870F-A2D55A97FD1F}"); + Chain("{0AE053F6-2683-4234-87B2-E963F8CE9498}"); + Chain("{D454541C-15C5-41CF-8109-937F26A78E71}"); + + // merging from 7.9.0 + Chain("{89A728D1-FF4C-4155-A269-62CC09AD2131}"); + Chain("{FD8631BC-0388-425C-A451-5F58574F6F05}"); + Chain("{2821F53E-C58B-4812-B184-9CD240F990D7}"); + Chain("{8918450B-3DA0-4BB7-886A-6FA8B7E4186E}"); // final state + + // BEWARE! whenever changing the final state, update below! + + + // INSTALL + // + // when installing, the source state is empty, and the target state should be the final state. + // BEWARE! this MUST match the final state above! + + Add(string.Empty, "{8918450B-3DA0-4BB7-886A-6FA8B7E4186E}"); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs new file mode 100644 index 0000000000..ddb084a609 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs @@ -0,0 +1,27 @@ +using System.Linq; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 +{ + internal class AddIndexToPropertyTypeAliasColumn : MigrationBase + { + public AddIndexToPropertyTypeAliasColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsPropertyTypeAlias")) == false) + { + //we can apply the index + Create.Index("IX_cmsPropertyTypeAlias").OnTable(Constants.DatabaseSchema.Tables.PropertyType) + .OnColumn("Alias") + .Ascending().WithOptions().NonClustered() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs new file mode 100644 index 0000000000..0ce2c91f2e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs @@ -0,0 +1,20 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 +{ + internal class AddInstructionCountColumn : MigrationBase + { + public AddInstructionCountColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.CacheInstruction) && x.ColumnName.InvariantEquals("instructionCount")) == false) + AddColumn("instructionCount"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs new file mode 100644 index 0000000000..b4c0062770 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Factories; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 +{ + internal class AddMediaVersionTable : MigrationBase + { + public AddMediaVersionTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains(Constants.DatabaseSchema.Tables.MediaVersion)) return; + + Create.Table().Do(); + MigrateMediaPaths(); + } + + private void MigrateMediaPaths() + { + // this may not be the most efficient way to do it, compared to how it's done in v7, but this + // migration should only run for v8 sites that are being developed, before v8 is released, so + // no big sites and performances don't matter here - keep it simple + + var sql = Sql() + .Select(x => x.VarcharValue, x => x.TextValue) + .AndSelect(x => Alias(x.Id, "versionId")) + .From() + .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) + .InnerJoin().On((left, right) => left.VersionId == right.Id) + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + .Where(x => x.Alias == "umbracoFile") + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media); + + var paths = new List(); + + //using QUERY = a db cursor, we won't load this all into memory first, just row by row + foreach (var row in Database.Query(sql)) + { + // if there's values then ensure there's a media path match and extract it + string mediaPath = null; + if ( + (row.varcharValue != null && ContentBaseFactory.TryMatch((string) row.varcharValue, out mediaPath)) + || (row.textValue != null && ContentBaseFactory.TryMatch((string) row.textValue, out mediaPath))) + { + paths.Add(new MediaVersionDto + { + Id = (int) row.versionId, + Path = mediaPath + }); + } + } + + // bulk insert + Database.BulkInsertRecords(paths); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs new file mode 100644 index 0000000000..cd2678205f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs @@ -0,0 +1,21 @@ +using System.Linq; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 +{ + internal class AddTourDataUserColumn : MigrationBase + { + public AddTourDataUserColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.User) && x.ColumnName.InvariantEquals("tourData")) == false) + AddColumn("tourData"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs new file mode 100644 index 0000000000..7a55362072 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 +{ + internal class AddUserLoginTable : MigrationBase + { + public AddUserLoginTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains(Constants.DatabaseSchema.Tables.UserLogin) == false) + { + Create.Table().Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs new file mode 100644 index 0000000000..4e1a7d1470 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs @@ -0,0 +1,20 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 +{ + internal class AddIsSensitiveMemberTypeColumn : MigrationBase + { + public AddIsSensitiveMemberTypeColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.MemberType) && x.ColumnName.InvariantEquals("isSensitive")) == false) + AddColumn("isSensitive"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs new file mode 100644 index 0000000000..e7880dfc73 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 +{ + internal class AddUmbracoAuditTable : MigrationBase + { + public AddUmbracoAuditTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains(Constants.DatabaseSchema.Tables.AuditEntry)) + return; + + Create.Table().Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs new file mode 100644 index 0000000000..e3656f69ac --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 +{ + internal class AddUmbracoConsentTable : MigrationBase + { + public AddUmbracoConsentTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains(Constants.DatabaseSchema.Tables.Consent)) + return; + + Create.Table().Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs new file mode 100644 index 0000000000..a3749f7be5 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs @@ -0,0 +1,27 @@ +using System; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 +{ + internal class CreateSensitiveDataUserGroup : MigrationBase + { + public CreateSensitiveDataUserGroup(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var sql = Sql() + .SelectCount() + .From() + .Where(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); + + var exists = Database.ExecuteScalar(sql) > 0; + if (exists) return; + + var groupId = Database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", new UserGroupDto { StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); + Database.Insert(new User2UserGroupDto { UserGroupId = Convert.ToInt32(groupId), UserId = Constants.Security.SuperId }); // add super to sensitive data + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs index 202a4f0c0a..08121df815 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs @@ -35,7 +35,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 // re-create *all* keys and indexes foreach (var x in DatabaseSchemaCreator.OrderedTables) - Create.KeysAndIndexes(x.Value).Do(); + Create.KeysAndIndexes(x).Do(); // renames Database.Execute(Sql() diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs index e8b06c4a31..72241fb496 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs @@ -52,7 +52,7 @@ HAVING COUNT(v2.id) <> 1").Any()) // re-create *all* keys and indexes foreach (var x in DatabaseSchemaCreator.OrderedTables) - Create.KeysAndIndexes(x.Value).Do(); + Create.KeysAndIndexes(x).Do(); } private void MigratePropertyData() diff --git a/src/Umbraco.Core/Models/AuditEntry.cs b/src/Umbraco.Core/Models/AuditEntry.cs new file mode 100644 index 0000000000..2076e5328c --- /dev/null +++ b/src/Umbraco.Core/Models/AuditEntry.cs @@ -0,0 +1,94 @@ +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents an audited event. + /// + [Serializable] + [DataContract(IsReference = true)] + internal class AuditEntry : EntityBase, IAuditEntry + { + private static PropertySelectors _selectors; + + private int _performingUserId; + private string _performingDetails; + private string _performingIp; + private int _affectedUserId; + private string _affectedDetails; + private string _eventType; + private string _eventDetails; + + private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); + + private class PropertySelectors + { + public readonly PropertyInfo PerformingUserId = ExpressionHelper.GetPropertyInfo(x => x.PerformingUserId); + public readonly PropertyInfo PerformingDetails = ExpressionHelper.GetPropertyInfo(x => x.PerformingDetails); + public readonly PropertyInfo PerformingIp = ExpressionHelper.GetPropertyInfo(x => x.PerformingIp); + public readonly PropertyInfo AffectedUserId = ExpressionHelper.GetPropertyInfo(x => x.AffectedUserId); + public readonly PropertyInfo AffectedDetails = ExpressionHelper.GetPropertyInfo(x => x.AffectedDetails); + public readonly PropertyInfo EventType = ExpressionHelper.GetPropertyInfo(x => x.EventType); + public readonly PropertyInfo EventDetails = ExpressionHelper.GetPropertyInfo(x => x.EventDetails); + } + + /// + public int PerformingUserId + { + get => _performingUserId; + set => SetPropertyValueAndDetectChanges(value, ref _performingUserId, Selectors.PerformingUserId); + } + + /// + public string PerformingDetails + { + get => _performingDetails; + set => SetPropertyValueAndDetectChanges(value, ref _performingDetails, Selectors.PerformingDetails); + } + + /// + public string PerformingIp + { + get => _performingIp; + set => SetPropertyValueAndDetectChanges(value, ref _performingIp, Selectors.PerformingIp); + } + + /// + public DateTime EventDateUtc + { + get => CreateDate; + set => CreateDate = value; + } + + /// + public int AffectedUserId + { + get => _affectedUserId; + set => SetPropertyValueAndDetectChanges(value, ref _affectedUserId, Selectors.AffectedUserId); + } + + /// + public string AffectedDetails + { + get => _affectedDetails; + set => SetPropertyValueAndDetectChanges(value, ref _affectedDetails, Selectors.AffectedDetails); + } + + /// + public string EventType + { + get => _eventType; + set => SetPropertyValueAndDetectChanges(value, ref _eventType, Selectors.EventType); + } + + /// + public string EventDetails + { + get => _eventDetails; + set => SetPropertyValueAndDetectChanges(value, ref _eventDetails, Selectors.EventDetails); + } + } +} diff --git a/src/Umbraco.Core/Models/AuditItem.cs b/src/Umbraco.Core/Models/AuditItem.cs index 519f288ef0..6bfe32bd77 100644 --- a/src/Umbraco.Core/Models/AuditItem.cs +++ b/src/Umbraco.Core/Models/AuditItem.cs @@ -2,18 +2,29 @@ namespace Umbraco.Core.Models { - public sealed class AuditItem : EntityBase + public sealed class AuditItem : EntityBase, IAuditItem { + /// + /// Constructor for creating an item to be created + /// + /// + /// + /// + /// public AuditItem(int objectId, string comment, AuditType type, int userId) { + DisableChangeTracking(); + Id = objectId; Comment = comment; AuditType = type; UserId = userId; + + EnableChangeTracking(); } - public string Comment { get; private set; } - public AuditType AuditType { get; private set; } - public int UserId { get; private set; } + public string Comment { get; } + public AuditType AuditType { get; } + public int UserId { get; } } } diff --git a/src/Umbraco.Core/Models/Consent.cs b/src/Umbraco.Core/Models/Consent.cs new file mode 100644 index 0000000000..87dd9767a0 --- /dev/null +++ b/src/Umbraco.Core/Models/Consent.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a consent. + /// + [Serializable] + [DataContract(IsReference = true)] + internal class Consent : EntityBase, IConsent + { + private static PropertySelectors _selector; + + private bool _current; + private string _source; + private string _context; + private string _action; + private ConsentState _state; + private string _comment; + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo Current = ExpressionHelper.GetPropertyInfo(x => x.Current); + public readonly PropertyInfo Source = ExpressionHelper.GetPropertyInfo(x => x.Source); + public readonly PropertyInfo Context = ExpressionHelper.GetPropertyInfo(x => x.Context); + public readonly PropertyInfo Action = ExpressionHelper.GetPropertyInfo(x => x.Action); + public readonly PropertyInfo State = ExpressionHelper.GetPropertyInfo(x => x.State); + public readonly PropertyInfo Comment = ExpressionHelper.GetPropertyInfo(x => x.Comment); + } + + private static PropertySelectors Selectors => _selector ?? (_selector = new PropertySelectors()); + + /// + public bool Current + { + get => _current; + set => SetPropertyValueAndDetectChanges(value, ref _current, Selectors.Current); + } + + /// + public string Source + { + get => _source; + set + { + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); + SetPropertyValueAndDetectChanges(value, ref _source, Selectors.Source); + } + } + + /// + public string Context + { + get => _context; + set + { + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); + SetPropertyValueAndDetectChanges(value, ref _context, Selectors.Context); + } + } + + /// + public string Action + { + get => _action; + set + { + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); + SetPropertyValueAndDetectChanges(value, ref _action, Selectors.Action); + } + } + + /// + public ConsentState State + { + get => _state; + // note: we probably should validate the state here, but since the + // enum is [Flags] with many combinations, this could be expensive + set => SetPropertyValueAndDetectChanges(value, ref _state, Selectors.State); + } + + /// + public string Comment + { + get => _comment; + set => SetPropertyValueAndDetectChanges(value, ref _comment, Selectors.Comment); + } + + /// + public IEnumerable History => HistoryInternal; + + /// + /// Gets the previous states of this consent. + /// + internal List HistoryInternal { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/ConsentExtensions.cs b/src/Umbraco.Core/Models/ConsentExtensions.cs new file mode 100644 index 0000000000..fabeaf5809 --- /dev/null +++ b/src/Umbraco.Core/Models/ConsentExtensions.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Models +{ + /// + /// Provides extension methods for the interface. + /// + public static class ConsentExtensions + { + /// + /// Determines whether the consent is granted. + /// + public static bool IsGranted(this IConsent consent) => (consent.State & ConsentState.Granted) > 0; + + /// + /// Determines whether the consent is revoked. + /// + public static bool IsRevoked(this IConsent consent) => (consent.State & ConsentState.Revoked) > 0; + } +} diff --git a/src/Umbraco.Core/Models/ConsentState.cs b/src/Umbraco.Core/Models/ConsentState.cs new file mode 100644 index 0000000000..ed370823f3 --- /dev/null +++ b/src/Umbraco.Core/Models/ConsentState.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core.Models +{ + /// + /// Represents the state of a consent. + /// + [Flags] + public enum ConsentState // : int + { + // note - this is a [Flags] enumeration + // on can create detailed flags such as: + //GrantedOptIn = Granted | 0x0001 + //GrandedByForce = Granted | 0x0002 + // + // 16 situations for each Pending/Granted/Revoked should be ok + + /// + /// There is no consent. + /// + None = 0, + + /// + /// Consent is pending and has not been granted yet. + /// + Pending = 0x10000, + + /// + /// Consent has been granted. + /// + Granted = 0x20000, + + /// + /// Consent has been revoked. + /// + Revoked = 0x40000 + } +} diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 3bf614a3ef..a1df720a83 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -223,9 +223,8 @@ namespace Umbraco.Core.Models #region Dirty - /// - /// Resets dirty properties. - /// + /// + /// Overriden to include user properties. public override void ResetDirtyProperties(bool rememberDirty) { base.ResetDirtyProperties(rememberDirty); @@ -235,17 +234,15 @@ namespace Umbraco.Core.Models prop.ResetDirtyProperties(rememberDirty); } - /// - /// Gets a value indicating whether the current entity is dirty. - /// + /// + /// Overriden to include user properties. public override bool IsDirty() { return IsEntityDirty() || this.IsAnyUserPropertyDirty(); } - /// - /// Gets a value indicating whether the current entity was dirty. - /// + /// + /// Overriden to include user properties. public override bool WasDirty() { return WasEntityDirty() || this.WasAnyUserPropertyDirty(); @@ -267,9 +264,8 @@ namespace Umbraco.Core.Models return base.WasDirty(); } - /// - /// Gets a value indicating whether a user property is dirty. - /// + /// + /// Overriden to include user properties. public override bool IsPropertyDirty(string propertyName) { if (base.IsPropertyDirty(propertyName)) @@ -278,9 +274,8 @@ namespace Umbraco.Core.Models return Properties.Contains(propertyName) && Properties[propertyName].IsDirty(); } - /// - /// Gets a value indicating whether a user property was dirty. - /// + /// + /// Overriden to include user properties. public override bool WasPropertyDirty(string propertyName) { if (base.WasPropertyDirty(propertyName)) @@ -289,6 +284,24 @@ namespace Umbraco.Core.Models return Properties.Contains(propertyName) && Properties[propertyName].WasDirty(); } + /// + /// Overriden to include user properties. + public override IEnumerable GetDirtyProperties() + { + var instanceProperties = base.GetDirtyProperties(); + var propertyTypes = Properties.Where(x => x.IsDirty()).Select(x => x.Alias); + return instanceProperties.Concat(propertyTypes); + } + + /// + /// Overriden to include user properties. + public override IEnumerable GetWereDirtyProperties() + { + var instanceProperties = base.GetWereDirtyProperties(); + var propertyTypes = Properties.Where(x => x.WasDirty()).Select(x => x.Alias); + return instanceProperties.Concat(propertyTypes); + } + #endregion } } diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index d0f3edea8b..a7418a1441 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -97,11 +97,13 @@ namespace Umbraco.Core.Models [DataMember] public IEnumerable AllowedTemplates { - get { return _allowedTemplates; } + get => _allowedTemplates; set { - SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, - Ps.Value.TemplateComparer); + SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, Ps.Value.TemplateComparer); + + if (_allowedTemplates.Any(x => x.Id == _defaultTemplate) == false) + DefaultTemplateId = 0; } } diff --git a/src/Umbraco.Core/Models/IAuditEntry.cs b/src/Umbraco.Core/Models/IAuditEntry.cs new file mode 100644 index 0000000000..c097f84752 --- /dev/null +++ b/src/Umbraco.Core/Models/IAuditEntry.cs @@ -0,0 +1,60 @@ +using System; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents an audited event. + /// + /// + /// The free-form details properties can be used to capture relevant infos (for example, + /// a user email and identifier) at the time of the audited event, even though they may change + /// later on - but we want to keep a track of their value at that time. + /// Depending on audit loggers, these properties can be purely free-form text, or + /// contain json serialized objects. + /// + public interface IAuditEntry : IEntity, IRememberBeingDirty + { + /// + /// Gets or sets the identifier of the user triggering the audited event. + /// + int PerformingUserId { get; set; } + + /// + /// Gets or sets free-form details about the user triggering the audited event. + /// + string PerformingDetails { get; set; } + + /// + /// Gets or sets the IP address or the request triggering the audited event. + /// + string PerformingIp { get; set; } + + /// + /// Gets or sets the date and time of the audited event. + /// + DateTime EventDateUtc { get; set; } + + /// + /// Gets or sets the identifier of the user affected by the audited event. + /// + /// Not used when no single user is affected by the event. + int AffectedUserId { get; set; } + + /// + /// Gets or sets free-form details about the entity affected by the audited event. + /// + /// The entity affected by the event can be another user, a member... + string AffectedDetails { get; set; } + + /// + /// Gets or sets the type of the audited event. + /// + string EventType { get; set; } + + /// + /// Gets or sets free-form details about the audited event. + /// + string EventDetails { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/IAuditItem.cs b/src/Umbraco.Core/Models/IAuditItem.cs new file mode 100644 index 0000000000..9416e2a055 --- /dev/null +++ b/src/Umbraco.Core/Models/IAuditItem.cs @@ -0,0 +1,12 @@ +using System; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface IAuditItem : IEntity + { + string Comment { get; } + AuditType AuditType { get; } + int UserId { get; } + } +} diff --git a/src/Umbraco.Core/Models/IConsent.cs b/src/Umbraco.Core/Models/IConsent.cs new file mode 100644 index 0000000000..7e0156fd6e --- /dev/null +++ b/src/Umbraco.Core/Models/IConsent.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a consent state. + /// + /// + /// A consent is fully identified by a source (whoever is consenting), a context (for + /// example, an application), and an action (whatever is consented). + /// A consent state registers the state of the consent (granted, revoked...). + /// + public interface IConsent : IEntity, IRememberBeingDirty + { + /// + /// Determines whether the consent entity represents the current state. + /// + bool Current { get; } + + /// + /// Gets the unique identifier of whoever is consenting. + /// + string Source { get; } + + /// + /// Gets the unique identifier of the context of the consent. + /// + /// + /// Represents the domain, application, scope... of the action. + /// When the action is a Udi, this should be the Udi type. + /// + string Context { get; } + + /// + /// Gets the unique identifier of the consented action. + /// + string Action { get; } + + /// + /// Gets the state of the consent. + /// + ConsentState State { get; } + + /// + /// Gets some additional free text. + /// + string Comment { get; } + + /// + /// Gets the previous states of this consent. + /// + IEnumerable History { get; } + } +} diff --git a/src/Umbraco.Core/Models/IMemberType.cs b/src/Umbraco.Core/Models/IMemberType.cs index a1ce10dac8..9596d88cca 100644 --- a/src/Umbraco.Core/Models/IMemberType.cs +++ b/src/Umbraco.Core/Models/IMemberType.cs @@ -19,6 +19,13 @@ /// bool MemberCanViewProperty(string propertyTypeAlias); + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + bool IsSensitiveProperty(string propertyTypeAlias); + /// /// Sets a boolean indicating whether a Property is editable by the Member. /// @@ -32,5 +39,12 @@ /// PropertyType Alias of the Property to set /// Boolean value, true or false void SetMemberCanViewProperty(string propertyTypeAlias, bool value); + + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetIsSensitiveProperty(string propertyTypeAlias, bool value); } } diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index f09094f466..a45b1e356b 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -168,7 +168,19 @@ namespace Umbraco.Core.Models public string RawPasswordValue { get { return _rawPasswordValue; } - set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); } + set + { + if (value == null) + { + //special case, this is used to ensure that the password is not updated when persisting, in this case + //we don't want to track changes either + _rawPasswordValue = null; + } + else + { + SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); + } + } } /// diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index df087920f8..76450e9115 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -79,7 +79,7 @@ namespace Umbraco.Core.Models } /// - /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile) by the PropertyTypes' alias. + /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias. /// [DataMember] internal IDictionary MemberTypePropertyTypes { get; private set; } @@ -91,7 +91,7 @@ namespace Umbraco.Core.Models /// public bool MemberCanEditProperty(string propertyTypeAlias) { - return MemberTypePropertyTypes.ContainsKey(propertyTypeAlias) && MemberTypePropertyTypes[propertyTypeAlias].IsEditable; + return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsEditable; } /// @@ -101,7 +101,16 @@ namespace Umbraco.Core.Models /// public bool MemberCanViewProperty(string propertyTypeAlias) { - return MemberTypePropertyTypes.ContainsKey(propertyTypeAlias) && MemberTypePropertyTypes[propertyTypeAlias].IsVisible; + return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsVisible; + } + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + public bool IsSensitiveProperty(string propertyTypeAlias) + { + return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsSensitive; } /// @@ -111,13 +120,13 @@ namespace Umbraco.Core.Models /// Boolean value, true or false public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) { - MemberTypePropertyTypes[propertyTypeAlias].IsEditable = value; + propertyProfile.IsEditable = value; } else { - var tuple = new MemberTypePropertyProfileAccess(false, value); + var tuple = new MemberTypePropertyProfileAccess(false, value, false); MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); } } @@ -129,13 +138,31 @@ namespace Umbraco.Core.Models /// Boolean value, true or false public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) { - MemberTypePropertyTypes[propertyTypeAlias].IsVisible = value; + propertyProfile.IsVisible = value; } else { - var tuple = new MemberTypePropertyProfileAccess(value, false); + var tuple = new MemberTypePropertyProfileAccess(value, false, false); + MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); + } + } + + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetIsSensitiveProperty(string propertyTypeAlias, bool value) + { + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) + { + propertyProfile.IsSensitive = value; + } + else + { + var tuple = new MemberTypePropertyProfileAccess(false, false, true); MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); } } diff --git a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs index 0f6b2a3dce..386fdf560b 100644 --- a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs +++ b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs @@ -5,13 +5,15 @@ /// internal class MemberTypePropertyProfileAccess { - public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable) + public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable, bool isSenstive) { IsVisible = isVisible; IsEditable = isEditable; + IsSensitive = isSenstive; } public bool IsVisible { get; set; } public bool IsEditable { get; set; } + public bool IsSensitive { get; set; } } } diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index c58f69d223..31b5f2e513 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -188,11 +188,11 @@ namespace Umbraco.Core.Models Language, /// - /// Document + /// Document Blueprint /// [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentBlueprint, typeof(IContent))] [FriendlyName("DocumentBlueprint")] - [UmbracoUdiType(Constants.UdiEntityType.DocumentBluePrint)] + [UmbracoUdiType(Constants.UdiEntityType.DocumentBlueprint)] DocumentBlueprint, /// diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 2ec9f43a53..f66f0b4ef7 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -48,12 +48,11 @@ namespace Umbraco.Core.Models /// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL /// /// - /// /// /// /// A list of 5 different sized avatar URLs /// - internal static string[] GetCurrentUserAvatarUrls(this IUser user, IUserService userService, ICacheProvider staticCache) + internal static string[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache) { //check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none" if (user.Avatar == "none") @@ -276,6 +275,16 @@ namespace Umbraco.Core.Models return false; } + /// + /// Determines whether this user has access to view sensitive data + /// + /// + public static bool HasAccessToSensitiveData(this IUser user) + { + if (user == null) throw new ArgumentNullException("user"); + return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); + } + // calc. start nodes, combining groups' and user's, and excluding what's in the bin public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService) { diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index 68f9435219..59eee2fd80 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -28,6 +28,7 @@ namespace Umbraco.Core public const string ContentVersion = TableNamePrefix + "ContentVersion"; public const string Document = TableNamePrefix + "Document"; public const string DocumentVersion = TableNamePrefix + "DocumentVersion"; + public const string MediaVersion = TableNamePrefix + "MediaVersion"; public const string PropertyType = /*TableNamePrefix*/ "cms" + "PropertyType"; public const string PropertyTypeGroup = /*TableNamePrefix*/ "cms" + "PropertyTypeGroup"; diff --git a/src/Umbraco.Core/Persistence/Dtos/MediaDto.cs b/src/Umbraco.Core/Persistence/Dtos/MediaDto.cs new file mode 100644 index 0000000000..6990a891c4 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/MediaDto.cs @@ -0,0 +1,21 @@ +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + // this is a special Dto that does not have a corresponding table + // and is only used in our code to represent a media item, similar + // to document items. + + internal class MediaDto + { + public int NodeId { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] + public ContentDto ContentDto { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne)] + public MediaVersionDto MediaVersionDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/MediaVersionDto.cs b/src/Umbraco.Core/Persistence/Dtos/MediaVersionDto.cs new file mode 100644 index 0000000000..e27a99a2ae --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/MediaVersionDto.cs @@ -0,0 +1,25 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.MediaVersion)] + [PrimaryKey("id", AutoIncrement = false)] + [ExplicitColumns] + internal class MediaVersionDto + { + [Column("id")] + [PrimaryKeyColumn(AutoIncrement = false)] + [ForeignKey(typeof(ContentVersionDto))] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + Constants.DatabaseSchema.Tables.MediaVersion, ForColumns = "id, path")] + public int Id { get; set; } + + [Column("path")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Path { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne)] + public ContentVersionDto ContentVersionDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/AuditEntryFactory.cs b/src/Umbraco.Core/Persistence/Factories/AuditEntryFactory.cs new file mode 100644 index 0000000000..bbf6058055 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/AuditEntryFactory.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal static class AuditEntryFactory + { + public static IEnumerable BuildEntities(IEnumerable dtos) + { + return dtos.Select(BuildEntity).ToList(); + } + + public static IAuditEntry BuildEntity(AuditEntryDto dto) + { + var entity = new AuditEntry + { + Id = dto.Id, + PerformingUserId = dto.PerformingUserId, + PerformingDetails = dto.PerformingDetails, + PerformingIp = dto.PerformingIp, + EventDateUtc = dto.EventDateUtc, + AffectedUserId = dto.AffectedUserId, + AffectedDetails = dto.AffectedDetails, + EventType = dto.EventType, + EventDetails = dto.EventDetails + }; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + return entity; + } + + public static AuditEntryDto BuildDto(IAuditEntry entity) + { + return new AuditEntryDto + { + Id = entity.Id, + PerformingUserId = entity.PerformingUserId, + PerformingDetails = entity.PerformingDetails, + PerformingIp = entity.PerformingIp, + EventDateUtc = entity.EventDateUtc, + AffectedUserId = entity.AffectedUserId, + AffectedDetails = entity.AffectedDetails, + EventType = entity.EventType, + EventDetails = entity.EventDetails + }; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/ConsentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ConsentFactory.cs new file mode 100644 index 0000000000..5c3b90fee8 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/ConsentFactory.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal static class ConsentFactory + { + public static IEnumerable BuildEntities(IEnumerable dtos) + { + var ix = new Dictionary(); + var output = new List(); + + foreach (var dto in dtos) + { + var k = dto.Source + "::" + dto.Context + "::" + dto.Action; + + var consent = new Consent + { + Id = dto.Id, + Current = dto.Current, + CreateDate = dto.CreateDate, + Source = dto.Source, + Context = dto.Context, + Action = dto.Action, + State = (ConsentState) dto.State, // assume value is valid + Comment = dto.Comment + }; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + consent.ResetDirtyProperties(false); + + if (ix.TryGetValue(k, out var current)) + { + if (current.HistoryInternal == null) + current.HistoryInternal = new List(); + current.HistoryInternal.Add(consent); + } + else + { + ix[k] = consent; + output.Add(consent); + } + } + + return output; + } + + public static ConsentDto BuildDto(IConsent entity) + { + return new ConsentDto + { + Id = entity.Id, + Current = entity.Current, + CreateDate = entity.CreateDate, + Source = entity.Source, + Context = entity.Context, + Action = entity.Action, + State = (int) entity.State, + Comment = entity.Comment + }; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index c48ea4feca..b1a0a35432 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Text.RegularExpressions; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -6,6 +7,8 @@ namespace Umbraco.Core.Persistence.Factories { internal class ContentBaseFactory { + private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled); + /// /// Builds an IContent item from a dto and content type. /// @@ -177,11 +180,18 @@ namespace Umbraco.Core.Persistence.Factories /// /// Buils a dto from an IMedia item. /// - public static ContentDto BuildDto(IMedia entity) + public static MediaDto BuildDto(IMedia entity) { var contentBase = (Models.Media) entity; - var dto = BuildContentDto(contentBase, Constants.ObjectTypes.Media); - dto.ContentVersionDto = BuildContentVersionDto(contentBase, dto); + var contentDto = BuildContentDto(contentBase, Constants.ObjectTypes.Media); + + var dto = new MediaDto + { + NodeId = entity.Id, + ContentDto = contentDto, + MediaVersionDto = BuildMediaVersionDto(contentBase, contentDto) + }; + return dto; } @@ -273,5 +283,37 @@ namespace Umbraco.Core.Persistence.Factories return dto; } + + private static MediaVersionDto BuildMediaVersionDto(Models.Media entity, ContentDto contentDto) + { + // try to get a path from the string being stored for media + // fixme - only considering umbracoFile ?! + + TryMatch(entity.GetValue("umbracoFile"), out var path); + + var dto = new MediaVersionDto + { + Id = entity.VersionId, + Path = path, + + ContentVersionDto = BuildContentVersionDto(entity, contentDto) + }; + + return dto; + } + + // fixme - this should NOT be here?! + // more dark magic ;-( + internal static bool TryMatch(string text, out string path) + { + path = null; + if (string.IsNullOrWhiteSpace(text)) return false; + + var m = MediaPathPattern.Match(text); + if (!m.Success || m.Groups.Count != 2) return false; + + path = m.Groups[1].Value; + return true; + } } } diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 9cd9f8ab0a..f592bfddcb 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -82,7 +82,8 @@ namespace Umbraco.Core.Persistence.Factories NodeId = entity.Id, PropertyTypeId = x.Id, CanEdit = memberType.MemberCanEditProperty(x.Alias), - ViewOnProfile = memberType.MemberCanViewProperty(x.Alias) + ViewOnProfile = memberType.MemberCanViewProperty(x.Alias), + IsSensitive = memberType.IsSensitiveProperty(x.Alias) }).ToList(); return dtos; } diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index 848bcb3909..6f79f03c73 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -57,10 +57,10 @@ namespace Umbraco.Core.Persistence.Factories //Add the standard PropertyType to the current list propertyTypes.Add(standardPropertyType.Value); - - //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + + //Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, - new MemberTypePropertyProfileAccess(false, false)); + new MemberTypePropertyProfileAccess(false, false, false)); } memberType.NoGroupPropertyTypes = propertyTypes; @@ -103,7 +103,7 @@ namespace Umbraco.Core.Persistence.Factories { //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(typeDto.Alias, - new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit)); + new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); var tempGroupDto = groupDto; @@ -158,7 +158,7 @@ namespace Umbraco.Core.Persistence.Factories { //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(typeDto.Alias, - new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit)); + new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); //ensures that any built-in membership properties have their correct dbtype assigned no matter //what the underlying data type is diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index f9bf830ceb..394477cb51 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -36,6 +36,7 @@ namespace Umbraco.Core.Persistence.Factories user.Avatar = dto.Avatar; user.EmailConfirmedDate = dto.EmailConfirmedDate; user.InvitedDate = dto.InvitedDate; + user.TourData = dto.TourData; // reset dirty initial properties (U4-1946) user.ResetDirtyProperties(false); @@ -68,7 +69,8 @@ namespace Umbraco.Core.Persistence.Factories UpdateDate = entity.UpdateDate, Avatar = entity.Avatar, EmailConfirmedDate = entity.EmailConfirmedDate, - InvitedDate = entity.InvitedDate + InvitedDate = entity.InvitedDate, + TourData = entity.TourData }; foreach (var startNodeId in entity.StartContentIds) diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs new file mode 100644 index 0000000000..e8d6d72f85 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs @@ -0,0 +1,40 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a mapper for audit entry entities. + /// + [MapperFor(typeof(IAuditEntry))] + [MapperFor(typeof(AuditEntry))] + public sealed class AuditEntryMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + /// + /// Initializes a new instance of the class. + /// + public AuditEntryMapper() + { + // note: why the base ctor does not invoke BuildMap is a mystery to me + BuildMap(); + } + + protected override void BuildMap() + { + CacheMap(entity => entity.Id, dto => dto.Id); + CacheMap(entity => entity.PerformingUserId, dto => dto.PerformingUserId); + CacheMap(entity => entity.PerformingDetails, dto => dto.PerformingDetails); + CacheMap(entity => entity.PerformingIp, dto => dto.PerformingIp); + CacheMap(entity => entity.EventDateUtc, dto => dto.EventDateUtc); + CacheMap(entity => entity.AffectedUserId, dto => dto.AffectedUserId); + CacheMap(entity => entity.AffectedDetails, dto => dto.AffectedDetails); + CacheMap(entity => entity.EventType, dto => dto.EventType); + CacheMap(entity => entity.EventDetails, dto => dto.EventDetails); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditMapper.cs new file mode 100644 index 0000000000..a8f76944b7 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/AuditMapper.cs @@ -0,0 +1,32 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof(AuditItem))] + [MapperFor(typeof(IAuditItem))] + public sealed class AuditMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + public AuditMapper() + { + BuildMap(); + } + + protected override void BuildMap() + { + if (PropertyInfoCache.IsEmpty) + { + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.Datestamp); + CacheMap(src => src.UserId, dto => dto.UserId); + CacheMap(src => src.AuditType, dto => dto.Header); + CacheMap(src => src.Comment, dto => dto.Comment); + } + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs new file mode 100644 index 0000000000..9df1f68fd0 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs @@ -0,0 +1,39 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a mapper for consent entities. + /// + [MapperFor(typeof(IConsent))] + [MapperFor(typeof(Consent))] + public sealed class ConsentMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + /// + /// Initializes a new instance of the class. + /// + public ConsentMapper() + { + // note: why the base ctor does not invoke BuildMap is a mystery to me + BuildMap(); + } + + protected override void BuildMap() + { + CacheMap(entity => entity.Id, dto => dto.Id); + CacheMap(entity => entity.Current, dto => dto.Current); + CacheMap(entity => entity.CreateDate, dto => dto.CreateDate); + CacheMap(entity => entity.Source, dto => dto.Source); + CacheMap(entity => entity.Context, dto => dto.Context); + CacheMap(entity => entity.Action, dto => dto.Action); + CacheMap(entity => entity.State, dto => dto.State); + CacheMap(entity => entity.Comment, dto => dto.Comment); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7ffdaa909c..a2854ea1be 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -326,10 +326,23 @@ + + + + + + + + + + + + + @@ -342,6 +355,9 @@ + + + @@ -349,7 +365,14 @@ + + + + + + + diff --git a/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs b/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs index 9ae6535662..cdab84ddb9 100644 --- a/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs +++ b/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs @@ -240,9 +240,9 @@ namespace Umbraco.Tests.Migrations foreach (var x in DatabaseSchemaCreator.OrderedTables) { // ok - for tests, restrict to Node - if (x.Value != typeof(NodeDto)) continue; + if (x != typeof(NodeDto)) continue; - Create.KeysAndIndexes(x.Value).Do(); + Create.KeysAndIndexes(x).Do(); } } }