diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index c94def143b..a55eff33a9 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -33,7 +33,7 @@ - + diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 56e0376196..156b91187f 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.16.0")] -[assembly: AssemblyInformationalVersion("8.16.0-rc")] +[assembly: AssemblyInformationalVersion("8.16.0")] diff --git a/src/Umbraco.Core/Diagnostics/MiniDump.cs b/src/Umbraco.Core/Diagnostics/MiniDump.cs index e8c2e82f94..ca9596b4ae 100644 --- a/src/Umbraco.Core/Diagnostics/MiniDump.cs +++ b/src/Umbraco.Core/Diagnostics/MiniDump.cs @@ -79,24 +79,26 @@ namespace Umbraco.Core.Diagnostics private static bool Write(SafeHandle fileHandle, Option options, bool withException = false) { - var currentProcess = Process.GetCurrentProcess(); - var currentProcessHandle = currentProcess.Handle; - var currentProcessId = (uint)currentProcess.Id; + using (var currentProcess = Process.GetCurrentProcess()) + { + var currentProcessHandle = currentProcess.Handle; + var currentProcessId = (uint)currentProcess.Id; - MiniDumpExceptionInformation exp; + MiniDumpExceptionInformation exp; - exp.ThreadId = GetCurrentThreadId(); - exp.ClientPointers = false; - exp.ExceptionPointers = IntPtr.Zero; + exp.ThreadId = GetCurrentThreadId(); + exp.ClientPointers = false; + exp.ExceptionPointers = IntPtr.Zero; - if (withException) - exp.ExceptionPointers = Marshal.GetExceptionPointers(); + if (withException) + exp.ExceptionPointers = Marshal.GetExceptionPointers(); - var bRet = exp.ExceptionPointers == IntPtr.Zero - ? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero) - : MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, ref exp, IntPtr.Zero, IntPtr.Zero); + var bRet = exp.ExceptionPointers == IntPtr.Zero + ? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero) + : MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, ref exp, IntPtr.Zero, IntPtr.Zero); - return bRet; + return bRet; + } } public static bool Dump(Option options = Option.WithFullMemory, bool withException = false) diff --git a/src/Umbraco.Core/Logging/Serilog/Enrichers/Log4NetLevelMapperEnricher.cs b/src/Umbraco.Core/Logging/Serilog/Enrichers/Log4NetLevelMapperEnricher.cs index 0c255fa8b4..327dc13526 100644 --- a/src/Umbraco.Core/Logging/Serilog/Enrichers/Log4NetLevelMapperEnricher.cs +++ b/src/Umbraco.Core/Logging/Serilog/Enrichers/Log4NetLevelMapperEnricher.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers { public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { - var log4NetLevel = string.Empty; + string log4NetLevel; switch (logEvent.Level) { @@ -28,21 +28,21 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers break; case LogEventLevel.Information: - log4NetLevel = "INFO"; + log4NetLevel = "INFO "; //Padded string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely) break; case LogEventLevel.Verbose: - log4NetLevel = "ALL"; + log4NetLevel = "ALL "; //Padded string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely) break; case LogEventLevel.Warning: - log4NetLevel = "WARN"; + log4NetLevel = "WARN "; //Padded string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely) + break; + default: + log4NetLevel = string.Empty; break; } - //Pad string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely) - log4NetLevel = log4NetLevel.PadRight(5); - logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Log4NetLevel", log4NetLevel)); } } diff --git a/src/Umbraco.Core/Persistence/Dtos/AxisDefintionDto.cs b/src/Umbraco.Core/Persistence/Dtos/AxisDefintionDto.cs new file mode 100644 index 0000000000..8c56d8ea76 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/AxisDefintionDto.cs @@ -0,0 +1,16 @@ +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + internal class AxisDefintionDto + { + [Column("nodeId")] + public int NodeId { get; set; } + + [Column("alias")] + public string Alias { get; set; } + + [Column("ParentID")] + public int ParentId { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/ColumnInSchemaDto.cs b/src/Umbraco.Core/Persistence/Dtos/ColumnInSchemaDto.cs new file mode 100644 index 0000000000..64cc60cdc5 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/ColumnInSchemaDto.cs @@ -0,0 +1,25 @@ +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + internal class ColumnInSchemaDto + { + [Column("TABLE_NAME")] + public string TableName { get; set; } + + [Column("COLUMN_NAME")] + public string ColumnName { get; set; } + + [Column("ORDINAL_POSITION")] + public int OrdinalPosition { get; set; } + + [Column("COLUMN_DEFAULT")] + public string ColumnDefault { get; set; } + + [Column("IS_NULLABLE")] + public string IsNullable { get; set; } + + [Column("DATA_TYPE")] + public string DataType { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/ConstraintPerColumnDto.cs b/src/Umbraco.Core/Persistence/Dtos/ConstraintPerColumnDto.cs new file mode 100644 index 0000000000..aef50aa48e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/ConstraintPerColumnDto.cs @@ -0,0 +1,16 @@ +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + internal class ConstraintPerColumnDto + { + [Column("TABLE_NAME")] + public string TableName { get; set; } + + [Column("COLUMN_NAME")] + public string ColumnName { get; set; } + + [Column("CONSTRAINT_NAME")] + public string ConstraintName { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/ConstraintPerTableDto.cs b/src/Umbraco.Core/Persistence/Dtos/ConstraintPerTableDto.cs new file mode 100644 index 0000000000..e506e78aea --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/ConstraintPerTableDto.cs @@ -0,0 +1,13 @@ +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + internal class ConstraintPerTableDto + { + [Column("TABLE_NAME")] + public string TableName { get; set; } + + [Column("CONSTRAINT_NAME")] + public string ConstraintName { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/DefaultConstraintPerColumnDto.cs b/src/Umbraco.Core/Persistence/Dtos/DefaultConstraintPerColumnDto.cs new file mode 100644 index 0000000000..b21f04defc --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/DefaultConstraintPerColumnDto.cs @@ -0,0 +1,19 @@ +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + internal class DefaultConstraintPerColumnDto + { + [Column("TABLE_NAME")] + public string TableName { get; set; } + + [Column("COLUMN_NAME")] + public string ColumnName { get; set; } + + [Column("NAME")] + public string Name { get; set; } + + [Column("DEFINITION")] + public string Definition { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/DefinedIndexDto.cs b/src/Umbraco.Core/Persistence/Dtos/DefinedIndexDto.cs new file mode 100644 index 0000000000..2230a912e8 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/DefinedIndexDto.cs @@ -0,0 +1,20 @@ +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + internal class DefinedIndexDto + { + + [Column("TABLE_NAME")] + public string TableName { get; set; } + + [Column("INDEX_NAME")] + public string IndexName { get; set; } + + [Column("COLUMN_NAME")] + public string ColumnName { get; set; } + + [Column("UNIQUE")] + public short Unique { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/UserNotificationDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserNotificationDto.cs new file mode 100644 index 0000000000..81e8c8c405 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/UserNotificationDto.cs @@ -0,0 +1,20 @@ +using System; +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + internal class UserNotificationDto + { + [Column("nodeId")] + public int NodeId { get; set; } + + [Column("userId")] + public int UserId { get; set; } + + [Column("nodeObjectType")] + public Guid NodeObjectType { get; set; } + + [Column("action")] + public string Action { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/LocalDb.cs b/src/Umbraco.Core/Persistence/LocalDb.cs index 55d6565344..0e1da4c0d1 100644 --- a/src/Umbraco.Core/Persistence/LocalDb.cs +++ b/src/Umbraco.Core/Persistence/LocalDb.cs @@ -901,7 +901,7 @@ namespace Umbraco.Core.Persistence return -1; } - var p = new Process + using (var p = new Process { StartInfo = { @@ -913,13 +913,16 @@ namespace Umbraco.Core.Persistence CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden } - }; - p.Start(); - output = p.StandardOutput.ReadToEnd(); - error = p.StandardError.ReadToEnd(); - p.WaitForExit(); + }) + { + p.Start(); + output = p.StandardOutput.ReadToEnd(); + error = p.StandardError.ReadToEnd(); + p.WaitForExit(); - return p.ExitCode; + return p.ExitCode; + } + } /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/NotificationsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/NotificationsRepository.cs index e24d964d2d..69d8f4b4d1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/NotificationsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/NotificationsRepository.cs @@ -37,22 +37,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql .OrderBy(x => x.Id) .OrderBy(dto => dto.NodeId); - return AmbientScope.Database.Fetch(sql).Select(x => new Notification(x.nodeId, x.userId, x.action, objectType)); + return AmbientScope.Database.Fetch(sql).Select(x => new Notification(x.NodeId, x.UserId, x.Action, objectType)); } public IEnumerable GetUserNotifications(IUser user) { var sql = AmbientScope.SqlContext.Sql() - .Select("DISTINCT umbracoNode.id, umbracoUser2NodeNotify.userId, umbracoNode.nodeObjectType, umbracoUser2NodeNotify.action") + .Select("DISTINCT umbracoNode.id AS nodeId, umbracoUser2NodeNotify.userId, umbracoNode.nodeObjectType, umbracoUser2NodeNotify.action") .From() .InnerJoin() .On(dto => dto.NodeId, dto => dto.NodeId) .Where(dto => dto.UserId == (int)user.Id) .OrderBy(dto => dto.NodeId); - var dtos = AmbientScope.Database.Fetch(sql); + var dtos = AmbientScope.Database.Fetch(sql); //need to map the results - return dtos.Select(d => new Notification(d.id, d.userId, d.action, d.nodeObjectType)).ToList(); + return dtos.Select(d => new Notification(d.NodeId, d.UserId, d.Action, d.NodeObjectType)).ToList(); } public IEnumerable SetNotifications(IUser user, IEntity entity, string[] actions) @@ -64,16 +64,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public IEnumerable GetEntityNotifications(IEntity entity) { var sql = AmbientScope.SqlContext.Sql() - .Select("DISTINCT umbracoNode.id, umbracoUser2NodeNotify.userId, umbracoNode.nodeObjectType, umbracoUser2NodeNotify.action") + .Select("DISTINCT umbracoNode.id as nodeId, umbracoUser2NodeNotify.userId, umbracoNode.nodeObjectType, umbracoUser2NodeNotify.action") .From() .InnerJoin() .On(dto => dto.NodeId, dto => dto.NodeId) .Where(dto => dto.NodeId == entity.Id) .OrderBy(dto => dto.NodeId); - var dtos = AmbientScope.Database.Fetch(sql); + var dtos = AmbientScope.Database.Fetch(sql); //need to map the results - return dtos.Select(d => new Notification(d.id, d.userId, d.action, d.nodeObjectType)).ToList(); + return dtos.Select(d => new Notification(d.NodeId, d.UserId, d.Action, d.NodeObjectType)).ToList(); } public int DeleteNotifications(IEntity entity) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs index f26b7fa6ab..854f0770da 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs @@ -302,12 +302,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .Where("umbracoNode." + SqlContext.SqlSyntax.GetQuotedColumnName("id") + " IN (@parentIds) OR umbracoNode.parentID IN (@childIds)", new {parentIds = templates.Select(x => x.NodeDto.ParentId), childIds = templates.Select(x => x.NodeId)}); - var childIds = Database.Fetch(childIdsSql) + var childIds = Database.Fetch(childIdsSql) .Select(x => new EntitySlim { - Id = x.nodeId, - ParentId = x.parentID, - Name = x.alias + Id = x.NodeId, + ParentId = x.ParentId, + Name = x.Alias }); return childIds; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 846a547d81..a4b19ea2f3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -149,30 +149,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public IDictionary GetUserStates() { - var sql = @"SELECT '1CountOfAll' AS colName, COUNT(id) AS num FROM umbracoUser + // These keys in this query map to the `Umbraco.Core.Models.Membership.UserState` enum + var sql = @"SELECT -1 AS [Key], COUNT(id) AS [Value] FROM umbracoUser UNION -SELECT '2CountOfActive' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NOT NULL +SELECT 0 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NOT NULL UNION -SELECT '3CountOfDisabled' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userDisabled = 1 +SELECT 1 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 1 UNION -SELECT '4CountOfLockedOut' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userNoConsole = 1 +SELECT 2 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userNoConsole = 1 UNION -SELECT '5CountOfInvited' AS colName, COUNT(id) AS num FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL +SELECT 3 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL UNION -SELECT '6CountOfDisabled' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL -ORDER BY colName"; +SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL"; - var result = Database.Fetch(sql); + var result = Database.Dictionary(sql); - return new Dictionary - { - {UserState.All, (int) result[0].num}, - {UserState.Active, (int) result[1].num}, - {UserState.Disabled, (int) result[2].num}, - {UserState.LockedOut, (int) result[3].num}, - {UserState.Invited, (int) result[4].num}, - {UserState.Inactive, (int) result[5].num} - }; + return result.ToDictionary(x => (UserState)x.Key, x => x.Value); } public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 6168778f5b..2494e4bd7e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -9,6 +9,7 @@ using NPoco; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.SqlSyntax @@ -175,51 +176,50 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// public IEnumerable> GetDefaultConstraintsPerColumn(IDatabase db) { - var items = db.Fetch("SELECT TableName = t.Name, ColumnName = c.Name, dc.Name, dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id INNER JOIN sys.schemas as s on t.[schema_id] = s.[schema_id] WHERE s.name = (SELECT SCHEMA_NAME())"); + var items = db.Fetch("SELECT TableName = t.Name, ColumnName = c.Name, dc.Name, dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id INNER JOIN sys.schemas as s on t.[schema_id] = s.[schema_id] WHERE s.name = (SELECT SCHEMA_NAME())"); return items.Select(x => new Tuple(x.TableName, x.ColumnName, x.Name, x.Definition)); } public override IEnumerable GetTablesInSchema(IDatabase db) { - var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); - return items.Select(x => x.TABLE_NAME).Cast().ToList(); + return db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); } public override IsolationLevel DefaultIsolationLevel => IsolationLevel.ReadCommitted; public override IEnumerable GetColumnsInSchema(IDatabase db) { - var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); + var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); return items.Select( item => - new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT, - item.IS_NULLABLE, item.DATA_TYPE)).ToList(); + new ColumnInfo(item.TableName, item.ColumnName, item.OrdinalPosition, item.ColumnDefault, + item.IsNullable, item.DataType)).ToList(); } /// public override IEnumerable> GetConstraintsPerTable(IDatabase db) { var items = - db.Fetch( + db.Fetch( "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); - return items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); + return items.Select(item => new Tuple(item.TableName, item.ConstraintName)).ToList(); } /// public override IEnumerable> GetConstraintsPerColumn(IDatabase db) { var items = - db.Fetch( + db.Fetch( "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); - return items.Select(item => new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)).ToList(); + return items.Select(item => new Tuple(item.TableName, item.ColumnName, item.ConstraintName)).ToList(); } /// public override IEnumerable> GetDefinedIndexes(IDatabase db) { var items = - db.Fetch( + db.Fetch( @"select T.name as TABLE_NAME, I.name as INDEX_NAME, AC.Name as COLUMN_NAME, CASE WHEN I.is_unique_constraint = 1 OR I.is_unique = 1 THEN 1 ELSE 0 END AS [UNIQUE] from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id] @@ -228,8 +228,8 @@ from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id inner join sys.schemas as S on T.[schema_id] = S.[schema_id] WHERE S.name = (SELECT SCHEMA_NAME()) AND I.is_primary_key = 0 order by T.name, I.name"); - return items.Select(item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, - item.UNIQUE == 1)).ToList(); + return items.Select(item => new Tuple(item.TableName, item.IndexName, item.ColumnName, + item.Unique == 1)).ToList(); } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index d927d10052..add523ecf6 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -19,7 +19,6 @@ namespace Umbraco.Core.PropertyEditors public class DataEditor : IDataEditor { private IDictionary _defaultConfiguration; - private IDataValueEditor _reusableEditor; /// /// Initializes a new instance of the class. @@ -91,8 +90,7 @@ namespace Umbraco.Core.PropertyEditors /// simple enough for now. /// // TODO: point of that one? shouldn't we always configure? - public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_reusableEditor ?? (_reusableEditor = CreateValueEditor())); - + public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor(); /// /// diff --git a/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs index 2dcc37e25f..1f49f494cc 100644 --- a/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs +++ b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs @@ -20,10 +20,11 @@ namespace Umbraco.Core.Runtime public MainDomSemaphoreLock(ILogger logger) { - var lockName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-LCK"; + var mainDomId = MainDom.GetMainDomId(); + var lockName = "UMBRACO-" + mainDomId + "-MAINDOM-LCK"; _systemLock = new SystemLock(lockName); - var eventName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-EVT"; + var eventName = "UMBRACO-" + mainDomId + "-MAINDOM-EVT"; _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); _logger = logger; } diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 75ccf5e4f9..1cfca49160 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -535,10 +535,18 @@ namespace Umbraco.Core.Sync /// protected static readonly string LocalIdentity = NetworkHelper.MachineName // eg DOMAIN\SERVER + "/" + HttpRuntime.AppDomainAppId // eg /LM/S3SVC/11/ROOT - + " [P" + Process.GetCurrentProcess().Id // eg 1234 + + " [P" + GetProcessId() // eg 1234 + "/D" + AppDomain.CurrentDomain.Id // eg 22 + "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique + private static int GetProcessId() + { + using(var p = Process.GetCurrentProcess()) + { + return p.Id; + } + } + private string GetDistCacheFilePath(IGlobalSettings globalSettings) { var fileName = HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty) + "-lastsynced.txt"; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 03d75b68d3..f2b57e8c96 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -97,7 +97,7 @@ 2.10.0 - 2.0.1 + 2.0.2 3.1.0 @@ -167,9 +167,16 @@ + + + + + + + diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 96e24e89db..3ba004a939 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using Umbraco.Core.Composing; @@ -137,6 +138,14 @@ namespace Umbraco.Core return false; } + /// + /// Non Client Side request Extensions + /// + internal readonly static HashSet NonClientSideRequestExtensions = new (5, StringComparer.InvariantCultureIgnoreCase) + { + ".aspx", ".ashx", ".asmx", ".axd", ".svc" + }; + /// /// This is a performance tweak to check if this not an ASP.Net server file /// .Net will pass these requests through to the module when in integrated mode. @@ -150,8 +159,7 @@ namespace Umbraco.Core { var ext = Path.GetExtension(url.LocalPath); if (ext.IsNullOrWhiteSpace()) return false; - var toInclude = new[] {".aspx", ".ashx", ".asmx", ".axd", ".svc"}; - return toInclude.Any(ext.InvariantEquals) == false; + return !NonClientSideRequestExtensions.Contains(ext); } catch (ArgumentException) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js index 717cefbb0a..a4508c8879 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js @@ -41,7 +41,7 @@ (function () { 'use strict'; - function UmbCheckboxController($timeout, localizationService) { + function UmbCheckboxController($timeout, $attrs, localizationService) { var vm = this; @@ -50,7 +50,12 @@ function onInit() { vm.inputId = vm.inputId || "umb-check_" + String.CreateGuid(); - + vm.disableDirtyCheck = + $attrs.hasOwnProperty("disableDirtyCheck") && + vm.disableDirtyCheck !== '0' && + vm.disableDirtyCheck !== 0 && + vm.disableDirtyCheck !== 'false' && + vm.disableDirtyCheck !== false; vm.icon = vm.icon || vm.iconClass || null; // If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js index 8c7157c414..dd0d2fc31b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js @@ -40,7 +40,7 @@ (function () { 'use strict'; - function UmbRadiobuttonController($timeout, localizationService) { + function UmbRadiobuttonController($timeout, $attrs, localizationService) { var vm = this; @@ -49,7 +49,12 @@ function onInit() { vm.inputId = vm.inputId || "umb-radio_" + String.CreateGuid(); - + vm.disableDirtyCheck = + $attrs.hasOwnProperty("disableDirtyCheck") && + vm.disableDirtyCheck !== '0' && + vm.disableDirtyCheck !== 0 && + vm.disableDirtyCheck !== 'false' && + vm.disableDirtyCheck !== false; vm.icon = vm.icon || vm.iconClass || null; // If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index d80b884dab..e926ca23d7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -187,6 +187,11 @@ angular.module("umbraco.directives") function _requestChooseMediaTypeDialog() { + if (scope.queue.length === 0) { + // if queue has no items so there is nothing to choose a type for + return false; + } + if (scope.acceptedMediatypes.length === 1) { // if only one accepted type, then we wont ask to choose. return false; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js index 800ac87480..31ef125511 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js @@ -10,13 +10,18 @@ function noDirtyCheck() { require: 'ngModel', link: function (scope, elm, attrs, ctrl) { + // if "no-dirty-check" attribute is explicitly falsy, then skip and use default behaviour. In all other cases we consider it truthy + var skipNoDirtyCheck = attrs.noDirtyCheck === '0' || attrs.noDirtyCheck === 0 || attrs.noDirtyCheck.toString().toLowerCase() === 'false'; + if (skipNoDirtyCheck) + return; + var alwaysFalse = { - get: function () { return false; }, - set: function () { } - }; + get: function () { return false; }, + set: function () { } + }; + Object.defineProperty(ctrl, '$pristine', alwaysFalse); Object.defineProperty(ctrl, '$dirty', alwaysFalse); - } }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index df13ce8184..8379062807 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -153,7 +153,7 @@
-
+
@@ -162,7 +162,7 @@
-
+
-
+
@@ -81,7 +81,7 @@
-
+
-
+
- + diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index a9e59043e2..02db433b1c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -54,7 +54,7 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/listview/listview.html index 5cd04bd834..7eea4d541b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/listview/listview.html @@ -2,8 +2,8 @@
-
- +
+
-
- +
+
-
- +
+
@@ -43,8 +43,8 @@
-
- +
+
@@ -61,8 +61,8 @@
-
- +
+
@@ -77,9 +77,9 @@
-
- -
+
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html index 385979a572..279ffb73c0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html @@ -4,8 +4,8 @@
-
- +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/create.html b/src/Umbraco.Web.UI.Client/src/views/media/create.html index ed9df57c50..e18a88652f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/create.html @@ -5,11 +5,11 @@
Create under {{currentNode.name}}
-

+

diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html index 3747f59217..6f74174776 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html @@ -24,7 +24,7 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/listview/listview.html index 21d29b2161..7a88502fde 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/listview/listview.html @@ -2,8 +2,8 @@
-
- +
+
- \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html index 09e7ab4f41..3fe28e49b3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html @@ -3,8 +3,8 @@
-
- +
+
-
- +
+
-

+

Modifying a row configuration name will result in loss of diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index cfad66456d..1b8d60601f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -58,6 +58,8 @@ function multiUrlPickerController($scope, localizationService, entityResource, i $scope.multiUrlPickerForm.maxCount.$setValidity("maxCount", true); } $scope.sortableOptions.disabled = $scope.renderModel.length === 1; + //Update value + $scope.model.value = $scope.renderModel; } ); diff --git a/src/Umbraco.Web/Compose/BlockEditorComponent.cs b/src/Umbraco.Web/Compose/BlockEditorComponent.cs index a8b4cfb8ca..ac92aa6918 100644 --- a/src/Umbraco.Web/Compose/BlockEditorComponent.cs +++ b/src/Umbraco.Web/Compose/BlockEditorComponent.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Models.Blocks; using Umbraco.Core.PropertyEditors; @@ -17,6 +18,17 @@ namespace Umbraco.Web.Compose { private ComplexPropertyEditorContentEventHandler _handler; private readonly BlockListEditorDataConverter _converter = new BlockListEditorDataConverter(); + private readonly ILogger _logger; + + [Obsolete("Use the ctor injecting dependencies.")] + public BlockEditorComponent() : this(Current.Logger) + { + } + + public BlockEditorComponent(ILogger logger) + { + _logger = logger; + } public void Initialize() { @@ -116,8 +128,23 @@ namespace Umbraco.Web.Compose // this gets a little ugly because there could be some other complex editor that contains another block editor // and since we would have no idea how to parse that, all we can do is try JSON Path to find another block editor // of our type - var json = JToken.Parse(asString); - if (ProcessJToken(json, createGuid, out var result)) + JToken json = null; + try + { + json = JToken.Parse(asString); + } + catch (Exception e) + { + // See issue https://github.com/umbraco/Umbraco-CMS/issues/10879 + // We are detecting JSON data by seeing if a string is surrounded by [] or {} + // If people enter text like [PLACEHOLDER] JToken parsing fails, it's safe to ignore though + // Logging this just in case in the future we find values that are not safe to ignore + _logger.Warn( + "The property {PropertyAlias} on content type {ContentTypeKey} has a value of: {BlockItemValue} - this was recognized as JSON but could not be parsed", + data.Key, propertyAliasToBlockItemData.Key, asString); + } + + if (json != null && ProcessJToken(json, createGuid, out var result)) { // need to re-save this back to the RawPropertyValues data.RawPropertyValues[propertyAliasToBlockItemData.Key] = result; diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs index 18d27ba028..15906af96a 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs @@ -92,24 +92,26 @@ namespace Umbraco.Web.HealthCheck.Checks.Security // Hat-tip: https://stackoverflow.com/a/15343898/489433 const int NumberOfDaysForExpiryWarning = 14; var cert = request.ServicePoint.Certificate; - var cert2 = new X509Certificate2(cert); - var expirationDate = cert2.NotAfter; + using (var cert2 = new X509Certificate2(cert)) + { + var expirationDate = cert2.NotAfter; - var daysToExpiry = (int)Math.Floor((cert2.NotAfter - DateTime.Now).TotalDays); - if (daysToExpiry <= 0) - { - result = StatusResultType.Error; - message = _textService.Localize("healthcheck", "httpsCheckExpiredCertificate"); - } - else if (daysToExpiry < NumberOfDaysForExpiryWarning) - { - result = StatusResultType.Warning; - message = _textService.Localize("healthcheck", "httpsCheckExpiringCertificate", new[] { daysToExpiry.ToString() }); - } - else - { - result = StatusResultType.Success; - message = _textService.Localize("healthcheck", "httpsCheckValidCertificate"); + var daysToExpiry = (int)Math.Floor((cert2.NotAfter - DateTime.Now).TotalDays); + if (daysToExpiry <= 0) + { + result = StatusResultType.Error; + message = _textService.Localize("healthcheck", "httpsCheckExpiredCertificate"); + } + else if (daysToExpiry < NumberOfDaysForExpiryWarning) + { + result = StatusResultType.Warning; + message = _textService.Localize("healthcheck", "httpsCheckExpiringCertificate", new[] { daysToExpiry.ToString() }); + } + else + { + result = StatusResultType.Success; + message = _textService.Localize("healthcheck", "httpsCheckValidCertificate"); + } } } else diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 73d06db12b..7a07dcf051 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -347,28 +347,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return path; } - private void DeleteLocalFilesForContent() - { - if (_isReady && _localContentDb != null) - throw new InvalidOperationException("Cannot delete local files while the cache uses them."); - - var path = GetLocalFilesPath(); - var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); - if (File.Exists(localContentDbPath)) - File.Delete(localContentDbPath); - } - - private void DeleteLocalFilesForMedia() - { - if (_isReady && _localMediaDb != null) - throw new InvalidOperationException("Cannot delete local files while the cache uses them."); - - var path = GetLocalFilesPath(); - var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); - if (File.Exists(localMediaDbPath)) - File.Delete(localMediaDbPath); - } - #endregion #region Environment @@ -660,38 +638,13 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Handle Notifications - // note: if the service is not ready, ie _isReady is false, then notifications are ignored - - // SetUmbracoVersionStep issues a DistributedCache.Instance.RefreshAll...() call which should cause - // the entire content, media etc caches to reload from database -- and then the app restarts -- however, - // at the time SetUmbracoVersionStep runs, Umbraco is not fully initialized and therefore some property - // value converters, etc are not registered, and rebuilding the NuCache may not work properly. - // - // More details: ApplicationContext.IsConfigured being false, ApplicationEventHandler.ExecuteWhen... is - // called and in most cases events are skipped, so property value converters are not registered or - // removed, so PublishedPropertyType either initializes with the wrong converter, or throws because it - // detects more than one converter for a property type. - // - // It's not an issue for XmlStore - the app restart takes place *after* the install has refreshed the - // cache, and XmlStore just writes a new umbraco.config file upon RefreshAll, so that's OK. - // - // But for NuCache... we cannot rebuild the cache now. So it will NOT work and we are not fixing it, - // because now we should ALWAYS run with the database server messenger, and then the RefreshAll will - // be processed as soon as we are configured and the messenger processes instructions. - // note: notifications for content type and data type changes should be invoked with the // pure live model factory, if any, locked and refreshed - see ContentTypeCacheRefresher and // DataTypeCacheRefresher public override void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) { - // no cache, trash everything - if (_isReady == false) - { - DeleteLocalFilesForContent(); - draftChanged = publishedChanged = true; - return; - } + EnsureCaches(); using (_contentStore.GetScopedWriteLock(_scopeProvider)) { @@ -785,13 +738,7 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) { - // no cache, trash everything - if (_isReady == false) - { - DeleteLocalFilesForMedia(); - anythingChanged = true; - return; - } + EnsureCaches(); using (_mediaStore.GetScopedWriteLock(_scopeProvider)) { @@ -878,9 +825,7 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) { - // no cache, nothing we can do - if (_isReady == false) - return; + EnsureCaches(); foreach (var payload in payloads) _logger.Debug("Notified {ChangeTypes} for {ItemType} {ItemId}", payload.ChangeTypes, payload.ItemType, payload.Id); @@ -960,9 +905,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) { - // no cache, nothing we can do - if (_isReady == false) - return; + EnsureCaches(); var idsA = payloads.Select(x => x.Id).ToArray(); @@ -1000,9 +943,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Notify(DomainCacheRefresher.JsonPayload[] payloads) { - // no cache, nothing we can do - if (_isReady == false) - return; + EnsureCaches(); // see note in LockAndLoadContent using (_domainStore.GetScopedWriteLock(_scopeProvider))