diff --git a/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs b/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs index 4aaf6d25f2..b39a3f38df 100644 --- a/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs +++ b/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs @@ -26,26 +26,26 @@ namespace Umbraco.Core.Logging.Viewer /// A count of number of errors /// By counting Warnings with Exceptions, Errors & Fatal messages /// - int GetNumberOfErrors(DateTimeOffset startDate, DateTimeOffset endDate); + int GetNumberOfErrors(LogTimePeriod logTimePeriod); /// /// Returns a number of the different log level entries /// - LogLevelCounts GetLogLevelCounts(DateTimeOffset startDate, DateTimeOffset endDate); + LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod); /// /// Returns a list of all unique message templates and their counts /// - IEnumerable GetMessageTemplates(DateTimeOffset startDate, DateTimeOffset endDate); + IEnumerable GetMessageTemplates(LogTimePeriod logTimePeriod); bool CanHandleLargeLogs { get; } - bool CheckCanOpenLogs(DateTimeOffset startDate, DateTimeOffset endDate); + bool CheckCanOpenLogs(LogTimePeriod logTimePeriod); /// /// Returns the collection of logs /// - PagedResult GetLogs(DateTimeOffset startDate, DateTimeOffset endDate, + PagedResult GetLogs(LogTimePeriod logTimePeriod, int pageNumber = 1, int pageSize = 100, Direction orderDirection = Direction.Descending, diff --git a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs index 9e6d911489..bbe2f3704d 100644 --- a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs +++ b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Logging.Viewer public override bool CanHandleLargeLogs => false; - public override bool CheckCanOpenLogs(DateTimeOffset startDate, DateTimeOffset endDate) + public override bool CheckCanOpenLogs(LogTimePeriod logTimePeriod) { //Log Directory var logDirectory = _logsPath; @@ -36,7 +36,7 @@ namespace Umbraco.Core.Logging.Viewer //foreach full day in the range - see if we can find one or more filenames that end with //yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing - for (var day = startDate.Date; day.Date <= endDate.Date; day = day.AddDays(1)) + for (var day = logTimePeriod.StartTime.Date; day.Date <= logTimePeriod.EndTime.Date; day = day.AddDays(1)) { //Filename ending to search for (As could be multiple) var filesToFind = GetSearchPattern(day); @@ -57,7 +57,7 @@ namespace Umbraco.Core.Logging.Viewer return $"*{day:yyyyMMdd}*.json"; } - protected override IReadOnlyList GetLogs(DateTimeOffset startDate, DateTimeOffset endDate, ILogFilter filter, int skip, int take) + protected override IReadOnlyList GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take) { var logs = new List(); @@ -68,7 +68,7 @@ namespace Umbraco.Core.Logging.Viewer //foreach full day in the range - see if we can find one or more filenames that end with //yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing - for (var day = startDate.Date; day.Date <= endDate.Date; day = day.AddDays(1)) + for (var day = logTimePeriod.StartTime.Date; day.Date <= logTimePeriod.EndTime.Date; day = day.AddDays(1)) { //Filename ending to search for (As could be multiple) var filesToFind = GetSearchPattern(day); diff --git a/src/Umbraco.Core/Logging/Viewer/LogTimePeriod.cs b/src/Umbraco.Core/Logging/Viewer/LogTimePeriod.cs new file mode 100644 index 0000000000..0f41faef0a --- /dev/null +++ b/src/Umbraco.Core/Logging/Viewer/LogTimePeriod.cs @@ -0,0 +1,16 @@ +using System; + +namespace Umbraco.Core.Logging.Viewer +{ + public class LogTimePeriod + { + public LogTimePeriod(DateTime startTime, DateTime endTime) + { + StartTime = startTime; + EndTime = endTime; + } + + public DateTime StartTime { get; } + public DateTime EndTime { get; } + } +} diff --git a/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs b/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs index 3578a56e74..acb2f5dbf9 100644 --- a/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs +++ b/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs @@ -27,9 +27,9 @@ namespace Umbraco.Core.Logging.Viewer /// /// Get all logs from your chosen data source back as Serilog LogEvents /// - protected abstract IReadOnlyList GetLogs(DateTimeOffset startDate, DateTimeOffset endDate, ILogFilter filter, int skip, int take); + protected abstract IReadOnlyList GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take); - public abstract bool CheckCanOpenLogs(DateTimeOffset startDate, DateTimeOffset endDate); + public abstract bool CheckCanOpenLogs(LogTimePeriod logTimePeriod); public virtual IReadOnlyList GetSavedSearches() { @@ -82,24 +82,24 @@ namespace Umbraco.Core.Logging.Viewer return searches; } - public int GetNumberOfErrors(DateTimeOffset startDate, DateTimeOffset endDate) + public int GetNumberOfErrors(LogTimePeriod logTimePeriod) { var errorCounter = new ErrorCounterFilter(); - GetLogs(startDate, endDate, errorCounter, 0, int.MaxValue); + GetLogs(logTimePeriod, errorCounter, 0, int.MaxValue); return errorCounter.Count; } - public LogLevelCounts GetLogLevelCounts(DateTimeOffset startDate, DateTimeOffset endDate) + public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod) { var counter = new CountingFilter(); - GetLogs(startDate, endDate, counter, 0, int.MaxValue); + GetLogs(logTimePeriod, counter, 0, int.MaxValue); return counter.Counts; } - public IEnumerable GetMessageTemplates(DateTimeOffset startDate, DateTimeOffset endDate) + public IEnumerable GetMessageTemplates(LogTimePeriod logTimePeriod) { var messageTemplates = new MessageTemplateFilter(); - GetLogs(startDate, endDate, messageTemplates, 0, int.MaxValue); + GetLogs(logTimePeriod, messageTemplates, 0, int.MaxValue); var templates = messageTemplates.Counts. Select(x => new LogTemplate { MessageTemplate = x.Key, Count = x.Value }) @@ -108,14 +108,14 @@ namespace Umbraco.Core.Logging.Viewer return templates; } - public PagedResult GetLogs(DateTimeOffset startDate, DateTimeOffset endDate, + public PagedResult GetLogs(LogTimePeriod logTimePeriod, int pageNumber = 1, int pageSize = 100, Direction orderDirection = Direction.Descending, string filterExpression = null, string[] logLevels = null) { var expression = new ExpressionFilter(filterExpression); - var filteredLogs = GetLogs(startDate, endDate, expression, 0, int.MaxValue); + var filteredLogs = GetLogs(logTimePeriod, expression, 0, int.MaxValue); //This is user used the checkbox UI to toggle which log levels they wish to see //If an empty array or null - its implied all levels to be viewed diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index 2295745637..d86c682bd5 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -389,7 +389,7 @@ namespace Umbraco.Core.Migrations.Install private DatabaseSchemaResult ValidateSchema(IScope scope) { - if (_databaseFactory.Configured == false) + if (_databaseFactory.Initialized == false) return new DatabaseSchemaResult(_databaseFactory.SqlContext.SqlSyntax); if (_databaseSchemaValidationResult != null) @@ -513,7 +513,7 @@ namespace Umbraco.Core.Migrations.Install private Attempt CheckReadyForInstall() { - if (_databaseFactory.Configured == false) + if (_databaseFactory.CanConnect == false) { return Attempt.Fail(new Result { diff --git a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs index 39ece5fa10..8536b1ded3 100644 --- a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs @@ -3,6 +3,7 @@ using System.Linq; namespace Umbraco.Core.Models.Entities { + /// /// Implements . /// diff --git a/src/Umbraco.Core/Models/Entities/EntitySlim.cs b/src/Umbraco.Core/Models/Entities/EntitySlim.cs index 3b8f997602..b095965056 100644 --- a/src/Umbraco.Core/Models/Entities/EntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/EntitySlim.cs @@ -111,52 +111,6 @@ namespace Umbraco.Core.Models.Entities public virtual bool IsContainer { get; set; } - /// - /// Represents a lightweight property. - /// - public class PropertySlim - { - /// - /// Initializes a new instance of the class. - /// - public PropertySlim(string editorAlias, object value) - { - PropertyEditorAlias = editorAlias; - Value = value; - } - - /// - /// Gets the property editor alias. - /// - public string PropertyEditorAlias { get; } - - /// - /// Gets the property value. - /// - public object Value { get; } - - protected bool Equals(PropertySlim other) - { - return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && Equals(Value, other.Value); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((PropertySlim) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0); - } - } - } - #region IDeepCloneable /// diff --git a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs index 38fd9a02f1..0258d49114 100644 --- a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs @@ -2,6 +2,7 @@ namespace Umbraco.Core.Models.Entities { + /// /// Represents a lightweight document entity, managed by the entity service. /// diff --git a/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs new file mode 100644 index 0000000000..9440000146 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Models.Entities +{ + /// + /// Represents a lightweight media entity, managed by the entity service. + /// + public interface IMediaEntitySlim : IContentEntitySlim + { + + /// + /// The media file's path/url + /// + string MediaPath { get; } + } +} diff --git a/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs b/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs new file mode 100644 index 0000000000..6292a5a35a --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Models.Entities +{ + /// + /// Implements . + /// + public class MediaEntitySlim : ContentEntitySlim, IMediaEntitySlim + { + public string MediaPath { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs index 0236fc4bd5..c2d65b824f 100644 --- a/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs @@ -10,22 +10,35 @@ namespace Umbraco.Core.Persistence /// /// Creates a new database. /// - /// The new database must be disposed after being used. + /// + /// The new database must be disposed after being used. + /// Creating a database causes the factory to initialize if it is not already initialized. + /// IUmbracoDatabase CreateDatabase(); /// - /// Gets a value indicating whether the database factory is configured. + /// Gets a value indicating whether the database factory is configured, i.e. whether + /// its connection string and provider name have been set. The factory may however not + /// be initialized (see ). /// bool Configured { get; } + /// + /// Gets a value indicating whether the database factory is initialized, i.e. whether + /// its internal state is ready and it has been possible to connect to the database. + /// + bool Initialized { get; } + /// /// Gets the connection string. /// - /// Throws if the factory is not configured. + /// May return null if the database factory is not configured. string ConnectionString { get; } /// - /// Gets a value indicating whether the database can connect. + /// Gets a value indicating whether the database factory is configured (see ), + /// and it is possible to connect to the database. The factory may however not be initialized (see + /// ). /// bool CanConnect { get; } @@ -37,6 +50,9 @@ namespace Umbraco.Core.Persistence /// /// Gets the Sql context. /// + /// + /// Getting the Sql context causes the factory to initialize if it is not already initialized. + /// ISqlContext SqlContext { get; } /// diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs index c9d85feb25..0574e37c4c 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs @@ -232,6 +232,14 @@ namespace Umbraco.Core.Persistence using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction) { BulkCopyTimeout = 10000, DestinationTableName = tableName }) using (var bulkReader = new PocoDataDataReader(records, pocoData, syntax)) { + //we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared + //to the order in which they are declared in the model then this will not work, so instead we will add column mappings by name so that this explicitly uses + //the names instead of their ordering. + foreach(var col in bulkReader.ColumnMappings) + { + copy.ColumnMappings.Add(col.DestinationColumn, col.DestinationColumn); + } + copy.WriteToServer(bulkReader); return bulkReader.RecordsAffected; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index 645ab9f924..ccafb9f771 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -140,10 +140,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement while (templateDtoIx < templateDtos.Count && templateDtos[templateDtoIx].ContentTypeNodeId == contentType.Id) { var allowedDto = templateDtos[templateDtoIx]; + templateDtoIx++; if (!templates.TryGetValue(allowedDto.TemplateNodeId, out var template)) continue; allowedTemplates.Add(template); - templateDtoIx++; - + if (allowedDto.IsDefault) defaultTemplateId = template.Id; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index b4c0e51c22..4a32e373c1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -75,6 +75,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement dtos = page.Items; totalRecords = page.TotalItems; } + else if (isMedia) + { + var page = Database.Page(pageIndexToFetch, pageSize, sql); + dtos = page.Items; + totalRecords = page.TotalItems; + } else { var page = Database.Page(pageIndexToFetch, pageSize, sql); @@ -87,10 +93,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent) BuildVariants(entities.Cast()); - // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - if (isMedia) - BuildProperties(entities, dtos.ToList()); - return entities; } @@ -112,14 +114,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return cdtos.Count == 0 ? null : BuildVariants(BuildDocumentEntity(cdtos[0])); } - var dto = Database.FirstOrDefault(sql); + var dto = isMedia + ? Database.FirstOrDefault(sql) + : Database.FirstOrDefault(sql); + if (dto == null) return null; var entity = BuildEntity(false, isMedia, dto); - if (isMedia) - BuildProperties(entity, dto); - return entity; } @@ -162,7 +164,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : PerformGetAll(objectType); } - private IEnumerable GetEntities(Sql sql, bool isContent, bool isMedia, bool loadMediaProperties) + private IEnumerable GetEntities(Sql sql, bool isContent, bool isMedia) { //isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) @@ -174,15 +176,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : BuildVariants(cdtos.Select(BuildDocumentEntity)).ToList(); } - var dtos = Database.Fetch(sql); - if (dtos.Count == 0) return Enumerable.Empty(); + var dtos = isMedia + ? (IEnumerable)Database.Fetch(sql) + : Database.Fetch(sql); var entities = dtos.Select(x => BuildEntity(false, isMedia, x)).ToArray(); - // TODO: See https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - if (isMedia && loadMediaProperties) - BuildProperties(entities, dtos); - return entities; } @@ -192,7 +191,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectType == Constants.ObjectTypes.Media; var sql = GetFullSqlForEntityType(isContent, isMedia, objectType, filter); - return GetEntities(sql, isContent, isMedia, true); + return GetEntities(sql, isContent, isMedia); } public IEnumerable GetAllPaths(Guid objectType, params int[] ids) @@ -238,22 +237,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql = translator.Translate(); sql = AddGroupBy(isContent, isMedia, sql, true); - return GetEntities(sql, isContent, isMedia, true); - } - - // TODO: See https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - internal IEnumerable GetMediaByQueryWithoutPropertyData(IQuery query) - { - var isContent = false; - var isMedia = true; - - var sql = GetBaseWhere(isContent, isMedia, false, null, Constants.ObjectTypes.Media); - - var translator = new SqlTranslator(sql, query); - sql = translator.Translate(); - sql = AddGroupBy(isContent, isMedia, sql, true); - - return GetEntities(sql, isContent, isMedia, false); + return GetEntities(sql, isContent, isMedia); } public UmbracoObjectTypes GetObjectType(int id) @@ -280,41 +264,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return Database.ExecuteScalar(sql) > 0; } - // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - private void BuildProperties(EntitySlim entity, BaseDto dto) - { - var pdtos = Database.Fetch(GetPropertyData(dto.VersionId)); - foreach (var pdto in pdtos) - BuildProperty(entity, pdto); - } - - // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - private void BuildProperties(EntitySlim[] entities, List dtos) - { - var versionIds = dtos.Select(x => x.VersionId).Distinct().ToList(); - var pdtos = Database.FetchByGroups(versionIds, 2000, GetPropertyData); - - var xentity = entities.ToDictionary(x => x.Id, x => x); // nodeId -> entity - var xdto = dtos.ToDictionary(x => x.VersionId, x => x.NodeId); // versionId -> nodeId - foreach (var pdto in pdtos) - { - var nodeId = xdto[pdto.VersionId]; - var entity = xentity[nodeId]; - BuildProperty(entity, pdto); - } - } - - // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - private void BuildProperty(EntitySlim entity, PropertyDataDto pdto) - { - // explain ?! - var value = string.IsNullOrWhiteSpace(pdto.TextValue) - ? pdto.VarcharValue - : pdto.TextValue.ConvertToJsonIfPossible(); - - entity.AdditionalData[pdto.PropertyTypeDto.Alias] = new EntitySlim.PropertySlim(pdto.PropertyTypeDto.DataTypeDto.EditorAlias, value); - } - private DocumentEntitySlim BuildVariants(DocumentEntitySlim entity) => BuildVariants(new[] { entity }).First(); @@ -400,31 +349,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return AddGroupBy(isContent, isMedia, sql, true); } - private Sql GetPropertyData(int versionId) - { - return Sql() - .Select(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto))) - .From() - .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) - .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) - .Where(x => x.VersionId == versionId); - } - - private Sql GetPropertyData(IEnumerable versionIds) - { - return Sql() - .Select(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto))) - .From() - .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) - .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) - .WhereIn(x => x.VersionId, versionIds) - .OrderBy(x => x.VersionId); - } - // gets the base SELECT + FROM [+ filter] sql // always from the 'current' content version protected Sql GetBase(bool isContent, bool isMedia, Action> filter, bool isCount = false) - { + { var sql = Sql(); if (isCount) @@ -448,6 +376,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql .AndSelect(x => x.Published, x => x.Edited); } + + if (isMedia) + { + sql + .AndSelect(x => Alias(x.Path, "MediaPath")); + } } sql @@ -467,6 +401,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin().On((left, right) => left.NodeId == right.NodeId); } + if (isMedia) + { + sql + .InnerJoin().On((left, right) => left.Id == right.Id); + } + //Any LeftJoin statements need to come last if (isCount == false) { @@ -536,6 +476,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .AndBy(x => x.Published, x => x.Edited); } + if (isMedia) + { + sql + .AndBy(x => Alias(x.Path, "MediaPath")); + } + if (isContent || isMedia) sql @@ -566,23 +512,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Classes - [ExplicitColumns] - internal class UmbracoPropertyDto - { - [Column("propertyEditorAlias")] - public string PropertyEditorAlias { get; set; } - - [Column("propertyTypeAlias")] - public string PropertyAlias { get; set; } - - [Column("varcharValue")] - public string VarcharValue { get; set; } - - [Column("textValue")] - public string TextValue { get; set; } - } - - /// /// The DTO used to fetch results for a content item with its variation info /// @@ -594,6 +523,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public bool Edited { get; set; } } + private class MediaEntityDto : BaseDto + { + public string MediaPath { get; set; } + } + public class VariantInfoDto { public int NodeId { get; set; } @@ -645,7 +579,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent) return BuildDocumentEntity(dto); if (isMedia) - return BuildContentEntity(dto); + return BuildMediaEntity(dto); // EntitySlim does not track changes var entity = new EntitySlim(); @@ -678,11 +612,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.ContentTypeThumbnail = dto.Thumbnail; } - private static EntitySlim BuildContentEntity(BaseDto dto) + private MediaEntitySlim BuildMediaEntity(BaseDto dto) { // EntitySlim does not track changes - var entity = new ContentEntitySlim(); + var entity = new MediaEntitySlim(); BuildContentEntity(entity, dto); + + if (dto is MediaEntityDto contentDto) + { + // fill in the media info + entity.MediaPath = contentDto.MediaPath; + } + return entity; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs index 9f27b6b9e3..99e824757d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs @@ -99,7 +99,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (uniqueing) { - if (name.NumPos > 0 && name.Name.StartsWith(nodeName) && name.NumVal == uniqueNumber) + if (name.NumPos > 0 && name.Name.InvariantStartsWith(nodeName) && name.NumVal == uniqueNumber) uniqueNumber++; else break; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs index 3935027ada..3b247950e4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -411,8 +411,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return; //now the user association - RemoveAllUsersFromGroup(entity.UserGroup.Id); - AddUsersToGroup(entity.UserGroup.Id, entity.UserIds); + RefreshUsersInGroup(entity.UserGroup.Id, entity.UserIds); } protected override void PersistUpdatedItem(UserGroupWithUsers entity) @@ -424,8 +423,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return; //now the user association - RemoveAllUsersFromGroup(entity.UserGroup.Id); - AddUsersToGroup(entity.UserGroup.Id, entity.UserIds); + RefreshUsersInGroup(entity.UserGroup.Id, entity.UserIds); + } + + /// + /// Adds a set of users to a group, first removing any that exist + /// + /// Id of group + /// Ids of users + private void RefreshUsersInGroup(int groupId, int[] userIds) + { + RemoveAllUsersFromGroup(groupId); + AddUsersToGroup(groupId, userIds); } /// @@ -444,7 +453,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// Ids of users private void AddUsersToGroup(int groupId, int[] userIds) { - // TODO: Check if the user exists? foreach (var userId in userIds) { var dto = new User2UserGroupDto diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index dc86ff060c..13422f43b1 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -28,7 +28,8 @@ namespace Umbraco.Core.Persistence { private readonly Lazy _mappers; private readonly ILogger _logger; - private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + + private object _lock = new object(); private DatabaseFactory _npocoDatabaseFactory; private IPocoDataFactory _pocoDataFactory; @@ -36,12 +37,13 @@ namespace Umbraco.Core.Persistence private string _providerName; private DbProviderFactory _dbProviderFactory; private DatabaseType _databaseType; - private bool _serverVersionDetected; private ISqlSyntaxProvider _sqlSyntax; private RetryPolicy _connectionRetryPolicy; private RetryPolicy _commandRetryPolicy; private NPoco.MapperCollection _pocoMappers; + private SqlContext _sqlContext; private bool _upgrading; + private bool _initialized; #region Constructors @@ -106,36 +108,30 @@ namespace Umbraco.Core.Persistence #endregion /// - public bool Configured { get; private set; } - - /// - public string ConnectionString + public bool Configured { get { - EnsureConfigured(); - return _connectionString; + lock (_lock) + { + return !_connectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace(); + } } } /// - public bool CanConnect - { - get - { - if (!Configured || !DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName)) return false; + public bool Initialized => Volatile.Read(ref _initialized); - if (_serverVersionDetected) return true; + /// + public string ConnectionString => _connectionString; - if (_databaseType.IsSqlServer()) - DetectSqlServerVersion(); - _serverVersionDetected = true; + /// + public bool CanConnect => + // actually tries to connect to the database (regardless of configured/initialized) + !_connectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace() && + DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName); - return true; - } - } - - private void DetectSqlServerVersion() + private void UpdateSqlServerDatabaseType() { // replace NPoco database type by a more efficient one @@ -171,7 +167,15 @@ namespace Umbraco.Core.Persistence } /// - public ISqlContext SqlContext { get; private set; } + public ISqlContext SqlContext + { + get + { + // must be initialized to have a context + EnsureInitialized(); + return _sqlContext; + } + } /// public void ConfigureForUpgrade() @@ -182,63 +186,79 @@ namespace Umbraco.Core.Persistence /// public void Configure(string connectionString, string providerName) { - try + if (connectionString.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(connectionString)); + if (providerName.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(providerName)); + + lock (_lock) { - _lock.EnterWriteLock(); - - _logger.Debug("Configuring."); - - if (Configured) throw new InvalidOperationException("Already configured."); - - if (connectionString.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(connectionString)); - if (providerName.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(providerName)); + if (Volatile.Read(ref _initialized)) + throw new InvalidOperationException("Already initialized."); _connectionString = connectionString; _providerName = providerName; - - _connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(_connectionString); - _commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(_connectionString); - - _dbProviderFactory = DbProviderFactories.GetFactory(_providerName); - if (_dbProviderFactory == null) - throw new Exception($"Can't find a provider factory for provider name \"{_providerName}\"."); - _databaseType = DatabaseType.Resolve(_dbProviderFactory.GetType().Name, _providerName); - if (_databaseType == null) - throw new Exception($"Can't find an NPoco database type for provider name \"{_providerName}\"."); - - _sqlSyntax = GetSqlSyntaxProvider(_providerName); - if (_sqlSyntax == null) - throw new Exception($"Can't find a sql syntax provider for provider name \"{_providerName}\"."); - - // ensure we have only 1 set of mappers, and 1 PocoDataFactory, for all database - // so that everything NPoco is properly cached for the lifetime of the application - _pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; - var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver); - _pocoDataFactory = factory; - var config = new FluentConfig(xmappers => factory); - - // create the database factory - _npocoDatabaseFactory = DatabaseFactory.Config(x => x - .UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances - .WithFluentConfig(config)); // with proper configuration - - if (_npocoDatabaseFactory == null) throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance."); - - SqlContext = new SqlContext(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers); - - _logger.Debug("Configured."); - Configured = true; - } - finally - { - if (_lock.IsWriteLockHeld) - _lock.ExitWriteLock(); } + + // rest to be lazy-initialized + } + + private void EnsureInitialized() + { + LazyInitializer.EnsureInitialized(ref _sqlContext, ref _initialized, ref _lock, Initialize); + } + + private SqlContext Initialize() + { + _logger.Debug("Initializing."); + + if (_connectionString.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper connection string."); + if (_providerName.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper provider name."); + + // cannot initialize without being able to talk to the database + if (!DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName)) + throw new Exception("Cannot connect to the database."); + + _connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(_connectionString); + _commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(_connectionString); + + _dbProviderFactory = DbProviderFactories.GetFactory(_providerName); + if (_dbProviderFactory == null) + throw new Exception($"Can't find a provider factory for provider name \"{_providerName}\"."); + _databaseType = DatabaseType.Resolve(_dbProviderFactory.GetType().Name, _providerName); + if (_databaseType == null) + throw new Exception($"Can't find an NPoco database type for provider name \"{_providerName}\"."); + + _sqlSyntax = GetSqlSyntaxProvider(_providerName); + if (_sqlSyntax == null) + throw new Exception($"Can't find a sql syntax provider for provider name \"{_providerName}\"."); + + if (_databaseType.IsSqlServer()) + UpdateSqlServerDatabaseType(); + + // ensure we have only 1 set of mappers, and 1 PocoDataFactory, for all database + // so that everything NPoco is properly cached for the lifetime of the application + _pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; + var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver); + _pocoDataFactory = factory; + var config = new FluentConfig(xmappers => factory); + + // create the database factory + _npocoDatabaseFactory = DatabaseFactory.Config(x => x + .UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances + .WithFluentConfig(config)); // with proper configuration + + if (_npocoDatabaseFactory == null) + throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance."); + + _logger.Debug("Initialized."); + + return new SqlContext(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers); } /// public IUmbracoDatabase CreateDatabase() { + // must be initialized to create a database + EnsureInitialized(); return (IUmbracoDatabase) _npocoDatabaseFactory.GetDatabase(); } @@ -260,22 +280,6 @@ namespace Umbraco.Core.Persistence } } - // ensures that the database is configured, else throws - private void EnsureConfigured() - { - _lock.EnterReadLock(); - try - { - if (Configured == false) - throw new InvalidOperationException("Not configured."); - } - finally - { - if (_lock.IsReadLockHeld) - _lock.ExitReadLock(); - } - } - // method used by NPoco's UmbracoDatabaseFactory to actually create the database instance private UmbracoDatabase CreateDatabaseInstance() { @@ -292,7 +296,7 @@ namespace Umbraco.Core.Persistence //var db = _umbracoDatabaseAccessor.UmbracoDatabase; //_umbracoDatabaseAccessor.UmbracoDatabase = null; //db?.Dispose(); - Configured = false; + Volatile.Write(ref _initialized, false); } // during tests, the thread static var can leak between tests diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index 9d0399f324..f03bc640ec 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -15,92 +15,40 @@ namespace Umbraco.Core.Services /// The identifier of the entity. IEntitySlim Get(int id); - /// - /// Gets an entity. - /// - /// The identifier of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(int id, bool full); - /// /// Gets an entity. /// /// The unique key of the entity. IEntitySlim Get(Guid key); - /// - /// Gets an entity. - /// - /// The unique key of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(Guid key, bool full); - /// /// Gets an entity. /// /// The identifier of the entity. /// The object type of the entity. IEntitySlim Get(int id, UmbracoObjectTypes objectType); - - /// - /// Gets an entity. - /// - /// The identifier of the entity. - /// The object type of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(int id, UmbracoObjectTypes objectType, bool full); - + /// /// Gets an entity. /// /// The unique key of the entity. /// The object type of the entity. IEntitySlim Get(Guid key, UmbracoObjectTypes objectType); - - /// - /// Gets an entity. - /// - /// The unique key of the entity. - /// The object type of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(Guid key, UmbracoObjectTypes objectType, bool full); - + /// /// Gets an entity. /// /// The type used to determine the object type of the entity. /// The identifier of the entity. IEntitySlim Get(int id) where T : IUmbracoEntity; - - /// - /// Gets an entity. - /// - /// The type used to determine the object type of the entity. - /// The identifier of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(int id, bool full) where T : IUmbracoEntity; - + /// /// Gets an entity. /// /// The type used to determine the object type of the entity. /// The unique key of the entity. IEntitySlim Get(Guid key) where T : IUmbracoEntity; - - /// - /// Gets an entity. - /// - /// The type used to determine the object type of the entity. - /// The unique key of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(Guid key, bool full) where T : IUmbracoEntity; - + /// /// Determines whether an entity exists. /// diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index e2733a311d..ef22632d6e 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -142,52 +142,43 @@ namespace Umbraco.Core.Services /// Gets the Child object from a Relation as an /// /// Relation to retrieve child object from - /// Optional bool to load the complete object graph when set to False /// An - IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false); + IUmbracoEntity GetChildEntityFromRelation(IRelation relation); /// /// Gets the Parent object from a Relation as an /// /// Relation to retrieve parent object from - /// Optional bool to load the complete object graph when set to False /// An - IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false); + IUmbracoEntity GetParentEntityFromRelation(IRelation relation); /// /// Gets the Parent and Child objects from a Relation as a "/> with . /// /// Relation to retrieve parent and child object from - /// Optional bool to load the complete object graph when set to False /// Returns a Tuple with Parent (item1) and Child (item2) - Tuple GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false); + Tuple GetEntitiesFromRelation(IRelation relation); /// /// Gets the Child objects from a list of Relations as a list of objects. /// /// List of relations to retrieve child objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of - IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false); + IEnumerable GetChildEntitiesFromRelations(IEnumerable relations); /// /// Gets the Parent objects from a list of Relations as a list of objects. /// /// List of relations to retrieve parent objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of - IEnumerable GetParentEntitiesFromRelations(IEnumerable relations, - bool loadBaseType = false); + IEnumerable GetParentEntitiesFromRelations(IEnumerable relations); /// /// Gets the Parent and Child objects from a list of Relations as a list of objects. /// /// List of relations to retrieve parent and child objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of with - IEnumerable> GetEntitiesFromRelations( - IEnumerable relations, - bool loadBaseType = false); + IEnumerable> GetEntitiesFromRelations(IEnumerable relations); /// /// Relates two objects by their entity Ids. diff --git a/src/Umbraco.Core/Services/Implement/EntityService.cs b/src/Umbraco.Core/Services/Implement/EntityService.cs index 00edde48f3..04e2624592 100644 --- a/src/Umbraco.Core/Services/Implement/EntityService.cs +++ b/src/Umbraco.Core/Services/Implement/EntityService.cs @@ -7,11 +7,9 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; namespace Umbraco.Core.Services.Implement @@ -19,30 +17,25 @@ namespace Umbraco.Core.Services.Implement public class EntityService : ScopeRepositoryService, IEntityService { private readonly IEntityRepository _entityRepository; - private readonly Dictionary GetById, Func GetByKey)> _objectTypes; + private readonly Dictionary _objectTypes; private IQuery _queryRootEntity; private readonly IdkMap _idkMap; - public EntityService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, - IContentService contentService, IContentTypeService contentTypeService, - IMediaService mediaService, IMediaTypeService mediaTypeService, - IDataTypeService dataTypeService, - IMemberService memberService, IMemberTypeService memberTypeService, IdkMap idkMap, - IEntityRepository entityRepository) + public EntityService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IdkMap idkMap, IEntityRepository entityRepository) : base(provider, logger, eventMessagesFactory) { _idkMap = idkMap; _entityRepository = entityRepository; - _objectTypes = new Dictionary, Func)> + _objectTypes = new Dictionary { - { typeof (IDataType).FullName, (UmbracoObjectTypes.DataType, dataTypeService.GetDataType, dataTypeService.GetDataType) }, - { typeof (IContent).FullName, (UmbracoObjectTypes.Document, contentService.GetById, contentService.GetById) }, - { typeof (IContentType).FullName, (UmbracoObjectTypes.DocumentType, contentTypeService.Get, contentTypeService.Get) }, - { typeof (IMedia).FullName, (UmbracoObjectTypes.Media, mediaService.GetById, mediaService.GetById) }, - { typeof (IMediaType).FullName, (UmbracoObjectTypes.MediaType, mediaTypeService.Get, mediaTypeService.Get) }, - { typeof (IMember).FullName, (UmbracoObjectTypes.Member, memberService.GetById, memberService.GetByKey) }, - { typeof (IMemberType).FullName, (UmbracoObjectTypes.MemberType, memberTypeService.Get, memberTypeService.Get) }, + { typeof (IDataType).FullName, UmbracoObjectTypes.DataType }, + { typeof (IContent).FullName, UmbracoObjectTypes.Document }, + { typeof (IContentType).FullName, UmbracoObjectTypes.DocumentType }, + { typeof (IMedia).FullName, UmbracoObjectTypes.Media }, + { typeof (IMediaType).FullName, UmbracoObjectTypes.MediaType }, + { typeof (IMember).FullName, UmbracoObjectTypes.Member }, + { typeof (IMemberType).FullName, UmbracoObjectTypes.MemberType }, }; } @@ -54,162 +47,68 @@ namespace Umbraco.Core.Services.Implement #endregion - // gets the getters, throws if not supported - private (UmbracoObjectTypes ObjectType, Func GetById, Func GetByKey) GetGetters(Type type) + // gets the object type, throws if not supported + private UmbracoObjectTypes GetObjectType(Type type) { - if (type?.FullName == null || !_objectTypes.TryGetValue(type.FullName, out var getters)) + if (type?.FullName == null || !_objectTypes.TryGetValue(type.FullName, out var objType)) throw new NotSupportedException($"Type \"{type?.FullName ?? ""}\" is not supported here."); - return getters; + return objType; } /// public IEntitySlim Get(int id) { - return (IEntitySlim) Get(id, false); - } - - /// - public IUmbracoEntity Get(int id, bool full) - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(id); - } + return _entityRepository.Get(id); } - - // get the full entity - var objectType = GetObjectType(id); - var entityType = objectType.GetClrType(); - var getters = GetGetters(entityType); - return getters.GetById(id); } /// public IEntitySlim Get(Guid key) { - return (IEntitySlim) Get(key, false); - } - - /// - public IUmbracoEntity Get(Guid key, bool full) - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(key); - } + return _entityRepository.Get(key); } - - // get the full entity - var objectType = GetObjectType(key); - var entityType = objectType.GetClrType(); - var getters = GetGetters(entityType); - return getters.GetByKey(key); } /// public virtual IEntitySlim Get(int id, UmbracoObjectTypes objectType) { - return (IEntitySlim) Get(id, objectType, false); - } - - /// - public virtual IUmbracoEntity Get(int id, UmbracoObjectTypes objectType, bool full) - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(id, objectType.GetGuid()); - } + return _entityRepository.Get(id, objectType.GetGuid()); } - - // get the full entity - var entityType = objectType.GetClrType(); - var getters = GetGetters(entityType); - return getters.GetById(id); } /// public IEntitySlim Get(Guid key, UmbracoObjectTypes objectType) { - return (IEntitySlim) Get(key, objectType, false); - } - - /// - public IUmbracoEntity Get(Guid key, UmbracoObjectTypes objectType, bool full) - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(key, objectType.GetGuid()); - } + return _entityRepository.Get(key, objectType.GetGuid()); } - - // get the full entity - var entityType = objectType.GetClrType(); - var getters = GetGetters(entityType); - return getters.GetByKey(key); } /// public virtual IEntitySlim Get(int id) where T : IUmbracoEntity { - return (IEntitySlim) Get(id, false); - } - - /// - public virtual IUmbracoEntity Get(int id, bool full) - where T : IUmbracoEntity - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(id); - } + return _entityRepository.Get(id); } - - // get the full entity - var entityType = typeof (T); - var getters = GetGetters(entityType); - return getters.GetById(id); } /// public virtual IEntitySlim Get(Guid key) where T : IUmbracoEntity { - return (IEntitySlim) Get(key, false); - } - - /// - public IUmbracoEntity Get(Guid key, bool full) - where T : IUmbracoEntity - { - if (!full) + using (ScopeProvider.CreateScope(autoComplete: true)) { - // get the light entity - using (ScopeProvider.CreateScope(autoComplete: true)) - { - return _entityRepository.Get(key); - } + return _entityRepository.Get(key); } - - // get the full entity - var entityType = typeof (T); - var getters = GetGetters(entityType); - return getters.GetByKey(key); } /// @@ -240,8 +139,7 @@ namespace Umbraco.Core.Services.Implement where T : IUmbracoEntity { var entityType = typeof (T); - var getters = GetGetters(entityType); - var objectType = getters.ObjectType; + var objectType = GetObjectType(entityType); var objectTypeId = objectType.GetGuid(); using (ScopeProvider.CreateScope(autoComplete: true)) @@ -261,7 +159,7 @@ namespace Umbraco.Core.Services.Implement if (entityType == null) throw new NotSupportedException($"Type \"{objectType}\" is not supported here."); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -277,7 +175,7 @@ namespace Umbraco.Core.Services.Implement public virtual IEnumerable GetAll(Guid objectType, params int[] ids) { var entityType = ObjectTypes.GetClrType(objectType); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -290,8 +188,7 @@ namespace Umbraco.Core.Services.Implement where T : IUmbracoEntity { var entityType = typeof (T); - var getters = GetGetters(entityType); - var objectType = getters.ObjectType; + var objectType = GetObjectType(entityType); var objectTypeId = objectType.GetGuid(); using (ScopeProvider.CreateScope(autoComplete: true)) @@ -304,7 +201,7 @@ namespace Umbraco.Core.Services.Implement public IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] keys) { var entityType = objectType.GetClrType(); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -316,7 +213,7 @@ namespace Umbraco.Core.Services.Implement public virtual IEnumerable GetAll(Guid objectType, params Guid[] keys) { var entityType = ObjectTypes.GetClrType(objectType); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -377,22 +274,6 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of children by the parent's Id and UmbracoObjectType without adding property data - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - internal IEnumerable GetMediaChildrenWithoutPropertyData(int parentId) - { - using (ScopeProvider.CreateScope(autoComplete: true)) - { - var query = Query().Where(x => x.ParentId == parentId); - - // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media - return ((EntityRepository)_entityRepository).GetMediaByQueryWithoutPropertyData(query); - } - } - /// public virtual IEnumerable GetDescendants(int id) { @@ -578,7 +459,7 @@ namespace Umbraco.Core.Services.Implement public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params int[] ids) { var entityType = objectType.GetClrType(); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -590,7 +471,7 @@ namespace Umbraco.Core.Services.Implement public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys) { var entityType = objectType.GetClrType(); - GetGetters(entityType); + GetObjectType(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index a316d04f8e..405c3a2800 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -285,39 +285,36 @@ namespace Umbraco.Core.Services.Implement /// Gets the Child object from a Relation as an /// /// Relation to retrieve child object from - /// Optional bool to load the complete object graph when set to False /// An - public IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false) + public IUmbracoEntity GetChildEntityFromRelation(IRelation relation) { var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - return _entityService.Get(relation.ChildId, objectType, loadBaseType); + return _entityService.Get(relation.ChildId, objectType); } /// /// Gets the Parent object from a Relation as an /// /// Relation to retrieve parent object from - /// Optional bool to load the complete object graph when set to False /// An - public IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false) + public IUmbracoEntity GetParentEntityFromRelation(IRelation relation) { var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - return _entityService.Get(relation.ParentId, objectType, loadBaseType); + return _entityService.Get(relation.ParentId, objectType); } /// /// Gets the Parent and Child objects from a Relation as a "/> with . /// /// Relation to retrieve parent and child object from - /// Optional bool to load the complete object graph when set to False /// Returns a Tuple with Parent (item1) and Child (item2) - public Tuple GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false) + public Tuple GetEntitiesFromRelation(IRelation relation) { var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType); - var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType); + var child = _entityService.Get(relation.ChildId, childObjectType); + var parent = _entityService.Get(relation.ParentId, parentObjectType); return new Tuple(parent, child); } @@ -326,14 +323,13 @@ namespace Umbraco.Core.Services.Implement /// Gets the Child objects from a list of Relations as a list of objects. /// /// List of relations to retrieve child objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of - public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) + public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations) { foreach (var relation in relations) { var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - yield return _entityService.Get(relation.ChildId, objectType, loadBaseType); + yield return _entityService.Get(relation.ChildId, objectType); } } @@ -341,14 +337,13 @@ namespace Umbraco.Core.Services.Implement /// Gets the Parent objects from a list of Relations as a list of objects. /// /// List of relations to retrieve parent objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of - public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) + public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations) { foreach (var relation in relations) { var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - yield return _entityService.Get(relation.ParentId, objectType, loadBaseType); + yield return _entityService.Get(relation.ParentId, objectType); } } @@ -356,17 +351,16 @@ namespace Umbraco.Core.Services.Implement /// Gets the Parent and Child objects from a list of Relations as a list of objects. /// /// List of relations to retrieve parent and child objects from - /// Optional bool to load the complete object graph when set to False /// An enumerable list of with - public IEnumerable> GetEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) + public IEnumerable> GetEntitiesFromRelations(IEnumerable relations) { foreach (var relation in relations) { var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType); - var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType); + var child = _entityService.Get(relation.ChildId, childObjectType); + var parent = _entityService.Get(relation.ParentId, parentObjectType); yield return new Tuple(parent, child); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8a6acc107e..7c0e41348b 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -218,6 +218,7 @@ + @@ -249,6 +250,8 @@ + + diff --git a/src/Umbraco.Tests/Logging/LogviewerTests.cs b/src/Umbraco.Tests/Logging/LogviewerTests.cs index 35981f5368..ed9deec177 100644 --- a/src/Umbraco.Tests/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests/Logging/LogviewerTests.cs @@ -23,9 +23,10 @@ namespace Umbraco.Tests.Logging private string _newSearchfilePath; private string _newSearchfileDirPath; - private DateTimeOffset _startDate = new DateTime(year: 2018, month: 11, day: 12, hour:0, minute:0, second:0); - private DateTimeOffset _endDate = new DateTime(year: 2018, month: 11, day: 13, hour: 0, minute: 0, second: 0); - + private LogTimePeriod _logTimePeriod = new LogTimePeriod( + new DateTime(year: 2018, month: 11, day: 12, hour:0, minute:0, second:0), + new DateTime(year: 2018, month: 11, day: 13, hour: 0, minute: 0, second: 0) + ); [OneTimeSetUp] public void Setup() { @@ -67,7 +68,7 @@ namespace Umbraco.Tests.Logging [Test] public void Logs_Contain_Correct_Error_Count() { - var numberOfErrors = _logViewer.GetNumberOfErrors(startDate: _startDate, endDate: _endDate); + var numberOfErrors = _logViewer.GetNumberOfErrors(_logTimePeriod); //Our dummy log should contain 2 errors Assert.AreEqual(2, numberOfErrors); @@ -76,8 +77,8 @@ namespace Umbraco.Tests.Logging [Test] public void Logs_Contain_Correct_Log_Level_Counts() { - var logCounts = _logViewer.GetLogLevelCounts(startDate: _startDate, endDate: _endDate); - + var logCounts = _logViewer.GetLogLevelCounts(_logTimePeriod); + Assert.AreEqual(1954, logCounts.Debug); Assert.AreEqual(2, logCounts.Error); Assert.AreEqual(0, logCounts.Fatal); @@ -88,7 +89,7 @@ namespace Umbraco.Tests.Logging [Test] public void Logs_Contains_Correct_Message_Templates() { - var templates = _logViewer.GetMessageTemplates(startDate: _startDate, endDate: _endDate); + var templates = _logViewer.GetMessageTemplates(_logTimePeriod); //Count no of templates Assert.AreEqual(43, templates.Count()); @@ -112,7 +113,7 @@ namespace Umbraco.Tests.Logging { //We are just testing a return value (as we know the example file is less than 200MB) //But this test method does not test/check that - var canOpenLogs = _logViewer.CheckCanOpenLogs(startDate: _startDate, endDate: _endDate); + var canOpenLogs = _logViewer.CheckCanOpenLogs(_logTimePeriod); Assert.IsTrue(canOpenLogs); } @@ -120,7 +121,7 @@ namespace Umbraco.Tests.Logging public void Logs_Can_Be_Queried() { //Should get me the most 100 recent log entries & using default overloads for remaining params - var allLogs = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1); + var allLogs = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1); //Check we get 100 results back for a page & total items all good :) Assert.AreEqual(100, allLogs.Items.Count()); @@ -138,7 +139,7 @@ namespace Umbraco.Tests.Logging //Check we call method again with a smaller set of results & in ascending - var smallQuery = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, pageSize: 10, orderDirection: Direction.Ascending); + var smallQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, pageSize: 10, orderDirection: Direction.Ascending); Assert.AreEqual(10, smallQuery.Items.Count()); Assert.AreEqual(241, smallQuery.TotalPages); @@ -152,17 +153,17 @@ namespace Umbraco.Tests.Logging //Check invalid log levels //Rather than expect 0 items - get all items back & ignore the invalid levels string[] invalidLogLevels = { "Invalid", "NotALevel" }; - var queryWithInvalidLevels = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, logLevels: invalidLogLevels); + var queryWithInvalidLevels = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, logLevels: invalidLogLevels); Assert.AreEqual(2410, queryWithInvalidLevels.TotalItems); //Check we can call method with an array of logLevel (error & warning) string [] logLevels = { "Warning", "Error" }; - var queryWithLevels = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, logLevels: logLevels); + var queryWithLevels = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, logLevels: logLevels); Assert.AreEqual(9, queryWithLevels.TotalItems); - + //Query @Level='Warning' BUT we pass in array of LogLevels for Debug & Info (Expect to get 0 results) string[] logLevelMismatch = { "Debug", "Information" }; - var filterLevelQuery = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); ; + var filterLevelQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); ; Assert.AreEqual(0, filterLevelQuery.TotalItems); } @@ -177,10 +178,10 @@ namespace Umbraco.Tests.Logging [Test] public void Logs_Can_Query_With_Expressions(string queryToVerify, int expectedCount) { - var testQuery = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, filterExpression: queryToVerify); + var testQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: queryToVerify); Assert.AreEqual(expectedCount, testQuery.TotalItems); } - + [Test] public void Log_Search_Can_Persist() { diff --git a/src/Umbraco.Tests/Models/LightEntityTest.cs b/src/Umbraco.Tests/Models/LightEntityTest.cs index f1752a7681..41ce830cff 100644 --- a/src/Umbraco.Tests/Models/LightEntityTest.cs +++ b/src/Umbraco.Tests/Models/LightEntityTest.cs @@ -37,9 +37,7 @@ namespace Umbraco.Tests.Models }; item.AdditionalData.Add("test1", 3); item.AdditionalData.Add("test2", "valuie"); - item.AdditionalData.Add("test3", new EntitySlim.PropertySlim("TestPropertyEditor", "test")); - item.AdditionalData.Add("test4", new EntitySlim.PropertySlim("TestPropertyEditor2", "test2")); - + var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); Debug.Print(json); // FIXME: compare with v7 diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index c276dc35ca..fb451b1d5c 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -43,16 +43,6 @@ namespace Umbraco.Tests.Persistence _databaseFactory = null; } - [Test] - public void GetDatabaseType() - { - using (var database = _databaseFactory.CreateDatabase()) - { - var databaseType = database.DatabaseType; - Assert.AreEqual(DatabaseType.SQLCe, databaseType); - } - } - [Test] public void CreateDatabase() // FIXME: move to DatabaseBuilderTest! { @@ -79,6 +69,13 @@ namespace Umbraco.Tests.Persistence // re-create the database factory and database context with proper connection string _databaseFactory = new UmbracoDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _logger, new Lazy(() => Mock.Of())); + // test get database type (requires an actual database) + using (var database = _databaseFactory.CreateDatabase()) + { + var databaseType = database.DatabaseType; + Assert.AreEqual(DatabaseType.SQLCe, databaseType); + } + // create application context //var appCtx = new ApplicationContext( // _databaseFactory, diff --git a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs index 3c23223c9f..582e5a4815 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs @@ -36,6 +36,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsTrue(result > 0, "Expected >0 but was " + result); } + + [Test] public void OrderByTest() { @@ -75,6 +77,7 @@ namespace Umbraco.Tests.Persistence.Repositories [TestCase(0, "Alpha", "Alpha (3)")] [TestCase(0, "Kilo (1)", "Kilo (1) (1)")] // though... we might consider "Kilo (2)" [TestCase(6, "Kilo (1)", "Kilo (1)")] // because of the id + [TestCase(0, "alpha", "alpha (3)")] [TestCase(0, "", " (1)")] [TestCase(0, null, " (1)")] public void Test(int nodeId, string nodeName, string expected) @@ -95,5 +98,22 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(expected, SimilarNodeName.GetUniqueName(names, nodeId, nodeName)); } + + [Test] + [Explicit("This test fails! We need to fix up the logic")] + public void TestMany() + { + var names = new[] + { + new SimilarNodeName { Id = 1, Name = "Alpha (2)" }, + new SimilarNodeName { Id = 2, Name = "Test" }, + new SimilarNodeName { Id = 3, Name = "Test (1)" }, + new SimilarNodeName { Id = 4, Name = "Test (2)" }, + new SimilarNodeName { Id = 22, Name = "Test (1) (1)" }, + }; + + //fixme - this will yield "Test (2)" which is already in use + Assert.AreEqual("Test (3)", SimilarNodeName.GetUniqueName(names, 0, "Test")); + } } } diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index 2425f8b74a..0598b8cea2 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -675,15 +675,10 @@ namespace Umbraco.Tests.Services foreach (var entity in entities) { - Console.WriteLine(); - foreach (var data in entity.AdditionalData) - { - Console.WriteLine($"{entity.Id} {data.Key} {data.Value} {(data.Value is EntitySlim.PropertySlim p ? p.PropertyEditorAlias : "")}"); - } + Assert.IsTrue(entity.GetType().Implements()); + Console.WriteLine(((IMediaEntitySlim)entity).MediaPath); + Assert.IsNotEmpty(((IMediaEntitySlim)entity).MediaPath); } - - Assert.That(entities.Any(x => - x.AdditionalData.Any(y => y.Value is EntitySlim.PropertySlim && ((EntitySlim.PropertySlim)y.Value).PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField)), Is.True); } [Test] diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 110accdecf..31d11952ef 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -164,11 +164,7 @@ namespace Umbraco.Tests.TestHelpers var fileService = GetLazyService(factory, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var memberTypeService = GetLazyService(factory, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); - var entityService = GetLazyService(factory, c => new EntityService( - scopeProvider, logger, eventMessagesFactory, - contentService.Value, contentTypeService.Value, mediaService.Value, mediaTypeService.Value, dataTypeService.Value, memberService.Value, memberTypeService.Value, - idkMap, - GetRepo(c))); + var entityService = GetLazyService(factory, c => new EntityService(scopeProvider, logger, eventMessagesFactory, idkMap, GetRepo(c))); var macroService = GetLazyService(factory, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); var packagingService = GetLazyService(factory, c => diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index c27a2c5f53..4dae0dac08 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -41,12 +41,12 @@ module.exports = { assets: "./src/assets/**" } }, - root: "../Umbraco.Web.UI/Umbraco/", + root: "../Umbraco.Web.UI/", targets: { - js: "js/", - lib: "lib/", - views: "views/", - css: "assets/css/", - assets: "assets/" + js: "Umbraco/js/", + lib: "Umbraco/lib/", + views: "Umbraco/views/", + css: "Umbraco/assets/css/", + assets: "Umbraco/assets/" } }; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index c1c85a9688..90fb2437de 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -955,7 +955,7 @@ }, "ansi-escapes": { "version": "3.1.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, @@ -1104,6 +1104,7 @@ "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-3.2.0.tgz", "integrity": "sha1-nNnABpV+vpX62tW9YJiUKoE3N/Y=", "dev": true, + "optional": true, "requires": { "file-type": "^3.1.0" }, @@ -1112,7 +1113,8 @@ "version": "3.9.0", "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "dev": true, + "optional": true } } }, @@ -1176,7 +1178,7 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true }, "array-sort": { @@ -1538,6 +1540,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -1547,13 +1550,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1566,9 +1571,10 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -1740,7 +1746,7 @@ "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", "dev": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", @@ -1750,14 +1756,15 @@ "buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", "dev": true }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "dev": true, + "optional": true }, "buffer-fill": { "version": "1.0.0", @@ -1776,6 +1783,7 @@ "resolved": "https://registry.npmjs.org/buffer-to-vinyl/-/buffer-to-vinyl-1.1.0.tgz", "integrity": "sha1-APFfruOreh3aLN5tkSG//dB7ImI=", "dev": true, + "optional": true, "requires": { "file-type": "^3.1.0", "readable-stream": "^2.0.2", @@ -1787,19 +1795,22 @@ "version": "3.9.0", "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "dev": true, + "optional": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1815,6 +1826,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -1823,13 +1835,15 @@ "version": "2.0.3", "resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", - "dev": true + "dev": true, + "optional": true }, "vinyl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -1950,7 +1964,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", - "dev": true + "dev": true, + "optional": true }, "caseless": { "version": "0.12.0", @@ -1963,6 +1978,7 @@ "resolved": "https://registry.npmjs.org/caw/-/caw-1.2.0.tgz", "integrity": "sha1-/7Im/n78VHKI3GLuPpcHPCEtEDQ=", "dev": true, + "optional": true, "requires": { "get-proxy": "^1.0.1", "is-obj": "^1.0.0", @@ -1974,7 +1990,8 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true + "dev": true, + "optional": true } } }, @@ -2249,7 +2266,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=", - "dev": true + "dev": true, + "optional": true }, "coa": { "version": "2.0.1", @@ -2373,6 +2391,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "dev": true, + "optional": true, "requires": { "graceful-readlink": ">= 1.0.0" } @@ -2436,7 +2455,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -2559,7 +2578,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha1-+XJgj/DOrWi4QaFqky0LGDeRgU4=", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", "dev": true }, "core-util-is": { @@ -2585,6 +2604,7 @@ "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "dev": true, + "optional": true, "requires": { "capture-stack-trace": "^1.0.0" } @@ -2839,6 +2859,7 @@ "resolved": "https://registry.npmjs.org/decompress/-/decompress-3.0.0.tgz", "integrity": "sha1-rx3VDQbjv8QyRh033hGzjA2ZG+0=", "dev": true, + "optional": true, "requires": { "buffer-to-vinyl": "^1.0.0", "concat-stream": "^1.4.6", @@ -2856,6 +2877,7 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.0.1" } @@ -2864,13 +2886,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, + "optional": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", @@ -2882,6 +2906,7 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, + "optional": true, "requires": { "is-posix-bracket": "^0.1.0" } @@ -2891,6 +2916,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -2900,6 +2926,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, + "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", @@ -2913,6 +2940,7 @@ "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", "dev": true, + "optional": true, "requires": { "extend": "^3.0.0", "glob": "^5.0.3", @@ -2928,13 +2956,15 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -2946,13 +2976,15 @@ "version": "0.10.31", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true + "dev": true, + "optional": true }, "through2": { "version": "0.6.5", "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -2964,19 +2996,22 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "dev": true, + "optional": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -2985,13 +3020,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -3001,6 +3038,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, + "optional": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", @@ -3021,13 +3059,15 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "ordered-read-streams": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "dev": true, + "optional": true, "requires": { "is-stream": "^1.0.1", "readable-stream": "^2.0.1" @@ -3038,6 +3078,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3053,6 +3094,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3062,6 +3104,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, + "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -3071,6 +3114,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, + "optional": true, "requires": { "first-chunk-stream": "^1.0.0", "strip-bom": "^2.0.0" @@ -3081,6 +3125,7 @@ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, + "optional": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", "through2-filter": "^3.0.0" @@ -3091,6 +3136,7 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, + "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -3103,6 +3149,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3114,6 +3161,7 @@ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "dev": true, + "optional": true, "requires": { "duplexify": "^3.2.0", "glob-stream": "^5.3.2", @@ -3141,6 +3189,7 @@ "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-3.1.0.tgz", "integrity": "sha1-IXx4n5uURQ76rcXF5TeXj8MzxGY=", "dev": true, + "optional": true, "requires": { "is-tar": "^1.0.0", "object-assign": "^2.0.0", @@ -3154,19 +3203,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3179,6 +3231,7 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3189,6 +3242,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, + "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3201,6 +3255,7 @@ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-3.1.0.tgz", "integrity": "sha1-iyOTVoE1X58YnYclag+L3ZbZZm0=", "dev": true, + "optional": true, "requires": { "is-bzip2": "^1.0.0", "object-assign": "^2.0.0", @@ -3215,19 +3270,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3240,6 +3298,7 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3250,6 +3309,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, + "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3262,6 +3322,7 @@ "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-3.1.0.tgz", "integrity": "sha1-ssE9+YFmJomRtxXWRH9kLpaW9aA=", "dev": true, + "optional": true, "requires": { "is-gzip": "^1.0.0", "object-assign": "^2.0.0", @@ -3275,19 +3336,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3300,6 +3364,7 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3310,6 +3375,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, + "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3322,6 +3388,7 @@ "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-3.4.0.tgz", "integrity": "sha1-YUdbQVIGa74/7hL51inRX+ZHjus=", "dev": true, + "optional": true, "requires": { "is-zip": "^1.0.0", "read-all-stream": "^3.0.0", @@ -3337,6 +3404,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3349,7 +3417,8 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "dev": true, + "optional": true }, "deep-is": { "version": "0.1.3", @@ -3493,7 +3562,7 @@ "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -3568,6 +3637,7 @@ "resolved": "https://registry.npmjs.org/download/-/download-4.4.3.tgz", "integrity": "sha1-qlX9rTktldS2jowr4D4MKqIbqaw=", "dev": true, + "optional": true, "requires": { "caw": "^1.0.1", "concat-stream": "^1.4.7", @@ -3591,6 +3661,7 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.0.1" } @@ -3599,13 +3670,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, + "optional": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", @@ -3617,6 +3690,7 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, + "optional": true, "requires": { "is-posix-bracket": "^0.1.0" } @@ -3626,6 +3700,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3635,6 +3710,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, + "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", @@ -3648,6 +3724,7 @@ "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", "dev": true, + "optional": true, "requires": { "extend": "^3.0.0", "glob": "^5.0.3", @@ -3663,13 +3740,15 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3681,13 +3760,15 @@ "version": "0.10.31", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true + "dev": true, + "optional": true }, "through2": { "version": "0.6.5", "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3699,19 +3780,22 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "dev": true, + "optional": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3720,13 +3804,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -3736,6 +3822,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, + "optional": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", @@ -3756,13 +3843,15 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "ordered-read-streams": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "dev": true, + "optional": true, "requires": { "is-stream": "^1.0.1", "readable-stream": "^2.0.1" @@ -3773,6 +3862,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3788,6 +3878,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3797,6 +3888,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, + "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -3806,6 +3898,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, + "optional": true, "requires": { "first-chunk-stream": "^1.0.0", "strip-bom": "^2.0.0" @@ -3816,6 +3909,7 @@ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, + "optional": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", "through2-filter": "^3.0.0" @@ -3826,6 +3920,7 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, + "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -3838,6 +3933,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3849,6 +3945,7 @@ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "dev": true, + "optional": true, "requires": { "duplexify": "^3.2.0", "glob-stream": "^5.3.2", @@ -3891,6 +3988,7 @@ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", "dev": true, + "optional": true, "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -3903,6 +4001,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, + "optional": true, "requires": { "once": "^1.4.0" } @@ -3911,13 +4010,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3927,6 +4028,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3942,6 +4044,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3953,6 +4056,7 @@ "resolved": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz", "integrity": "sha1-3uUim98KtrogEqOV4bhpq/iBNHM=", "dev": true, + "optional": true, "requires": { "onetime": "^1.0.0", "set-immediate-shim": "^1.0.0" @@ -3962,7 +4066,8 @@ "version": "1.1.0", "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4061,7 +4166,7 @@ }, "engine.io-client": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", "dev": true, "requires": { @@ -4263,7 +4368,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true } @@ -4358,7 +4463,7 @@ "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4368,13 +4473,13 @@ "eslint-utils": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", "dev": true }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", "dev": true }, "espree": { @@ -4397,7 +4502,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -4406,7 +4511,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -4482,7 +4587,7 @@ "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -4691,7 +4796,7 @@ "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", "dev": true, "requires": { "is-number": "^2.1.0", @@ -4945,6 +5050,7 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, + "optional": true, "requires": { "pend": "~1.2.0" } @@ -4992,13 +5098,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=", - "dev": true + "dev": true, + "optional": true }, "filenamify": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=", "dev": true, + "optional": true, "requires": { "filename-reserved-regex": "^1.0.0", "strip-outer": "^1.0.0", @@ -5244,8 +5352,9 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", + "dev": true, + "optional": true }, "fs-extra": { "version": "1.0.0", @@ -5869,6 +5978,7 @@ "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-1.1.0.tgz", "integrity": "sha1-iUhUSRvFkbDxR9euVw9cZ4tyVus=", "dev": true, + "optional": true, "requires": { "rc": "^1.1.2" } @@ -6030,7 +6140,7 @@ "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -6175,6 +6285,7 @@ "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", "dev": true, + "optional": true, "requires": { "create-error-class": "^3.0.1", "duplexer2": "^0.1.4", @@ -6198,6 +6309,7 @@ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.0.2" } @@ -6206,19 +6318,22 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, + "optional": true, "requires": { "error-ex": "^1.2.0" } @@ -6228,6 +6343,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6243,6 +6359,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -6262,7 +6379,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true + "dev": true, + "optional": true }, "growly": { "version": "1.3.0", @@ -6579,6 +6697,7 @@ "resolved": "https://registry.npmjs.org/gulp-decompress/-/gulp-decompress-1.2.0.tgz", "integrity": "sha1-jutlpeAV+O2FMsr+KEVJYGJvDcc=", "dev": true, + "optional": true, "requires": { "archive-type": "^3.0.0", "decompress": "^3.0.0", @@ -6590,13 +6709,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6612,6 +6733,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -6621,7 +6743,7 @@ "gulp-eslint": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", - "integrity": "sha1-KiaECV93Syz3kxAmIHjFbMehK1I=", + "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", "dev": true, "requires": { "eslint": "^5.0.1", @@ -7229,6 +7351,7 @@ "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", "dev": true, + "optional": true, "requires": { "convert-source-map": "^1.1.1", "graceful-fs": "^4.1.2", @@ -7241,13 +7364,15 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "dev": true, + "optional": true }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, + "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -7257,6 +7382,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -8072,7 +8198,7 @@ "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { "is-relative": "^1.0.0", @@ -8139,7 +8265,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-bzip2/-/is-bzip2-1.0.0.tgz", "integrity": "sha1-XuWOqlounIDiFAe+3yOuWsCRs/w=", - "dev": true + "dev": true, + "optional": true }, "is-callable": { "version": "1.1.4", @@ -8274,7 +8401,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", "integrity": "sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=", - "dev": true + "dev": true, + "optional": true }, "is-jpg": { "version": "1.0.1", @@ -8287,7 +8415,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.1.1.tgz", "integrity": "sha1-fUxXKDd+84bD4ZSpkRv1fG3DNec=", - "dev": true + "dev": true, + "optional": true }, "is-number": { "version": "3.0.0", @@ -8353,7 +8482,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true + "dev": true, + "optional": true }, "is-regex": { "version": "1.0.4", @@ -8367,7 +8497,7 @@ "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { "is-unc-path": "^1.0.0" @@ -8376,14 +8506,15 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true + "dev": true, + "optional": true }, "is-stream": { "version": "1.1.0", @@ -8413,7 +8544,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-tar/-/is-tar-1.0.0.tgz", "integrity": "sha1-L2suF5LB9bs2UZrKqdZcDSb+hT0=", - "dev": true + "dev": true, + "optional": true }, "is-typedarray": { "version": "1.0.0", @@ -8424,7 +8556,7 @@ "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { "unc-path-regex": "^0.1.2" @@ -8434,7 +8566,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true + "dev": true, + "optional": true }, "is-utf8": { "version": "0.2.1", @@ -8446,7 +8579,8 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", - "dev": true + "dev": true, + "optional": true }, "is-windows": { "version": "1.0.2", @@ -8464,7 +8598,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-zip/-/is-zip-1.0.0.tgz", "integrity": "sha1-R7Co/004p2QxzP2ZqOFaTIa6IyU=", - "dev": true + "dev": true, + "optional": true }, "isarray": { "version": "0.0.1", @@ -8581,7 +8716,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8804,6 +8939,7 @@ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.0.5" }, @@ -8812,13 +8948,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -8834,6 +8972,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -9139,7 +9278,8 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true + "dev": true, + "optional": true }, "lodash.isobject": { "version": "2.4.1", @@ -9335,8 +9475,9 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", + "dev": true, + "optional": true }, "lpad-align": { "version": "1.1.2", @@ -9369,7 +9510,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "requires": { "pify": "^3.0.0" @@ -9386,7 +9527,7 @@ "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -9583,7 +9724,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "minimatch": { @@ -9772,7 +9913,8 @@ "version": "1.0.0", "resolved": "http://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=", - "dev": true + "dev": true, + "optional": true }, "node.extend": { "version": "1.1.8", @@ -12735,7 +12877,7 @@ "dependencies": { "minimist": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, @@ -12822,7 +12964,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "dev": true, + "optional": true }, "p-pipe": { "version": "1.2.0", @@ -13126,7 +13269,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, "posix-character-classes": { @@ -13533,7 +13676,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true + "dev": true, + "optional": true }, "preserve": { "version": "0.2.0", @@ -13665,6 +13809,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "optional": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -13677,6 +13822,7 @@ "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", "dev": true, + "optional": true, "requires": { "pinkie-promise": "^2.0.0", "readable-stream": "^2.0.0" @@ -13686,13 +13832,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13708,6 +13856,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -13812,7 +13961,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -14310,7 +14459,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "sax": { @@ -14324,6 +14473,7 @@ "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", "dev": true, + "optional": true, "requires": { "commander": "~2.8.1" } @@ -14469,7 +14619,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true + "dev": true, + "optional": true }, "set-value": { "version": "2.0.0", @@ -14497,7 +14648,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, "shebang-command": { @@ -14775,7 +14926,7 @@ }, "socket.io-parser": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", "dev": true, "requires": { @@ -14920,7 +15071,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "optional": true, @@ -14974,7 +15125,8 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", - "dev": true + "dev": true, + "optional": true }, "static-extend": { "version": "0.1.2", @@ -15018,6 +15170,7 @@ "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", "dev": true, + "optional": true, "requires": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" @@ -15028,6 +15181,7 @@ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.0.2" } @@ -15036,13 +15190,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15058,6 +15214,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15067,19 +15224,20 @@ "stream-consume": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", - "integrity": "sha1-0721mMK9CugrjKx6xQsRB6eZbEg=", + "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", "dev": true }, "stream-shift": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true + "dev": true, + "optional": true }, "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", + "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { "date-format": "^1.2.0", @@ -15106,7 +15264,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -15121,7 +15279,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -15138,7 +15296,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -15252,6 +15410,7 @@ "resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", "integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=", "dev": true, + "optional": true, "requires": { "chalk": "^1.0.0", "get-stdin": "^4.0.1", @@ -15265,13 +15424,15 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "dev": true, + "optional": true }, "chalk": { "version": "1.1.3", "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, + "optional": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -15285,6 +15446,7 @@ "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz", "integrity": "sha1-hHSREZ/MtftDYhfMc39/qtUPYD8=", "dev": true, + "optional": true, "requires": { "is-relative": "^0.1.0" } @@ -15293,13 +15455,15 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", "integrity": "sha1-kF/uiuhvRbPsYUvDwVyGnfCHboI=", - "dev": true + "dev": true, + "optional": true }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "dev": true, + "optional": true } } }, @@ -15328,8 +15492,9 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15363,6 +15528,7 @@ "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.3.tgz", "integrity": "sha1-HGYfZnBX9jvLeHWqFDi8FiUlFW4=", "dev": true, + "optional": true, "requires": { "chalk": "^1.0.0" }, @@ -15371,13 +15537,15 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "dev": true, + "optional": true }, "chalk": { "version": "1.1.3", "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, + "optional": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -15390,7 +15558,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "dev": true, + "optional": true } } }, @@ -15452,6 +15621,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "dev": true, + "optional": true, "requires": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -15467,6 +15637,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "dev": true, + "optional": true, "requires": { "once": "^1.4.0" } @@ -15475,22 +15646,25 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15506,6 +15680,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15610,6 +15785,7 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", "dev": true, + "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -15634,7 +15810,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.3.tgz", "integrity": "sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=", - "dev": true + "dev": true, + "optional": true }, "timers-ext": { "version": "0.1.7", @@ -15696,7 +15873,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -15707,6 +15884,7 @@ "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz", "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1" }, @@ -15716,6 +15894,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -15731,8 +15910,9 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", + "dev": true, + "optional": true }, "to-fast-properties": { "version": "2.0.0", @@ -15811,6 +15991,7 @@ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -16059,18 +16240,19 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=", - "dev": true + "dev": true, + "optional": true }, "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { "punycode": "^2.1.0" @@ -16087,6 +16269,7 @@ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, + "optional": true, "requires": { "prepend-http": "^1.0.1" } @@ -16172,7 +16355,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", - "dev": true + "dev": true, + "optional": true }, "validate-npm-package-license": { "version": "3.0.4", @@ -16217,6 +16401,7 @@ "resolved": "https://registry.npmjs.org/vinyl-assign/-/vinyl-assign-1.2.1.tgz", "integrity": "sha1-TRmIkbVRWRHXcajNnFSApGoHSkU=", "dev": true, + "optional": true, "requires": { "object-assign": "^4.0.1", "readable-stream": "^2.0.0" @@ -16226,19 +16411,22 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16254,6 +16442,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -16393,6 +16582,7 @@ "resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz", "integrity": "sha1-0bFPOdLiy0q4xAmPdW/ksWTkc9Q=", "dev": true, + "optional": true, "requires": { "wrap-fn": "^0.1.0" } @@ -16483,6 +16673,7 @@ "resolved": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.5.tgz", "integrity": "sha1-8htuQQFv9KfjFyDbxjoJAWvfmEU=", "dev": true, + "optional": true, "requires": { "co": "3.1.0" } @@ -16586,6 +16777,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, + "optional": true, "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 3a874f83c6..a548820138 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -8,6 +8,7 @@ var evts = []; var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode; + var watchingCulture = false; //setup scope vars $scope.defaultButton = null; @@ -26,26 +27,58 @@ $scope.allowOpen = true; $scope.app = null; + //initializes any watches + function startWatches(content) { + + //watch for changes to isNew & the content.id, set the page.isNew accordingly and load the breadcrumb if we can + $scope.$watchGroup(['isNew', 'content.id'], function (newVal, oldVal) { + + var contentId = newVal[1]; + $scope.page.isNew = Object.toBoolean(newVal[0]); + + //We fetch all ancestors of the node to generate the footer breadcrumb navigation + if (!$scope.page.isNew && contentId && content.parentId && content.parentId !== -1) { + loadBreadcrumb(); + if (!watchingCulture) { + $scope.$watch('culture', + function (value, oldValue) { + if (value !== oldValue) { + loadBreadcrumb(); + } + }); + } + } + }); + + } + + //this initializes the editor with the data which will be called more than once if the data is re-loaded function init() { - + var content = $scope.content; - + + if (content.id && content.isChildOfListView && content.trashed === false) { + $scope.page.listViewPath = ($routeParams.page) ? + "/content/content/edit/" + content.parentId + "?page=" + $routeParams.page : + "/content/content/edit/" + content.parentId; + } + // we need to check wether an app is present in the current data, if not we will present the default app. var isAppPresent = false; - + // on first init, we dont have any apps. but if we are re-initializing, we do, but ... if ($scope.app) { - + // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.) - _.forEach(content.apps, function(app) { + _.forEach(content.apps, function (app) { if (app === $scope.app) { isAppPresent = true; } }); - + // if we did reload our DocType, but still have the same app we will try to find it by the alias. if (isAppPresent === false) { - _.forEach(content.apps, function(app) { + _.forEach(content.apps, function (app) { if (app.alias === $scope.app.alias) { isAppPresent = true; app.active = true; @@ -53,7 +86,7 @@ } }); } - + } // if we still dont have a app, lets show the first one: @@ -61,21 +94,8 @@ content.apps[0].active = true; $scope.appChanged(content.apps[0]); } - - editorState.set(content); - //We fetch all ancestors of the node to generate the footer breadcrumb navigation - if (!$scope.page.isNew) { - if (content.parentId && content.parentId !== -1) { - loadBreadcrumb(); - $scope.$watch('culture', - function (value, oldValue) { - if (value !== oldValue) { - loadBreadcrumb(); - } - }); - } - } + editorState.set(content); bindEvents(); @@ -118,10 +138,10 @@ function isContentCultureVariant() { return $scope.content.variants.length > 1; } - + function reload() { $scope.page.loading = true; - loadContent().then(function() { + loadContent().then(function () { $scope.page.loading = false; }); } @@ -134,7 +154,7 @@ evts.push(eventsService.on("editors.documentType.saved", function (name, args) { // if this content item uses the updated doc type we need to reload the content item - if(args && args.documentType && $scope.content.documentType.id === args.documentType.id) { + if (args && args.documentType && $scope.content.documentType.id === args.documentType.id) { reload(); } })); @@ -152,12 +172,6 @@ $scope.content = data; - if (data.isChildOfListView && data.trashed === false) { - $scope.page.listViewPath = ($routeParams.page) ? - "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page : - "/content/content/edit/" + data.parentId; - } - init(); syncTreeNode($scope.content, $scope.content.path, true); @@ -219,7 +233,7 @@ $scope.page.showPreviewButton = true; } - + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ function syncTreeNode(content, path, initialLoad) { @@ -228,9 +242,13 @@ } if (!$scope.content.isChildOfListView) { - navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - $scope.page.menu.currentNode = syncArgs.node; - }); + navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }) + .then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; + }, function () { + //handle the rejection + console.log("A problem occurred syncing the tree! A path is probably incorrect.") + }); } else if (initialLoad === true) { @@ -328,7 +346,7 @@ $scope.contentForm.$dirty = false; for (var i = 0; i < $scope.content.variants.length; i++) { - if($scope.content.variants[i].isDirty){ + if ($scope.content.variants[i].isDirty) { $scope.contentForm.$dirty = true; return; } @@ -337,7 +355,7 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid var fieldsToRollback = checkValidility(); @@ -348,7 +366,8 @@ scope: $scope, content: $scope.content, action: args.action, - showNotifications: args.showNotifications + showNotifications: args.showNotifications, + softRedirect: true }).then(function (data) { //success init(); @@ -364,11 +383,6 @@ function (err) { syncTreeNode($scope.content, $scope.content.path); - //error - if (err) { - editorState.set($scope.content); - } - resetNestedFieldValiation(fieldsToRollback); return $q.reject(err); @@ -421,9 +435,9 @@ //need to show a notification else it's not clear there was an error. localizationService.localizeMany([ - "speechBubbles_validationFailedHeader", - "speechBubbles_validationFailedMessage" - ] + "speechBubbles_validationFailedHeader", + "speechBubbles_validationFailedMessage" + ] ).then(function (data) { notificationsService.error(data[0], data[1]); }); @@ -440,6 +454,7 @@ $scope.content = data; init(); + startWatches($scope.content); resetLastListPageNumber($scope.content); @@ -454,13 +469,14 @@ $scope.page.loading = true; loadContent().then(function () { + startWatches($scope.content); $scope.page.loading = false; }); } $scope.unpublish = function () { clearNotifications($scope.content); - if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { + if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { var dialog = { parentScope: $scope, view: "views/content/overlays/unpublish.html", @@ -494,6 +510,7 @@ overlayService.close(); } }; + overlayService.open(dialog); } }; @@ -510,7 +527,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_saveToPublish", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -518,14 +535,14 @@ saveMethod: contentResource.sendToPublish, action: "sendToPublish", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -534,7 +551,7 @@ return $q.when(err); }); }, - close: function() { + close: function () { overlayService.close(); } }; @@ -563,14 +580,13 @@ if (isContentCultureVariant()) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { - var dialog = { parentScope: $scope, view: "views/content/overlays/publish.html", variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_saveAndPublish", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -578,14 +594,14 @@ saveMethod: contentResource.publish, action: "publish", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -594,11 +610,10 @@ return $q.when(err); }); }, - close: function() { + close: function () { overlayService.close(); } }; - overlayService.open(dialog); } else { @@ -617,7 +632,7 @@ $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; - });; + }); } }; @@ -634,7 +649,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_save", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -642,14 +657,14 @@ saveMethod: $scope.saveMethod(), action: "save", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -658,7 +673,7 @@ return $q.when(err); }); }, - close: function(oldModel) { + close: function (oldModel) { overlayService.close(); } }; @@ -685,7 +700,7 @@ }; - $scope.schedule = function() { + $scope.schedule = function () { clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "schedule" })) { @@ -755,7 +770,7 @@ } }; - $scope.publishDescendants = function() { + $scope.publishDescendants = function () { clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) { @@ -883,13 +898,13 @@ * @param {any} app */ $scope.appChanged = function (app) { - + $scope.app = app; - + $scope.$broadcast("editors.apps.appChanged", { app: app }); - + createButtons($scope.content); - + }; /** @@ -907,11 +922,11 @@ $scope.infiniteModel.close($scope.infiniteModel); } }; - + /** * Call back when user click the back-icon */ - $scope.onBack = function() { + $scope.onBack = function () { if ($scope.infiniteModel && $scope.infiniteModel.close) { $scope.infiniteModel.close($scope.infiniteModel); } else { @@ -927,9 +942,7 @@ } //since we are not notifying and clearing server validation messages when they are received due to how the variant //switching works, we need to ensure they are cleared when this editor is destroyed - if (!$scope.page.isNew) { - serverValidationManager.clear(); - } + serverValidationManager.clear(); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index e58ff14e21..e2f5f71781 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -328,6 +328,8 @@ isInfoTab = true; loadAuditTrail(); loadRedirectUrls(); + setNodePublishStatus(); + formatDatesToLocal(); } else { isInfoTab = false; } @@ -344,6 +346,7 @@ loadAuditTrail(true); loadRedirectUrls(); setNodePublishStatus(); + formatDatesToLocal(); } updateCurrentUrls(); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js index 4999f7007a..75fa0469bb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js @@ -1,10 +1,12 @@ (function () { 'use strict'; - function EditorContentHeader() { + function EditorContentHeader(serverValidationManager) { function link(scope, el, attr, ctrl) { - + + var unsubscribe = []; + if (!scope.serverValidationNameField) { scope.serverValidationNameField = "Name"; } @@ -15,9 +17,44 @@ scope.vm = {}; scope.vm.dropdownOpen = false; scope.vm.currentVariant = ""; - + scope.vm.variantsWithError = []; + scope.vm.defaultVariant = null; + + scope.vm.errorsOnOtherVariants = false;// indicating wether to show that other variants, than the current, have errors. + + function checkErrorsOnOtherVariants() { + var check = false; + angular.forEach(scope.content.variants, function (variant) { + if (scope.openVariants.indexOf(variant.language.culture) === -1 && scope.variantHasError(variant.language.culture)) { + check = true; + } + }); + scope.vm.errorsOnOtherVariants = check; + } + + function onCultureValidation(valid, errors, allErrors, culture) { + var index = scope.vm.variantsWithError.indexOf(culture); + if(valid === true) { + if (index !== -1) { + scope.vm.variantsWithError.splice(index, 1); + } + } else { + if (index === -1) { + scope.vm.variantsWithError.push(culture); + } + } + checkErrorsOnOtherVariants(); + } + function onInit() { + // find default. + angular.forEach(scope.content.variants, function (variant) { + if (variant.language.isDefault) { + scope.vm.defaultVariant = variant; + } + }); + setCurrentVariant(); angular.forEach(scope.content.apps, (app) => { @@ -26,12 +63,22 @@ } }); + + angular.forEach(scope.content.variants, function (variant) { + unsubscribe.push(serverValidationManager.subscribe(null, variant.language.culture, null, onCultureValidation)); + }); + + unsubscribe.push(serverValidationManager.subscribe(null, null, null, onCultureValidation)); + + + } function setCurrentVariant() { angular.forEach(scope.content.variants, function (variant) { if (variant.active) { scope.vm.currentVariant = variant; + checkErrorsOnOtherVariants(); } }); } @@ -80,9 +127,24 @@ * @param {any} culture */ scope.variantIsOpen = function(culture) { - if(scope.openVariants.indexOf(culture) !== -1) { + return (scope.openVariants.indexOf(culture) !== -1); + } + + /** + * Check whether a variant has a error, used to display errors in variant switcher. + * @param {any} culture + */ + scope.variantHasError = function(culture) { + // if we are looking for the default language we also want to check for invariant. + if (culture === scope.vm.defaultVariant.language.culture) { + if(scope.vm.variantsWithError.indexOf("invariant") !== -1) { + return true; + } + } + if(scope.vm.variantsWithError.indexOf(culture) !== -1) { return true; } + return false; } onInit(); @@ -103,6 +165,12 @@ } }); } + + scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index eb5fd055eb..3541a1cf68 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -3,107 +3,113 @@ **/ angular.module('umbraco.directives') -.directive('onDragEnter', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragEnter); - }; - elm.on("dragenter", f); - scope.$on("$destroy", function(){ elm.off("dragenter", f);} ); - } - }; -}) - -.directive('onDragLeave', function () { - return function (scope, elm, attrs) { - var f = function (event) { - var rect = this.getBoundingClientRect(); - var getXY = function getCursorPosition(event) { - var x, y; - - if (typeof event.clientX === 'undefined') { - // try touch screen - x = event.pageX + document.documentElement.scrollLeft; - y = event.pageY + document.documentElement.scrollTop; - } else { - x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; - y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; - } - - return { x: x, y : y }; - }; - - var e = getXY(event.originalEvent); - - // Check the mouseEvent coordinates are outside of the rectangle - if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { - scope.$apply(attrs.onDragLeave); + .directive('onDragEnter', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnter); + }; + elm.on("dragenter", f); + scope.$on("$destroy", function () { elm.off("dragenter", f); }); } }; + }) - elm.on("dragleave", f); - scope.$on("$destroy", function(){ elm.off("dragleave", f);} ); - }; -}) + .directive('onDragLeave', function () { + return function (scope, elm, attrs) { + var f = function (event) { + var rect = this.getBoundingClientRect(); + var getXY = function getCursorPosition(event) { + var x, y; -.directive('onDragOver', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragOver); + if (typeof event.clientX === 'undefined') { + // try touch screen + x = event.pageX + document.documentElement.scrollLeft; + y = event.pageY + document.documentElement.scrollTop; + } else { + x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + + return { x: x, y: y }; + }; + + var e = getXY(event.originalEvent); + + // Check the mouseEvent coordinates are outside of the rectangle + if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { + scope.$apply(attrs.onDragLeave); + } }; - elm.on("dragover", f); - scope.$on("$destroy", function(){ elm.off("dragover", f);} ); - } - }; -}) -.directive('onDragStart', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragStart); - }; - elm.on("dragstart", f); - scope.$on("$destroy", function(){ elm.off("dragstart", f);} ); - } - }; -}) + elm.on("dragleave", f); + scope.$on("$destroy", function () { elm.off("dragleave", f); }); + }; + }) -.directive('onDragEnd', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragEnd); - }; - elm.on("dragend", f); - scope.$on("$destroy", function(){ elm.off("dragend", f);} ); - } - }; -}) + .directive('onDragOver', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragOver); + }; + elm.on("dragover", f); + scope.$on("$destroy", function () { elm.off("dragover", f); }); + } + }; + }) -.directive('onDrop', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDrop); - }; - elm.on("drop", f); - scope.$on("$destroy", function(){ elm.off("drop", f);} ); - } - }; -}) + .directive('onDragStart', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragStart); + }; + elm.on("dragstart", f); + scope.$on("$destroy", function () { elm.off("dragstart", f); }); + } + }; + }) -.directive('onOutsideClick', function ($timeout, angularHelper) { - return function (scope, element, attrs) { + .directive('onDragEnd', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnd); + }; + elm.on("dragend", f); + scope.$on("$destroy", function () { elm.off("dragend", f); }); + } + }; + }) - var eventBindings = []; + .directive('onDrop', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDrop); + }; + elm.on("drop", f); + scope.$on("$destroy", function () { elm.off("drop", f); }); + } + }; + }) + + .directive('onOutsideClick', function ($timeout, angularHelper) { + return function (scope, element, attrs) { + + var eventBindings = []; + + function oneTimeClick(event) { + var el = event.target.nodeName; + + //ignore link and button clicks + var els = ["INPUT", "A", "BUTTON"]; + if (els.indexOf(el) >= 0) { return; } - function oneTimeClick(event) { // ignore clicks on new overlay - var parents = $(event.target).parents(".umb-overlay,.umb-tour"); - if(parents.length > 0){ + var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour"); + if (parents.length > 0) { return; } @@ -126,70 +132,70 @@ angular.module('umbraco.directives') } //ignore clicks inside this element - if( $(element).has( $(event.target) ).length > 0 ){ + if ($(element).has($(event.target)).length > 0) { return; } - scope.$apply(attrs.onOutsideClick); - } - - - $timeout(function(){ - - if ("bindClickOn" in attrs) { - - eventBindings.push(scope.$watch(function() { - return attrs.bindClickOn; - }, function(newValue) { - if (newValue === "true") { - $(document).on("click", oneTimeClick); - } else { - $(document).off("click", oneTimeClick); - } - })); - - } else { - $(document).on("click", oneTimeClick); + angularHelper.safeApply(scope, attrs.onOutsideClick); } - scope.$on("$destroy", function() { - $(document).off("click", oneTimeClick); - // unbind watchers - for (var e in eventBindings) { - eventBindings[e](); + $timeout(function () { + + if ("bindClickOn" in attrs) { + + eventBindings.push(scope.$watch(function () { + return attrs.bindClickOn; + }, function (newValue) { + if (newValue === "true") { + $(document).on("click", oneTimeClick); + } else { + $(document).off("click", oneTimeClick); + } + })); + + } else { + $(document).on("click", oneTimeClick); } + scope.$on("$destroy", function () { + $(document).off("click", oneTimeClick); + + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + + }); + }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. + + }; + }) + + .directive('onRightClick', function ($parse) { + + document.oncontextmenu = function (e) { + if (e.target.hasAttribute('on-right-click')) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }; + + return function (scope, el, attrs) { + el.on('contextmenu', function (e) { + e.preventDefault(); + e.stopPropagation(); + var fn = $parse(attrs.onRightClick); + scope.$apply(function () { + fn(scope, { $event: e }); + }); + return false; }); - }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. + }; + }) - }; -}) - -.directive('onRightClick',function($parse){ - - document.oncontextmenu = function (e) { - if(e.target.hasAttribute('on-right-click')) { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - return function(scope,el,attrs){ - el.on('contextmenu',function(e){ - e.preventDefault(); - e.stopPropagation(); - var fn = $parse(attrs.onRightClick); - scope.$apply(function () { - fn(scope, { $event: e }); - }); - return false; - }); - }; -}) - -.directive('onDelayedMouseleave', function ($timeout, $parse) { + .directive('onDelayedMouseleave', function ($timeout, $parse) { return { restrict: 'A', @@ -198,20 +204,20 @@ angular.module('umbraco.directives') var active = false; var fn = $parse(attrs.onDelayedMouseleave); - var leave_f = function(event) { - var callback = function() { - fn(scope, {$event:event}); + var leave_f = function (event) { + var callback = function () { + fn(scope, { $event: event }); }; active = false; - $timeout(function(){ - if(active === false){ + $timeout(function () { + if (active === false) { scope.$apply(callback); } }, 650); }; - var enter_f = function(event, args){ + var enter_f = function (event, args) { active = true; }; @@ -220,7 +226,7 @@ angular.module('umbraco.directives') element.on("mouseenter", enter_f); //unsub events - scope.$on("$destroy", function(){ + scope.$on("$destroy", function () { element.off("mouseleave", leave_f); element.off("mouseenter", enter_f); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index b81e62a66b..4ba4cf96bb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -12,7 +12,6 @@ function treeSearchBox(localizationService, searchService, $q) { searchFromName: "@", showSearch: "@", section: "@", - ignoreUserStartNodes: "@", hideSearchCallback: "=", searchCallback: "=" }, @@ -35,7 +34,6 @@ function treeSearchBox(localizationService, searchService, $q) { scope.showSearch = "false"; } - //used to cancel any request in progress if another one needs to take it's place var canceler = null; @@ -62,11 +60,6 @@ function treeSearchBox(localizationService, searchService, $q) { searchArgs["searchFrom"] = scope.searchFromId; } - //append ignoreUserStartNodes value if there is one - if (scope.ignoreUserStartNodes) { - searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes; - } - searcher(searchArgs).then(function (data) { scope.searchCallback(data); //set back to null so it can be re-created diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index bbbdd392b2..396699866c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -505,6 +505,7 @@ property.showOnMemberProfile = propertyModel.showOnMemberProfile; property.memberCanEdit = propertyModel.memberCanEdit; property.isSensitiveValue = propertyModel.isSensitiveValue; + property.allowCultureVariant = propertyModel.allowCultureVariant; // update existing data types if(model.updateSameDataTypes) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js index 4c1a8747d1..ea57a3fad6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js @@ -96,13 +96,13 @@ Use this directive to generate a pagination. scope.pageNumber = parseInt(scope.pageNumber); } - scope.pagination = []; + let tempPagination = []; var i = 0; if (scope.totalPages <= 10) { for (i = 0; i < scope.totalPages; i++) { - scope.pagination.push({ + tempPagination.push({ val: (i + 1), isActive: scope.pageNumber === (i + 1) }); @@ -119,7 +119,7 @@ Use this directive to generate a pagination. start = Math.min(maxIndex, start); for (i = start; i < (10 + start) ; i++) { - scope.pagination.push({ + tempPagination.push({ val: (i + 1), isActive: scope.pageNumber === (i + 1) }); @@ -129,7 +129,7 @@ Use this directive to generate a pagination. if (start > 0) { localizationService.localize("general_first").then(function(value){ var firstLabel = value; - scope.pagination.unshift({ name: firstLabel, val: 1, isActive: false }, {val: "...",isActive: false}); + tempPagination.unshift({ name: firstLabel, val: 1, isActive: false }, {val: "...",isActive: false}); }); } @@ -137,9 +137,11 @@ Use this directive to generate a pagination. if (start < maxIndex) { localizationService.localize("general_last").then(function(value){ var lastLabel = value; - scope.pagination.push({ val: "...", isActive: false }, { name: lastLabel, val: scope.totalPages, isActive: false }); + tempPagination.push({ val: "...", isActive: false }, { name: lastLabel, val: scope.totalPages, isActive: false }); }); } + + scope.pagination = tempPagination; } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index 61f7f039d9..39d903d85e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -12,27 +12,50 @@ function valPropertyMsg(serverValidationManager) { return { - require: ['^^form', '^^valFormManager', '^^umbProperty'], + require: ['^^form', '^^valFormManager', '^^umbProperty', '?^^umbVariantContent'], replace: true, restrict: "E", template: "
{{errorMsg}}
", scope: {}, link: function (scope, element, attrs, ctrl) { + + var unsubscribe = []; + var watcher = null; + var hasError = false; + //create properties on our custom scope so we can use it in our template + scope.errorMsg = ""; + + //the property form controller api var formCtrl = ctrl[0]; //the valFormManager controller api var valFormManager = ctrl[1]; //the property controller api var umbPropCtrl = ctrl[2]; + //the variants controller api + var umbVariantCtrl = ctrl[3]; - scope.currentProperty = umbPropCtrl.property; + var currentProperty = umbPropCtrl.property; + scope.currentProperty = currentProperty; + var currentCulture = currentProperty.culture; - //if the property is invariant (no culture), then we will explicitly set it to the string 'invariant' - //since this matches the value that the server will return for an invariant property. - var currentCulture = scope.currentProperty.culture || "invariant"; + if (umbVariantCtrl) { + //if we are inside of an umbVariantContent directive - var watcher = null; + var currentVariant = umbVariantCtrl.editor.content; + + // Lets check if we have variants and we are on the default language then ... + if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) { + //This property is locked cause its a invariant property shown on a non-default language. + //Therefor do not validate this field. + return; + } + } + + // if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language. + currentCulture = currentCulture || "invariant"; + // Gets the error message to display function getErrorMsg() { @@ -138,13 +161,6 @@ function valPropertyMsg(serverValidationManager) { } - var hasError = false; - - //create properties on our custom scope so we can use it in our template - scope.errorMsg = ""; - - var unsubscribe = []; - //listen for form validation changes. //The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then // subscribing to this single watch. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js index eb522fe783..a0cc7e3033 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js @@ -7,7 +7,7 @@ **/ function valServer(serverValidationManager) { return { - require: ['ngModel', '?^^umbProperty'], + require: ['ngModel', '?^^umbProperty', '?^^umbVariantContent'], restrict: "A", scope: {}, link: function (scope, element, attr, ctrls) { @@ -18,9 +18,29 @@ function valServer(serverValidationManager) { //we cannot proceed, this validator will be disabled return; } + + // optional reference to the varaint-content-controller, needed to avoid validation when the field is invariant on non-default languages. + var umbVariantCtrl = ctrls.length > 2 ? ctrls[2] : null; var currentProperty = umbPropCtrl.property; var currentCulture = currentProperty.culture; + + if (umbVariantCtrl) { + //if we are inside of an umbVariantContent directive + + var currentVariant = umbVariantCtrl.editor.content; + + // Lets check if we have variants and we are on the default language then ... + if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) { + //This property is locked cause its a invariant property shown on a non-default language. + //Therefor do not validate this field. + return; + } + } + + // if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language. + currentCulture = currentCulture || "invariant"; + var watcher = null; var unsubscribe = []; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index d571de0e2d..b807a4dc31 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -365,28 +365,17 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * * @param {Int} id id of content item to return - * @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to + * @param {Int} culture optional culture to retrieve the item in * @returns {Promise} resourcePromise object containing the content item. * */ - getById: function (id, options) { - var defaults = { - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - + getById: function (id) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - [{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])), + { id: id })), 'Failed to retrieve data for content id ' + id) .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index d5145727ac..753d180880 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -284,31 +284,15 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getAncestors: function (id, type, culture, options) { - var defaults = { - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; + getAncestors: function (id, type, culture) { if (culture === undefined) culture = ""; return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [ - { id: id }, - { type: type }, - { culture: culture }, - { ignoreUserStartNodes: options.ignoreUserStartNodes } - ])), - - 'Failed to retrieve ancestor data for id ' + id); + [{ id: id }, { type: type }, { culture: culture }])), + 'Failed to retrieve ancestor data for id ' + id); }, /** @@ -440,8 +424,7 @@ function entityResource($q, $http, umbRequestHelper) { pageNumber: 1, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder", - ignoreUserStartNodes: false + orderBy: "SortOrder" }; if (options === undefined) { options = {}; @@ -470,8 +453,7 @@ function entityResource($q, $http, umbRequestHelper) { pageSize: options.pageSize, orderBy: options.orderBy, orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter), - ignoreUserStartNodes: options.ignoreUserStartNodes + filter: encodeURIComponent(options.filter) } )), 'Failed to retrieve child data for id ' + parentId); @@ -499,19 +481,12 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, options, canceler) { + search: function (query, type, searchFrom, canceler) { var args = [{ query: query }, { type: type }]; - - if(options !== undefined) { - if (options.searchFrom) { - args.push({ searchFrom: options.searchFrom }); - } - if (options.ignoreUserStartNodes) { - args.push({ ignoreUserStartNodes: options.ignoreUserStartNodes }); - } + if (searchFrom) { + args.push({ searchFrom: searchFrom }); } - var httpConfig = {}; if (canceler) { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js index 75c23e7adf..b52021ed38 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js @@ -1,39 +1,39 @@ /** - * @ngdoc service - * @name umbraco.resources.logViewerResource - * @description Retrives Umbraco log items (by default from JSON files on disk) - * - * - **/ - function logViewerResource($q, $http, umbRequestHelper) { + * @ngdoc service + * @name umbraco.resources.logViewerResource + * @description Retrives Umbraco log items (by default from JSON files on disk) + * + * + **/ +function logViewerResource($q, $http, umbRequestHelper) { //the factory object returned return { - getNumberOfErrors: function () { + getNumberOfErrors: function (startDate, endDate) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "logViewerApiBaseUrl", - "GetNumberOfErrors")), + "GetNumberOfErrors")+ '?startDate='+startDate+ '&endDate='+ endDate ), 'Failed to retrieve number of errors in logs'); }, - getLogLevelCounts: function () { + getLogLevelCounts: function (startDate, endDate) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "logViewerApiBaseUrl", - "GetLogLevelCounts")), + "GetLogLevelCounts")+ '?startDate='+startDate+ '&endDate='+ endDate ), 'Failed to retrieve log level counts'); }, - getMessageTemplates: function () { + getMessageTemplates: function (startDate, endDate) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "logViewerApiBaseUrl", - "GetMessageTemplates")), + "GetMessageTemplates")+ '?startDate='+startDate+ '&endDate='+ endDate ), 'Failed to retrieve log templates'); }, @@ -93,12 +93,12 @@ 'Failed to retrieve common log messages'); }, - canViewLogs: function () { + canViewLogs: function (startDate, endDate) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "logViewerApiBaseUrl", - "GetCanViewLogs")), + "GetCanViewLogs") + '?startDate='+startDate+ '&endDate='+ endDate ), 'Failed to retrieve state if logs can be viewed'); } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 462184c9f2..1d6d5171a1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -329,8 +329,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { filter: '', orderDirection: "Ascending", orderBy: "SortOrder", - orderBySystemField: true, - ignoreUserStartNodes: false + orderBySystemField: true }; if (options === undefined) { options = {}; @@ -368,7 +367,6 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { "GetChildren", [ { id: parentId }, - { ignoreUserStartNodes: options.ignoreUserStartNodes }, { pageNumber: options.pageNumber }, { pageSize: options.pageSize }, { orderBy: options.orderBy }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 1b2be5f635..732f682082 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -5,7 +5,7 @@ * @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by * all editors to share logic and reduce the amount of replicated code among editors. **/ -function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, navigationService, localizationService, serverValidationManager, formHelper) { +function contentEditingHelper(fileManager, $q, $location, $routeParams, editorState, notificationsService, navigationService, localizationService, serverValidationManager, formHelper) { function isValidIdentifier(id) { @@ -34,9 +34,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica return { + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + /** Used by the content editor and mini content editor to perform saving operations */ - // TODO: Make this a more helpful/reusable method for other form operations! we can simplify this form most forms - // = this is already done in the formhelper service contentEditorPerformSave: function (args) { if (!angular.isObject(args)) { throw "args must be an object"; @@ -53,18 +54,19 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica if (args.showNotifications === undefined) { args.showNotifications = true; } - - var redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; - var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true; + if (args.softRedirect === undefined) { + //when true, the url will change but it won't actually re-route + //this is merely here for compatibility, if only the content/media/members used this service we'd prob be ok but tons of editors + //use this service unfortunately and probably packages too. + args.softRedirect = false; + } var self = this; //we will use the default one for content if not specified var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback; - if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, action: args.action })) { - - args.scope.busy = true; + if (formHelper.submitForm({ scope: args.scope, action: args.action })) { return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles(), args.showNotifications) .then(function (data) { @@ -74,26 +76,30 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica self.handleSuccessfulSave({ scope: args.scope, savedContent: data, - redirectOnSuccess: redirectOnSuccess, + softRedirect: args.softRedirect, rebindCallback: function () { rebindCallback.apply(self, [args.content, data]); } }); - args.scope.busy = false; + //update editor state to what is current + editorState.set(args.content); + return $q.resolve(data); }, function (err) { self.handleSaveError({ showNotifications: args.showNotifications, - redirectOnFailure: redirectOnFailure, + softRedirect: args.softRedirect, err: err, rebindCallback: function () { rebindCallback.apply(self, [args.content, err.data]); } }); - args.scope.busy = false; + //update editor state to what is current + editorState.set(args.content); + return $q.reject(err); }); } @@ -265,7 +271,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // if publishing is allowed also allow schedule publish // we add this manually becuase it doesn't have a permission so it wont // get picked up by the loop through permissions - if( _.contains(args.content.allowedActions, "U")) { + if (_.contains(args.content.allowedActions, "U")) { buttons.subButtons.push(createButtonDefinition("SCHEDULE")); buttons.subButtons.push(createButtonDefinition("PUBLISH_DESCENDANTS")); } @@ -274,7 +280,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // so long as it's already published and if the user has access to publish // and the user has access to unpublish (may have been removed via Event) if (!args.create) { - var hasPublishedVariant = args.content.variants.filter(function(variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0; + var hasPublishedVariant = args.content.variants.filter(function (variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0; if (hasPublishedVariant && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) { buttons.subButtons.push(createButtonDefinition("Z")); } @@ -444,7 +450,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var shouldIgnore = function (propName) { return _.some([ "variants", - + "tabs", "properties", "apps", @@ -574,15 +580,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * A function to handle what happens when we have validation issues from the server side * */ + + //TODO: Too many editors use this method for saving when this entire service should only be used for content/media/members, + // there is formHelper.handleError for other editors which should be used! + handleSaveError: function (args) { if (!args.err) { throw "args.err cannot be null"; } - if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) { - throw "args.redirectOnFailure must be set to true or false"; - } - + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error @@ -600,16 +607,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } } - if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. Then we need - // to re-bind any server validation errors after the digest takes place. + if (!this.redirectToCreatedContent(args.err.data.id) || args.softRedirect) { + // If we are not redirecting it's because this is not newly created content, else in some cases we are + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). + // In this case we need to detect what properties have changed and re-bind them with the server data. if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { args.rebindCallback(); } - //notify all validators (don't clear the server validations though since we need to maintain their state because of + // In this case notify all validators (don't clear the server validations though since we need to maintain their state because of // how the variant switcher works in content). server validation state is always cleared when an editor first loads // and in theory when an editor is destroyed. serverValidationManager.notify(); @@ -633,6 +640,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect * when we're creating new content. */ + + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + handleSuccessfulSave: function (args) { if (!args) { @@ -642,14 +653,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica throw "args.savedContent cannot be null"; } - // the default behaviour is to redirect on success. This adds option to prevent when false - args.redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; + if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id) || args.softRedirect) { - if (!args.redirectOnSuccess || !this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { + // If we are not redirecting it's because this is not newly created content, else in some cases we are + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. - //call the callback + // In this case we need to detect what properties have changed and re-bind them with the server data. if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { args.rebindCallback(); } @@ -667,7 +676,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * We need to decide if we need to redirect to edito mode or if we will remain in create mode. * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID */ - redirectToCreatedContent: function (id, modelState) { + redirectToCreatedContent: function (id) { //only continue if we are currently in create mode and not in infinite mode and if the resulting ID is valid if ($routeParams.create && (isValidIdentifier(id))) { @@ -679,7 +688,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //clear the query strings navigationService.clearSearch(["cculture"]); - + //change to new path $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this @@ -699,6 +708,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * For some editors like scripts or entites that have names as ids, these names can change and we need to redirect * to their new paths, this is helper method to do that. */ + + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + redirectToRenamedContent: function (id) { //clear the query strings navigationService.clearSearch(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js index 5e42af9c5e..d00edae410 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -5,13 +5,15 @@ * * @description * Tracks the parent object for complex editors by exposing it as - * an object reference via editorState.current.entity + * an object reference via editorState.current.getCurrent(). + * The state is cleared on each successful route. * * it is possible to modify this object, so should be used with care */ -angular.module('umbraco.services').factory("editorState", function() { +angular.module('umbraco.services').factory("editorState", function ($rootScope) { var current = null; + var state = { /** @@ -40,7 +42,7 @@ angular.module('umbraco.services').factory("editorState", function() { * Since the editorstate entity is read-only, you cannot set it to null * only through the reset() method */ - reset: function() { + reset: function () { current = null; }, @@ -59,9 +61,10 @@ angular.module('umbraco.services').factory("editorState", function() { * editorState.current can not be overwritten, you should only read values from it * since modifying individual properties should be handled by the property editors */ - getCurrent: function() { + getCurrent: function () { return current; - } + }, + }; // TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing. @@ -76,5 +79,13 @@ angular.module('umbraco.services').factory("editorState", function() { } }); + //execute on each successful route (this is only bound once per application since a service is a singleton) + $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { + + //reset the editorState on each successful route chage + state.reset(); + + }); + return state; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 6d319ad90a..8fe6761c94 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -8,11 +8,12 @@ * that need to attach files. * When a route changes successfully, we ensure that the collection is cleared. */ -function fileManager() { +function fileManager($rootScope) { var fileCollection = []; - return { + + var mgr = { /** * @ngdoc function * @name umbraco.services.fileManager#addFiles @@ -24,7 +25,7 @@ function fileManager() { * for the files collection that effectively clears the files for the specified editor. */ setFiles: function (args) { - + //propertyAlias, files if (!angular.isString(args.propertyAlias)) { throw "args.propertyAlias must be a non empty string"; @@ -52,7 +53,7 @@ function fileManager() { fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, metaData: metaData }); } }, - + /** * @ngdoc function * @name umbraco.services.fileManager#getFiles @@ -62,10 +63,10 @@ function fileManager() { * @description * Returns all of the files attached to the file manager */ - getFiles: function() { + getFiles: function () { return fileCollection; }, - + /** * @ngdoc function * @name umbraco.services.fileManager#clearFiles @@ -78,7 +79,17 @@ function fileManager() { clearFiles: function () { fileCollection = []; } -}; + }; + + //execute on each successful route (this is only bound once per application since a service is a singleton) + $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { + //reset the file manager on each route change, the file collection is only relavent + // when working in an editor and submitting data to the server. + //This ensures that memory remains clear of any files and that the editors don't have to manually clear the files. + mgr.clearFiles(); + }); + + return mgr; } angular.module('umbraco.services').factory('fileManager', fileManager); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index b6bcafbddf..0555318bae 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -40,7 +40,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService else { currentForm = args.formCtrl; } - + //the first thing any form must do is broadcast the formSubmitting event args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); @@ -53,10 +53,10 @@ function formHelper(angularHelper, serverValidationManager, notificationsService //reset the server validations serverValidationManager.reset(); - + return true; }, - + /** * @ngdoc function * @name umbraco.services.formHelper#submitForm @@ -75,21 +75,21 @@ function formHelper(angularHelper, serverValidationManager, notificationsService if (!args.scope) { throw "args.scope cannot be null"; } - + args.scope.$broadcast("formSubmitted", { scope: args.scope }); }, showNotifications: function (args) { - if (!args || !args.notifications) { - return false; - } - if (angular.isArray(args.notifications)) { - for (var i = 0; i < args.notifications.length; i++) { - notificationsService.showNotification(args.notifications[i]); + if (!args || !args.notifications) { + return false; } - return true; - } - return false; + if (angular.isArray(args.notifications)) { + for (var i = 0; i < args.notifications.length; i++) { + notificationsService.showNotification(args.notifications[i]); + } + return true; + } + return false; }, /** @@ -104,7 +104,12 @@ function formHelper(angularHelper, serverValidationManager, notificationsService * * @param {object} err The error object returned from the http promise */ - handleError: function (err) { + handleError: function (err) { + + //TODO: Potentially add in the logic to showNotifications like the contentEditingHelper.handleSaveError does so that + // non content editors can just use this method instead of contentEditingHelper.handleSaveError which they should not use + // and they won't need to manually do it. + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error @@ -116,7 +121,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService this.handleServerValidation(err.data.ModelState); //execute all server validation events and subscribers - serverValidationManager.notifyAndClearAllSubscriptions(); + serverValidationManager.notifyAndClearAllSubscriptions(); } } else { @@ -124,7 +129,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService // TODO: All YSOD handling should be done with an interceptor overlayService.ysod(err); } - + }, /** @@ -184,8 +189,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService serverValidationManager.addPropertyError(propertyAlias, culture, "", modelState[e][0]); } - } - else { + } else { //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: // Groups[0].Properties[2].Alias diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 16c5b38a79..d85a59d836 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -143,27 +143,21 @@ function mediaHelper(umbRequestHelper) { */ resolveFileFromEntity: function (mediaEntity, thumbnail) { - if (!angular.isObject(mediaEntity.metaData)) { + if (!angular.isObject(mediaEntity.metaData) || !mediaEntity.metaData.MediaPath) { throw "Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"; } - var values = _.values(mediaEntity.metaData); - for (var i = 0; i < values.length; i++) { - var val = values[i]; - if (angular.isObject(val) && val.PropertyEditorAlias) { - for (var resolver in _mediaFileResolvers) { - if (val.PropertyEditorAlias === resolver) { - //we need to format a property variable that coincides with how the property would be structured - // if it came from the mediaResource just to keep things slightly easier for the file resolvers. - var property = { value: val.Value }; - - return _mediaFileResolvers[resolver](property, mediaEntity, thumbnail); - } - } + if (thumbnail) { + if (this.detectIfImageByExtension(mediaEntity.metaData.MediaPath)) { + return this.getThumbnailFromPath(mediaEntity.metaData.MediaPath); + } + else { + return null; } } - - return ""; + else { + return mediaEntity.metaData.MediaPath; + } }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index ba8334d307..e51b7b818e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -13,7 +13,7 @@ * Section navigation and search, and maintain their state for the entire application lifetime * */ -function navigationService($routeParams, $location, $q, $timeout, $injector, eventsService, umbModelMapper, treeService, appState) { +function navigationService($routeParams, $location, $q, $injector, eventsService, umbModelMapper, treeService, appState) { //the promise that will be resolved when the navigation is ready var navReadyPromise = $q.defer(); @@ -31,7 +31,8 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve //A list of query strings defined that when changed will not cause a reload of the route var nonRoutingQueryStrings = ["mculture", "cculture", "lq"]; var retainedQueryStrings = ["mculture"]; - + //A list of trees that don't cause a route when creating new items (TODO: eventually all trees should do this!) + var nonRoutingTreesOnCreate = ["content", "contentblueprints"]; function setMode(mode) { switch (mode) { @@ -115,16 +116,17 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve } var service = { - + /** * @ngdoc method * @name umbraco.services.navigationService#isRouteChangingNavigation * @methodOf umbraco.services.navigationService * * @description - * Detects if the route param differences will cause a navigation change or if the route param differences are + * Detects if the route param differences will cause a navigation/route change or if the route param differences are * only tracking state changes. - * This is used for routing operations where reloadOnSearch is false and when detecting form dirty changes when navigating to a different page. + * This is used for routing operations where "reloadOnSearch: false" or "reloadOnUrl: false", when detecting form dirty changes when navigating to a different page, + * and when we are creating new entities and moving from a route with the ?create=true parameter to an ID based parameter once it's created. * @param {object} currUrlParams Either a string path or a dictionary of route parameters * @param {object} nextUrlParams Either a string path or a dictionary of route parameters */ @@ -138,6 +140,14 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve nextUrlParams = pathToRouteParts(nextUrlParams); } + //first check if this is a ?create=true url being redirected to it's true url + if (currUrlParams.create === "true" && currUrlParams.id && currUrlParams.section && currUrlParams.tree && currUrlParams.method === "edit" && + !nextUrlParams.create && nextUrlParams.id && nextUrlParams.section === currUrlParams.section && nextUrlParams.tree === currUrlParams.tree && nextUrlParams.method === currUrlParams.method && + nonRoutingTreesOnCreate.indexOf(nextUrlParams.tree.toLowerCase()) >= 0) { + //this means we're coming from a path like /content/content/edit/1234?create=true to the created path like /content/content/edit/9999 + return false; + } + var allowRoute = true; //The only time that we want to not route is if only any of the nonRoutingQueryStrings have changed/added. diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index e853e07092..2165c1b7cb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -77,4 +77,5 @@ angular.module("umbraco.services").factory("overlayService", overlayService); + })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index a2010d20f2..04c431767c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -42,11 +42,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom - } - - return entityResource.search(args.term, "Member", options).then(function (data) { + return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMemberResult(item); }); @@ -71,12 +67,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom, - ignoreUserStartNodes: args.ignoreUserStartNodes - } - - return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { + return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureContentResult(item); }); @@ -101,12 +92,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom, - ignoreUserStartNodes: args.ignoreUserStartNodes - } - - return entityResource.search(args.term, "Media", options).then(function (data) { + return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMediaResult(item); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js index 43022d0e86..b9bfa51122 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js @@ -13,12 +13,15 @@ function serverValidationManager($timeout) { var callbacks = []; /** calls the callback specified with the errors specified, used internally */ - function executeCallback(self, errorsForCallback, callback) { + function executeCallback(self, errorsForCallback, callback, culture) { callback.apply(self, [ - false, //pass in a value indicating it is invalid - errorsForCallback, //pass in the errors for this item - self.items]); //pass in all errors in total + false, // pass in a value indicating it is invalid + errorsForCallback, // pass in the errors for this item + self.items, // pass in all errors in total + culture // pass the culture that we are listing for. + ] + ); } function getFieldErrors(self, fieldName) { @@ -28,7 +31,7 @@ function serverValidationManager($timeout) { //find errors for this field name return _.filter(self.items, function (item) { - return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); }); } @@ -39,27 +42,50 @@ function serverValidationManager($timeout) { if (fieldName && !angular.isString(fieldName)) { throw "fieldName must be a string"; } + + if (!culture) { + culture = "invariant"; + } //find all errors for this property - return _.filter(self.items, function (item) { + return _.filter(self.items, function (item) { return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); } + + function getCultureErrors(self, culture) { + + if (!culture) { + culture = "invariant"; + } + + //find all errors for this property + return _.filter(self.items, function (item) { + return (item.culture === culture); + }); + } function notifyCallbacks(self) { for (var cb in callbacks) { - if (callbacks[cb].propertyAlias === null) { + if (callbacks[cb].propertyAlias === null && callbacks[cb].fieldName !== null) { //its a field error callback var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName); if (fieldErrors.length > 0) { - executeCallback(self, fieldErrors, callbacks[cb].callback); + executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture); } } - else { + else if (callbacks[cb].propertyAlias != null) { //its a property error var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].fieldName); if (propErrors.length > 0) { - executeCallback(self, propErrors, callbacks[cb].callback); + executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture); + } + } + else { + //its a culture error + var cultureErrors = getCultureErrors(self, callbacks[cb].culture); + if (cultureErrors.length > 0) { + executeCallback(self, cultureErrors, callbacks[cb].callback, callbacks[cb].culture); } } } @@ -130,11 +156,14 @@ function serverValidationManager($timeout) { } var id = String.CreateGuid(); - + if (!culture) { + culture = "invariant"; + } + if (propertyAlias === null) { callbacks.push({ propertyAlias: null, - culture: null, + culture: culture, fieldName: fieldName, callback: callback, id: id @@ -142,12 +171,11 @@ function serverValidationManager($timeout) { } else if (propertyAlias !== undefined) { //normalize culture to null - if (!culture) { - culture = null; - } + callbacks.push({ propertyAlias: propertyAlias, - culture: culture, fieldName: fieldName, + culture: culture, + fieldName: fieldName, callback: callback, id: id }); @@ -173,21 +201,20 @@ function serverValidationManager($timeout) { */ unsubscribe: function (propertyAlias, culture, fieldName) { + //normalize culture to null + if (!culture) { + culture = "invariant"; + } + if (propertyAlias === null) { //remove all callbacks for the content field callbacks = _.reject(callbacks, function (item) { - return item.propertyAlias === null && item.culture === null && item.fieldName === fieldName; + return item.propertyAlias === null && item.culture === culture && item.fieldName === fieldName; }); } else if (propertyAlias !== undefined) { - - //normalize culture to null - if (!culture) { - culture = null; - } - //remove all callbacks for the content property callbacks = _.reject(callbacks, function (item) { return item.propertyAlias === propertyAlias && item.culture === culture && @@ -213,7 +240,7 @@ function serverValidationManager($timeout) { //normalize culture to null if (!culture) { - culture = null; + culture = "invariant"; } var found = _.filter(callbacks, function (item) { @@ -235,7 +262,24 @@ function serverValidationManager($timeout) { getFieldCallbacks: function (fieldName) { var found = _.filter(callbacks, function (item) { //returns any callback that have been registered directly against the field - return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); + }); + return found; + }, + + /** + * @ngdoc function + * @name getCultureCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the culture. + */ + getCultureCallbacks: function (culture) { + var found = _.filter(callbacks, function (item) { + //returns any callback that have been registered directly/ONLY against the culture + return (item.culture === culture && item.propertyAlias === null && item.fieldName === null); }); return found; }, @@ -258,7 +302,7 @@ function serverValidationManager($timeout) { if (!this.hasFieldError(fieldName)) { this.items.push({ propertyAlias: null, - culture: null, + culture: "invariant", fieldName: fieldName, errorMsg: errorMsg }); @@ -270,7 +314,7 @@ function serverValidationManager($timeout) { var cbs = this.getFieldCallbacks(fieldName); //call each callback for this error for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback); + executeCallback(this, errorsForCallback, cbs[cb].callback, null); } }, @@ -288,9 +332,9 @@ function serverValidationManager($timeout) { return; } - //normalize culture to null + //normalize culture to "invariant" if (!culture) { - culture = null; + culture = "invariant"; } //only add the item if it doesn't exist @@ -309,9 +353,16 @@ function serverValidationManager($timeout) { var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName); //call each callback for this error for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback); + executeCallback(this, errorsForCallback, cbs[cb].callback, culture); } - }, + + //execute culture specific callbacks here too when a propery error is added + var cultureCbs = this.getCultureCallbacks(culture); + //call each callback for this error + for (var cb in cultureCbs) { + executeCallback(this, errorsForCallback, cultureCbs[cb].callback, culture); + } + }, /** * @ngdoc function @@ -330,7 +381,7 @@ function serverValidationManager($timeout) { //normalize culture to null if (!culture) { - culture = null; + culture = "invariant"; } //remove the item @@ -384,7 +435,7 @@ function serverValidationManager($timeout) { //normalize culture to null if (!culture) { - culture = null; + culture = "invariant"; } var err = _.find(this.items, function (item) { @@ -406,7 +457,7 @@ function serverValidationManager($timeout) { getFieldError: function (fieldName) { var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); }); return err; }, @@ -424,7 +475,7 @@ function serverValidationManager($timeout) { //normalize culture to null if (!culture) { - culture = null; + culture = "invariant"; } var err = _.find(this.items, function (item) { @@ -446,11 +497,33 @@ function serverValidationManager($timeout) { hasFieldError: function (fieldName) { var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); }); return err ? true : false; }, + + /** + * @ngdoc function + * @name hasCultureError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if the given culture has an error + */ + hasCultureError: function (culture) { + + //normalize culture to null + if (!culture) { + culture = "invariant"; + } + + var err = _.find(this.items, function (item) { + return item.culture === culture; + }); + return err ? true : false; + }, /** The array of error messages */ items: [] }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 555e276485..f72c447627 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -422,23 +422,12 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s insertMediaInEditor: function (editor, img) { if (img) { - var hasUdi = img.udi ? true : false; - var data = { alt: img.altText || "", src: (img.url) ? img.url : "nothing.jpg", - id: '__mcenew' + id: '__mcenew', + 'data-udi': img.udi }; - - if (hasUdi) { - data["data-udi"] = img.udi; - } else { - //Considering these fixed because UDI will now be used and thus - // we have no need for rel http://issues.umbraco.org/issue/U4-6228, http://issues.umbraco.org/issue/U4-6595 - //TODO: Kill rel attribute - data["rel"] = img.id; - data["data-id"] = img.id; - } editor.selection.setContent(editor.dom.createHTML('img', data)); @@ -990,7 +979,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } } - if (!href) { + if (!href && !target.anchor) { editor.execCommand('unlink'); return; } @@ -1004,6 +993,10 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s return; } + if (!href) { + href = ""; + } + // Is email and not //user@domain.com and protocol (e.g. mailto:, sip:) is not specified if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf(':') === -1) { // assume it's a mailto link @@ -1153,25 +1146,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s let self = this; - function getIgnoreUserStartNodes(args) { - var ignoreUserStartNodes = false; - // Most property editors have a "config" property with ignoreUserStartNodes on then - if (args.model.config) { - ignoreUserStartNodes = Object.toBoolean(args.model.config.ignoreUserStartNodes); - } - // EXCEPT for the grid's TinyMCE editor, that one wants to be special and the config is called "configuration" instead - else if (args.model.configuration) { - ignoreUserStartNodes = Object.toBoolean(args.model.configuration.ignoreUserStartNodes); - } - return ignoreUserStartNodes; - } - //create link picker self.createLinkPicker(args.editor, function (currentTarget, anchorElement) { var linkPicker = { currentTarget: currentTarget, anchors: editorState.current ? self.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], - ignoreUserStartNodes: getIgnoreUserStartNodes(args), submit: function (model) { self.insertLinkInEditor(args.editor, model.target, anchorElement); editorService.close(); @@ -1185,25 +1164,13 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //Create the insert media plugin self.createMediaPicker(args.editor, function (currentTarget, userData) { - - var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - var startNodeIsVirtual = userData.startMediaIds.length !== 1; - - var ignoreUserStartNodes = getIgnoreUserStartNodes(args); - if (ignoreUserStartNodes) { - ignoreUserStartNodes = true; - startNodeId = -1; - startNodeIsVirtual = true; - } - var mediaPicker = { currentTarget: currentTarget, onlyImages: true, showDetails: true, disableFolderSelect: true, - startNodeId: startNodeId, - startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, + startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], + startNodeIsVirtual: userData.startMediaIds.length !== 1, submit: function (model) { self.insertMediaInEditor(args.editor, model.selection[0]); editorService.close(); diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index eaa2fe7b31..e169d78b36 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -1,6 +1,6 @@ /** Executed when the application starts, binds to events and set global state */ -app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService', 'tourService', 'dashboardResource', - function (userService, $q, $log, $rootScope, $route, $location, urlHelper, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService, tourService, dashboardResource) { +app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', + function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService) { //This sets the default jquery ajax headers to include our csrf token, we // need to user the beforeSend method because our token changes per user/login so @@ -91,13 +91,6 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH $rootScope.locationTitle = "Umbraco - " + $location.$$host; } - //reset the editorState on each successful route chage - editorState.reset(); - - //reset the file manager on each route change, the file collection is only relavent - // when working in an editor and submitting data to the server. - //This ensures that memory remains clear of any files and that the editors don't have to manually clear the files. - fileManager.clearFiles(); }); /** When the route change is rejected - based on checkAuth - we'll prevent the rejected route from executing including @@ -122,7 +115,7 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH }); //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. - //This is the case when a route uses reloadOnSearch: false which is the case for many or our routes so that we are able to maintain + //This is the case when a route uses "reloadOnSearch: false" or "reloadOnUrl: false" which is the case for many or our routes so that we are able to maintain //global state query strings without force re-loading views. //We can then detect if it's a location change that should force a route or not programatically. $rootScope.$on('$routeUpdate', function (event, next) { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index 21d459c598..52dd7ea678 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -174,6 +174,8 @@ a.umb-editor-header__close-split-view:hover { max-width: 50%; white-space: nowrap; + user-select: none; + span { text-overflow: ellipsis; overflow: hidden; @@ -189,6 +191,25 @@ a.umb-variant-switcher__toggle { color: @ui-action-discreet-type-hover; } } + + &.--error { + &::before { + content: '!'; + position: absolute; + top: -8px; + right: -10px; + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 10px; + text-align: center; + font-weight: bold; + background-color: @errorBackground; + color: @errorText; + } + } } .umb-variant-switcher__expand { @@ -205,20 +226,16 @@ a.umb-variant-switcher__toggle { align-items: center; border-bottom: 1px solid @gray-9; position: relative; - &:hover .umb-variant-switcher__name-wrapper { - - } } .umb-variant-switcher__item:last-child { border-bottom: none; } -.umb-variant-switcher_item--current { +.umb-variant-switcher__item.--current { color: @ui-light-active-type; } -.umb-variant-switcher_item--current .umb-variant-switcher__name-wrapper { - //background-color: @gray-10; +.umb-variant-switcher__item.--current .umb-variant-switcher__name-wrapper { border-left: 4px solid @ui-active; } @@ -227,7 +244,7 @@ a.umb-variant-switcher__toggle { outline: none; } -.umb-variant-switcher_item--not-allowed:not(.umb-variant-switcher_item--current) .umb-variant-switcher__name-wrapper:hover { +.umb-variant-switcher__item.--not-allowed:not(.--current) .umb-variant-switcher__name-wrapper:hover { //background-color: @white !important; cursor: default; } @@ -237,6 +254,29 @@ a.umb-variant-switcher__toggle { cursor: pointer; } +.umb-variant-switcher__item.--error { + .umb-variant-switcher__name { + color: @red; + &::after { + content: '!'; + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 5px; + top: -6px; + width: 14px; + height: 14px; + border-radius: 7px; + font-size: 8px; + text-align: center; + font-weight: bold; + background-color: @errorBackground; + color: @errorText; + } + } +} + .umb-variant-switcher__name-wrapper { font-size: 14px; flex: 1; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less index f7aa0e4558..7b8845542e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less @@ -20,6 +20,15 @@ .umb-logviewer__sidebar { flex: 0 0 @sidebarwidth; + + .flatpickr-input { + background-color: @white; + border: 0; + width: 100%; + text-align: center; + font-size: larger; + padding-top: 20px; + } } @media (max-width: 768px) { diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index a13dbaeb43..9f537a7931 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -72,3 +72,7 @@ line-height: 20px; } } + +.mce-fullscreen { + position:absolute; +} diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index e2a2cfe938..556a4d6aef 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -228,6 +228,7 @@ app.config(function ($routeProvider) { }, reloadOnSearch: false, + reloadOnUrl: false, resolve: canRoute(true) }) .otherwise({ redirectTo: '/login' }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index fb93df28f9..6568cfb567 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -28,11 +28,9 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", searchFromName: null, showSearch: false, results: [], - selectedSearchResults: [], - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes + selectedSearchResults: [] }; - $scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : ""; $scope.showTarget = $scope.model.hideTarget !== true; // this ensures that we only sync the tree once and only when it's ready @@ -89,11 +87,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", }); // get the content properties to build the anchor name list - - var options = {}; - options.ignoreUserStartNodes = dialogOptions.ignoreUserStartNodes; - - contentResource.getById(id, options).then(function (resp) { + contentResource.getById(id).then(function (resp) { handleContentTarget(resp); }); } @@ -143,11 +137,9 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", if (args.node.id < 0) { $scope.model.target.url = "/"; } else { - var options = {}; - options.ignoreUserStartNodes = dialogOptions.ignoreUserStartNodes; - - contentResource.getById(args.node.id, options).then(function (resp) { + contentResource.getById(args.node.id).then(function (resp) { handleContentTarget(resp); + }); } @@ -170,17 +162,9 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.switchToMediaPicker = function () { userService.getCurrentUser().then(function (userData) { - var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - var startNodeIsVirtual = userData.startMediaIds.length !== 1; - if (dialogOptions.ignoreUserStartNodes) { - startNodeId = -1; - startNodeIsVirtual = true; - } - var mediaPicker = { - startNodeId: startNodeId, - startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], + startNodeIsVirtual: userData.startMediaIds.length !== 1, submit: function (model) { var media = model.selection[0]; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html index b7f63cfe22..0bb91d8da6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html @@ -66,7 +66,6 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" - ignore-user-start-nodes="{{searchInfo.ignoreUserStartNodes}}" section="{{section}}"> @@ -83,7 +82,6 @@ section="content" hideheader="true" hideoptions="true" - customtreeparams="{{customTreeParams}}" api="dialogTreeApi" on-init="onTreeInit()" enablelistviewexpand="true" diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index f37e1156a8..3a0deb812e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Editors.MediaPickerController", - function ($scope, mediaResource, entityResource, mediaHelper, mediaTypeHelper, eventsService, userService, treeService, localStorageService, localizationService, editorService) { + function($scope, mediaResource, entityResource, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) { if (!$scope.model.title) { localizationService.localizeMany(["defaultdialogs_selectMedia", "general_includeFromsubFolders"]) @@ -20,13 +20,11 @@ angular.module("umbraco") $scope.showDetails = dialogOptions.showDetails; $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - $scope.ignoreUserStartNodes = Object.toBoolean(dialogOptions.ignoreUserStartNodes); $scope.cropSize = dialogOptions.cropSize; $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); $scope.lockedFolder = true; $scope.allowMediaEdit = dialogOptions.allowMediaEdit ? dialogOptions.allowMediaEdit : false; - var userStartNodes = []; var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); if ($scope.onlyImages) { @@ -56,8 +54,7 @@ angular.module("umbraco") pageSize: 100, totalItems: 0, totalPages: 0, - filter: "", - ignoreUserStartNodes: $scope.model.ignoreUserStartNodes + filter: '' }; //preload selected item @@ -67,19 +64,15 @@ angular.module("umbraco") } function onInit() { - userService.getCurrentUser().then(function(userData) { - userStartNodes = userData.startMediaIds; - - if ($scope.startNodeId !== -1) { - entityResource.getById($scope.startNodeId, "media") - .then(function(ent) { - $scope.startNodeId = ent.id; - run(); - }); - } else { - run(); - } - }); + if ($scope.startNodeId !== -1) { + entityResource.getById($scope.startNodeId, "media") + .then(function (ent) { + $scope.startNodeId = ent.id; + run(); + }); + } else { + run(); + } } function run() { @@ -150,7 +143,7 @@ angular.module("umbraco") } }; - $scope.gotoFolder = function (folder) { + $scope.gotoFolder = function(folder) { if (!$scope.multiPicker) { deselectAllImages($scope.model.selection); } @@ -159,10 +152,8 @@ angular.module("umbraco") folder = { id: -1, name: "Media", icon: "icon-folder" }; } - var options = {}; if (folder.id > 0) { - options.ignoreUserStartNodes = $scope.model.ignoreUserStartNodes; - entityResource.getAncestors(folder.id, "media", options) + entityResource.getAncestors(folder.id, "media") .then(function(anc) { $scope.path = _.filter(anc, function(f) { @@ -178,26 +169,13 @@ angular.module("umbraco") $scope.path = []; } - $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; - + $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual; $scope.currentFolder = folder; localStorageService.set("umbLastOpenedMediaNodeId", folder.id); - options.ignoreUserStartNodes = $scope.ignoreUserStartNodes; - return getChildren(folder.id, options); + return getChildren(folder.id); }; - function hasFolderAccess(node) { - var nodePath = node.path ? node.path.split(',') : [node.id]; - - for (var i = 0; i < nodePath.length; i++) { - if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) - return true; - } - - return false; - } - $scope.clickHandler = function(image, event, index) { if (image.isFolder) { if ($scope.disableFolderSelect) { @@ -321,8 +299,7 @@ angular.module("umbraco") pageSize: 100, totalItems: 0, totalPages: 0, - filter: "", - ignoreUserStartNodes: $scope.model.ignoreUserStartNodes + filter: '' }; getChildren($scope.currentFolder.id); } @@ -390,9 +367,9 @@ angular.module("umbraco") } } - function getChildren(id, options) { + function getChildren(id) { $scope.loading = true; - return mediaResource.getChildren(id, options) + return mediaResource.getChildren(id) .then(function(data) { $scope.searchOptions.filter = ""; $scope.images = data.items ? data.items : []; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 247f694470..915abf62b0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -36,7 +36,6 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", selectedSearchResults: [] } vm.startNodeId = $scope.model.startNodeId; - vm.ignoreUserStartNodes = $scope.model.ignoreUserStartNodes; //Used for toggling an empty-state message //Some trees can have no items (dictionary & forms email templates) vm.hasItems = true; @@ -172,9 +171,6 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", if (vm.startNodeId) { queryParams["startNodeId"] = $scope.model.startNodeId; } - if (vm.ignoreUserStartNodes) { - queryParams["ignoreUserStartNodes"] = $scope.model.ignoreUserStartNodes; - } if (vm.selectedLanguage && vm.selectedLanguage.id) { queryParams["culture"] = vm.selectedLanguage.culture; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html index acd838f7bf..c592b4ec3b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html @@ -27,7 +27,7 @@ {{language.name}} - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index 829582329f..737253feb2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -12,7 +12,7 @@   diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html index 0f7a61fa01..87e94193e1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html @@ -29,7 +29,7 @@ autocomplete="off" maxlength="255" /> - + {{vm.currentVariant.language.name}}   @@ -39,9 +39,9 @@ - + - {{variant.language.name}} + {{variant.language.name}}
Open in split view
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html index 817a4f2a94..fae431a11a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html @@ -7,7 +7,7 @@ -
  • - {{ language.name }} + {{ language.name }} {{ language.culture }} diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js index b64ad36245..fc24fbe1bc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js @@ -22,16 +22,32 @@ } }; + let querystring = $location.search(); + if(querystring.startDate){ + vm.startDate = querystring.startDate; + }else{ + vm.startDate = new Date(Date.now()); + vm.startDate.setDate(vm.startDate.getDate()-1); + vm.startDate = vm.startDate.toIsoDateString(); + } + + if(querystring.endDate){ + vm.endDate = querystring.endDate; + }else{ + vm.endDate = new Date(Date.now()).toIsoDateString(); + } + vm.period = [vm.startDate, vm.endDate]; + + //functions vm.searchLogQuery = searchLogQuery; vm.findMessageTemplate = findMessageTemplate; - + function preFlightCheck(){ vm.loading = true; - //Do our pre-flight check (to see if we can view logs) //IE the log file is NOT too big such as 1GB & crash the site - logViewerResource.canViewLogs().then(function(result){ + logViewerResource.canViewLogs(vm.startDate, vm.endDate).then(function(result){ vm.loading = false; vm.canLoadLogs = result; @@ -46,7 +62,7 @@ function init() { vm.loading = true; - + var savedSearches = logViewerResource.getSavedSearches().then(function (data) { vm.searches = data; }, @@ -80,11 +96,11 @@ ] }); - var numOfErrors = logViewerResource.getNumberOfErrors().then(function (data) { + var numOfErrors = logViewerResource.getNumberOfErrors(vm.startDate, vm.endDate).then(function (data) { vm.numberOfErrors = data; }); - var logCounts = logViewerResource.getLogLevelCounts().then(function (data) { + var logCounts = logViewerResource.getLogLevelCounts(vm.startDate, vm.endDate).then(function (data) { vm.logTypeData = []; vm.logTypeData.push(data.Information); vm.logTypeData.push(data.Debug); @@ -93,7 +109,7 @@ vm.logTypeData.push(data.Fatal); }); - var commonMsgs = logViewerResource.getMessageTemplates().then(function(data){ + var commonMsgs = logViewerResource.getMessageTemplates(vm.startDate, vm.endDate).then(function(data){ vm.commonLogMessages = data; }); @@ -108,7 +124,7 @@ } function searchLogQuery(logQuery){ - $location.path("/settings/logViewer/search").search({lq: logQuery}); + $location.path("/settings/logViewer/search").search({lq: logQuery, startDate: vm.startDate, endDate: vm.endDate}); } function findMessageTemplate(template){ @@ -116,8 +132,38 @@ searchLogQuery(logQuery); } - preFlightCheck(); + + + preFlightCheck(); + + ///////////////////// + + vm.config = { + enableTime: false, + dateFormat: "Y-m-d", + time_24hr: false, + mode: "range", + maxDate: "today", + conjunction: " to " + }; + + vm.dateRangeChange = function(selectedDates, dateStr, instance) { + + if(selectedDates.length > 0){ + vm.startDate = selectedDates[0].toIsoDateString(); + vm.endDate = selectedDates[selectedDates.length-1].toIsoDateString(); // Take the last date as end + + if(vm.startDate === vm.endDate){ + vm.period = [vm.startDate]; + }else{ + vm.period = [vm.startDate, vm.endDate]; + } + + preFlightCheck(); + } + + } } angular.module("umbraco").controller("Umbraco.Editors.LogViewer.OverviewController", LogViewerOverviewController); diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html index a46853f97e..854bed755f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html @@ -14,45 +14,45 @@ -
    - - - -

    Today's log file is too large to be viewed and would cause performance problems.

    -

    If you need to view the log files, try opening them manually

    -
    -
    -
    - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js index 74395ffb25..d4b0ea8f8e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js @@ -96,6 +96,14 @@ vm.logOptions.filterExpression = querystring.lq; } + if(querystring.startDate){ + vm.logOptions.startDate = querystring.startDate; + } + + if(querystring.endDate){ + vm.logOptions.endDate = querystring.endDate; + } + vm.loading = true; logViewerResource.getSavedSearches().then(function (data) { @@ -270,7 +278,11 @@ submitButtonLabel: "Save Search", disableSubmitButton: true, view: "logviewersearch", - query: vm.logOptions.filterExpression, + query: { + filterExpression: vm.logOptions.filterExpression, + startDate: vm.logOptions.startDate, + endDate: vm.logOptions.endDate + }, submit: function (model) { //Resource call with two params (name & query) //API that opens the JSON and adds it to the bottom diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js index e91d8ae366..79d837e516 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js @@ -35,7 +35,6 @@ function MacrosEditController($scope, $q, $routeParams, macroResource, editorSta vm.page.saveButtonState = "success"; }, function (error) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: error }); diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index d4d538b82c..5f8000569d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -184,7 +184,6 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, contentEditingHelper.handleSuccessfulSave({ scope: $scope, savedContent: data, - redirectOnSuccess: !infiniteMode, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) }); @@ -206,7 +205,6 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, contentEditingHelper.handleSaveError({ err: err, - redirectOnFailure: !infiniteMode, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js index 7ff3cee835..a0da3d0ea8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js @@ -269,10 +269,6 @@ saveMethod: mediaTypeResource.save, scope: $scope, content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, // we need to rebind... the IDs that have been created! rebindCallback: function (origContentType, savedContentType) { vm.contentType.id = savedContentType.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index a76862a1d9..b2d91e2f66 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -137,6 +137,13 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR $scope.page.saveButtonState = "busy"; + //anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here + var passwordProp = _.find(contentEditingHelper.getAllProps($scope.content), function (e) { return e.alias === '_umb_password' }); + if (!passwordProp.value.reset) { + //so if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered + passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword; + } + memberResource.save($scope.content, $routeParams.create, fileManager.getFiles()) .then(function(data) { @@ -161,7 +168,6 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR }, function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); diff --git a/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js index c963d5c0c4..9fb7321712 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js @@ -86,7 +86,6 @@ function MemberGroupsEditController($scope, $routeParams, appState, navigationSe }, function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js index f1d5db0ebe..dff5c65308 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js @@ -180,10 +180,6 @@ saveMethod: memberTypeResource.save, scope: $scope, content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, // we need to rebind... the IDs that have been created! rebindCallback: function (origContentType, savedContentType) { vm.contentType.id = savedContentType.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js index 372cecb36c..a26681652e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js @@ -65,10 +65,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.partialViewMacro, - // We do not redirect on failure for partial view macros - this is because it is not possible to actually save the partial view - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { // create macro if needed diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js index 292898814d..51cca0b5af 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js @@ -83,10 +83,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.partialView, - //We do not redirect on failure for partialviews - this is because it is not possible to actually save the partialviews - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 4d695c97b5..9d810fb433 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -80,7 +80,6 @@ function contentPickerController($scope, entityResource, editorState, iconHelper showOpenButton: false, showEditButton: false, showPathOnHover: false, - ignoreUserStartNodes: false, maxNumber: 1, minNumber: 0, startNode: { @@ -118,8 +117,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.model.config.showOpenButton = Object.toBoolean($scope.model.config.showOpenButton); $scope.model.config.showEditButton = Object.toBoolean($scope.model.config.showEditButton); $scope.model.config.showPathOnHover = Object.toBoolean($scope.model.config.showPathOnHover); - $scope.model.config.ignoreUserStartNodes = Object.toBoolean($scope.model.config.ignoreUserStartNodes); - + var entityType = $scope.model.config.startNode.type === "member" ? "Member" : $scope.model.config.startNode.type === "media" @@ -135,7 +133,6 @@ function contentPickerController($scope, entityResource, editorState, iconHelper entityType: entityType, filterCssClass: "not-allowed not-published", startNodeId: null, - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes, currentNode: editorState ? editorState.current : null, callback: function (data) { if (angular.isArray(data)) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index be4dbb9b12..a589cf8947 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -14,7 +14,6 @@ sortable="!sortableOptions.disabled" allow-remove="allowRemoveButton" allow-open="model.config.showOpenButton && allowOpenButton && !dialogEditor" - ignore-user-startnodes="model.config.ignoreUserStartNodes" on-remove="remove($index)" on-open="openContentEditor(node)"> diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 71bb51f686..eb1032a9c7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -1,32 +1,25 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", function ($scope, $timeout, userService, editorService) { - var ignoreUserStartNodes = Object.toBoolean($scope.model.config.ignoreUserStartNodes); $scope.thumbnailUrl = getThumbnailUrl(); if (!$scope.model.config.startNodeId) { - if (ignoreUserStartNodes === true) { - $scope.model.config.startNodeId = -1; - $scope.model.config.startNodeIsVirtual = true; - - } else { - userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); - } + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); } $scope.setImage = function(){ var startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; var startNodeIsVirtual = startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; + var mediaPicker = { startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, cropSize: $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined, showDetails: true, disableFolderSelect: true, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index 8c5bae56a7..1b84ce4463 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -34,7 +34,7 @@ angular.module("umbraco") // Sortable options // ********************************************* - var draggedRteSettings; + var draggedRteSettings;// holds a dictionary of RTE settings to remember when dragging things around. $scope.sortableOptionsRow = { distance: 10, @@ -92,7 +92,7 @@ angular.module("umbraco") } }; - var notIncludedRte = []; + var notIncludedRte = [];// used for RTEs that has been affected by the sorting var cancelMove = false; var startingArea; $scope.sortableOptionsCell = { @@ -134,17 +134,17 @@ angular.module("umbraco") (startingArea != area && area.maxItems != '' && area.maxItems > 0 && area.maxItems < area.controls.length + 1)) { $scope.$apply(function () { - event.target.getScope_HackForSortable().area.dropNotAllowed = true; + area.dropNotAllowed = true; }); ui.placeholder.hide(); cancelMove = true; } else { - if (event.target.getScope_HackForSortable().area.controls.length == 0) { + if (area.controls.length == 0) { $scope.$apply(function () { - event.target.getScope_HackForSortable().area.dropOnEmpty = true; + area.dropOnEmpty = true; }); ui.placeholder.hide(); } else { @@ -169,16 +169,32 @@ angular.module("umbraco") ui.item.sortable.cancel(); } ui.item.parents(".umb-cell-content").find(".umb-rte").each(function (key, value) { - var v1 = value.id; + var rteId = value.id; - if ($.inArray(v1, notIncludedRte) < 0) { - notIncludedRte.splice(0, 0, v1); + if ($.inArray(rteId, notIncludedRte) < 0) { + + // remember this RTEs settings, cause we need to update it later. + var editor = _.findWhere(tinyMCE.editors, { id: rteId }) + if (editor) { + draggedRteSettings[rteId] = editor.settings; + } + notIncludedRte.splice(0, 0, rteId); } }); } else { $(event.target).find(".umb-rte").each(function () { - if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { + + var rteId = $(this).attr("id"); + + if ($.inArray(rteId, notIncludedRte) < 0) { + + // remember this RTEs settings, cause we need to update it later. + var editor = _.findWhere(tinyMCE.editors, { id: rteId }) + if (editor) { + draggedRteSettings[rteId] = editor.settings; + } + notIncludedRte.splice(0, 0, $(this).attr("id")); } }); @@ -196,17 +212,20 @@ angular.module("umbraco") ui.item[0].style.opacity = "0.5"; // reset dragged RTE settings in case a RTE isn't dragged - draggedRteSettings = undefined; + draggedRteSettings = {}; + notIncludedRte = []; + ui.item[0].style.display = "block"; ui.item.find(".umb-rte").each(function (key, value) { - notIncludedRte = []; + var rteId = value.id; - - var editors = _.findWhere(tinyMCE.editors, { id: rteId }); + + // remember this RTEs settings, cause we need to update it later. + var editor = _.findWhere(tinyMCE.editors, { id: rteId }); // save the dragged RTE settings - if (editors) { - draggedRteSettings = editors.settings; + if (editor) { + draggedRteSettings[rteId] = editor.settings; // remove the dragged RTE tinyMCE.execCommand("mceRemoveEditor", false, rteId); @@ -223,22 +242,29 @@ angular.module("umbraco") ui.item.offsetParent().find(".umb-rte").each(function (key, value) { var rteId = value.id; if ($.inArray(rteId, notIncludedRte) < 0) { + + var editor = _.findWhere(tinyMCE.editors, { id: rteId }); + if (editor) { + draggedRteSettings[rteId] = editor.settings; + } + // add all dragged's neighbouring RTEs in the new cell notIncludedRte.splice(0, 0, rteId); } }); - + // reconstruct the dragged RTE (could be undefined when dragging something else than RTE) if (draggedRteSettings !== undefined) { tinyMCE.init(draggedRteSettings); } - _.forEach(notIncludedRte, function (id) { + _.forEach(notIncludedRte, function (rteId) { // reset all the other RTEs - if (draggedRteSettings === undefined || id !== draggedRteSettings.id) { - var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings; - tinyMCE.execCommand("mceRemoveEditor", false, id); - tinyMCE.init(rteSettings); + if (draggedRteSettings === undefined || rteId !== draggedRteSettings.id) { + tinyMCE.execCommand("mceRemoveEditor", false, rteId); + if (draggedRteSettings[rteId]) { + tinyMCE.init(draggedRteSettings[rteId]); + } } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewpublish.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewpublish.html index ccab8ea97b..ebdc1eb4c4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewpublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewpublish.html @@ -3,19 +3,19 @@

    - +
    - +

    - +
    - +
    - +
    @@ -28,20 +28,20 @@ style="margin-right: 8px;" />
    - +
    - +
    - +
    - +
    - + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.html index 7085400f4f..a171fb1327 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.html @@ -4,16 +4,16 @@

    - +

    - +
    - +
    @@ -26,21 +26,21 @@ style="margin-right: 8px;" />
    - +
    - +
    - +
    - +
    - + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index c937360693..d7cac59348 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -7,12 +7,9 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; - var ignoreUserStartNodes = Object.toBoolean($scope.model.config.ignoreUserStartNodes); $scope.allowEditMedia = false; $scope.allowAddMedia = false; - - function setupViewModel() { $scope.mediaItems = []; $scope.ids = []; @@ -108,16 +105,12 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl } function init() { + userService.getCurrentUser().then(function (userData) { if (!$scope.model.config.startNodeId) { $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; } - if (ignoreUserStartNodes === true) { - $scope.model.config.startNodeId = -1; - $scope.model.config.startNodeIsVirtual = true; - } - // only allow users to add and edit media if they have access to the media section var hasAccessToMedia = userData.allowedSections.indexOf("media") !== -1; $scope.allowEditMedia = hasAccessToMedia; @@ -175,7 +168,6 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl var mediaPicker = { startNodeId: $scope.model.config.startNodeId, startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, multiPicker: multiPicker, onlyImages: onlyImages, disableFolderSelect: disableFolderSelect, 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 f20cfd3caa..734b06536d 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 @@ -68,10 +68,9 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en url: link.url, target: link.target } : null; - + var linkPicker = { currentTarget: target, - ignoreUserStartNodes: Object.toBoolean($scope.model.config.ignoreUserStartNodes), submit: function (model) { if (model.target.url || model.target.anchor) { // if an anchor exists, check that it is appropriately prefixed diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 979baef0f7..d54a17e15a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -25,7 +25,6 @@ section: "content", treeAlias: "content", multiPicker: false, - ignoreUserStartNodes: Object.toBoolean($scope.model.config.ignoreUserStartNodes), idType: $scope.model.config.idType ? $scope.model.config.idType : "int", submit: function (model) { select(model.selection[0]); @@ -48,7 +47,6 @@ section: "content", treeAlias: "content", multiPicker: false, - ignoreUserStartNodes: Object.toBoolean($scope.model.config.ignoreUserStartNodes), idType: $scope.model.config.idType ? $scope.model.config.idType : "udi", submit: function (model) { select(model.selection[0]); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index d1fedf7db6..3ed5f49b92 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -20,8 +20,8 @@ angular.module("umbraco") editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; } - var width = parseInt(editorConfig.dimensions.width, 10) || null; - var height = parseInt(editorConfig.dimensions.height, 10) || null; + var width = editorConfig.dimensions ? parseInt(editorConfig.dimensions.width, 10) || null : null; + var height = editorConfig.dimensions ? parseInt(editorConfig.dimensions.height, 10) || null : null; $scope.containerWidth = editorConfig.mode === "distraction-free" ? (width ? width : "auto") : "auto"; $scope.containerHeight = editorConfig.mode === "distraction-free" ? (height ? height : "auto") : "auto"; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index 0d083f95b9..4aa424396d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -24,13 +24,6 @@ text="{{css.name}}"/> - - -
    - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js index 1667a89c35..7bcbe0716e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js @@ -119,7 +119,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, }, function (error) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: error }); diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js index 2ca93fba4c..34702230b8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js @@ -45,10 +45,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.script, - // We do not redirect on failure for scripts - this is because it is not possible to actually save the script - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js index 541d329e05..75e59605d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js @@ -68,10 +68,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.stylesheet, - // We do not redirect on failure for style sheets - this is because it is not possible to actually save the style sheet - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index d17f556b2f..69b60746c6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -83,10 +83,6 @@ saveMethod: templateResource.save, scope: $scope, content: vm.template, - // We do not redirect on failure for templates - this is because it is not possible to actually save the template - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js index f37cc95174..d110e5d329 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js @@ -81,10 +81,6 @@ saveMethod: userGroupsResource.saveUserGroup, scope: $scope, content: vm.userGroup, - // We do not redirect on failure for users - this is because it is not possible to actually save a user - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) { } }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 2d9fccc399..4cbd779236 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -136,7 +136,10 @@ //anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here if (vm.user.changePassword) { - vm.user.changePassword.reset = !vm.user.changePassword.oldPassword && !vm.user.isCurrentUser; + //NOTE: the check for allowManuallyChangingPassword is due to this legacy user membership provider setting, if that is true, then the current user + //can change their own password without entering their current one (this is a legacy setting since that is a security issue but we need to maintain compat). + //if allowManuallyChangingPassword=false, then we are using default settings and the user will need to enter their old password to change their own password. + vm.user.changePassword.reset = (!vm.user.changePassword.oldPassword && !vm.user.isCurrentUser) || vm.changePasswordModel.config.allowManuallyChangingPassword; } vm.page.saveButtonState = "busy"; @@ -169,7 +172,6 @@ }, function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err, showNotifications: true }); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index fdae716b39..031bff0a47 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -261,7 +261,8 @@
    - - + + paste @@ -47,6 +48,7 @@ directionality tabfocus searchreplace + fullscreen ( controller => controller.PostChangePassword(null)) - }, + }, { "entityApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetById(0, UmbracoEntityTypes.Media)) @@ -307,7 +307,7 @@ namespace Umbraco.Web.Editors }, { "logViewerApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetNumberOfErrors()) + controller => controller.GetNumberOfErrors(null, null)) } } }, diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index de65689353..754d5aca1e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -576,8 +576,13 @@ namespace Umbraco.Web.Editors Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); //we need to reuse the underlying logic so return the result that it wants return OperationResult.Succeed(new EventMessages()); + }, + content => + { + var display = MapToDisplay(content); + SetupBlueprint(display, content); + return display; }); - SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); return contentItemDisplay; } @@ -591,11 +596,15 @@ namespace Umbraco.Web.Editors [OutgoingEditorModelEvent] public ContentItemDisplay PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) { - var contentItemDisplay = PostSaveInternal(contentItem, content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + var contentItemDisplay = PostSaveInternal( + contentItem, + content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id), + MapToDisplay); + return contentItemDisplay; } - private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod) + private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func mapToDisplay) { //Recent versions of IE/Edge may send in the full client side file path instead of just the file name. //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all @@ -626,7 +635,7 @@ namespace Umbraco.Web.Editors { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the model state to the outgoing object and throw a validation message - var forDisplay = MapToDisplay(contentItem.PersistedContent); + var forDisplay = mapToDisplay(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); } @@ -717,11 +726,7 @@ namespace Umbraco.Web.Editors case ContentSaveAction.PublishNew: { var publishStatus = PublishInternal(contentItem, defaultCulture, cultureForInvariantErrors, out wasCancelled, out var successfulCultures); - //global notifications - AddMessageForPublishStatus(new[] { publishStatus }, globalNotifications, successfulCultures); - //variant specific notifications - foreach (var c in successfulCultures) - AddMessageForPublishStatus(new[] { publishStatus }, notifications.GetOrCreate(c), successfulCultures); + AddPublishStatusNotifications(new[] { publishStatus }, globalNotifications, notifications, successfulCultures); } break; case ContentSaveAction.PublishWithDescendants: @@ -737,12 +742,7 @@ namespace Umbraco.Web.Editors } var publishStatus = PublishBranchInternal(contentItem, false, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList(); - - //global notifications - AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); - //variant specific notifications - foreach (var c in successfulCultures) - AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); + AddPublishStatusNotifications(publishStatus, globalNotifications, notifications, successfulCultures); } break; case ContentSaveAction.PublishWithDescendantsForce: @@ -758,12 +758,7 @@ namespace Umbraco.Web.Editors } var publishStatus = PublishBranchInternal(contentItem, true, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList(); - - //global notifications - AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); - //variant specific notifications - foreach (var c in successfulCultures) - AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); + AddPublishStatusNotifications(publishStatus, globalNotifications, notifications, successfulCultures); } break; default: @@ -771,7 +766,7 @@ namespace Umbraco.Web.Editors } //get the updated model - var display = MapToDisplay(contentItem.PersistedContent); + var display = mapToDisplay(contentItem.PersistedContent); //merge the tracked success messages with the outgoing model display.Notifications.AddRange(globalNotifications.Notifications); @@ -801,6 +796,15 @@ namespace Umbraco.Web.Editors return display; } + private void AddPublishStatusNotifications(IReadOnlyCollection publishStatus, SimpleNotificationModel globalNotifications, Dictionary variantNotifications, string[] successfulCultures) + { + //global notifications + AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); + //variant specific notifications + foreach (var c in successfulCultures ?? Array.Empty()) + AddMessageForPublishStatus(publishStatus, variantNotifications.GetOrCreate(c), successfulCultures); + } + /// /// Validates critical data for persistence and updates the ModelState and result accordingly /// @@ -1143,7 +1147,7 @@ namespace Umbraco.Web.Editors var publishStatus = Services.ContentService.SaveAndPublishBranch(contentItem.PersistedContent, force, userId: Security.CurrentUser.Id); // TODO: Deal with multiple cancellations wasCancelled = publishStatus.Any(x => x.Result == PublishResultType.FailedPublishCancelledByEvent); - successfulCultures = Array.Empty(); + successfulCultures = null; //must be null! this implies invariant return publishStatus; } @@ -1215,7 +1219,7 @@ namespace Umbraco.Web.Editors //its invariant, proceed normally var publishStatus = Services.ContentService.SaveAndPublish(contentItem.PersistedContent, userId: Security.CurrentUser.Id); wasCancelled = publishStatus.Result == PublishResultType.FailedPublishCancelledByEvent; - successfulCultures = Array.Empty(); + successfulCultures = null; //must be null! this implies invariant return publishStatus; } @@ -2128,7 +2132,7 @@ namespace Umbraco.Web.Editors { foreach (var c in successfulCultures) { - var names = string.Join(", ", status.Select(x => $"'{x.Content.GetCultureName(c)}'")); + var names = string.Join(", ", status.Select(x => $"'{(x.Content.ContentType.VariesByCulture() ? x.Content.GetCultureName(c) : x.Content.Name)}'")); display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedInvalid", diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index e62438cc6f..9b8a098e21 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -304,9 +304,12 @@ namespace Umbraco.Web.Editors //check if the type is trying to allow type 0 below itself - id zero refers to the currently unsaved type //always filter these 0 types out var allowItselfAsChild = false; + var allowIfselfAsChildSortOrder = -1; if (contentTypeSave.AllowedContentTypes != null) { + allowIfselfAsChildSortOrder = contentTypeSave.AllowedContentTypes.IndexOf(0); allowItselfAsChild = contentTypeSave.AllowedContentTypes.Any(x => x == 0); + contentTypeSave.AllowedContentTypes = contentTypeSave.AllowedContentTypes.Where(x => x > 0).ToList(); } @@ -335,10 +338,12 @@ namespace Umbraco.Web.Editors saveContentType(newCt); //we need to save it twice to allow itself under itself. - if (allowItselfAsChild) + if (allowItselfAsChild && newCt != null) { - //NOTE: This will throw if the composition isn't right... but it shouldn't be at this stage - newCt.AddContentType(newCt); + newCt.AllowedContentTypes = + newCt.AllowedContentTypes.Union( + new []{ new ContentTypeSort(newCt.Id, allowIfselfAsChildSortOrder) } + ); saveContentType(newCt); } return newCt; diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 3ef8a92cdc..dbd17205bb 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -99,25 +99,8 @@ namespace Umbraco.Web.Editors /// A starting point for the search, generally a node id, but for members this is a member type alias /// /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [HttpGet] public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) - { - return Search(query, type, ignoreUserStartNodes: false, searchFrom); - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, bool? ignoreUserStartNodes, string searchFrom = null) { // TODO: Should we restrict search results based on what app the user has access to? // - Theoretically you shouldn't be able to see member data if you don't have access to members right? @@ -127,7 +110,7 @@ namespace Umbraco.Web.Editors //TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type - return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes != null && ignoreUserStartNodes.Value); + return ExamineSearch(query, type, searchFrom); } /// @@ -549,7 +532,6 @@ namespace Umbraco.Web.Editors } } - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public PagedResult GetPagedDescendants( int id, UmbracoEntityTypes type, @@ -558,20 +540,6 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") - { - return GetPagedDescendants(id, type, pageNumber, pageSize, - ignoreUserStartNodes: false, orderBy, orderDirection, filter); - } - - public PagedResult GetPagedDescendants( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - bool ignoreUserStartNodes, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -599,7 +567,7 @@ namespace Umbraco.Web.Editors break; } - entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes + entities = aids == null || aids.Contains(Constants.System.Root) ? Services.EntityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords, SqlContext.Query().Where(x => x.Name.Contains(filter)), Ordering.By(orderBy, orderDirection), includeTrashed: false) @@ -641,15 +609,9 @@ namespace Umbraco.Web.Editors } } - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormDataCollection queryStrings) + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) { - return GetResultForAncestors(id, type, queryStrings, ignoreUserStartNodes: false); - } - - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings, bool ignoreUserStartNodes) - { - return GetResultForAncestors(id, type, queryStrings, ignoreUserStartNodes); + return GetResultForAncestors(id, type, queryStrings); } /// @@ -658,11 +620,10 @@ namespace Umbraco.Web.Editors /// /// /// - /// If set to true, user and group start node permissions will be ignored. /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null) { - return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, ignoreUserStartNodes, searchFrom); + return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, searchFrom); } private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) @@ -688,7 +649,7 @@ namespace Umbraco.Web.Editors } } - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormDataCollection queryStrings = null, bool ignoreUserStartNodes = false) + private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormDataCollection queryStrings = null) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) @@ -697,38 +658,35 @@ namespace Umbraco.Web.Editors var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); - if (ignoreUserStartNodes == false) + int[] aids = null; + switch (entityType) { - int[] aids = null; - switch (entityType) - { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } - if (aids != null) + if (aids != null) + { + var lids = new List(); + var ok = false; + foreach (var i in ids) { - var lids = new List(); - var ok = false; - foreach (var i in ids) + if (ok) { - if (ok) - { - lids.Add(i); - continue; - } - if (aids.Contains(i)) - { - lids.Add(i); - ok = true; - } + lids.Add(i); + continue; + } + if (aids.Contains(i)) + { + lids.Add(i); + ok = true; } - ids = lids.ToArray(); } + ids = lids.ToArray(); } var culture = queryStrings?.GetValue("culture"); diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index 0eb0f54882..b29c166765 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -61,7 +61,7 @@ namespace Umbraco.Web.Editors //redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file var response = Request.CreateResponse(HttpStatusCode.Found); var imageLastModified = _mediaFileSystem.GetLastModified(imagePath); - response.Headers.Location = new Uri($"{imagePath}?rnd={imageLastModified:yyyyMMddHHmmss}&upscale=false&width={width}", UriKind.Relative); + response.Headers.Location = new Uri($"{imagePath}?rnd={imageLastModified:yyyyMMddHHmmss}&upscale=false&width={width}&animationprocessmode=first&mode=max", UriKind.Relative); return response; } diff --git a/src/Umbraco.Web/Editors/LanguageController.cs b/src/Umbraco.Web/Editors/LanguageController.cs index 2ee77ca418..650dcea6e9 100644 --- a/src/Umbraco.Web/Editors/LanguageController.cs +++ b/src/Umbraco.Web/Editors/LanguageController.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Editors { var allLanguages = Services.LocalizationService.GetAllLanguages(); - return Mapper.MapEnumerable(allLanguages); + return Mapper.Map, IEnumerable>(allLanguages); } [HttpGet] diff --git a/src/Umbraco.Web/Editors/LogViewerController.cs b/src/Umbraco.Web/Editors/LogViewerController.cs index e5c75926f6..79eb3bb312 100644 --- a/src/Umbraco.Web/Editors/LogViewerController.cs +++ b/src/Umbraco.Web/Editors/LogViewerController.cs @@ -22,69 +22,97 @@ namespace Umbraco.Web.Editors _logViewer = logViewer; } - private bool CanViewLogs() + private bool CanViewLogs(LogTimePeriod logTimePeriod) { //Can the interface deal with Large Files if (_logViewer.CanHandleLargeLogs) return true; //Interface CheckCanOpenLogs - return _logViewer.CheckCanOpenLogs(startDate: DateTime.Now.AddDays(-1), endDate: DateTime.Now); + return _logViewer.CheckCanOpenLogs(logTimePeriod); } [HttpGet] - public bool GetCanViewLogs() + public bool GetCanViewLogs([FromUri] DateTime? startDate = null,[FromUri] DateTime? endDate = null) { - return CanViewLogs(); + var logTimePeriod = GetTimePeriod(startDate, endDate); + return CanViewLogs(logTimePeriod); } [HttpGet] - public int GetNumberOfErrors() + public int GetNumberOfErrors([FromUri] DateTime? startDate = null,[FromUri] DateTime? endDate = null) { + var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file - if (CanViewLogs() == false) + if (CanViewLogs(logTimePeriod) == false) { throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size")); } - return _logViewer.GetNumberOfErrors(startDate: DateTime.Now.AddDays(-1), endDate: DateTime.Now); + return _logViewer.GetNumberOfErrors(logTimePeriod); } [HttpGet] - public LogLevelCounts GetLogLevelCounts() + public LogLevelCounts GetLogLevelCounts([FromUri] DateTime? startDate = null,[FromUri] DateTime? endDate = null) { + var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file - if (CanViewLogs() == false) + if (CanViewLogs(logTimePeriod) == false) { throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size")); } - return _logViewer.GetLogLevelCounts(startDate: DateTime.Now.AddDays(-1), endDate: DateTime.Now); + return _logViewer.GetLogLevelCounts(logTimePeriod); } [HttpGet] - public IEnumerable GetMessageTemplates() + public IEnumerable GetMessageTemplates([FromUri] DateTime? startDate = null,[FromUri] DateTime? endDate = null) { + var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file - if (CanViewLogs() == false) + if (CanViewLogs(logTimePeriod) == false) { throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size")); } - return _logViewer.GetMessageTemplates(startDate: DateTime.Now.AddDays(-1), endDate: DateTime.Now); + return _logViewer.GetMessageTemplates(logTimePeriod); } [HttpGet] - public PagedResult GetLogs(string orderDirection = "Descending", int pageNumber = 1, string filterExpression = null, [FromUri]string[] logLevels = null) + public PagedResult GetLogs(string orderDirection = "Descending", int pageNumber = 1, string filterExpression = null, [FromUri]string[] logLevels = null, [FromUri] DateTime? startDate = null,[FromUri] DateTime? endDate = null) { + var logTimePeriod = GetTimePeriod(startDate, endDate); + //We will need to stop the request if trying to do this on a 1GB file - if (CanViewLogs() == false) + if (CanViewLogs(logTimePeriod) == false) { throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size")); } var direction = orderDirection == "Descending" ? Direction.Descending : Direction.Ascending; - return _logViewer.GetLogs(startDate: DateTime.Now.AddDays(-1), endDate: DateTime.Now, filterExpression: filterExpression, pageNumber: pageNumber, orderDirection: direction, logLevels: logLevels); + + + + return _logViewer.GetLogs(logTimePeriod, filterExpression: filterExpression, pageNumber: pageNumber, orderDirection: direction, logLevels: logLevels); + } + + private static LogTimePeriod GetTimePeriod(DateTime? startDate, DateTime? endDate) + { + if (startDate == null || endDate == null) + { + var now = DateTime.Now; + if (startDate == null) + { + startDate = now.AddDays(-1); + } + + if (endDate == null) + { + endDate = now; + } + } + + return new LogTimePeriod(startDate.Value, endDate.Value); } [HttpGet] diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 1097646830..a673f06e1d 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -246,7 +246,6 @@ namespace Umbraco.Web.Editors /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(int id, - bool ignoreUserStartNodes, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -256,7 +255,7 @@ namespace Umbraco.Web.Editors { //if a request is made for the root node data but the user's start node is not the default, then // we need to return their start nodes - if (id == Constants.System.Root && UserStartNodes.Length > 0 && (UserStartNodes.Contains(Constants.System.Root) == false && ignoreUserStartNodes == false)) + if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) { if (pageNumber > 0) return new PagedResult>(0, 0, 0); @@ -312,7 +311,6 @@ namespace Umbraco.Web.Editors } /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Returns the child media objects - using the entity GUID id /// /// @@ -323,34 +321,8 @@ namespace Umbraco.Web.Editors /// /// /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Guid id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - return GetChildren(id, ignoreUserStartNodes: false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Returns the child media objects - using the entity GUID id - /// - /// - /// /// If set to true, user and group start node permissions will be ignored. - /// - /// - /// - /// - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Guid id, - bool ignoreUserStartNodes, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -361,13 +333,12 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.Get(id); if (entity != null) { - return GetChildren(entity.Id, ignoreUserStartNodes, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } throw new HttpResponseException(HttpStatusCode.NotFound); } /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Returns the child media objects - using the entity UDI id /// /// @@ -378,7 +349,6 @@ namespace Umbraco.Web.Editors /// /// /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Udi id, int pageNumber = 0, @@ -387,31 +357,6 @@ namespace Umbraco.Web.Editors Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") - { - return GetChildren(id, ignoreUserStartNodes: false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Returns the child media objects - using the entity UDI id - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - /// - /// - /// - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Udi id, - bool ignoreUserStartNodes, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") { var guidUdi = id as GuidUdi; if (guidUdi != null) @@ -419,7 +364,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.Get(guidUdi.Guid); if (entity != null) { - return GetChildren(entity.Id, ignoreUserStartNodes, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } } diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs index e124f3a9a4..2698a68b40 100644 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -151,6 +151,8 @@ namespace Umbraco.Web.Editors /// public Attempt ChangePasswordWithMembershipProvider(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) { + var umbracoBaseProvider = membershipProvider as MembershipProviderBase; + // YES! It is completely insane how many options you have to take into account based on the membership provider. yikes! if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel)); @@ -179,7 +181,7 @@ namespace Umbraco.Web.Editors //this is only possible when using a membership provider if the membership provider supports AllowManuallyChangingPassword if (passwordModel.NewPassword.IsNullOrWhiteSpace() == false) { - if (membershipProvider is MembershipProviderBase umbracoBaseProvider && umbracoBaseProvider.AllowManuallyChangingPassword) + if (umbracoBaseProvider !=null && umbracoBaseProvider.AllowManuallyChangingPassword) { //this provider allows manually changing the password without the old password, so we can just do it try diff --git a/src/Umbraco.Web/Editors/UserGroupsController.cs b/src/Umbraco.Web/Editors/UserGroupsController.cs index e79cfd625c..1b64722735 100644 --- a/src/Umbraco.Web/Editors/UserGroupsController.cs +++ b/src/Umbraco.Web/Editors/UserGroupsController.cs @@ -50,13 +50,8 @@ namespace Umbraco.Web.Editors if (isAuthorized == false) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, isAuthorized.Result)); - //current user needs to be added to a new group if not an admin (possibly only if no other users are added?) to avoid a 401 - if(!Security.CurrentUser.IsAdmin() && (userGroupSave.Id == null || Convert.ToInt32(userGroupSave.Id) >= 0)/* && !userGroupSave.Users.Any() */) - { - var userIds = userGroupSave.Users.ToList(); - userIds.Add(Security.CurrentUser.Id); - userGroupSave.Users = userIds; - } + //need to ensure current user is in a group if not an admin to avoid a 401 + EnsureNonAdminUserIsInSavedUserGroup(userGroupSave); //save the group Services.UserService.Save(userGroupSave.PersistedUserGroup, userGroupSave.Users.ToArray()); @@ -87,6 +82,23 @@ namespace Umbraco.Web.Editors return display; } + private void EnsureNonAdminUserIsInSavedUserGroup(UserGroupSave userGroupSave) + { + if (Security.CurrentUser.IsAdmin()) + { + return; + } + + var userIds = userGroupSave.Users.ToList(); + if (userIds.Contains(Security.CurrentUser.Id)) + { + return; + } + + userIds.Add(Security.CurrentUser.Id); + userGroupSave.Users = userIds; + } + /// /// Returns the scaffold for creating a new user group /// diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index 3bbcdfb0ac..706ebf2825 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -148,11 +148,33 @@ namespace Umbraco.Web var delimitedParts = string.Join(".", parts); foreach (var memberName in result.MemberNames) { - modelState.AddModelError($"{delimitedParts}.{memberName}", result.ErrorMessage); + modelState.TryAddModelError($"{delimitedParts}.{memberName}", result.ErrorMessage); withNames = true; } if (!withNames) - modelState.AddModelError($"{delimitedParts}", result.ErrorMessage); + { + modelState.TryAddModelError($"{delimitedParts}", result.ErrorMessage); + } + + } + + /// + /// Will add an error to model state for a key if that key and error don't already exist + /// + /// + /// + /// + /// + private static bool TryAddModelError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, string key, string errorMsg) + { + if (modelState.TryGetValue(key, out var errs)) + { + foreach(var e in errs.Errors) + if (e.ErrorMessage == errorMsg) return false; //if this same error message exists for the same key, just exit + } + + modelState.AddModelError(key, errorMsg); + return true; } public static IDictionary ToErrorDictionary(this System.Web.Http.ModelBinding.ModelStateDictionary modelState) diff --git a/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs b/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs index 3a8496ac3f..1f526a50f3 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs @@ -1,10 +1,15 @@  +using System.Linq; +using Umbraco.Core; + namespace Umbraco.Web.Models.ContentEditing { public static class MessagesExtensions { public static void AddNotification(this INotificationModel model, string header, string msg, NotificationStyle type) { + if (model.Exists(header, msg, type)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -15,6 +20,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddSuccessNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Success)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -25,6 +32,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddErrorNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Error)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -35,6 +44,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddWarningNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Warning)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -45,6 +56,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddInfoNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Info)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -52,5 +65,7 @@ namespace Umbraco.Web.Models.ContentEditing NotificationType = NotificationStyle.Info }); } + + private static bool Exists(this INotificationModel model, string header, string message, NotificationStyle notificationType) => model.Notifications.Any(x => x.Header.InvariantEquals(header) && x.Message.InvariantEquals(message) && x.NotificationType == notificationType); } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs index 560d398a2c..c279ae2c70 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Models.Mapping variants.Remove(defaultLang); //Sort the remaining languages a-z - variants = variants.OrderBy(x => x.Name).ToList(); + variants = variants.OrderBy(x => x.Language.Name).ToList(); //Insert the default language as the first item variants.Insert(0, defaultLang); diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs index 94698f84bd..2598523bd5 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs @@ -45,6 +45,12 @@ namespace Umbraco.Web.Models.Mapping if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace()) target.Icon = "icon-user"; + if (source.NodeObjectType == Constants.ObjectTypes.Media && source is IContentEntitySlim contentSlim) + source.AdditionalData["ContentTypeAlias"] = contentSlim.ContentTypeAlias; + + if (source.NodeObjectType == Constants.ObjectTypes.Media && source is IMediaEntitySlim mediaSlim) + source.AdditionalData["MediaPath"] = mediaSlim.MediaPath; + // NOTE: we're mapping the objects in AdditionalData by object reference here. // it works fine for now, but it's something to keep in mind in the future foreach(var kvp in source.AdditionalData) diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs index 5653e3fe03..7879e2b42b 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs @@ -10,8 +10,5 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor public Udi StartNodeId { get; set; } - - [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs index 136ab88204..e2b46b360d 100644 --- a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs @@ -15,8 +15,5 @@ namespace Umbraco.Web.PropertyEditors // TODO: Make these strongly typed, for now this works though [ConfigurationField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] public JObject Rte { get; set; } - - [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.
    Note: this applies to all editors in this grid editor except for the rich text editor, which has it's own option for that.")] - public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs index fa430e103b..4844e2f822 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs @@ -19,8 +19,5 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("startNodeId", "Start node", "mediapicker")] public Udi StartNodeId { get; set; } - - [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs index a0a2467b1c..b6333c3140 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs @@ -22,8 +22,5 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = "Opens the node in a dialog")] public bool ShowOpen { get; set; } - - [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs index ec9439ceea..515512eff8 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs @@ -9,8 +9,5 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("maxNumber", "Maximum number of items", "number")] public int MaxNumber { get; set; } - - [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs index d99c2b17e0..13bf269bcd 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs @@ -14,8 +14,5 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("hideLabel", "Hide Label", "boolean")] public bool HideLabel { get; set; } - - [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index c05a983346..297f9b7fb8 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -90,7 +90,8 @@ namespace Umbraco.Web.PropertyEditors if (editorValue.Value == null) return null; - var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValue.Value.ToString()); + var editorValueWithMediaUrlsRemoved = TemplateUtilities.RemoveMediaUrlsFromTextString(editorValue.Value.ToString()); + var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); return parsed; } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index fbbf058c49..fa14bd8488 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -89,32 +89,35 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters // Find all images with rel attribute var imgNodes = doc.DocumentNode.SelectNodes("//img[@rel]"); + var modified = false; if (imgNodes != null) { - var modified = false; - foreach (var img in imgNodes) { - var firstOrDefault = img.Attributes.FirstOrDefault(x => x.Name == "rel"); - if (firstOrDefault != null) + var nodeId = img.GetAttributeValue("rel", string.Empty); + if (int.TryParse(nodeId, out _)) { - var rel = firstOrDefault.Value; - - // Check that the rel attribute is a integer before removing - int nodeId; - if (int.TryParse(rel, out nodeId)) - { - img.Attributes.Remove("rel"); - modified = true; - } + img.Attributes.Remove("rel"); + modified = true; } } + } - if (modified) + // Find all a and img tags with a data-udi attribute + var dataUdiNodes = doc.DocumentNode.SelectNodes("(//a|//img)[@data-udi]"); + if (dataUdiNodes != null) + { + foreach (var node in dataUdiNodes) { - return doc.DocumentNode.OuterHtml; + node.Attributes.Remove("data-udi"); + modified = true; } } + + if (modified) + { + return doc.DocumentNode.OuterHtml; + } } return sourceString; diff --git a/src/Umbraco.Web/Routing/IContentFinder.cs b/src/Umbraco.Web/Routing/IContentFinder.cs index fe5699ac7b..2e388c4814 100644 --- a/src/Umbraco.Web/Routing/IContentFinder.cs +++ b/src/Umbraco.Web/Routing/IContentFinder.cs @@ -8,9 +8,9 @@ namespace Umbraco.Web.Routing /// /// Tries to find and assign an Umbraco document to a PublishedRequest. /// - /// The PublishedRequest. + /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. /// Optionally, can also assign the template or anything else on the document request, although that is not required. - bool TryFindContent(PublishedRequest frequest); + bool TryFindContent(PublishedRequest request); } } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 7fb51a61eb..43db9ff0ba 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -39,7 +39,6 @@ namespace Umbraco.Web.Search } /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Searches for results based on the entity type /// /// @@ -51,39 +50,11 @@ namespace Umbraco.Web.Search /// /// /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public IEnumerable ExamineSearch( string query, UmbracoEntityTypes entityType, int pageSize, - long pageIndex, - out long totalFound, - string searchFrom = null) - { - return ExamineSearch(query, entityType, pageSize, pageIndex, out totalFound, ignoreUserStartNodes: false, searchFrom); - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - public IEnumerable ExamineSearch( - string query, - UmbracoEntityTypes entityType, - int pageSize, - long pageIndex, - out long totalFound, - bool ignoreUserStartNodes, - string searchFrom = null) + long pageIndex, out long totalFound, string searchFrom = null) { var sb = new StringBuilder(); @@ -114,12 +85,12 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Media: type = "media"; var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService); - AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); + AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, _entityService); break; case UmbracoEntityTypes.Document: type = "content"; var allContentStartNodes = _umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService); - AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); + AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, _entityService); break; default: throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); @@ -317,7 +288,7 @@ namespace Umbraco.Web.Search } } - private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, bool ignoreUserStartNodes, IEntityService entityService) + private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, IEntityService entityService) { if (sb == null) throw new ArgumentNullException(nameof(sb)); if (entityService == null) throw new ArgumentNullException(nameof(entityService)); @@ -340,7 +311,7 @@ namespace Umbraco.Web.Search // make sure we don't find anything sb.Append("+__Path:none "); } - else if (startNodeIds.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction + else if (startNodeIds.Contains(-1) == false) // -1 = no restriction { var entityPaths = entityService.GetAllPaths(objectType, startNodeIds); diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 98aacffe42..8e6e1dcfd0 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -157,9 +157,8 @@ namespace Umbraco.Web.Templates // - 3 = anything after group 2 and before the data-udi attribute value begins // - 4 = the data-udi attribute value // - 5 = anything after group 4 until the image tag is closed - var src = match.Groups[2].Value; var udi = match.Groups[4].Value; - if(src.IsNullOrWhiteSpace() || udi.IsNullOrWhiteSpace() || GuidUdi.TryParse(udi, out var guidUdi) == false) + if(udi.IsNullOrWhiteSpace() || GuidUdi.TryParse(udi, out var guidUdi) == false) { return match.Value; } @@ -175,5 +174,14 @@ namespace Umbraco.Web.Templates return $"{match.Groups[1].Value}{url}{match.Groups[3].Value}{udi}{match.Groups[5].Value}"; }); } + + /// + /// Removes media urls from <img> tags where a data-udi attribute is present + /// + /// + /// + internal static string RemoveMediaUrlsFromTextString(string text) + // see comment in ResolveMediaFromTextString for group reference + => ResolveImgPattern.Replace(text, "$1$3$4$5"); } } diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 748c97c522..9e481fc4c9 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -201,9 +201,6 @@ namespace Umbraco.Web.Trees return HasPathAccess(entity, queryStrings); } - internal override IEnumerable GetChildrenFromEntityService(int entityId) - => Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToList(); - protected override IEnumerable GetChildEntities(string id, FormDataCollection queryStrings) { var result = base.GetChildEntities(id, queryStrings); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index d01e9fffb4..1b8f3b1434 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -69,7 +69,7 @@ namespace Umbraco.Web.Trees { var node = base.CreateRootNode(queryStrings); - if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false && IgnoreUserStartNodes(queryStrings) == false) + if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false) { node.AdditionalData["noAccess"] = true; } @@ -90,11 +90,11 @@ namespace Umbraco.Web.Trees internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormDataCollection queryStrings) { var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out var hasPathAccess); - if (IgnoreUserStartNodes(queryStrings) == false && entityIsAncestorOfStartNodes == false) + if (entityIsAncestorOfStartNodes == false) return null; var treeNode = GetSingleTreeNode(e, parentId, queryStrings); - if (IgnoreUserStartNodes(queryStrings) == false && hasPathAccess == false) + if (hasPathAccess == false) { treeNode.AdditionalData["noAccess"] = true; } @@ -134,7 +134,7 @@ namespace Umbraco.Web.Trees // ensure that the user has access to that node, otherwise return the empty tree nodes collection // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access - if (IgnoreUserStartNodes(queryStrings) == false && HasPathAccess(id, queryStrings) == false) + if (HasPathAccess(id, queryStrings) == false) { Logger.Warn("User {Username} does not have access to node with id {Id}", Security.CurrentUser.Username, id); return nodes; @@ -214,12 +214,8 @@ namespace Umbraco.Web.Trees return result; } - /// - /// Abstract method to fetch the entities from the entity service - /// - /// - /// - internal abstract IEnumerable GetChildrenFromEntityService(int entityId); + internal virtual IEnumerable GetChildrenFromEntityService(int entityId) + => Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToList(); /// /// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access @@ -255,9 +251,8 @@ namespace Umbraco.Web.Trees /// protected sealed override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { - var ignoreUserStartNodes = queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes); //check if we're rendering the root - if (id == Constants.System.RootString && UserStartNodes.Contains(Constants.System.Root) || ignoreUserStartNodes) + if (id == Constants.System.RootString && UserStartNodes.Contains(Constants.System.Root)) { var altStartId = string.Empty; diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index d050b51a91..22ad4ed355 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -167,11 +167,5 @@ namespace Umbraco.Web.Trees return _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Media, pageSize, pageIndex, out totalFound, searchFrom); } - internal override IEnumerable GetChildrenFromEntityService(int entityId) - // Not pretty having to cast the service, but it is the only way to get to use an internal method that we - // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. - // See this issue for details on why we need this: - // https://github.com/umbraco/Umbraco-CMS/issues/3457 - => ((EntityService)Services.EntityService).GetMediaChildrenWithoutPropertyData(entityId).ToList(); } } diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 2e409c2820..4acf807b77 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -369,16 +369,6 @@ namespace Umbraco.Web.Trees return queryStrings.GetValue(TreeQueryStringParameters.Use) == "dialog"; } - /// - /// If the request should allows a user to choose nodes that they normally don't have access to - /// - /// - /// - protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) - { - return queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes); - } - /// /// An event that allows developers to modify the tree node collection that is being rendered /// diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs index 0fcf5321e4..466aff5a1f 100644 --- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs +++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs @@ -8,7 +8,6 @@ public const string Use = "use"; public const string Application = "application"; public const string StartNodeId = "startNodeId"; - public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; //public const string OnNodeClick = "OnNodeClick"; //public const string RenderParent = "RenderParent"; } diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 0c53838592..efee045890 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -11,7 +11,6 @@ using Umbraco.Core.Models; using Umbraco.Web.Actions; using Umbraco.Core.Security; using System.Net; -using System.Web; namespace Umbraco.Web.WebApi.Filters { @@ -67,7 +66,7 @@ namespace Umbraco.Web.WebApi.Filters //not logged in throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); } - + int nodeId; if (_nodeId.HasValue == false) { @@ -117,29 +116,24 @@ namespace Umbraco.Web.WebApi.Filters nodeId = _nodeId.Value; } - var queryStringCollection = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.Query); - bool.TryParse(queryStringCollection["ignoreUserStartNodes"], out var ignoreUserStartNodes); - if (ignoreUserStartNodes == false) + var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId, + Current.UmbracoContext.Security.CurrentUser, + Current.Services.UserService, + Current.Services.ContentService, + Current.Services.EntityService, + out var contentItem, + _permissionToCheck.HasValue ? new[] { _permissionToCheck.Value } : null); + + if (permissionResult == ContentPermissionsHelper.ContentAccess.NotFound) + throw new HttpResponseException(HttpStatusCode.NotFound); + + if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied) + throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); + + if (contentItem != null) { - var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId, - Current.UmbracoContext.Security.CurrentUser, - Current.Services.UserService, - Current.Services.ContentService, - Current.Services.EntityService, - out var contentItem, - _permissionToCheck.HasValue ? new[] {_permissionToCheck.Value} : null); - - if (permissionResult == ContentPermissionsHelper.ContentAccess.NotFound) - throw new HttpResponseException(HttpStatusCode.NotFound); - - if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied) - throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); - - if (contentItem != null) - { - //store the content item in request cache so it can be resolved in the controller without re-looking it up - actionContext.Request.Properties[typeof(IContent).ToString()] = contentItem; - } + //store the content item in request cache so it can be resolved in the controller without re-looking it up + actionContext.Request.Properties[typeof(IContent).ToString()] = contentItem; } base.OnActionExecuting(actionContext); diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index f8b02f08ca..21dc60e6cc 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -3,14 +3,12 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Web; using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Composing; using Umbraco.Core.Security; -using Umbraco.Web.Trees; namespace Umbraco.Web.WebApi.Filters { @@ -74,12 +72,7 @@ namespace Umbraco.Web.WebApi.Filters protected virtual void FilterItems(IUser user, IList items) { - bool.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.IgnoreUserStartNodes), out var ignoreUserStartNodes); - - if (ignoreUserStartNodes == false) - { - FilterBasedOnStartNode(items, user); - } + FilterBasedOnStartNode(items, user); } internal void FilterBasedOnStartNode(IList items, IUser user)