diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index b7bfaaff5b..97e9ef3df2 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -30,7 +30,6 @@ - diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index b55dc0ca18..eb2b3525a7 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -85,12 +85,7 @@ namespace Umbraco.Core /// ListView. /// public const string ListView = "Umbraco.ListView"; - - /// - /// Macro Container. - /// - public const string MacroContainer = "Umbraco.MacroContainer"; - + /// /// Media Picker. /// diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 7d5c05d584..94d8cfbc62 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -227,8 +227,8 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 35, UniqueId = 35.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, SortOrder = 7, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 36, UniqueId = 36.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, SortOrder = 8, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 35, UniqueId = 35.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, SortOrder = 7, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 36, UniqueId = 36.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, SortOrder = 8, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); } diff --git a/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs new file mode 100644 index 0000000000..050a999cc2 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Models.Entities +{ + public interface IMemberEntitySlim : IContentEntitySlim + { + + } +} diff --git a/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs b/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs new file mode 100644 index 0000000000..335e269467 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Models.Entities +{ + public class MemberEntitySlim : EntitySlim, IMemberEntitySlim + { + public string ContentTypeAlias { get; set; } + + /// + public string ContentTypeIcon { get; set; } + + /// + public string ContentTypeThumbnail { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs index 3903fe405b..b1d0189c56 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs @@ -15,6 +15,12 @@ namespace Umbraco.Core.Models.Membership return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, group.Alias, group.AllowedSections, group.Permissions); } + public static bool IsSystemUserGroup(this IUserGroup group) => + IsSystemUserGroup(group.Alias); + + public static bool IsSystemUserGroup(this IReadOnlyUserGroup group) => + IsSystemUserGroup(group.Alias); + public static IReadOnlyUserGroup ToReadOnlyGroup(this UserGroupDto group) { return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, @@ -22,5 +28,12 @@ namespace Umbraco.Core.Models.Membership group.UserGroup2AppDtos.Select(x => x.AppAlias).ToArray(), group.DefaultPermissions == null ? Enumerable.Empty() : group.DefaultPermissions.ToCharArray().Select(x => x.ToString())); } + + private static bool IsSystemUserGroup(this string groupAlias) + { + return groupAlias == Constants.Security.AdminGroupAlias + || groupAlias == Constants.Security.SensitiveDataGroupAlias + || groupAlias == Constants.Security.TranslatorGroupAlias; + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 344557d815..2649b9993f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -248,14 +248,63 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return dto == null ? null : MapDtoToContent(dto); } + // deletes a specific version + public override void DeleteVersion(int versionId) + { + // TODO: test object node type? + + // get the version we want to delete + var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetVersion", tsql => + tsql.Select() + .AndSelect() + .From() + .InnerJoin() + .On((c, d) => c.Id == d.Id) + .Where(x => x.Id == SqlTemplate.Arg("versionId")) + ); + var versionDto = Database.Fetch(template.Sql(new { versionId })).FirstOrDefault(); + + // nothing to delete + if (versionDto == null) + return; + + // don't delete the current or published version + if (versionDto.ContentVersionDto.Current) + throw new InvalidOperationException("Cannot delete the current version."); + else if (versionDto.Published) + throw new InvalidOperationException("Cannot delete the published version."); + + PerformDeleteVersion(versionDto.ContentVersionDto.NodeId, versionId); + } + + // deletes all versions of an entity, older than a date. + public override void DeleteVersions(int nodeId, DateTime versionDate) + { + // TODO: test object node type? + + // get the versions we want to delete, excluding the current one + var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetVersions", tsql => + tsql.Select() + .From() + .InnerJoin() + .On((c, d) => c.Id == d.Id) + .Where(x => x.NodeId == SqlTemplate.Arg("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg("versionDate")) + .Where( x => !x.Published) + ); + var versionDtos = Database.Fetch(template.Sql(new { nodeId, versionDate })); + foreach (var versionDto in versionDtos) + PerformDeleteVersion(versionDto.NodeId, versionDto.Id); + } + protected override void PerformDeleteVersion(int id, int versionId) { // raise event first else potential FK issues OnUowRemovingVersion(new ScopedVersionEventArgs(AmbientScope, id, versionId)); Database.Delete("WHERE versionId = @versionId", new { versionId }); - Database.Delete("WHERE id = @versionId", new { versionId }); + Database.Delete("WHERE versionId = @versionId", new { versionId }); Database.Delete("WHERE id = @versionId", new { versionId }); + Database.Delete("WHERE id = @versionId", new { versionId }); } #endregion diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index b6d39fe54f..161db543ba 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -42,8 +42,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint; var isMedia = objectType == Constants.ObjectTypes.Media; + var isMember = objectType == Constants.ObjectTypes.Member; - var sql = GetBaseWhere(isContent, isMedia, false, x => + var sql = GetBaseWhere(isContent, isMedia, isMember, false, x => { if (filter == null) return; foreach (var filterClause in filter.GetWhereClauses()) @@ -54,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var translator = new SqlTranslator(sql, query); sql = translator.Translate(); - sql = AddGroupBy(isContent, isMedia, sql, ordering.IsEmpty); + sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); if (!ordering.IsEmpty) { @@ -81,6 +82,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement dtos = page.Items; totalRecords = page.TotalItems; } + else if (isMember) + { + var page = Database.Page(pageIndexToFetch, pageSize, sql); + dtos = page.Items; + totalRecords = page.TotalItems; + } else { var page = Database.Page(pageIndexToFetch, pageSize, sql); @@ -88,7 +95,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement totalRecords = page.TotalItems; } - var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); + var entities = dtos.Select(x => BuildEntity(isContent, isMedia, isMember, x)).ToArray(); if (isContent) BuildVariants(entities.Cast()); @@ -98,13 +105,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public IEntitySlim Get(Guid key) { - var sql = GetBaseWhere(false, false, false, key); + var sql = GetBaseWhere(false, false, false, false, key); var dto = Database.FirstOrDefault(sql); - return dto == null ? null : BuildEntity(false, false, dto); + return dto == null ? null : BuildEntity(false, false, false, dto); } - private IEntitySlim GetEntity(Sql sql, bool isContent, bool isMedia) + private IEntitySlim GetEntity(Sql sql, bool isContent, bool isMedia, bool isMember) { //isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) @@ -120,7 +127,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (dto == null) return null; - var entity = BuildEntity(false, isMedia, dto); + var entity = BuildEntity(false, isMedia, isMember, dto); return entity; } @@ -129,25 +136,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { var isContent = objectTypeId == Constants.ObjectTypes.Document || objectTypeId == Constants.ObjectTypes.DocumentBlueprint; var isMedia = objectTypeId == Constants.ObjectTypes.Media; + var isMember = objectTypeId == Constants.ObjectTypes.Member; - var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, key); - return GetEntity(sql, isContent, isMedia); + var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectTypeId, key); + return GetEntity(sql, isContent, isMedia, isMember); } public IEntitySlim Get(int id) { - var sql = GetBaseWhere(false, false, false, id); + var sql = GetBaseWhere(false, false, false, false, id); var dto = Database.FirstOrDefault(sql); - return dto == null ? null : BuildEntity(false, false, dto); + return dto == null ? null : BuildEntity(false, false, false, dto); } public IEntitySlim Get(int id, Guid objectTypeId) { var isContent = objectTypeId == Constants.ObjectTypes.Document || objectTypeId == Constants.ObjectTypes.DocumentBlueprint; var isMedia = objectTypeId == Constants.ObjectTypes.Media; + var isMember = objectTypeId == Constants.ObjectTypes.Member; - var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, id); - return GetEntity(sql, isContent, isMedia); + var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectTypeId, id); + return GetEntity(sql, isContent, isMedia, isMember); } public IEnumerable GetAll(Guid objectType, params int[] ids) @@ -164,7 +173,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : PerformGetAll(objectType); } - private IEnumerable GetEntities(Sql sql, bool isContent, bool isMedia) + private IEnumerable GetEntities(Sql sql, bool isContent, bool isMedia, bool isMember) { //isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) @@ -180,7 +189,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ? (IEnumerable)Database.Fetch(sql) : Database.Fetch(sql); - var entities = dtos.Select(x => BuildEntity(false, isMedia, x)).ToArray(); + var entities = dtos.Select(x => BuildEntity(false, isMedia, isMember, x)).ToArray(); return entities; } @@ -189,9 +198,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint; var isMedia = objectType == Constants.ObjectTypes.Media; + var isMember = objectType == Constants.ObjectTypes.Member; - var sql = GetFullSqlForEntityType(isContent, isMedia, objectType, filter); - return GetEntities(sql, isContent, isMedia); + var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectType, filter); + return GetEntities(sql, isContent, isMedia, isMember); } public IEnumerable GetAllPaths(Guid objectType, params int[] ids) @@ -218,26 +228,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public IEnumerable GetByQuery(IQuery query) { - var sqlClause = GetBase(false, false, null); + var sqlClause = GetBase(false, false, false, null); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - sql = AddGroupBy(false, false, sql, true); + sql = AddGroupBy(false, false, false, sql, true); var dtos = Database.Fetch(sql); - return dtos.Select(x => BuildEntity(false, false, x)).ToList(); + return dtos.Select(x => BuildEntity(false, false, false, x)).ToList(); } public IEnumerable GetByQuery(IQuery query, Guid objectType) { var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint; var isMedia = objectType == Constants.ObjectTypes.Media; + var isMember = objectType == Constants.ObjectTypes.Member; - var sql = GetBaseWhere(isContent, isMedia, false, null, objectType); + var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, objectType); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); - sql = AddGroupBy(isContent, isMedia, sql, true); + sql = AddGroupBy(isContent, isMedia, isMember, sql, true); - return GetEntities(sql, isContent, isMedia); + return GetEntities(sql, isContent, isMedia, isMember); } public UmbracoObjectTypes GetObjectType(int id) @@ -329,29 +340,29 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // gets the full sql for a given object type and a given unique id - protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Guid uniqueId) + protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Guid uniqueId) { - var sql = GetBaseWhere(isContent, isMedia, false, objectType, uniqueId); - return AddGroupBy(isContent, isMedia, sql, true); + var sql = GetBaseWhere(isContent, isMedia, isMember, false, objectType, uniqueId); + return AddGroupBy(isContent, isMedia, isMember, sql, true); } // gets the full sql for a given object type and a given node id - protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, int nodeId) + protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, int nodeId) { - var sql = GetBaseWhere(isContent, isMedia, false, objectType, nodeId); - return AddGroupBy(isContent, isMedia, sql, true); + var sql = GetBaseWhere(isContent, isMedia, isMember, false, objectType, nodeId); + return AddGroupBy(isContent, isMedia, isMember, sql, true); } // gets the full sql for a given object type, with a given filter - protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Action> filter) + protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Action> filter) { - var sql = GetBaseWhere(isContent, isMedia, false, filter, objectType); - return AddGroupBy(isContent, isMedia, sql, true); + var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, objectType); + return AddGroupBy(isContent, isMedia, isMember, sql, true); } // 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) + protected Sql GetBase(bool isContent, bool isMedia, bool isMember, Action> filter, bool isCount = false) { var sql = Sql(); @@ -366,7 +377,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .AndSelect(x => x.SortOrder, x => x.UniqueId, x => x.Text, x => x.NodeObjectType, x => x.CreateDate) .Append(", COUNT(child.id) AS children"); - if (isContent || isMedia) + if (isContent || isMedia || isMember) sql .AndSelect(x => Alias(x.Id, "versionId")) .AndSelect(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations); @@ -387,7 +398,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql .From(); - if (isContent || isMedia) + if (isContent || isMedia || isMember) { sql .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) @@ -422,49 +433,49 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets the base SELECT + FROM [+ filter] + WHERE sql // for a given object type, with a given filter - protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Action> filter, Guid objectType) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action> filter, Guid objectType) { - return GetBase(isContent, isMedia, filter, isCount) + return GetBase(isContent, isMedia, isMember, filter, isCount) .Where(x => x.NodeObjectType == objectType); } // gets the base SELECT + FROM + WHERE sql // for a given node id - protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, int id) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, int id) { - var sql = GetBase(isContent, isMedia, null, isCount) + var sql = GetBase(isContent, isMedia, isMember, null, isCount) .Where(x => x.NodeId == id); - return AddGroupBy(isContent, isMedia, sql, true); + return AddGroupBy(isContent, isMedia, isMember, sql, true); } // gets the base SELECT + FROM + WHERE sql // for a given unique id - protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid uniqueId) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid uniqueId) { - var sql = GetBase(isContent, isMedia, null, isCount) + var sql = GetBase(isContent, isMedia, isMember, null, isCount) .Where(x => x.UniqueId == uniqueId); - return AddGroupBy(isContent, isMedia, sql, true); + return AddGroupBy(isContent, isMedia, isMember, sql, true); } // gets the base SELECT + FROM + WHERE sql // for a given object type and node id - protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, int nodeId) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid objectType, int nodeId) { - return GetBase(isContent, isMedia, null, isCount) + return GetBase(isContent, isMedia, isMember, null, isCount) .Where(x => x.NodeId == nodeId && x.NodeObjectType == objectType); } // gets the base SELECT + FROM + WHERE sql // for a given object type and unique id - protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, Guid uniqueId) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid objectType, Guid uniqueId) { - return GetBase(isContent, isMedia, null, isCount) + return GetBase(isContent, isMedia, isMember, null, isCount) .Where(x => x.UniqueId == uniqueId && x.NodeObjectType == objectType); } // gets the GROUP BY / ORDER BY sql // required in order to count children - protected Sql AddGroupBy(bool isContent, bool isMedia, Sql sql, bool defaultSort) + protected Sql AddGroupBy(bool isContent, bool isMedia, bool isMember, Sql sql, bool defaultSort) { sql .GroupBy(x => x.NodeId, x => x.Trashed, x => x.ParentId, x => x.UserId, x => x.Level, x => x.Path) @@ -483,7 +494,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } - if (isContent || isMedia) + if (isContent || isMedia || isMember) sql .AndBy(x => x.Id) .AndBy(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations); @@ -528,6 +539,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public string MediaPath { get; set; } } + private class MemberEntityDto : BaseDto + { + } + public class VariantInfoDto { public int NodeId { get; set; } @@ -574,12 +589,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Factory - private EntitySlim BuildEntity(bool isContent, bool isMedia, BaseDto dto) + private EntitySlim BuildEntity(bool isContent, bool isMedia, bool isMember, BaseDto dto) { if (isContent) return BuildDocumentEntity(dto); if (isMedia) return BuildMediaEntity(dto); + if (isMember) + return BuildMemberEntity(dto); // EntitySlim does not track changes var entity = new EntitySlim(); @@ -644,6 +661,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return entity; } + private MemberEntitySlim BuildMemberEntity(BaseDto dto) + { + // EntitySlim does not track changes + var entity = new MemberEntitySlim(); + BuildEntity(entity, dto); + + entity.ContentTypeAlias = dto.Alias; + entity.ContentTypeIcon = dto.Icon; + entity.ContentTypeThumbnail = dto.Thumbnail; + + return entity; + } + #endregion } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index ce63be9f2c..720713e9ad 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1848,7 +1848,7 @@ namespace Umbraco.Core.Services.Implement scope.WriteLock(Constants.Locks.ContentTree); var c = _documentRepository.Get(id); - if (c.VersionId != versionId) // don't delete the current version + if (c.VersionId != versionId && c.PublishedVersionId != versionId) // don't delete the current or published version _documentRepository.DeleteVersion(versionId); scope.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false,/* specificVersion:*/ versionId)); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index a0d0fad6d9..43d168f442 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -263,7 +263,9 @@ + + diff --git a/src/Umbraco.Examine/ContentIndexPopulator.cs b/src/Umbraco.Examine/ContentIndexPopulator.cs index 51b9de4a0b..dd4176a774 100644 --- a/src/Umbraco.Examine/ContentIndexPopulator.cs +++ b/src/Umbraco.Examine/ContentIndexPopulator.cs @@ -52,7 +52,7 @@ namespace Umbraco.Examine if (sqlContext == null) throw new ArgumentNullException(nameof(sqlContext)); _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); _contentValueSetBuilder = contentValueSetBuilder ?? throw new ArgumentNullException(nameof(contentValueSetBuilder)); - if (_publishedQuery != null) + if (_publishedQuery == null) _publishedQuery = sqlContext.Query().Where(x => x.Published); _publishedValuesOnly = publishedValuesOnly; _parentId = parentId; diff --git a/src/Umbraco.Examine/IUmbracoIndexConfig.cs b/src/Umbraco.Examine/IUmbracoIndexConfig.cs new file mode 100644 index 0000000000..02c6c51d0c --- /dev/null +++ b/src/Umbraco.Examine/IUmbracoIndexConfig.cs @@ -0,0 +1,12 @@ +using Examine; + +namespace Umbraco.Examine +{ + public interface IUmbracoIndexConfig + { + IContentValueSetValidator GetContentValueSetValidator(); + IContentValueSetValidator GetPublishedContentValueSetValidator(); + IValueSetValidator GetMemberValueSetValidator(); + + } +} diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index e28a8e674e..e30d355dfe 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -64,9 +64,11 @@ + + diff --git a/src/Umbraco.Examine/UmbracoIndexConfig.cs b/src/Umbraco.Examine/UmbracoIndexConfig.cs new file mode 100644 index 0000000000..7ad9c638d3 --- /dev/null +++ b/src/Umbraco.Examine/UmbracoIndexConfig.cs @@ -0,0 +1,34 @@ +using Examine; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Examine +{ + public class UmbracoIndexConfig : IUmbracoIndexConfig + { + public UmbracoIndexConfig(IPublicAccessService publicAccessService) + { + PublicAccessService = publicAccessService; + } + + protected IPublicAccessService PublicAccessService { get; } + public IContentValueSetValidator GetContentValueSetValidator() + { + return new ContentValueSetValidator(false, true, PublicAccessService); + } + + public IContentValueSetValidator GetPublishedContentValueSetValidator() + { + return new ContentValueSetValidator(true, false, PublicAccessService); + } + + /// + /// Returns the for the member indexer + /// + /// + public IValueSetValidator GetMemberValueSetValidator() + { + return new MemberValueSetValidator(); + } + } +} diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 9cd4f39c17..7459ae848b 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -268,7 +268,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(39, types.Count()); + Assert.AreEqual(38, types.Count()); } /// diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index fa9e7b676b..7f103e13e4 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -1087,7 +1087,7 @@ namespace Umbraco.Tests.Integration [Test] public void EmptyRecycleBinContent() { - ServiceContext.ContentService.EmptyRecycleBin(); + ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId); var content = CreateContent(); Assert.IsNotNull(content); @@ -1095,7 +1095,7 @@ namespace Umbraco.Tests.Integration ServiceContext.ContentService.MoveToRecycleBin(content); ResetEvents(); - ServiceContext.ContentService.EmptyRecycleBin(); + ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId); Assert.AreEqual(2, _msgCount); Assert.AreEqual(2, _events.Count); @@ -1109,7 +1109,7 @@ namespace Umbraco.Tests.Integration [Test] public void EmptyRecycleBinContents() { - ServiceContext.ContentService.EmptyRecycleBin(); + ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId); var content1 = CreateContent(); Assert.IsNotNull(content1); @@ -1120,7 +1120,7 @@ namespace Umbraco.Tests.Integration ServiceContext.ContentService.MoveToRecycleBin(content2); ResetEvents(); - ServiceContext.ContentService.EmptyRecycleBin(); + ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId); Assert.AreEqual(3, _msgCount); Assert.AreEqual(4, _events.Count); @@ -1136,7 +1136,7 @@ namespace Umbraco.Tests.Integration [Test] public void EmptyRecycleBinBranch() { - ServiceContext.ContentService.EmptyRecycleBin(); + ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId); var content1 = CreateBranch(); Assert.IsNotNull(content1); @@ -1151,7 +1151,7 @@ namespace Umbraco.Tests.Integration var content4C = Children(content1C[2]).ToArray(); var content5C = Children(content1C[3]).ToArray(); - ServiceContext.ContentService.EmptyRecycleBin(); + ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId); Assert.AreEqual(14, _msgCount); Assert.AreEqual(14, _events.Count); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 7cfc650510..e26e764cd1 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1616,7 +1616,7 @@ namespace Umbraco.Tests.Services Assert.IsTrue(descendants.All(x => x.Path.StartsWith("-1,-20,"))); Assert.True(descendants.All(x => x.Trashed)); - contentService.EmptyRecycleBin(); + contentService.EmptyRecycleBin(Constants.Security.SuperUserId); var trashed = contentService.GetPagedContentInRecycleBin(0, int.MaxValue, out var _).ToList(); Assert.IsEmpty(trashed); } @@ -1628,7 +1628,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; // Act - contentService.EmptyRecycleBin(); + contentService.EmptyRecycleBin(Constants.Security.SuperUserId); var contents = contentService.GetPagedContentInRecycleBin(0, int.MaxValue, out var _).ToList(); // Assert @@ -1804,7 +1804,7 @@ namespace Umbraco.Tests.Services // Act ServiceContext.ContentService.MoveToRecycleBin(content1); - ServiceContext.ContentService.EmptyRecycleBin(); + ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId); var contents = ServiceContext.ContentService.GetPagedContentInRecycleBin(0, int.MaxValue, out var _).ToList(); // Assert diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js index 0ea5006c4a..5d34ad2906 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js @@ -54,6 +54,7 @@ @param {boolean} checked Set to true or false to toggle the switch. +@param {string} inputId Set the id of the toggle. @param {callback} onClick The function which should be called when the toggle is clicked. @param {string=} showLabels Set to true or false to show a "On" or "Off" label next to the switch. @param {string=} labelOn Set a custom label for when the switched is turned on. It will default to "On". @@ -122,6 +123,7 @@ scope: { checked: "=", disabled: "=", + inputId: "@", onClick: "&", labelOn: "@?", labelOff: "@?", 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 552e95563f..2a2852c24f 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 @@ -766,16 +766,6 @@ clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "schedule" })) { - - //used to track the original values so if the user doesn't save the schedule and they close the dialog we reset the dates back to what they were. - let origDates = []; - for (let i = 0; i < $scope.content.variants.length; i++) { - origDates.push({ - releaseDate: $scope.content.variants[i].releaseDate, - expireDate: $scope.content.variants[i].expireDate - }); - } - if (!isContentCultureVariant()) { //ensure the flags are set $scope.content.variants[0].save = true; @@ -784,10 +774,17 @@ var dialog = { parentScope: $scope, view: "views/content/overlays/schedule.html", - variants: $scope.content.variants, //set a model property for the dialog + variants: angular.copy($scope.content.variants), //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_schedulePublish", submit: function (model) { + for (let i = 0; i < $scope.content.variants.length; i++) { + $scope.content.variants[i].releaseDate = model.variants[i].releaseDate; + $scope.content.variants[i].expireDate = model.variants[i].expireDate; + $scope.content.variants[i].releaseDateFormatted = model.variants[i].releaseDateFormatted; + $scope.content.variants[i].expireDateFormatted = model.variants[i].expireDateFormatted; + } + model.submitButtonState = "busy"; clearNotifications($scope.content); @@ -810,7 +807,7 @@ } model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties - dialog.variants = $scope.content.variants; + dialog.variants = angular.copy($scope.content.variants); //don't reject, we've handled the error return $q.when(err); }); @@ -818,11 +815,6 @@ }, close: function () { overlayService.close(); - //restore the dates - for (let i = 0; i < $scope.content.variants.length; i++) { - $scope.content.variants[i].releaseDate = origDates[i].releaseDate; - $scope.content.variants[i].expireDate = origDates[i].expireDate; - } } }; overlayService.open(dialog); 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 a289f8c225..78a2111fc5 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 @@ -307,6 +307,8 @@ // get current backoffice user and format dates userService.getCurrentUser().then(function (currentUser) { scope.currentVariant.createDateFormatted = dateHelper.getLocalDate(scope.currentVariant.createDate, currentUser.locale, 'LLL'); + scope.currentVariant.releaseDateFormatted = dateHelper.getLocalDate(scope.currentVariant.releaseDate, currentUser.locale, 'LLL'); + scope.currentVariant.expireDateFormatted = dateHelper.getLocalDate(scope.currentVariant.expireDate, currentUser.locale, 'LLL'); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/deepBlur.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/deepBlur.directive.js new file mode 100644 index 0000000000..fdff6459c8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/deepBlur.directive.js @@ -0,0 +1,45 @@ + // A slightly modified version of https://github.com/myplanet/angular-deep-blur/blob/master/angular-deep-blur.js - Kudos to Ufuk Kayserilioglu (paracycle) + (function () { + 'use strict'; + + function DeepBlurDirective($timeout){ + + function controller($scope, $element, $attrs) { + var leaveExpr = $attrs.deepBlur, + dom = $element[0]; + + function containsDom(parent, dom) { + while (dom) { + if (dom === parent) { + return true; + } + dom = dom.parentNode; + } + return false; + } + + function onBlur(e) { + var targetElement = e.relatedTarget; + + if (!containsDom(dom, targetElement)) { + $timeout(function () { + $scope.$apply(leaveExpr); + }, 10); + } + } + + dom.addEventListener('blur', onBlur, true); + } + + var directive = { + restrict: 'A', + controller: controller + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('deepBlur', DeepBlurDirective); + + })(); 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 deleted file mode 100644 index 53aa7475c4..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ /dev/null @@ -1,273 +0,0 @@ -/** -* @description Utillity directives for key and field events -**/ -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); - } - }; - - elm.on("dragleave", f); - scope.$on("$destroy", function () { elm.off("dragleave", 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('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('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('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; } - - // ignore clicks on new overlay - var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour"); - if (parents.length > 0) { - return; - } - - // ignore clicks on dialog from old dialog service - var oldDialog = $(event.target).parents("#old-dialog-service"); - if (oldDialog.length === 1) { - return; - } - - // ignore clicks in tinyMCE dropdown(floatpanel) - var floatpanel = $(event.target).closest(".mce-floatpanel"); - if (floatpanel.length === 1) { - return; - } - - // ignore clicks in flatpickr datepicker - var flatpickr = $(event.target).closest(".flatpickr-calendar"); - if (flatpickr.length === 1) { - return; - } - - //ignore clicks inside this element - if ($(element).has($(event.target)).length > 0) { - return; - } - - // please to not use angularHelper.safeApply here, it won't work - 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); - } - - 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; - }); - }; - }) - - .directive('onDelayedMouseleave', function ($timeout, $parse) { - return { - - restrict: 'A', - - link: function (scope, element, attrs, ctrl) { - var active = false; - var fn = $parse(attrs.onDelayedMouseleave); - - var leave_f = function (event) { - var callback = function () { - fn(scope, { $event: event }); - }; - - active = false; - $timeout(function () { - if (active === false) { - scope.$apply(callback); - } - }, 650); - }; - - var enter_f = function (event, args) { - active = true; - }; - - - element.on("mouseleave", leave_f); - element.on("mouseenter", enter_f); - - //unsub events - scope.$on("$destroy", function () { - element.off("mouseleave", leave_f); - element.off("mouseenter", enter_f); - }); - } - }; - }) - - // A slightly modified version of https://github.com/myplanet/angular-deep-blur/blob/master/angular-deep-blur.js - Kudos to Ufuk Kayserilioglu (paracycle) - .directive('deepBlur', function ($timeout) { - return { - - restrict: 'A', - - controller: function ($scope, $element, $attrs) { - var leaveExpr = $attrs.deepBlur, - dom = $element[0]; - - function containsDom(parent, dom) { - while (dom) { - if (dom === parent) { - return true; - } - dom = dom.parentNode; - } - return false; - } - - function onBlur(e) { - var targetElement = e.relatedTarget; - - if (!containsDom(dom, targetElement)) { - $timeout(function () { - $scope.$apply(leaveExpr); - }, 10); - } - } - - dom.addEventListener('blur', onBlur, true); - } - }; - }); - - diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDelayedMouseleave.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDelayedMouseleave.directive.js new file mode 100644 index 0000000000..8493e49d77 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDelayedMouseleave.directive.js @@ -0,0 +1,49 @@ +(function () { + 'use strict'; + + function onDelayedMouseleaveDirective($timeout, $parse){ + + function link(scope, element, attrs, ctrl) { + var active = false; + var fn = $parse(attrs.onDelayedMouseleave); + + var leave_f = function (event) { + var callback = function () { + fn(scope, { $event: event }); + }; + + active = false; + $timeout(function () { + if (active === false) { + scope.$apply(callback); + } + }, 650); + }; + + var enter_f = function (event, args) { + active = true; + }; + + + element.on("mouseleave", leave_f); + element.on("mouseenter", enter_f); + + //unsub events + scope.$on("$destroy", function () { + element.off("mouseleave", leave_f); + element.off("mouseenter", enter_f); + }); + } + + var directive = { + restrict: 'A', + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('onDelayedMouseleave', onDelayedMouseleaveDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragEnd.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragEnd.directive.js new file mode 100644 index 0000000000..84501e10d6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragEnd.directive.js @@ -0,0 +1,24 @@ +(function () { + 'use strict'; + + function onDragEndDirective(){ + + function link(scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnd); + }; + elm.on("dragend", f); + scope.$on("$destroy", function () { elm.off("dragend", f); }); + } + + var directive = { + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('onDragEnd', onDragEndDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragEnter.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragEnter.directive.js new file mode 100644 index 0000000000..03ae9465d0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragEnter.directive.js @@ -0,0 +1,24 @@ +(function () { + 'use strict'; + + function onDragEnterDirective(){ + + function link(scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnter); + }; + elm.on("dragenter", f); + scope.$on("$destroy", function () { elm.off("dragenter", f); }); + } + + var directive = { + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('onDragEnter', onDragEnterDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragLeave.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragLeave.directive.js new file mode 100644 index 0000000000..cd05ed9289 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragLeave.directive.js @@ -0,0 +1,40 @@ +(function () { + 'use strict'; + + function onDragLeaveDirective($timeout){ + + 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); + } + }; + + elm.on("dragleave", f); + scope.$on("$destroy", function () { elm.off("dragleave", f); }); + }; + + } + + angular.module('umbraco.directives').directive('onDragLeave', onDragLeaveDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragOver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragOver.directive.js new file mode 100644 index 0000000000..faf9050b8e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragOver.directive.js @@ -0,0 +1,24 @@ +(function () { + 'use strict'; + + function onDragOverDirective(){ + + function link(scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragOver); + }; + elm.on("dragover", f); + scope.$on("$destroy", function () { elm.off("dragover", f); }); + } + + var directive = { + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('onDragOver', onDragOverDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragStart.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragStart.directive.js new file mode 100644 index 0000000000..8da6211c08 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDragStart.directive.js @@ -0,0 +1,24 @@ +(function () { + 'use strict'; + + function onDragStartDirective($timeout){ + + function link(scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragStart); + }; + elm.on("dragstart", f); + scope.$on("$destroy", function () { elm.off("dragstart", f); }); + } + + var directive = { + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('onDragStart', onDragStartDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDrop.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDrop.directive.js new file mode 100644 index 0000000000..632549f21b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onDrop.directive.js @@ -0,0 +1,24 @@ +(function () { + 'use strict'; + + function onDropDirective(){ + + function link(scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDrop); + }; + elm.on("drop", f); + scope.$on("$destroy", function () { elm.off("drop", f); }); + } + + var directive = { + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('onDrop', onDropDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onOutsideClick.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onOutsideClick.directive.js new file mode 100644 index 0000000000..a657fcbc55 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onOutsideClick.directive.js @@ -0,0 +1,86 @@ +(function () { + 'use strict'; + + function onOutsideClickDirective($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; } + + // ignore clicks on new overlay + var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour"); + if (parents.length > 0) { + return; + } + + // ignore clicks on dialog from old dialog service + var oldDialog = $(event.target).parents("#old-dialog-service"); + if (oldDialog.length === 1) { + return; + } + + // ignore clicks in tinyMCE dropdown(floatpanel) + var floatpanel = $(event.target).closest(".mce-floatpanel"); + if (floatpanel.length === 1) { + return; + } + + // ignore clicks in flatpickr datepicker + var flatpickr = $(event.target).closest(".flatpickr-calendar"); + if (flatpickr.length === 1) { + return; + } + + //ignore clicks inside this element + if ($(element).has($(event.target)).length > 0) { + return; + } + + // please to not use angularHelper.safeApply here, it won't work + 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); + } + + 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. + + }; + + } + + angular.module('umbraco.directives').directive('onOutsideClick', onOutsideClickDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onRightClick.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onRightClick.directive.js new file mode 100644 index 0000000000..6b818997d7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onRightClick.directive.js @@ -0,0 +1,30 @@ +(function () { + 'use strict'; + + function onRightClickDirective($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; + }); + }; + + } + + angular.module('umbraco.directives').directive('onRightClick', onRightClickDirective); + +})(); 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 d1f35f8e03..32d3e83dac 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 @@ -418,6 +418,10 @@ }; + scope.canRemoveGroup = function(group){ + return _.find(group.properties, function(property) { return property.locked === true; }) == null; + } + scope.removeGroup = function(groupIndex) { scope.model.groups.splice(groupIndex, 1); }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 766c0107b2..1b011d2e19 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -193,6 +193,9 @@ Use this directive to generate a thumbnail grid of media items. * Returns wether a item should be selectable or not. */ function getSelectableState(item) { + if (item.filtered) { + return false; + } // check if item is a folder or image if (item.isFolder === true) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 9c140d572e..1142863bd0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -77,8 +77,7 @@ } } - // filter items if there is a filter and it's not advanced - // ** ignores advanced filter at the moment + // filter items if there is a filter and it's not advanced (advanced filtering is handled below) if (scope.entityTypeFilter && scope.entityTypeFilter.filter && !scope.entityTypeFilter.filterAdvanced) { var a = scope.entityTypeFilter.filter.toLowerCase().replace(/\s/g, '').split(','); var found = a.indexOf(c.metaData.ContentTypeAlias.toLowerCase()) >= 0; @@ -88,6 +87,15 @@ } } }); + + // advanced item filtering is handled here + if (scope.entityTypeFilter && scope.entityTypeFilter.filter && scope.entityTypeFilter.filterAdvanced) { + var filtered = angular.isFunction(scope.entityTypeFilter.filter) + ? _.filter(miniListView.children, scope.entityTypeFilter.filter) + : _.where(miniListView.children, scope.entityTypeFilter.filter); + _.each(filtered, (node) => node.allowed = false); + } + // update pagination miniListView.pagination.totalItems = data.totalItems; miniListView.pagination.totalPages = data.totalPages; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js index 6360b429b9..7aa23cad7c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js @@ -298,7 +298,8 @@ * Called when the file collection changes (i.e. a new file has been selected but maybe it wasn't this instance that caused the change) */ onFilesChanged: "&", - onInit: "&" + onInit: "&", + required: "@" }, transclude: true, controllerAs: 'vm', diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js index 601ff2f671..3874ff9bf6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js @@ -187,7 +187,6 @@ angular.module('umbraco.mocks'). "defaultdialogs_linkinternal": "Internal link:", "defaultdialogs_linklocaltip": "When using local links, insert '#' infront of link", "defaultdialogs_linknewwindow": "Open in new window?", - "defaultdialogs_macroContainerSettings": "Macro Settings", "defaultdialogs_macroDoesNotHaveProperties": "This macro does not contain any properties you can edit", "defaultdialogs_paste": "Paste", "defaultdialogs_permissionsEdit": "Edit Permissions for", 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 5d790070b5..20a47ae32c 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 @@ -464,6 +464,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt "properties", "apps", "createDateFormatted", + "releaseDateFormatted", + "expireDateFormatted", "releaseDate", "expireDate" ], function (i) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index ca4108e18c..bcfd9544a2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -898,6 +898,27 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#memberEditor + * @methodOf umbraco.services.editorService + * + * @description + * Opens a member editor in infinite editing, the submit callback returns the updated member + * @param {Object} editor rendering options + * @param {String} editor.id The id (GUID) of the member + * @param {Boolean} editor.create Create new member + * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object + * @param {Function} editor.close Callback function when the close button is clicked. + * @param {String} editor.doctype If editor.create is true, provide member type for the creation of the member + * + * @returns {Object} editor object + */ + function memberEditor(editor) { + editor.view = "views/member/edit.html"; + open(editor); + } + /////////////////////// /** @@ -978,7 +999,8 @@ When building a custom infinite editor view you can use the same components as a itemPicker: itemPicker, macroPicker: macroPicker, memberGroupPicker: memberGroupPicker, - memberPicker: memberPicker + memberPicker: memberPicker, + memberEditor: memberEditor }; return service; 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 da013d88ee..4ff5a05522 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 @@ -588,12 +588,15 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s var selectedElm = editor.selection.getNode(), - currentTarget; + currentTarget, + imgDomElement; if (selectedElm.nodeName === 'IMG') { var img = $(selectedElm); + imgDomElement = selectedElm; var hasUdi = img.attr("data-udi") ? true : false; + var hasDataTmpImg = img.attr("data-tmpimg") ? true : false; currentTarget = { altText: img.attr("alt"), @@ -605,12 +608,16 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } else { currentTarget["id"] = img.attr("rel"); } + + if(hasDataTmpImg){ + currentTarget["tmpimg"] = img.attr("data-tmpimg"); + } } userService.getCurrentUser().then(function (userData) { if (callback) { angularHelper.safeApply($rootScope, function() { - callback(currentTarget, userData); + callback(currentTarget, userData, imgDomElement); }); } }); @@ -618,25 +625,77 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }); }, - insertMediaInEditor: function (editor, img) { + insertMediaInEditor: function (editor, img, imgDomElement) { if (img) { + // imgElement is only definied if updating an image + // if null/undefinied then its a BRAND new image + if(imgDomElement){ + // Check if the img src has changed + // If it has we will need to do some resizing/recalc again + var hasImageSrcChanged = false; - var data = { - alt: img.altText || "", - src: (img.url) ? img.url : "nothing.jpg", - id: '__mcenew', - 'data-udi': img.udi - }; + if(img.url !== editor.dom.getAttrib(imgDomElement, "src")){ + hasImageSrcChanged = true; + } - editor.selection.setContent(editor.dom.createHTML('img', data)); + // If null/undefinied it will remove the attribute + editor.dom.setAttrib(imgDomElement, "alt", img.altText); - $timeout(function () { - var imgElm = editor.dom.get('__mcenew'); - sizeImageInEditor(editor, imgElm, img.url); - editor.dom.setAttrib(imgElm, 'id', null); - editor.fire('Change'); + // It's possible to pick a NEW image - so need to ensure this gets updated + if(img.udi){ + editor.dom.setAttrib(imgDomElement, "data-udi", img.udi); + } - }, 500); + // It's possible to pick a NEW image - so need to ensure this gets updated + if(img.url){ + editor.dom.setAttrib(imgDomElement, "src", img.url); + } + + // Remove width & height attributes (ONLY if imgSrc changed) + // So native image size is used as this needed to re-calc width & height + // For the function sizeImageInEditor() & apply the image resizing querystrings etc.. + if(hasImageSrcChanged){ + editor.dom.setAttrib(imgDomElement, "width", null); + editor.dom.setAttrib(imgDomElement, "height", null); + + //Re-calc the image dimensions + sizeImageInEditor(editor, imgDomElement, img.url); + } + + } else{ + // We need to create a NEW DOM element to insert + // setting an attribute of ID to __mcenew, so we can gather a reference to the node, to be able to update its size accordingly to the size of the image. + var data = { + alt: img.altText || "", + src: (img.url) ? img.url : "nothing.jpg", + id: "__mcenew", + "data-udi": img.udi + }; + + editor.selection.setContent(editor.dom.createHTML('img', data)); + + // Using settimeout to wait for a DoM-render, so we can find the new element by ID. + $timeout(function () { + + var imgElm = editor.dom.get("__mcenew"); + editor.dom.setAttrib(imgElm, "id", null); + + // When image is loaded we are ready to call sizeImageInEditor. + var onImageLoaded = function() { + sizeImageInEditor(editor, imgElm, img.url); + editor.fire("Change"); + } + + // Check if image already is loaded. + if(imgElm.complete === true) { + onImageLoaded(); + } else { + imgElm.onload = onImageLoaded; + } + + }); + + } } }, @@ -1287,8 +1346,9 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s var content = e.content; // Upload BLOB images (dragged/pasted ones) - if(content.indexOf('
- +
+ +
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 b562a50f65..0d184b71aa 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 @@ -37,7 +37,7 @@ angular.module("umbraco") $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); $scope.lockedFolder = true; $scope.allowMediaEdit = dialogOptions.allowMediaEdit ? dialogOptions.allowMediaEdit : false; - + var userStartNodes = []; var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; @@ -124,11 +124,15 @@ angular.module("umbraco") gotoStartNode(); } } else { - //if a target is specified, go look it up - generally this target will just contain ids not the actual full - //media object so we need to look it up + // if a target is specified, go look it up - generally this target will just contain ids not the actual full + // media object so we need to look it up var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; var altText = $scope.target.altText; - entityResource.getById(id, "Media") + + // ID of a UDI or legacy int ID still could be null/undefinied here + // As user may dragged in an image that has not been saved to media section yet + if(id){ + entityResource.getById(id, "Media") .then(function (node) { $scope.target = node; if (ensureWithinStartNode(node)) { @@ -138,6 +142,12 @@ angular.module("umbraco") openDetailsDialog(); } }, gotoStartNode); + } + else { + // No ID set - then this is going to be a tmpimg that has not been uploaded + // User editing this will want to be changing the ALT text + openDetailsDialog(); + } } } @@ -216,7 +226,7 @@ angular.module("umbraco") } function clickHandler(media, event, index) { - + if (media.isFolder) { if ($scope.disableFolderSelect) { gotoFolder(media); @@ -443,21 +453,25 @@ angular.module("umbraco") function getChildren(id) { vm.loading = true; return entityResource.getChildren(id, "Media", vm.searchOptions).then(function (data) { - - for (var i = 0; i < data.length; i++) { - if (data[i].metaData.MediaPath !== null) { - data[i].thumbnail = mediaHelper.resolveFileFromEntity(data[i], true); - data[i].image = mediaHelper.resolveFileFromEntity(data[i], false); - } + + var allowedTypes = dialogOptions.filter ? dialogOptions.filter.split(",") : null; + + for (var i = 0; i < data.length; i++) { + if (data[i].metaData.MediaPath !== null) { + data[i].thumbnail = mediaHelper.resolveFileFromEntity(data[i], true); + data[i].image = mediaHelper.resolveFileFromEntity(data[i], false); } - vm.searchOptions.filter = ""; - $scope.images = data ? data : []; + data[i].filtered = allowedTypes && allowedTypes.indexOf(data[i].metaData.ContentTypeAlias) < 0; + } - // set already selected medias to selected - preSelectMedia(); - vm.loading = false; - }); + vm.searchOptions.filter = ""; + $scope.images = data ? data : []; + + // set already selected medias to selected + preSelectMedia(); + vm.loading = false; + }); } function preSelectMedia() { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html index a08395143f..ed6be2e796 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html @@ -64,7 +64,7 @@
  • - + 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 737253feb2..7fcabda57b 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 @@ -6,13 +6,29 @@
  • - +
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html index 0f90239b0e..21ed753c15 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html @@ -1,5 +1,6 @@
    -
    \ No newline at end of file + 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 0c8d77eabb..5df324c60f 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 @@ -203,7 +203,9 @@ function contentPickerController($scope, entityResource, editorState, iconHelper //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, // but not much we can do about that since members require special filtering. var filterItem = currFilter.toLowerCase().split(','); - var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0; + // NOTE: when used in a mini list view, the item content type alias is metaData.ContentTypeAlias (in regular views it's metaData.contentType) + var itemContentType = i.metaData.contentType || i.metaData.ContentTypeAlias; + var found = filterItem.indexOf(itemContentType.toLowerCase()) >= 0; if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) { return true; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index 8f97456c60..2bc609714a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -2,6 +2,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html index 7438fa04da..a0faefe690 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html @@ -5,6 +5,7 @@ + +
    + +
    + +
    +
    +
    + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index e50411fe8b..fb01875733 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -262,10 +262,10 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs $scope.getContent = function (contentId) { - $scope.reloadView($scope.contentId); + $scope.reloadView($scope.contentId, true); } - $scope.reloadView = function (id) { + $scope.reloadView = function (id, reloadActiveNode) { $scope.viewLoaded = false; $scope.folders = []; @@ -297,9 +297,20 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs $scope.options.pageNumber = $scope.listViewResultSet.totalPages; //reload! - $scope.reloadView(id); + $scope.reloadView(id, reloadActiveNode); + } + // in the media section, the list view items are by default also shown in the tree, so we need + // to refresh the current tree node when changing the folder contents (adding and removing) + else if (reloadActiveNode && section === "media") { + var activeNode = appState.getTreeState("selectedNode"); + if (activeNode) { + if (activeNode.expanded) { + navigationService.reloadNode(activeNode); + } + } else { + navigationService.reloadSection(section); + } } - }); }; @@ -411,7 +422,7 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs var key = (total === 1 ? "bulk_deletedItem" : "bulk_deletedItems"); return localizationService.localize(key, [total]); }).then(function () { - $scope.reloadView($scope.contentId); + $scope.reloadView($scope.contentId, true); }); } @@ -489,6 +500,7 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs const dialog = { view: "views/propertyeditors/listview/overlays/listviewunpublish.html", submitButtonLabelKey: "actions_unpublish", + submitButtonStyle: "warning", submit: function (model) { // create a comma separated array of selected cultures @@ -536,7 +548,7 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs var key = (total === 1 ? "bulk_unpublishedItem" : "bulk_unpublishedItems"); return localizationService.localize(key, [total]); }).then(function () { - $scope.reloadView($scope.contentId); + $scope.reloadView($scope.contentId, true); }); } 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 ebdc1eb4c4..abe1db73d2 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 @@ -1,41 +1,46 @@
    +

    - -
    + +
    + +
    + + +

    -
    - -
    +
    -
    +
    + +
    - + + +
    -
    -
    - -
    - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.controller.js index b6a236700e..46604227da 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.controller.js @@ -56,7 +56,6 @@ } vm.loading = false; - } onInit(); 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 a171fb1327..f59b351181 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 @@ -1,12 +1,16 @@
    - +

    +
    + +
    + -
    +

    @@ -14,26 +18,29 @@
    +
    + +
    +
    - + + +
    -
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js deleted file mode 100644 index b258bf829a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js +++ /dev/null @@ -1,138 +0,0 @@ -//DO NOT DELETE THIS, this is in use... -angular.module('umbraco') -.controller("Umbraco.PropertyEditors.MacroContainerController", - - function($scope, macroService){ - - $scope.renderModel = []; - $scope.allowOpenButton = true; - $scope.allowRemoveButton = true; - $scope.sortableOptions = {}; - - if($scope.model.value){ - var macros = $scope.model.value.split('>'); - - angular.forEach(macros, function(syntax, key){ - if(syntax && syntax.length > 10){ - //re-add the char we split on - syntax = syntax + ">"; - var parsed = macroService.parseMacroSyntax(syntax); - if(!parsed){ - parsed = {}; - } - - parsed.syntax = syntax; - collectDetails(parsed); - $scope.renderModel.push(parsed); - setSortingState($scope.renderModel); - } - }); - } - - - function collectDetails(macro){ - macro.details = ""; - macro.icon = "icon-settings-alt"; - if(macro.macroParamsDictionary){ - angular.forEach((macro.macroParamsDictionary), function(value, key){ - macro.details += key + ": " + value + " "; - }); - } - } - - function openDialog(index){ - var dialogData = { - allowedMacros: $scope.model.config.allowed - }; - - if(index !== null && $scope.renderModel[index]) { - var macro = $scope.renderModel[index]; - dialogData["macroData"] = macro; - } - - $scope.macroPickerOverlay = {}; - $scope.macroPickerOverlay.view = "macropicker"; - $scope.macroPickerOverlay.dialogData = dialogData; - $scope.macroPickerOverlay.show = true; - - $scope.macroPickerOverlay.submit = function(model) { - - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); - collectDetails(macroObject); - - //update the raw syntax and the list... - if(index !== null && $scope.renderModel[index]) { - $scope.renderModel[index] = macroObject; - } else { - $scope.renderModel.push(macroObject); - } - - setSortingState($scope.renderModel); - - $scope.macroPickerOverlay.show = false; - $scope.macroPickerOverlay = null; - }; - - $scope.macroPickerOverlay.close = function(oldModel) { - $scope.macroPickerOverlay.show = false; - $scope.macroPickerOverlay = null; - }; - - } - - - - $scope.edit =function(index){ - openDialog(index); - }; - - $scope.add = function () { - - if ($scope.model.config.max && $scope.model.config.max > 0 && $scope.renderModel.length >= $scope.model.config.max) { - //cannot add more than the max - return; - } - - openDialog(); - }; - - $scope.remove =function(index){ - $scope.renderModel.splice(index, 1); - setSortingState($scope.renderModel); - }; - - $scope.clear = function() { - $scope.model.value = ""; - $scope.renderModel = []; - }; - - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var syntax = []; - angular.forEach($scope.renderModel, function(value, key){ - syntax.push(value.syntax); - }); - - $scope.model.value = syntax.join(""); - }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - unsubscribe(); - }); - - - function trim(str, chr) { - var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g'); - return str.replace(rgxtrim, ''); - } - - function setSortingState(items) { - // disable sorting if the list only consist of one item - if(items.length > 1) { - $scope.sortableOptions.disabled = false; - } else { - $scope.sortableOptions.disabled = true; - } - } - -}); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.html deleted file mode 100644 index 6d844e959b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.html +++ /dev/null @@ -1,33 +0,0 @@ -
    - -
    -
    - - -
    -
    - - - Add - - - - - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrolist.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrolist.prevalues.controller.js deleted file mode 100644 index ffede2abd4..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrolist.prevalues.controller.js +++ /dev/null @@ -1,15 +0,0 @@ -function MacroListController($scope, entityResource) { - - $scope.items = []; - - entityResource.getAll("Macro").then(function(items) { - _.each(items, function(i) { - $scope.items.push({ name: i.name, alias: i.alias }); - }); - - }); - - -} - -angular.module("umbraco").controller("Umbraco.PrevalueEditors.MacroList", MacroListController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrolist.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrolist.prevalues.html deleted file mode 100644 index d0081e7bc2..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrolist.prevalues.html +++ /dev/null @@ -1,7 +0,0 @@ -
    - - - -
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index 29e3a970e2..c4f3342000 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -36,16 +36,16 @@
  • - +
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js index 1e51d4585e..b21859f5c4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js @@ -81,7 +81,7 @@ } // Disallow selection of the admin/translators group, the checkbox is not visible in the UI, but clicking(and thus selecting) is still possible. // Currently selection can only be used for deleting, and the Controller will also disallow deleting the admin group. - if (userGroup.alias === "admin" || userGroup.alias === "translator") + if (userGroup.isSystemUserGroup) return; listViewHelper.selectHandler(userGroup, $index, vm.userGroups, vm.selection, $event); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html index 5d1496d90f..4d252a3ae0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html @@ -86,7 +86,7 @@
    + ng-class="{'-selected': group.selected, '-selectable': group.hasAccess && !group.isSystemUserGroup}">
    diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 6c20e8c765..ceeb2d076a 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -94,10 +94,6 @@ - - all - - @@ -429,4 +425,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index a229898673..9f233a1f8c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -225,7 +225,6 @@ Místní odkaz: Při používání místních odkazů vložte znak "#" před odkaz Otevřít v novém okně? - Nastavení makra Toto makro nemá žádné vlastnosti, které by bylo možno editovat Vložit Editovat oprávnění pro diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 09c4283443..e2a9b02086 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -11,6 +11,7 @@ Skift dokumenttype Kopier Opret + Eksportér Opret pakke Opret gruppe Slet @@ -69,6 +70,7 @@ Tillad adgang til at flytte en node Tillad adgang til at indstille og ændre offentlig adgang til en node Tillad adgang til at udgive en node + Tillad adgang til at afpublicere en node Tillad adgang til at ændre rettigheder for en node Tillad adgang til at returnere en node til en tidligere tilstand Tillad adgang til at sende en node til godkendelse før den udgives @@ -164,8 +166,10 @@ Brugeren har sorteret de underliggende sider Kopieret Udgivet + Udgivet Flyttet Gemt + Gemt Slettet Afpubliceret Indhold tilbagerullet @@ -230,14 +234,17 @@ Der er endnu ikke lavet nogle ændringer. Ingen dato valgt Sidetitel + Dette medie har ikke noget link Egenskaber Dette dokument er udgivet, men ikke synligt da den overliggende side '%0%' ikke er udgivet! + Dette sprog er udgivet, men ikke synligt, da den overliggende side '%0%' ikke er udgivet! Ups: dette dokument er udgivet, men er ikke i cachen (intern fejl) Kunne ikke hente url'en Dette dokument er udgivet, men dets url ville kollidere med indholdet %0% + Dette dokument er udgivet, men dets URL kan ikke dirigeres Udgiv Udgivet - Udgivet (Ventede ændringer) + Udgivet (Afventende ændringer) Udgivelsesstatus Udgiv med undersider for at udgive %0% og alle sider under og dermed gøre deres indhold offentligt tilgængelige.]]> Udgiv med undersider for at udgive de valgte sprog og de samme sprog for sider under og dermed gøre deres indhold offentligt tilgængelige.]]> @@ -267,6 +274,7 @@ Er du sikker på, at du vil slette dette element? Egenskaben %0% anvender editoren %1% som ikke er understøttet af Nested Content. Der er ikke konfigureret nogen indholdstyper for denne egenskab. + %0% fra %1% Tilføj en ny tekstboks Fjern denne tekstboks Indholdsrod @@ -325,7 +333,13 @@ Angiv et navn for mappen Vælg en type og skriv en titel "dokument typer".]]> + Document Types within the Settings section.]]> + Den valgte side i træet tillader ikke at sider oprettes under den. + Rediger tilladelser for denne dokumenttype. + Opret en ny dokumenttype "media typer".]]> + Det valgte medie i træet tillader ikke at medier oprettes under det. + Rediger tilladelser for denne medietype. Dokumenttype uden skabelon Ny mappe Ny datatype @@ -352,6 +366,8 @@ Kassér ændringer Du har ikke-gemte ændringer Er du sikker på du vil navigere væk fra denne side? - du har ikke-gemte ændringer + Udgivelse vil gøre de valgte sider synlige på sitet. + Afpublicering vil fjerne de valgte sider og deres undersider fra sitet. Afpublicering vil fjerne denne side og alle dets undersider fra websitet. Du har ikke-gemte ændringer. Hvis du ændrer dokumenttype, kasseres ændringerne. @@ -381,6 +397,7 @@ Link titel Link + Lokalt link / querystreng Navn på lokalt link Rediger domæner Luk denne dialog @@ -404,7 +421,6 @@ Internt link: Ved lokalt link, indsæt da en "#" foran linket Åben i nyt vindue? - Makroindstillinger Denne makro har ingen egenskaber du kan redigere Indsæt tekst Rediger rettigheder for @@ -450,6 +466,10 @@ Ingen ikoner blev fundet Der er ingen parametre for denne makro Der er ikke tilføjet nogen makroer + Eksterne login-udbydere + Undtagelsesdetaljer + Stacktrace + Indre undtagelse Link dit Fjern link fra dit konto @@ -509,12 +529,17 @@ Indtast din e-mail Indtast en besked... Dit brugernavn er typisk din e-mailadresse + #value eller ?key=value Indtast alias... Genererer alias... + Opret element + Rediger + Navn Opret brugerdefineret listevisning Fjern brugerdefineret listevisning + En dokumenttype, medietype eller medlemstype med dette alias findes allerede Omdøbt @@ -560,6 +585,7 @@ OBS! Selvom CodeMirror er slået til i konfigurationen, så er den deaktiveret i Internet Explorer fordi den ikke er stabil nok. Du skal udfylde både Alias & Navn på den nye egenskabstype! Der mangler læse/skrive rettigheder til bestemte filer og mapper + Fejl ved indlæsning af Partial View script (fil: %0%) Skriv venligst en titel Du skal vælge en type Du er ved at gøre billedet større end originalen. Det vil forringe kvaliteten af billedet. Ønsker du at fortsætte? @@ -576,6 +602,7 @@ Muligheder Tilføj Alias + Alle Er du sikker? Tilbage Tilbage til oversigt @@ -601,6 +628,7 @@ Slettet Sletter... Design + Ordbog Dimensioner Ned Hent @@ -623,14 +651,17 @@ Id Importer Inkludér undermapper i søgning + Info Indre margen Indsæt Installér Ugyldig Justering + Mærke Sprog Sidste Layout + Links Henter Låst Log ind @@ -645,8 +676,11 @@ Næste Nej af + Fra OK Åben + Valgmuligheder + Til eller Sortér efter Kodeord @@ -681,6 +715,7 @@ Indsend Type Skriv for at søge... + under Op Opdatér Opdatér @@ -730,6 +765,7 @@ Move Lines Down Generelt Editor + Skift tillad sprogvarianter Baggrundsfarve @@ -993,6 +1029,10 @@ Mange hilsner fra Umbraco robotten + + %0% kunne ikke udgives, fordi et 3. parts modul annullerede handlingen Medtag ikke-udgivede undersider @@ -1028,6 +1068,8 @@ Mange hilsner fra Umbraco robotten Nulstil + Gem beskæring + Tilføj ny beskæring Acceptér Fortryd @@ -1075,6 +1117,8 @@ Mange hilsner fra Umbraco robotten Faneblad Titel på faneblad Faneblade + Opret tilsvarende skabelon + Tilføj ikon Sorteringsrækkefølge @@ -1091,6 +1135,7 @@ Mange hilsner fra Umbraco robotten Utilstrækkelige brugerrettigheder, kunne ikke fuldføre handlingen Annulleret Handlingen blev annulleret af et 3. part tilføjelsesprogram + Udgivelsen blev annulleret af et 3. part tilføjelsesprogram Property type eksisterer allerede Egenskabstype oprettet DataType: %1%]]> @@ -1104,6 +1149,7 @@ Mange hilsner fra Umbraco robotten Stylesheet gemt uden fejl Datatype gemt Ordbogsnøgle gemt + Udgivelse fejlede da overliggende side ikke er udgivet Indhold publiceret og nu synligt for besøgende Indhold gemt @@ -1148,6 +1194,11 @@ Mange hilsner fra Umbraco robotten Brugergrupper er blevet indstillet Låste %0% brugere op %0% er nu låst op + Brugeren %0% blev slettet + Invitér bruger + Invitationen blev gensendt til %0% + Dokumenttypen blev eksporteret til en fil + Der skete en fejl under eksport af en dokumenttype Tilføj style @@ -1312,7 +1363,9 @@ Mange hilsner fra Umbraco robotten Konfiguration Ja, slet blev flyttet til + blev kopieret til Vælg hvor + Vælg hvor skal flyttes til Alle dokumenttyper Alle dokumenter @@ -1324,8 +1377,15 @@ Mange hilsner fra Umbraco robotten og alle medier, som benytter denne type og alle medlemmer, som benytter denne type Medlem kan redigere + Tillad at denne egenskab kan redigeres af medlemmet på dets profil. + Er følsom data + Skjul værdien af denne egenskab for indholdsredaktører der ikke har adgang til at se følsomme data Vis på medlemsprofil + Tillad at denne egenskab kan vises på medlemmets profil. fane har ingen sorteringsrækkefølge + Hvor er denne komposition brugt? + Denne komposition brugt i kompositionen af de følgende indholdstyper: + Tillad sprogvariation Tillad at redaktører kan oprette indhold af denne type på flere sprog. Tillad sprogvariation Element-type @@ -1354,14 +1414,28 @@ Mange hilsner fra Umbraco robotten Definér de parametre der skal være tilgængelige, når du bruger denne makro. Vælg partial view makrofil + + Bygger modeller + dette kan tage lidt tid + Modeller genereret + Modeller kunne ikke genereres + Modelgeneration fejlet, se fejlmeddelelse i log + + Tilføj fallback felt + Fallback felt + Tilføj standard værdi + Standard værdi Alternativt felt Alternativ tekst Casing + Kodning Felt som skal indsættes Konvertér linieskift Erstatter et linieskift med html-tag'et &lt;br&gt; + Custom felter Ja, kun dato + Format og kodning Formatér som dato HTML indkod Vil erstatte specielle karakterer med deres HTML jævnbyrdige. @@ -1433,6 +1507,7 @@ Mange hilsner fra Umbraco robotten Scripts Stylesheets Skabeloner + Logfremviser Brugertilladelser Brugertyper Brugere @@ -1461,6 +1536,7 @@ Mange hilsner fra Umbraco robotten Gentag dit nye kodeord Du kan ændre dit kodeord, som giver dig adgang til Umbraco Back Office ved at udfylde formularen og klikke på knappen 'Skift dit kodeord' Indholdskanal + Opret endnu en bruger Opret nye brugere for at give dem adgang til Umbraco. Når en ny bruger oprettes, genereres der en adgangskode, som du kan dele med brugeren. Beskrivelsesfelt Deaktivér bruger @@ -1474,7 +1550,7 @@ Mange hilsner fra Umbraco robotten Invitér nye brugere til at give dem adgang til Umbraco. En invitation vil blive sendt via e-mail til brugeren med oplysninger om, hvordan man logger ind i Umbraco. Sprog Indstil det sprog, du vil se i menuer og dialoger - Seneste låst ude dato + Senest låst ude Seneste login Kodeord sidst ændret Brugernavn @@ -1523,6 +1599,7 @@ Mange hilsner fra Umbraco robotten er blevet inviteret En invitation er blevet sendt til den nye bruger med oplysninger om, hvordan man logger ind i Umbraco. Hej og velkommen til Umbraco! På bare 1 minut vil du være klar til at komme i gang, vi skal bare have dig til at oprette en adgangskode og tilføje et billede til din avatar. + Velkommen til Umbraco! Desværre er din invitation udløbet. Kontakt din administrator og bed om at gensende invitationen. Hvis du uploader et billede af dig selv, gør du det nemt for andre brugere at genkende dig. Klik på cirklen ovenfor for at uploade et billede. Forfatter Skift @@ -1636,5 +1713,7 @@ Mange hilsner fra Umbraco robotten Åben backoffice søgning Åben/Luk backoffice hjælp Åben/Luk dine profil indstillinger + Aktivt sprog + Skift sprog til diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index f529595f65..03b333e6f2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -430,7 +430,6 @@ Anker: Wenn lokale Links verwendet werden, füge ein "#" vor den Link ein In einem neuen Fenster öffnen? - Macro Einstellungen Dieses Makro enthält keine einstellbaren Eigenschaften. Einfügen Berechtigungen bearbeiten für diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index d7d743e7e9..8804ec3870 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -333,8 +333,10 @@ Enter a folder name Choose a type and a title Document Types within the Settings section, by editing the Allowed child node types under Permissions.]]> + Document Types within the Settings section.]]> The selected page in the content tree doesn't allow for any pages to be created below it. Edit permissions for this document type + Create a new document type Media Types Types within the Settings section, by editing the Allowed child node types under Permissions.]]> The selected media in the tree doesn't allow for any other media to be created below it. Edit permissions for this media type @@ -419,7 +421,6 @@ Internal link: When using local links, insert "#" in front of link Open in new window? - Macro Settings This macro does not contain any properties you can edit Paste Edit permissions for @@ -2149,5 +2150,7 @@ To manage your website, simply open the Umbraco back office and start adding con Open backoffice search Open/Close backoffice help Open/Close your profile options + Current language + Switch language to diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index eff344d4f3..8a759fb6cc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -338,8 +338,10 @@ Enter a folder name Choose a type and a title Document Types within the Settings section, by editing the Allowed child node types under Permissions.]]> + Document Types within the Settings section.]]> The selected page in the content tree doesn't allow for any pages to be created below it. Edit permissions for this document type + Create a new document type Media Types Types within the Settings section, by editing the Allowed child node types under Permissions.]]> The selected media in the tree doesn't allow for any other media to be created below it. Edit permissions for this media type Document Type without a template @@ -422,7 +424,6 @@ Internal link: When using local links, insert "#" in front of link Open in new window? - Macro Settings This macro does not contain any properties you can edit Paste Edit permissions for @@ -2163,5 +2164,7 @@ To manage your website, simply open the Umbraco back office and start adding con Open backoffice search Open/Close backoffice help Open/Close your profile options + Current language + Switch language to diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index f8b31577e7..371e6de7a2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -311,7 +311,6 @@ Enlace interno Al usar enlaces locales, insertar "#" delante del enlace ¿Abrir en nueva ventana? - Ajustes para la Macro Esta macro no contiene ninguna propiedad que pueda editar Pegar Editar permisos para diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 83eb8ecc57..18a5fe9c5a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -324,7 +324,6 @@ Lien interne : Si vous utilisez des ancres, insérez # au début du lien Ouvrir dans une nouvelle fenêtre? - Paramètres de macro Cette macro ne contient aucune propriété éditable Coller Editer les permissions pour diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml index ece1810ef5..9b816b4682 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml @@ -175,7 +175,6 @@ קישור פנימי: בעת שימוש בקישוריים פנימיים, הוסף "#" בתחילת הקישור לפתוח בחלון חדש? - הגדרות מאקרו המאקרו לא מכיל מאפיינים שניתן לערוך הדבק ערוך הרשאות עבור diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml index 3ae078252a..5c82e061b0 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml @@ -180,7 +180,6 @@ - Impostazioni Macro Incolla Modifica il Permesso per diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml index 303d0f0427..dd68ed45e5 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml @@ -240,7 +240,6 @@ 内部リンク: 内部リンクを使うときは、リンクの前に "#" を挿入してください。 新規ウィンドウで開きますか? - マクロの設定 このマクロは編集できるプロパティがありません 貼り付け 許可の編集 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml index 512d192237..12f7c9ed50 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml @@ -174,7 +174,6 @@ 내부링크: 내부링크를 사용하실 때 링크앞에 "#"를 넣어주세요 새 창으로 여시겠습니까? - 매크로 세팅 이 매크로에는 편집할 수 있는 항목이 포함되어 있지 않습니다. 붙여넣기 권한 편집 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index cd6878a196..b389a9f1d7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -234,7 +234,6 @@ Intern link: Ved lokal link, sett inn "#" foran link Åpne i nytt vindu? - Makroinnstillinger Denne makroen har ingen egenskaper du kan endre Lim inn Endre rettigheter for diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index 556a8c4d48..90f06fe7a6 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -263,7 +263,6 @@ Interne link: Plaats een hekje (“#”) voor voor interne links. In nieuw venster openen? - Macro Settings Deze macro heeft geen eigenschappen die u kunt bewerken Plakken Bewerk rechten voor diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index 055d9eddcf..de3e988118 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -312,7 +312,6 @@ Link wewnętrzny: Kiedy używasz odnośników lokalnych, wstaw znak "#" na początku linku Otworzyć w nowym oknie? - Ustawienia Makro To makro nie posiada żadnych właściwości, które można edytować Wklej Edytuj Uprawnienia dla diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml index 767617a2d5..e7afd04acd 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml @@ -174,7 +174,6 @@ Link interno: Ao usar links locais insira "#" na frente do link Abrir em nova janela? - Configurações de Macro Este macro não contém nenhuma propriedade que possa ser editada Colar Editar Permissões para diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 5f138efc81..c52e17e829 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -360,7 +360,6 @@ Внутренняя ссылка: Для того чтобы определить локальную ссылку, используйте "#" первым символом Открыть в новом окне? - Свойства макроса Этот макрос не имеет редактируемых свойств Заголовок ссылки Ни одной пиктограммы не найдено diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index 9287856fb2..8ddf2fae26 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -282,7 +282,6 @@ Intern länk: När du använder lokala länkar, lägg till "#" framför länken Öppna i nytt fönster? - Makroinställningar Detta makro innehåller inga egenskaper som du kan redigera Klistra in Redigera rättigheter för diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index 347f205a1f..02069a53dc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -203,7 +203,6 @@ İç Bağlantı: Yerel bağlantıları kullanırken, bağlantının önündeki "# " insert Yeni pencerede aç - Makro ayarları Düzenleyebileceğiniz Bu makro herhangi bir özellikleri içermiyor Yapıştır İzinleri düzenle diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index 40a81413e5..a8dd8a8bef 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -251,7 +251,6 @@ 内部链接: 本地链接请用“#”号开头 在新窗口中打开? - 宏设置 粘贴 编辑权限 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml index a212826bbc..4b57fe7e53 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml @@ -248,7 +248,6 @@ 內部連結: 本地連結請用“#”號開頭 在新視窗中打開? - 巨集設置 本巨集沒有包含您可以編輯的屬性 粘貼 編輯許可權 diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs index ab605c7014..ae6d324e84 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs @@ -100,6 +100,15 @@ namespace Umbraco.Web.ContentApps if (configTabName != null && String.IsNullOrWhiteSpace(configTabName.ToString()) == false) contentApp.Name = configTabName.ToString(); } + + //Override Icon if icon is provided + if (listViewConfig.ContainsKey("icon")) + { + var configIcon = listViewConfig["icon"]; + if (configIcon != null && String.IsNullOrWhiteSpace(configIcon.ToString()) == false) + contentApp.Icon = configIcon.ToString(); + } + // if the list view is configured to show umbContent first, update the list view content app weight accordingly if(listViewConfig.ContainsKey("showContentFirst") && listViewConfig["showContentFirst"]?.ToString().TryConvertTo().Result == true) diff --git a/src/Umbraco.Web/Editors/Filters/UserGroupValidateAttribute.cs b/src/Umbraco.Web/Editors/Filters/UserGroupValidateAttribute.cs index 7d465a9ddc..78cd8e6a4d 100644 --- a/src/Umbraco.Web/Editors/Filters/UserGroupValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/UserGroupValidateAttribute.cs @@ -51,16 +51,11 @@ namespace Umbraco.Web.Editors.Filters return; } - if (persisted.Alias != userGroupSave.Alias) + if (persisted.Alias != userGroupSave.Alias && persisted.IsSystemUserGroup()) { - if (persisted.Alias == Constants.Security.AdminGroupAlias - || persisted.Alias == Constants.Security.SensitiveDataGroupAlias - || persisted.Alias == Constants.Security.TranslatorGroupAlias) - { - var message = $"User group with alias: {persisted.Alias} cannot be changed"; - actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, message); - return; - } + var message = $"User group with alias: {persisted.Alias} cannot be changed"; + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, message); + return; } //map the model to the persisted instance diff --git a/src/Umbraco.Web/Editors/UserGroupsController.cs b/src/Umbraco.Web/Editors/UserGroupsController.cs index 1b64722735..b081ca6137 100644 --- a/src/Umbraco.Web/Editors/UserGroupsController.cs +++ b/src/Umbraco.Web/Editors/UserGroupsController.cs @@ -154,8 +154,8 @@ namespace Umbraco.Web.Editors public HttpResponseMessage PostDeleteUserGroups([FromUri] int[] userGroupIds) { var userGroups = Services.UserService.GetAllUserGroups(userGroupIds) - //never delete the admin group or translators group - .Where(x => x.Alias != Constants.Security.AdminGroupAlias && x.Alias != Constants.Security.TranslatorGroupAlias) + //never delete the admin group, sensitive data or translators group + .Where(x => !x.IsSystemUserGroup()) .ToArray(); foreach (var userGroup in userGroups) { diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index 96ca16e18b..8671bfe538 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -144,7 +144,7 @@ namespace Umbraco.Web.Models.Mapping target.Id = source.Id; target.Key = source.Key; target.Name = source.Name; - target.Path = "-1" + source.Id; + target.Path = $"-1,{source.Id}"; target.Udi = Udi.Create(Constants.UdiEntityType.MemberGroup, source.Key); } diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs index abbb67c4aa..88960fb189 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs @@ -147,7 +147,9 @@ namespace Umbraco.Web.Models.Mapping target.Name = source.Name; target.ParentId = -1; target.Path = "-1," + source.Id; - MapUserGroupBasic(target, source.Alias, source.AllowedSections, source.StartContentId, source.StartMediaId, context); + target.IsSystemUserGroup = source.IsSystemUserGroup(); + + MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context); } // Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications @@ -162,7 +164,9 @@ namespace Umbraco.Web.Models.Mapping target.ParentId = -1; target.Path = "-1," + source.Id; target.UserCount = source.UserCount; - MapUserGroupBasic(target, source.Alias, source.AllowedSections, source.StartContentId, source.StartMediaId, context); + target.IsSystemUserGroup = source.IsSystemUserGroup(); + + MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context); } // Umbraco.Code.MapAll -Udi -Trashed -AdditionalData -AssignedPermissions @@ -198,7 +202,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications -Udi - // Umbraco.Code.MapAll -Trashed -AdditionalData -Users -AssignedPermissions -IsSystemUserGroup + // Umbraco.Code.MapAll -Trashed -AdditionalData -Users -AssignedPermissions private void Map(IUserGroup source, UserGroupDisplay target, MapperContext context) { target.Alias = source.Alias; @@ -210,8 +214,9 @@ namespace Umbraco.Web.Models.Mapping target.ParentId = -1; target.Path = "-1," + source.Id; target.UserCount = source.UserCount; + target.IsSystemUserGroup = source.IsSystemUserGroup(); - MapUserGroupBasic(target, source.Alias, source.AllowedSections, source.StartContentId, source.StartMediaId, context); + MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context); //Important! Currently we are never mapping to multiple UserGroupDisplay objects but if we start doing that // this will cause an N+1 and we'll need to change how this works. @@ -334,12 +339,8 @@ namespace Umbraco.Web.Models.Mapping // helpers - private void MapUserGroupBasic(UserGroupBasic target, string alias, IEnumerable sourceAllowedSections, int? sourceStartContentId, int? sourceStartMediaId, MapperContext context) + private void MapUserGroupBasic(UserGroupBasic target, IEnumerable sourceAllowedSections, int? sourceStartContentId, int? sourceStartMediaId, MapperContext context) { - target.IsSystemUserGroup = alias == Constants.Security.AdminGroupAlias - || alias == Constants.Security.TranslatorGroupAlias - || alias == Constants.Security.SensitiveDataGroupAlias; - var allSections = _sectionService.GetSections(); target.Sections = context.MapEnumerable(allSections.Where(x => sourceAllowedSections.Contains(x.Alias))); diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index f940ed5c15..f782f09289 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Logging; @@ -77,32 +78,72 @@ namespace Umbraco.Web.PropertyEditors if (editorValue.Value == null) return null; + // editorValue.Value is a JSON string of the grid + var rawJson = editorValue.Value.ToString(); + if (rawJson.IsNullOrWhiteSpace()) + return null; + var config = editorValue.DataTypeConfiguration as GridConfiguration; var mediaParent = config?.MediaParentId; var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid; - // editorValue.Value is a JSON string of the grid - var rawJson = editorValue.Value.ToString(); - var grid = JsonConvert.DeserializeObject(rawJson); + var grid = DeserializeGridValue(rawJson, out var rtes); - // Find all controls that use the RTE editor - var controls = grid.Sections.SelectMany(x => x.Rows.SelectMany(r => r.Areas).SelectMany(a => a.Controls)); - var rtes = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "rte"); + var userId = _umbracoContextAccessor.UmbracoContext?.Security.CurrentUser.Id ?? Constants.Security.SuperUserId; - foreach(var rte in rtes) + // Process the rte values + foreach (var rte in rtes) { // Parse the HTML var html = rte.Value?.ToString(); - var userId = _umbracoContextAccessor.UmbracoContext?.Security.CurrentUser.Id ?? Constants.Security.SuperUserId; + var parseAndSavedTempImages = TemplateUtilities.FindAndPersistPastedTempImages(html, mediaParentId, userId, _mediaService, _contentTypeBaseServiceProvider, _logger); + var editorValueWithMediaUrlsRemoved = TemplateUtilities.RemoveMediaUrlsFromTextString(parseAndSavedTempImages); - var parsedHtml = TemplateUtilities.FindAndPersistPastedTempImages(html, mediaParentId, userId, _mediaService, _contentTypeBaseServiceProvider, _logger); - rte.Value = parsedHtml; + rte.Value = editorValueWithMediaUrlsRemoved; } // Convert back to raw JSON for persisting return JsonConvert.SerializeObject(grid); } + + /// + /// Ensures that the rich text editor values are processed within the grid + /// + /// + /// + /// + /// + /// + public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + { + var val = property.GetValue(culture, segment); + if (val == null) return string.Empty; + + var grid = DeserializeGridValue(val.ToString(), out var rtes); + + //process the rte values + foreach (var rte in rtes.ToList()) + { + var html = rte.Value?.ToString(); + + var propertyValueWithMediaResolved = TemplateUtilities.ResolveMediaFromTextString(html); + rte.Value = propertyValueWithMediaResolved; + } + + return grid; + } + + private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues) + { + var grid = JsonConvert.DeserializeObject(rawJson); + + // Find all controls that use the RTE editor + var controls = grid.Sections.SelectMany(x => x.Rows.SelectMany(r => r.Areas).SelectMany(a => a.Controls)); + richTextValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "rte"); + + return grid; + } } } } diff --git a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs index 4f854a7556..ae0b32fde1 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs @@ -60,6 +60,9 @@ namespace Umbraco.Web.PropertyEditors Description = "The bulk actions that are allowed from the list view")] public BulkActionPermissionSettings BulkActionPermissions { get; set; } = new BulkActionPermissionSettings(); // TODO: managing defaults? + [ConfigurationField("icon", "Content app icon", "views/propertyeditors/listview/icon.prevalues.html", Description = "The icon of the listview content app")] + public string Icon { get; set; } + [ConfigurationField("tabName", "Content app name", "textstring", Description = "The name of the listview content app (default if empty: 'Child Items')")] public string TabName { get; set; } diff --git a/src/Umbraco.Web/PropertyEditors/MacroContainerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MacroContainerConfiguration.cs deleted file mode 100644 index 1490c4d2a8..0000000000 --- a/src/Umbraco.Web/PropertyEditors/MacroContainerConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// Represents the configuration for the macro container value editor. - /// - public class MacroContainerConfiguration - { - [ConfigurationField("max", "Max items", "number", Description = "The maximum number of macros that are allowed in the container")] - public int MaxItems { get; set; } - - [ConfigurationField("allowed", "Allowed items", "views/propertyeditors/macrocontainer/macrolist.prevalues.html", Description = "The macro types allowed, if none are selected all macros will be allowed")] - public object AllowedItems { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MacroContainerConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/MacroContainerConfigurationEditor.cs deleted file mode 100644 index 1ac4f5c874..0000000000 --- a/src/Umbraco.Web/PropertyEditors/MacroContainerConfigurationEditor.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// Represents the configuration editor for the macro container value editor. - /// - public class MacroContainerConfigurationEditor : ConfigurationEditor - { } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs deleted file mode 100644 index ff2c08bf62..0000000000 --- a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - // TODO: MacroContainerPropertyEditor is deprecated, but what's the alternative? - [DataEditor( - Constants.PropertyEditors.Aliases.MacroContainer, - "(Obsolete) Macro Picker", - "macrocontainer", - ValueType = ValueTypes.Text, - Group = Constants.PropertyEditors.Groups.RichContent, - Icon = Constants.Icons.Macro, - IsDeprecated = true)] - public class MacroContainerPropertyEditor : DataEditor - { - public MacroContainerPropertyEditor(ILogger logger) - : base(logger) - { } - - protected override IConfigurationEditor CreateConfigurationEditor() => new MacroContainerConfigurationEditor(); - } -} diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index d29b96c1db..3eed40c8bf 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -114,16 +114,16 @@ namespace Umbraco.Web.PropertyEditors if (editorValue.Value == null) return null; - var editorValueWithMediaUrlsRemoved = TemplateUtilities.RemoveMediaUrlsFromTextString(editorValue.Value.ToString()); - var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); - var userId = _umbracoContextAccessor.UmbracoContext?.Security.CurrentUser.Id ?? Constants.Security.SuperUserId; var config = editorValue.DataTypeConfiguration as RichTextConfiguration; var mediaParent = config?.MediaParentId; var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid; - parsed = TemplateUtilities.FindAndPersistPastedTempImages(parsed, mediaParentId, userId, _mediaService, _contentTypeBaseServiceProvider, _logger); + var parseAndSavedTempImages = TemplateUtilities.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId, _mediaService, _contentTypeBaseServiceProvider, _logger); + var editorValueWithMediaUrlsRemoved = TemplateUtilities.RemoveMediaUrlsFromTextString(parseAndSavedTempImages); + var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); + return parsed; } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs deleted file mode 100644 index ef356e3604..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Macros; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Macros; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - /// - /// Ensures macro syntax is parsed for the macro container which will work when getting the field - /// values in any way (i.e. dynamically, using Field(), or IPublishedContent) - /// - [DefaultPropertyValueConverter] - public class MacroContainerValueConverter : PropertyValueConverterBase - { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IMacroRenderer _macroRenderer; - - public MacroContainerValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer) - { - _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - _macroRenderer = macroRenderer ?? throw new ArgumentNullException(nameof(macroRenderer)); - } - - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.MacroContainer; - - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (IHtmlString); - - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Snapshot; - - // NOT thread-safe over a request because it modifies the - // global UmbracoContext.Current.InPreviewMode status. So it - // should never execute in // over the same UmbracoContext with - // different preview modes. - string RenderMacros(string source, bool preview) - { - var umbracoContext = _umbracoContextAccessor.UmbracoContext; - using (umbracoContext.ForcedPreview(preview)) // force for macro rendering - { - var sb = new StringBuilder(); - - MacroTagParser.ParseMacros( - source, - //callback for when text block is found - textBlock => sb.Append(textBlock), - //callback for when macro syntax is found - (macroAlias, macroAttributes) => sb.Append(_macroRenderer.Render( - macroAlias, - umbracoContext.PublishedRequest?.PublishedContent, - //needs to be explicitly casted to Dictionary - macroAttributes.ConvertTo(x => (string)x, x => x)).ToString())); - - return sb.ToString(); - } - } - - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) return null; - var sourceString = source.ToString(); - - // ensure string is parsed for macros and macros are executed correctly - sourceString = RenderMacros(sourceString, preview); - - return new HtmlString(sourceString); - } - } -} diff --git a/src/Umbraco.Web/Search/ExamineComposer.cs b/src/Umbraco.Web/Search/ExamineComposer.cs index 0ade432d70..b30f0cbe03 100644 --- a/src/Umbraco.Web/Search/ExamineComposer.cs +++ b/src/Umbraco.Web/Search/ExamineComposer.cs @@ -29,6 +29,7 @@ namespace Umbraco.Web.Search composition.Register(Lifetime.Singleton); composition.Register(Lifetime.Singleton); + composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(factory => new ContentValueSetBuilder( diff --git a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs index 5ac5529be5..ec536b9d75 100644 --- a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs +++ b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Examine; @@ -19,18 +20,20 @@ namespace Umbraco.Web.Search public UmbracoIndexesCreator(IProfilingLogger profilingLogger, ILocalizationService languageService, IPublicAccessService publicAccessService, - IMemberService memberService) + IMemberService memberService, IUmbracoIndexConfig umbracoIndexConfig) { ProfilingLogger = profilingLogger ?? throw new System.ArgumentNullException(nameof(profilingLogger)); LanguageService = languageService ?? throw new System.ArgumentNullException(nameof(languageService)); PublicAccessService = publicAccessService ?? throw new System.ArgumentNullException(nameof(publicAccessService)); MemberService = memberService ?? throw new System.ArgumentNullException(nameof(memberService)); + UmbracoIndexConfig = umbracoIndexConfig; } protected IProfilingLogger ProfilingLogger { get; } protected ILocalizationService LanguageService { get; } protected IPublicAccessService PublicAccessService { get; } protected IMemberService MemberService { get; } + protected IUmbracoIndexConfig UmbracoIndexConfig { get; } /// /// Creates the Umbraco indexes @@ -55,7 +58,8 @@ namespace Umbraco.Web.Search new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, LanguageService, - GetContentValueSetValidator()); + GetContentValueSetValidator() + ); return index; } @@ -80,28 +84,31 @@ namespace Umbraco.Web.Search CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.MembersIndexPath), new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, - GetMemberValueSetValidator()); + GetMemberValueSetValidator() + ); return index; } - + [Obsolete("This method should not be used and will be removed in future versions. GetContentValueSetValidator was moved to IUmbracoIndexConfig")] public virtual IContentValueSetValidator GetContentValueSetValidator() { - return new ContentValueSetValidator(false, true, PublicAccessService); + return UmbracoIndexConfig.GetContentValueSetValidator(); } - + [Obsolete("This method should not be used and will be removed in future versions. GetPublishedContentValueSetValidator was moved to IUmbracoIndexConfig")] public virtual IContentValueSetValidator GetPublishedContentValueSetValidator() { - return new ContentValueSetValidator(true, false, PublicAccessService); + return UmbracoIndexConfig.GetPublishedContentValueSetValidator(); } /// /// Returns the for the member indexer /// /// + [Obsolete("This method should not be used and will be removed in future versions. GetMemberValueSetValidator was moved to IUmbracoIndexConfig")] public virtual IValueSetValidator GetMemberValueSetValidator() { - return new MemberValueSetValidator(); + return UmbracoIndexConfig.GetMemberValueSetValidator(); } + } } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index c3c3dc75ce..b351cca972 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -7,6 +7,8 @@ using Examine; using Umbraco.Core; using Umbraco.Core.Mapping; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Examine; using Umbraco.Web.Models.ContentEditing; @@ -14,6 +16,7 @@ using Umbraco.Web.Trees; namespace Umbraco.Web.Search { + /// /// Used for internal Umbraco implementations of /// @@ -24,22 +27,25 @@ namespace Umbraco.Web.Search private readonly ILocalizationService _languageService; private readonly IEntityService _entityService; private readonly UmbracoMapper _mapper; + private readonly ISqlContext _sqlContext; public UmbracoTreeSearcher(IExamineManager examineManager, UmbracoContext umbracoContext, ILocalizationService languageService, IEntityService entityService, - UmbracoMapper mapper) + UmbracoMapper mapper, + ISqlContext sqlContext) { _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); _umbracoContext = umbracoContext; _languageService = languageService; _entityService = entityService; _mapper = mapper; + _sqlContext = sqlContext; } /// - /// Searches for results based on the entity type + /// Searches Examine for results based on the entity type /// /// /// @@ -61,7 +67,7 @@ namespace Umbraco.Web.Search string type; var indexName = Constants.UmbracoIndexes.InternalIndexName; - var fields = new[] { "id", "__NodeId" }; + var fields = new[] { "id", "__NodeId", "__Key" }; // TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string // manipulation for things like start paths, member types, etc... @@ -70,12 +76,18 @@ namespace Umbraco.Web.Search //} + //special GUID check since if a user searches on one specifically we need to escape it + if (Guid.TryParse(query, out var g)) + { + query = "\"" + g.ToString() + "\""; + } + switch (entityType) { case UmbracoEntityTypes.Member: indexName = Constants.UmbracoIndexes.MembersIndexName; type = "member"; - fields = new[] { "id", "__NodeId", "email", "loginName" }; + fields = new[] { "id", "__NodeId", "__Key", "email", "loginName" }; if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1") { sb.Append("+__NodeTypeAlias:"); @@ -129,6 +141,26 @@ namespace Umbraco.Web.Search } } + /// + /// Searches with the for results based on the entity type + /// + /// + /// + /// + /// + /// + /// + /// + public IEnumerable EntitySearch(UmbracoObjectTypes objectType, string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + //if it's a GUID, match it + Guid.TryParse(query, out var g); + + var results = _entityService.GetPagedDescendants(objectType, pageIndex, pageSize, out totalFound, + filter: _sqlContext.Query().Where(x => x.Name.Contains(query) || x.Key == g)); + return _mapper.MapEnumerable(results); + } + private bool BuildQuery(StringBuilder sb, string query, string searchFrom, string[] fields, string type) { //build a lucene query: diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 970191e510..3373a6fa8b 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -15,6 +15,10 @@ using Umbraco.Web.WebApi.Filters; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Search; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; namespace Umbraco.Web.Trees { @@ -36,20 +40,21 @@ namespace Umbraco.Web.Trees private readonly UmbracoTreeSearcher _treeSearcher; private readonly ActionCollection _actions; - public ContentTreeController(UmbracoTreeSearcher treeSearcher, ActionCollection actions) - { - _treeSearcher = treeSearcher; - _actions = actions; - } - protected override int RecycleBinId => Constants.System.RecycleBinContent; protected override bool RecycleBinSmells => Services.ContentService.RecycleBinSmells(); private int[] _userStartNodes; + protected override int[] UserStartNodes => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService)); + public ContentTreeController(UmbracoTreeSearcher treeSearcher, ActionCollection actions, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + { + _treeSearcher = treeSearcher; + _actions = actions; + } + /// protected override TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings) { diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 015c91cb81..c3458f93a2 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -18,11 +18,23 @@ using System.Web.Http.ModelBinding; using Umbraco.Web.Actions; using Umbraco.Web.Composing; using Umbraco.Core.Security; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence; namespace Umbraco.Web.Trees { public abstract class ContentTreeControllerBase : TreeController { + + protected ContentTreeControllerBase(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + { + } + + protected ContentTreeControllerBase() + { + } + #region Actions /// @@ -529,6 +541,8 @@ namespace Umbraco.Web.Trees private bool? _ignoreUserStartNodes; + + /// /// If the request should allows a user to choose nodes that they normally don't have access to /// diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index bead6aa141..a865df8f36 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -3,11 +3,17 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence; +using Umbraco.Core.Services; using Umbraco.Web.Actions; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Trees; +using Umbraco.Web.Search; using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees @@ -18,6 +24,13 @@ namespace Umbraco.Web.Trees [CoreTree] public class ContentTypeTreeController : TreeController, ISearchableTree { + private readonly UmbracoTreeSearcher _treeSearcher; + + public ContentTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + { + _treeSearcher = treeSearcher; + } + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { var root = base.CreateRootNode(queryStrings); @@ -135,10 +148,7 @@ namespace Umbraco.Web.Trees } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) - { - var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.DocumentType, pageIndex, pageSize, out totalFound, - filter: SqlContext.Query().Where(x => x.Name.Contains(query))); - return Mapper.MapEnumerable(results); - } + => _treeSearcher.EntitySearch(UmbracoObjectTypes.DocumentType, query, pageSize, pageIndex, out totalFound, searchFrom); + } } diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 2465b4d45a..4e5b1df631 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -12,6 +12,11 @@ using Umbraco.Core.Services; using Umbraco.Web.Actions; using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Web.Search; namespace Umbraco.Web.Trees { @@ -21,6 +26,13 @@ namespace Umbraco.Web.Trees [CoreTree] public class DataTypeTreeController : TreeController, ISearchableTree { + private readonly UmbracoTreeSearcher _treeSearcher; + + public DataTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + { + _treeSearcher = treeSearcher; + } + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { var intId = id.TryConvertTo(); @@ -148,10 +160,6 @@ namespace Umbraco.Web.Trees } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) - { - var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.DataType, pageIndex, pageSize, out totalFound, - filter: SqlContext.Query().Where(x => x.Name.Contains(query))); - return Mapper.MapEnumerable(results); - } + => _treeSearcher.EntitySearch(UmbracoObjectTypes.DataType, query, pageSize, pageIndex, out totalFound, searchFrom); } } diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index f4f373f9a4..df44a809c9 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -16,6 +16,10 @@ using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Search; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Services.Implement; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; namespace Umbraco.Web.Trees { @@ -35,7 +39,7 @@ namespace Umbraco.Web.Trees { private readonly UmbracoTreeSearcher _treeSearcher; - public MediaTreeController(UmbracoTreeSearcher treeSearcher) + public MediaTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { _treeSearcher = treeSearcher; } diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 53e30a4ee5..f85aefcace 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -10,6 +10,11 @@ using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Services; using Umbraco.Web.Actions; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Web.Search; namespace Umbraco.Web.Trees { @@ -19,6 +24,13 @@ namespace Umbraco.Web.Trees [CoreTree] public class MediaTypeTreeController : TreeController, ISearchableTree { + private readonly UmbracoTreeSearcher _treeSearcher; + + public MediaTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + { + _treeSearcher = treeSearcher; + } + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { var intId = id.TryConvertTo(); @@ -117,10 +129,7 @@ namespace Umbraco.Web.Trees } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) - { - var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.MediaType, pageIndex, pageSize, out totalFound, - filter: SqlContext.Query().Where(x => x.Name.Contains(query))); - return Mapper.MapEnumerable(results); - } + => _treeSearcher.EntitySearch(UmbracoObjectTypes.MediaType, query, pageSize, pageIndex, out totalFound, searchFrom); + } } diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index 8c37718c9b..d0ca8d1b2e 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -3,12 +3,18 @@ using System.Globalization; using System.Linq; using System.Net.Http.Formatting; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence; +using Umbraco.Core.Services; using Umbraco.Web.Actions; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; +using Umbraco.Web.Search; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; @@ -20,6 +26,13 @@ namespace Umbraco.Web.Trees [CoreTree] public class TemplatesTreeController : TreeController, ISearchableTree { + private readonly UmbracoTreeSearcher _treeSearcher; + + public TemplatesTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + { + _treeSearcher = treeSearcher; + } + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { var root = base.CreateRootNode(queryStrings); @@ -119,10 +132,6 @@ namespace Umbraco.Web.Trees } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) - { - var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.Template, pageIndex, pageSize, out totalFound, - filter: SqlContext.Query().Where(x => x.Name.Contains(query))); - return Mapper.MapEnumerable(results); - } + => _treeSearcher.EntitySearch(UmbracoObjectTypes.Template, query, pageSize, pageIndex, out totalFound, searchFrom); } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a0ce64943a..cf14996888 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -486,8 +486,6 @@ - - @@ -810,7 +808,6 @@ - @@ -848,7 +845,6 @@ -