mirror of https://github.com/Squidex/squidex.git
Browse Source
* Docs fixed * Parent path. * Fixes. * Parent path. * Just a renaming. * API cleanup. * Some tests. * Tests * Tests fixed * Delete unused test.pull/679/head
committed by
GitHub
93 changed files with 1192 additions and 1505 deletions
@ -1,18 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject |
|||
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
|||
{ |
|||
public interface IAssetFolderResolver |
|||
public interface IMoveAssetCommand : IAppCommand |
|||
{ |
|||
Task<DomainId> ResolveOrCreateAsync(Context context, ICommandBus commandBus, string path); |
|||
DomainId ParentId { get; set; } |
|||
} |
|||
} |
|||
@ -1,117 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Caching; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject |
|||
{ |
|||
public sealed class AssetFolderResolver : IAssetFolderResolver |
|||
{ |
|||
private static readonly char[] TrimChars = { '/', '\\' }; |
|||
private static readonly char[] SplitChars = { ' ', '/', '\\' }; |
|||
private readonly ILocalCache localCache; |
|||
private readonly IAssetQueryService assetQuery; |
|||
|
|||
public AssetFolderResolver(ILocalCache localCache, IAssetQueryService assetQuery) |
|||
{ |
|||
Guard.NotNull(localCache, nameof(localCache)); |
|||
Guard.NotNull(assetQuery, nameof(assetQuery)); |
|||
|
|||
this.localCache = localCache; |
|||
this.assetQuery = assetQuery; |
|||
} |
|||
|
|||
public async Task<DomainId> ResolveOrCreateAsync(Context context, ICommandBus commandBus, string path) |
|||
{ |
|||
Guard.NotNull(commandBus, nameof(commandBus)); |
|||
Guard.NotNull(path, nameof(path)); |
|||
|
|||
path = path.Trim(TrimChars); |
|||
|
|||
var elements = path.Split(SplitChars, StringSplitOptions.RemoveEmptyEntries); |
|||
|
|||
if (elements.Length == 0) |
|||
{ |
|||
return DomainId.Empty; |
|||
} |
|||
|
|||
var currentId = DomainId.Empty; |
|||
|
|||
var i = elements.Length; |
|||
|
|||
for (; i > 0; i--) |
|||
{ |
|||
var subPath = string.Join('/', elements.Take(i)); |
|||
|
|||
if (localCache.TryGetValue(GetCacheKey(subPath), out var cached) && cached is DomainId id) |
|||
{ |
|||
currentId = id; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
var creating = false; |
|||
|
|||
for (; i < elements.Length; i++) |
|||
{ |
|||
var name = elements[i]; |
|||
|
|||
var isResolved = false; |
|||
|
|||
if (!creating) |
|||
{ |
|||
var children = await assetQuery.QueryAssetFoldersAsync(context, currentId); |
|||
|
|||
foreach (var child in children) |
|||
{ |
|||
var childPath = string.Join('/', elements.Take(i).Union(Enumerable.Repeat(child.FolderName, 1))); |
|||
|
|||
localCache.Add(GetCacheKey(childPath), child.Id); |
|||
} |
|||
|
|||
foreach (var child in children) |
|||
{ |
|||
if (child.FolderName == name) |
|||
{ |
|||
currentId = child.Id; |
|||
|
|||
isResolved = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!isResolved) |
|||
{ |
|||
var command = new CreateAssetFolder { ParentId = currentId, FolderName = name }; |
|||
|
|||
await commandBus.PublishAsync(command); |
|||
|
|||
currentId = command.AssetFolderId; |
|||
creating = true; |
|||
} |
|||
|
|||
var newPath = string.Join('/', elements.Take(i).Union(Enumerable.Repeat(name, 1))); |
|||
|
|||
localCache.Add(GetCacheKey(newPath), currentId); |
|||
} |
|||
|
|||
return currentId; |
|||
} |
|||
|
|||
private static object GetCacheKey(string path) |
|||
{ |
|||
return $"ASSET_FOLDERS_{path}"; |
|||
} |
|||
} |
|||
} |
|||
@ -1,235 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.DefaultValues; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Core.Scripting; |
|||
using Squidex.Domain.Apps.Core.ValidateContent; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
using Squidex.Domain.Apps.Entities.Contents.Commands; |
|||
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Json; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Squidex.Log; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject |
|||
{ |
|||
public sealed class ContentOperationContext |
|||
{ |
|||
private static readonly ScriptOptions ScriptOptions = new ScriptOptions |
|||
{ |
|||
AsContext = true, |
|||
CanDisallow = true, |
|||
CanReject = true |
|||
}; |
|||
|
|||
private readonly IScriptEngine scriptEngine; |
|||
private readonly ISemanticLog log; |
|||
private readonly IAppProvider appProvider; |
|||
private readonly IEnumerable<IValidatorsFactory> validators; |
|||
private readonly IContentWorkflow contentWorkflow; |
|||
private readonly IContentRepository contentRepository; |
|||
private readonly IJsonSerializer jsonSerializer; |
|||
private ISchemaEntity schema; |
|||
private IAppEntity app; |
|||
private ContentCommand command; |
|||
private ValidationContext validationContext; |
|||
|
|||
public IContentWorkflow Workflow => contentWorkflow; |
|||
|
|||
public IContentRepository Repository => contentRepository; |
|||
|
|||
public ContentOperationContext( |
|||
IAppProvider appProvider, |
|||
IEnumerable<IValidatorsFactory> validators, |
|||
IContentWorkflow contentWorkflow, |
|||
IContentRepository contentRepository, |
|||
IJsonSerializer jsonSerializer, |
|||
IScriptEngine scriptEngine, |
|||
ISemanticLog log) |
|||
{ |
|||
Guard.NotDefault(appProvider, nameof(appProvider)); |
|||
Guard.NotDefault(validators, nameof(validators)); |
|||
Guard.NotDefault(contentWorkflow, nameof(contentWorkflow)); |
|||
Guard.NotDefault(contentRepository, nameof(contentRepository)); |
|||
Guard.NotDefault(jsonSerializer, nameof(jsonSerializer)); |
|||
Guard.NotDefault(scriptEngine, nameof(scriptEngine)); |
|||
Guard.NotDefault(log, nameof(log)); |
|||
|
|||
this.appProvider = appProvider; |
|||
this.validators = validators; |
|||
this.contentWorkflow = contentWorkflow; |
|||
this.contentRepository = contentRepository; |
|||
this.jsonSerializer = jsonSerializer; |
|||
this.scriptEngine = scriptEngine; |
|||
|
|||
this.log = log; |
|||
} |
|||
|
|||
public ISchemaEntity Schema |
|||
{ |
|||
get => schema; |
|||
} |
|||
|
|||
public async Task LoadAsync(NamedId<DomainId> appId, NamedId<DomainId> schemaId, ContentCommand command, bool optimized) |
|||
{ |
|||
this.command = command; |
|||
|
|||
var (app, schema) = await appProvider.GetAppWithSchemaAsync(appId.Id, schemaId.Id); |
|||
|
|||
if (app == null) |
|||
{ |
|||
throw new DomainObjectNotFoundException(appId.ToString()); |
|||
} |
|||
|
|||
this.app = app; |
|||
|
|||
if (schema == null) |
|||
{ |
|||
throw new DomainObjectNotFoundException(schemaId.ToString()); |
|||
} |
|||
|
|||
this.schema = schema; |
|||
|
|||
validationContext = new ValidationContext(jsonSerializer, appId, schemaId, schema.SchemaDef, command.ContentId).Optimized(optimized); |
|||
} |
|||
|
|||
public Task<Status> GetInitialStatusAsync() |
|||
{ |
|||
return contentWorkflow.GetInitialStatusAsync(schema); |
|||
} |
|||
|
|||
public Task GenerateDefaultValuesAsync(ContentData data) |
|||
{ |
|||
data.GenerateDefaultValues(schema.SchemaDef, Partition()); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public async Task ValidateInputAsync(ContentData data) |
|||
{ |
|||
var validator = |
|||
new ContentValidator(Partition(), |
|||
validationContext, validators, log); |
|||
|
|||
await validator.ValidateInputAsync(data); |
|||
|
|||
CheckErrors(validator); |
|||
} |
|||
|
|||
public async Task ValidateInputPartialAsync(ContentData data) |
|||
{ |
|||
var validator = |
|||
new ContentValidator(Partition(), |
|||
validationContext, validators, log); |
|||
|
|||
await validator.ValidateInputPartialAsync(data); |
|||
|
|||
CheckErrors(validator); |
|||
} |
|||
|
|||
public async Task ValidateContentAsync(ContentData data) |
|||
{ |
|||
var validator = |
|||
new ContentValidator(Partition(), |
|||
validationContext, validators, log); |
|||
|
|||
await validator.ValidateContentAsync(data); |
|||
|
|||
CheckErrors(validator); |
|||
} |
|||
|
|||
public async Task ValidateContentAndInputAsync(ContentData data) |
|||
{ |
|||
var validator = |
|||
new ContentValidator(Partition(), |
|||
validationContext.AsPublishing(), validators, log); |
|||
|
|||
await validator.ValidateInputAsync(data); |
|||
await validator.ValidateContentAsync(data); |
|||
|
|||
CheckErrors(validator); |
|||
} |
|||
|
|||
public Task ValidateOnPublishAsync(ContentData data) |
|||
{ |
|||
if (!schema.SchemaDef.Properties.ValidateOnPublish) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
return ValidateContentAndInputAsync(data); |
|||
} |
|||
|
|||
private static void CheckErrors(ContentValidator validator) |
|||
{ |
|||
if (validator.Errors.Count > 0) |
|||
{ |
|||
throw new ValidationException(validator.Errors.ToList()); |
|||
} |
|||
} |
|||
|
|||
public bool HasScript(Func<SchemaScripts, string> script) |
|||
{ |
|||
return !string.IsNullOrWhiteSpace(GetScript(script)); |
|||
} |
|||
|
|||
public async Task<ContentData> ExecuteScriptAndTransformAsync(Func<SchemaScripts, string> script, ScriptVars context) |
|||
{ |
|||
Enrich(context); |
|||
|
|||
var actualScript = GetScript(script); |
|||
|
|||
if (string.IsNullOrWhiteSpace(actualScript)) |
|||
{ |
|||
return context.Data!; |
|||
} |
|||
|
|||
return await scriptEngine.TransformAsync(context, actualScript, ScriptOptions); |
|||
} |
|||
|
|||
public async Task ExecuteScriptAsync(Func<SchemaScripts, string> script, ScriptVars context) |
|||
{ |
|||
Enrich(context); |
|||
|
|||
var actualScript = GetScript(script); |
|||
|
|||
if (string.IsNullOrWhiteSpace(actualScript)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
await scriptEngine.ExecuteAsync(context, GetScript(script), ScriptOptions); |
|||
} |
|||
|
|||
private PartitionResolver Partition() |
|||
{ |
|||
return app.PartitionResolver(); |
|||
} |
|||
|
|||
private void Enrich(ScriptVars context) |
|||
{ |
|||
context.ContentId = command.ContentId; |
|||
context.AppId = app.Id; |
|||
context.AppName = app.Name; |
|||
context.User = command.User; |
|||
} |
|||
|
|||
private string GetScript(Func<SchemaScripts, string> script) |
|||
{ |
|||
return script(schema.SchemaDef.Scripts); |
|||
} |
|||
} |
|||
} |
|||
@ -1,201 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Entities.Contents.Commands; |
|||
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Translations; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Squidex.Shared; |
|||
using Squidex.Shared.Identity; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|||
{ |
|||
public static class GuardContent |
|||
{ |
|||
public static void CanCreate(CreateContent command, ISchemaEntity schema) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
if (schema.SchemaDef.IsSingleton) |
|||
{ |
|||
if (command.ContentId != schema.Id) |
|||
{ |
|||
throw new DomainException(T.Get("contents.singletonNotCreatable")); |
|||
} |
|||
} |
|||
|
|||
Validate.It(e => |
|||
{ |
|||
if (command.Data == null) |
|||
{ |
|||
e(Not.Defined(nameof(command.Data)), nameof(command.Data)); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static async Task CanUpdate(UpdateContent command, IContentEntity content, IContentWorkflow contentWorkflow) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
CheckPermission(content, command, Permissions.AppContentsUpdate, Permissions.AppContentsUpsert); |
|||
|
|||
Validate.It(e => |
|||
{ |
|||
if (command.Data == null) |
|||
{ |
|||
e(Not.Defined(nameof(command.Data)), nameof(command.Data)); |
|||
} |
|||
}); |
|||
|
|||
if (!command.DoNotValidateWorkflow) |
|||
{ |
|||
var status = content.NewStatus ?? content.Status; |
|||
|
|||
if (!await contentWorkflow.CanUpdateAsync(content, status, command.User)) |
|||
{ |
|||
throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status })); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void CanDeleteDraft(DeleteContentDraft command, IContentEntity content) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
CheckPermission(content, command, Permissions.AppContentsVersionDelete); |
|||
|
|||
if (content.NewStatus == null) |
|||
{ |
|||
throw new DomainException(T.Get("contents.draftToDeleteNotFound")); |
|||
} |
|||
} |
|||
|
|||
public static void CanCreateDraft(CreateContentDraft command, IContentEntity content) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
CheckPermission(content, command, Permissions.AppContentsVersionCreate); |
|||
|
|||
if (content.Status != Status.Published) |
|||
{ |
|||
throw new DomainException(T.Get("contents.draftNotCreateForUnpublished")); |
|||
} |
|||
} |
|||
|
|||
public static async Task CanChangeStatus(ChangeContentStatus command, |
|||
IContentEntity content, |
|||
IContentWorkflow contentWorkflow, |
|||
IContentRepository contentRepository, |
|||
ISchemaEntity schema) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
CheckPermission(content, command, Permissions.AppContentsChangeStatus, Permissions.AppContentsUpsert); |
|||
|
|||
var newStatus = command.Status; |
|||
|
|||
if (schema.SchemaDef.IsSingleton) |
|||
{ |
|||
if (content.NewStatus == null || newStatus != Status.Published) |
|||
{ |
|||
throw new DomainException(T.Get("contents.singletonNotChangeable")); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
var oldStatus = content.NewStatus ?? content.Status; |
|||
|
|||
if (command.Status == oldStatus) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (oldStatus == Status.Published && command.CheckReferrers) |
|||
{ |
|||
var hasReferrer = await contentRepository.HasReferrersAsync(content.AppId.Id, command.ContentId, SearchScope.Published); |
|||
|
|||
if (hasReferrer) |
|||
{ |
|||
throw new DomainException(T.Get("contents.referenced")); |
|||
} |
|||
} |
|||
|
|||
await Validate.It(async e => |
|||
{ |
|||
if (!command.DoNotValidateWorkflow) |
|||
{ |
|||
if (!await contentWorkflow.CanMoveToAsync(content, oldStatus, newStatus, command.User)) |
|||
{ |
|||
var values = new { oldStatus, newStatus }; |
|||
|
|||
e(T.Get("contents.statusTransitionNotAllowed", values), "Status"); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
var info = await contentWorkflow.GetInfoAsync(content, newStatus); |
|||
|
|||
if (info == null) |
|||
{ |
|||
e(T.Get("contents.statusNotValid"), "Status"); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static async Task CanDelete(DeleteContent command, |
|||
IContentEntity content, |
|||
IContentRepository contentRepository, |
|||
ISchemaEntity schema) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
CheckPermission(content, command, Permissions.AppContentsDeleteOwn); |
|||
|
|||
if (schema.SchemaDef.IsSingleton) |
|||
{ |
|||
throw new DomainException(T.Get("contents.singletonNotDeletable")); |
|||
} |
|||
|
|||
if (command.CheckReferrers) |
|||
{ |
|||
var hasReferrer = await contentRepository.HasReferrersAsync(content.AppId.Id, content.Id, SearchScope.All); |
|||
|
|||
if (hasReferrer) |
|||
{ |
|||
throw new DomainException(T.Get("contents.referenced")); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void CanValidate(ValidateContent command, IContentEntity content) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
CheckPermission(content, command, Permissions.AppContentsRead); |
|||
} |
|||
|
|||
public static void CheckPermission(IContentEntity content, ContentCommand command, params string[] permissions) |
|||
{ |
|||
if (Equals(content.CreatedBy, command.Actor) || command.User == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (permissions.All(x => !command.User.Allows(x, content.AppId.Name, content.SchemaId.Name))) |
|||
{ |
|||
throw new DomainForbiddenException(T.Get("common.errorNoPermission")); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,122 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.Scripting; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|||
{ |
|||
public static class ScriptingExtensions |
|||
{ |
|||
private static readonly ScriptOptions Options = new ScriptOptions |
|||
{ |
|||
AsContext = true, |
|||
CanDisallow = true, |
|||
CanReject = true |
|||
}; |
|||
|
|||
public static async Task<ContentData> ExecuteCreateScriptAsync(this OperationContext context, ContentData data, Status status) |
|||
{ |
|||
var script = context.SchemaDef.Scripts.Create; |
|||
|
|||
if (!string.IsNullOrWhiteSpace(script)) |
|||
{ |
|||
var vars = Enrich(context, new ScriptVars |
|||
{ |
|||
Operation = "Create", |
|||
Data = data, |
|||
DataOld = default, |
|||
Status = status, |
|||
StatusOld = default |
|||
}); |
|||
|
|||
data = await GetScriptEngine(context).TransformAsync(vars, script, Options); |
|||
} |
|||
|
|||
return data; |
|||
} |
|||
|
|||
public static async Task<ContentData> ExecuteUpdateScriptAsync(this OperationContext context, ContentData data) |
|||
{ |
|||
var script = context.SchemaDef.Scripts.Update; |
|||
|
|||
if (!string.IsNullOrWhiteSpace(script)) |
|||
{ |
|||
var vars = Enrich(context, new ScriptVars |
|||
{ |
|||
Operation = "Update", |
|||
Data = data, |
|||
DataOld = context.Content.Data, |
|||
Status = context.Content.EditingStatus(), |
|||
StatusOld = default |
|||
}); |
|||
|
|||
data = await GetScriptEngine(context).TransformAsync(vars, script, Options); |
|||
} |
|||
|
|||
return data; |
|||
} |
|||
|
|||
public static async Task<ContentData> ExecuteChangeScriptAsync(this OperationContext context, Status status, StatusChange change) |
|||
{ |
|||
var script = context.SchemaDef.Scripts.Change; |
|||
|
|||
if (!string.IsNullOrWhiteSpace(script)) |
|||
{ |
|||
var data = context.Content.Data.Clone(); |
|||
|
|||
var vars = Enrich(context, new ScriptVars |
|||
{ |
|||
Operation = change.ToString(), |
|||
Data = data, |
|||
DataOld = default, |
|||
Status = status, |
|||
StatusOld = context.Content.EditingStatus() |
|||
}); |
|||
|
|||
return await GetScriptEngine(context).TransformAsync(vars, script, Options); |
|||
} |
|||
|
|||
return context.Content.Data; |
|||
} |
|||
|
|||
public static async Task ExecuteDeleteScriptAsync(this OperationContext context) |
|||
{ |
|||
var script = context.SchemaDef.Scripts.Delete; |
|||
|
|||
if (!string.IsNullOrWhiteSpace(script)) |
|||
{ |
|||
var vars = Enrich(context, new ScriptVars |
|||
{ |
|||
Operation = "Delete", |
|||
Data = context.Content.Data, |
|||
DataOld = default, |
|||
Status = context.Content.EditingStatus(), |
|||
StatusOld = default |
|||
}); |
|||
|
|||
await GetScriptEngine(context).ExecuteAsync(vars, script, Options); |
|||
} |
|||
} |
|||
|
|||
private static IScriptEngine GetScriptEngine(OperationContext context) |
|||
{ |
|||
return context.Resolve<IScriptEngine>(); |
|||
} |
|||
|
|||
private static ScriptVars Enrich(OperationContext context, ScriptVars vars) |
|||
{ |
|||
vars.ContentId = context.ContentId; |
|||
vars.AppId = context.App.Id; |
|||
vars.AppName = context.App.Name; |
|||
vars.User = context.User; |
|||
|
|||
return vars; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Translations; |
|||
using Squidex.Shared.Identity; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|||
{ |
|||
public static class SecurityExtensions |
|||
{ |
|||
public static void MustHavePermission(this OperationContext context, string permissionId) |
|||
{ |
|||
var content = context.Content; |
|||
|
|||
if (Equals(content.CreatedBy, context.Actor) || context.User == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!context.User.Allows(permissionId, content.AppId.Name, content.SchemaId.Name)) |
|||
{ |
|||
throw new DomainForbiddenException(T.Get("common.errorNoPermission")); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Translations; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|||
{ |
|||
public static class SingletonExtensions |
|||
{ |
|||
public static void MustNotCreateSingleton(this OperationContext context) |
|||
{ |
|||
if (context.SchemaDef.IsSingleton && context.ContentId != context.Schema.Id) |
|||
{ |
|||
throw new DomainException(T.Get("contents.singletonNotCreatable")); |
|||
} |
|||
} |
|||
|
|||
public static void MustNotChangeSingleton(this OperationContext context, Status status) |
|||
{ |
|||
if (context.SchemaDef.IsSingleton && (context.Content.NewStatus == null || status != Status.Published)) |
|||
{ |
|||
throw new DomainException(T.Get("contents.singletonNotChangeable")); |
|||
} |
|||
} |
|||
|
|||
public static void MustNotDeleteSingleton(this OperationContext context) |
|||
{ |
|||
if (context.SchemaDef.IsSingleton) |
|||
{ |
|||
throw new DomainException(T.Get("contents.singletonNotDeletable")); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.DefaultValues; |
|||
using Squidex.Domain.Apps.Core.ValidateContent; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Json; |
|||
using Squidex.Infrastructure.Translations; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Squidex.Log; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|||
{ |
|||
public static class ValidationExtensions |
|||
{ |
|||
public static void MustDeleteDraft(this OperationContext context) |
|||
{ |
|||
if (context.Content.NewStatus == null) |
|||
{ |
|||
throw new DomainException(T.Get("contents.draftToDeleteNotFound")); |
|||
} |
|||
} |
|||
|
|||
public static void MustCreateDraft(this OperationContext context) |
|||
{ |
|||
if (context.Content.EditingStatus() != Status.Published) |
|||
{ |
|||
throw new DomainException(T.Get("contents.draftNotCreateForUnpublished")); |
|||
} |
|||
} |
|||
|
|||
public static void MustHaveData(this OperationContext context, ContentData? data) |
|||
{ |
|||
if (data == null) |
|||
{ |
|||
context.AddError(Not.Defined(nameof(data)), nameof(data)).ThrowOnErrors(); |
|||
} |
|||
} |
|||
|
|||
public static async Task ValidateInputAsync(this OperationContext context, ContentData data, bool optimize) |
|||
{ |
|||
var validator = GetValidator(context, optimize); |
|||
|
|||
await validator.ValidateInputAsync(data); |
|||
|
|||
context.AddErrors(validator.Errors).ThrowOnErrors(); |
|||
} |
|||
|
|||
public static async Task ValidateInputPartialAsync(this OperationContext context, ContentData data, bool optimize) |
|||
{ |
|||
var validator = GetValidator(context, optimize); |
|||
|
|||
await validator.ValidateInputPartialAsync(data); |
|||
|
|||
context.AddErrors(validator.Errors).ThrowOnErrors(); |
|||
} |
|||
|
|||
public static async Task ValidateContentAsync(this OperationContext context, ContentData data, bool optimize) |
|||
{ |
|||
var validator = GetValidator(context, optimize); |
|||
|
|||
await validator.ValidateContentAsync(data); |
|||
|
|||
context.AddErrors(validator.Errors).ThrowOnErrors(); |
|||
} |
|||
|
|||
public static async Task ValidateContentAndInputAsync(this OperationContext operation, ContentData data, bool optimize) |
|||
{ |
|||
var validator = GetValidator(operation, optimize); |
|||
|
|||
await validator.ValidateInputAsync(data); |
|||
await validator.ValidateContentAsync(data); |
|||
|
|||
operation.AddErrors(validator.Errors).ThrowOnErrors(); |
|||
} |
|||
|
|||
public static void GenerateDefaultValues(this OperationContext context, ContentData data) |
|||
{ |
|||
data.GenerateDefaultValues(context.Schema.SchemaDef, context.Partition()); |
|||
} |
|||
|
|||
public static async Task CheckReferrersAsync(this OperationContext context) |
|||
{ |
|||
var contentRepository = context.Resolve<IContentRepository>(); |
|||
|
|||
var hasReferrer = await contentRepository.HasReferrersAsync(context.App.Id, context.ContentId, SearchScope.All); |
|||
|
|||
if (hasReferrer) |
|||
{ |
|||
throw new DomainException(T.Get("contents.referenced")); |
|||
} |
|||
} |
|||
|
|||
private static ContentValidator GetValidator(this OperationContext context, bool optimize) |
|||
{ |
|||
var validationContext = |
|||
new ValidationContext(context.Resolve<IJsonSerializer>(), |
|||
context.App.NamedId(), |
|||
context.Schema.NamedId(), |
|||
context.SchemaDef, |
|||
context.ContentId) |
|||
.Optimized(optimize); |
|||
|
|||
var validator = |
|||
new ContentValidator(context.Partition(), |
|||
validationContext, |
|||
context.Resolve<IEnumerable<IValidatorsFactory>>(), |
|||
context.Resolve<ISemanticLog>()); |
|||
|
|||
return validator; |
|||
} |
|||
|
|||
private static PartitionResolver Partition(this OperationContext context) |
|||
{ |
|||
return context.App.PartitionResolver(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Translations; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|||
{ |
|||
public static class WorkflowExtensions |
|||
{ |
|||
public static Task<Status> GetInitialStatusAsync(this OperationContext context) |
|||
{ |
|||
var workflow = GetWorkflow(context); |
|||
|
|||
return workflow.GetInitialStatusAsync(context.Schema); |
|||
} |
|||
|
|||
public static async Task CheckTransitionAsync(this OperationContext context, Status status) |
|||
{ |
|||
if (!context.SchemaDef.IsSingleton) |
|||
{ |
|||
var workflow = GetWorkflow(context); |
|||
|
|||
var oldStatus = context.Content.EditingStatus(); |
|||
|
|||
if (!await workflow.CanMoveToAsync(context.Content, oldStatus, status, context.User)) |
|||
{ |
|||
var values = new { oldStatus, newStatus = status }; |
|||
|
|||
context.AddError(T.Get("contents.statusTransitionNotAllowed", values), nameof(status)); |
|||
context.ThrowOnErrors(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static async Task CheckStatusAsync(this OperationContext context, Status status) |
|||
{ |
|||
if (!context.SchemaDef.IsSingleton) |
|||
{ |
|||
var workflow = GetWorkflow(context); |
|||
|
|||
var statusInfo = await workflow.GetInfoAsync(context.Content, status); |
|||
|
|||
if (statusInfo == null) |
|||
{ |
|||
context.AddError(T.Get("contents.statusNotValid"), nameof(status)); |
|||
context.ThrowOnErrors(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static async Task CheckUpdateAsync(this OperationContext context) |
|||
{ |
|||
if (context.User != null) |
|||
{ |
|||
var workflow = GetWorkflow(context); |
|||
|
|||
var status = context.Content.EditingStatus(); |
|||
|
|||
if (!await workflow.CanUpdateAsync(context.Content, status, context.User)) |
|||
{ |
|||
throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status })); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static IContentWorkflow GetWorkflow(OperationContext context) |
|||
{ |
|||
return context.Resolve<IContentWorkflow>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,122 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using GraphQL.Utilities; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
using Squidex.Domain.Apps.Entities.Contents.Commands; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Validation; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject |
|||
{ |
|||
public sealed class OperationContext |
|||
{ |
|||
private readonly List<ValidationError> errors = new List<ValidationError>(); |
|||
private readonly IServiceProvider serviceProvider; |
|||
|
|||
public ClaimsPrincipal? User { get; init; } |
|||
|
|||
public RefToken Actor { get; init; } |
|||
|
|||
public IAppEntity App { get; init; } |
|||
|
|||
public ISchemaEntity Schema { get; init; } |
|||
|
|||
public DomainId ContentId { get; init; } |
|||
|
|||
public Func<IContentEntity> ContentProvider { get; init; } |
|||
|
|||
public IContentEntity Content |
|||
{ |
|||
get => ContentProvider(); |
|||
} |
|||
|
|||
public Schema SchemaDef |
|||
{ |
|||
get => Schema.SchemaDef; |
|||
} |
|||
|
|||
public OperationContext(IServiceProvider serviceProvider) |
|||
{ |
|||
Guard.NotNull(serviceProvider, nameof(serviceProvider)); |
|||
|
|||
this.serviceProvider = serviceProvider; |
|||
} |
|||
|
|||
public static async Task<OperationContext> CreateAsync(IServiceProvider services, ContentCommand command, Func<IContentEntity> snapshot) |
|||
{ |
|||
var appProvider = services.GetRequiredService<IAppProvider>(); |
|||
|
|||
var (app, schema) = await appProvider.GetAppWithSchemaAsync(command.AppId.Id, command.SchemaId.Id); |
|||
|
|||
if (app == null) |
|||
{ |
|||
throw new DomainObjectNotFoundException(command.AppId.Id.ToString()); |
|||
} |
|||
|
|||
if (schema == null) |
|||
{ |
|||
throw new DomainObjectNotFoundException(command.SchemaId.Id.ToString()); |
|||
} |
|||
|
|||
return new OperationContext(services) |
|||
{ |
|||
App = app, |
|||
Actor = command.Actor, |
|||
ContentProvider = snapshot, |
|||
ContentId = command.ContentId, |
|||
Schema = schema, |
|||
User = command.User |
|||
}; |
|||
} |
|||
|
|||
public T Resolve<T>() |
|||
{ |
|||
return serviceProvider.GetRequiredService<T>(); |
|||
} |
|||
|
|||
public T? ResolveOptional<T>() where T : class |
|||
{ |
|||
return serviceProvider.GetService(typeof(T)) as T; |
|||
} |
|||
|
|||
public OperationContext AddError(string message, params string[] propertyNames) |
|||
{ |
|||
errors.Add(new ValidationError(message, propertyNames)); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public OperationContext AddError(ValidationError newError) |
|||
{ |
|||
errors.Add(newError); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public OperationContext AddErrors(IEnumerable<ValidationError> newErrors) |
|||
{ |
|||
errors.AddRange(newErrors); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public void ThrowOnErrors() |
|||
{ |
|||
if (errors.Count > 0) |
|||
{ |
|||
throw new ValidationException(errors); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,165 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Squidex.Caching; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject |
|||
{ |
|||
public class AssetFolderResolverTests |
|||
{ |
|||
private readonly ILocalCache localCache = new AsyncLocalCache(); |
|||
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); |
|||
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); |
|||
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app"); |
|||
private readonly Context requestContext; |
|||
private readonly AssetFolderResolver sut; |
|||
|
|||
public AssetFolderResolverTests() |
|||
{ |
|||
requestContext = Context.Anonymous(Mocks.App(appId)); |
|||
|
|||
localCache.StartContext(); |
|||
|
|||
sut = new AssetFolderResolver(localCache, assetQuery); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData("")] |
|||
[InlineData(" ")] |
|||
[InlineData("/")] |
|||
[InlineData("\\")] |
|||
public async Task Should_resolve_root_id_for_empty_path(string path) |
|||
{ |
|||
var folderId = await sut.ResolveOrCreateAsync(requestContext, commandBus, path); |
|||
|
|||
Assert.Equal(DomainId.Empty, folderId); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_create_and_cache_level1_folder() |
|||
{ |
|||
var folderId11_1 = await sut.ResolveOrCreateAsync(requestContext, commandBus, "level1"); |
|||
var folderId11_2 = await sut.ResolveOrCreateAsync(requestContext, commandBus, "level1"); |
|||
|
|||
Assert.NotEqual(DomainId.Empty, folderId11_1); |
|||
Assert.NotEqual(DomainId.Empty, folderId11_2); |
|||
|
|||
Assert.Equal(folderId11_2, folderId11_1); |
|||
|
|||
A.CallTo(() => commandBus.PublishAsync(A<CreateAssetFolder>.That.Matches(x => x.FolderName == "level1"))) |
|||
.MustHaveHappenedOnceExactly(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_create_and_cache_recursively() |
|||
{ |
|||
var folderId21_1 = await sut.ResolveOrCreateAsync(requestContext, commandBus, "level1/level2"); |
|||
var folderId21_2 = await sut.ResolveOrCreateAsync(requestContext, commandBus, "level1/level2"); |
|||
|
|||
Assert.NotEqual(DomainId.Empty, folderId21_1); |
|||
Assert.NotEqual(DomainId.Empty, folderId21_2); |
|||
|
|||
Assert.Equal(folderId21_1, folderId21_2); |
|||
|
|||
A.CallTo(() => commandBus.PublishAsync(A<CreateAssetFolder>.That.Matches(x => x.FolderName == "level1"))) |
|||
.MustHaveHappenedOnceExactly(); |
|||
|
|||
A.CallTo(() => commandBus.PublishAsync(A<CreateAssetFolder>.That.Matches(x => x.FolderName == "level2"))) |
|||
.MustHaveHappenedOnceExactly(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_cache_folders_on_same_level() |
|||
{ |
|||
var folder11 = CreateFolder("level1_1"); |
|||
var folder12 = CreateFolder("level1_2"); |
|||
|
|||
A.CallTo(() => assetQuery.QueryAssetFoldersAsync(requestContext, DomainId.Empty)) |
|||
.Returns(ResultList.CreateFrom(2, folder11, folder12)); |
|||
|
|||
var folderId11 = await sut.ResolveOrCreateAsync(requestContext, commandBus, folder11.FolderName); |
|||
var folderId12 = await sut.ResolveOrCreateAsync(requestContext, commandBus, folder12.FolderName); |
|||
|
|||
Assert.Equal(folder11.Id, folderId11); |
|||
Assert.Equal(folder12.Id, folderId12); |
|||
|
|||
A.CallTo(() => assetQuery.QueryAssetFoldersAsync(requestContext, A<DomainId>._)) |
|||
.MustHaveHappenedOnceExactly(); |
|||
|
|||
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_recursively() |
|||
{ |
|||
var folder11 = CreateFolder("level1"); |
|||
var folder21 = CreateFolder("level2"); |
|||
|
|||
A.CallTo(() => assetQuery.QueryAssetFoldersAsync(requestContext, DomainId.Empty)) |
|||
.Returns(ResultList.CreateFrom(1, folder11)); |
|||
|
|||
A.CallTo(() => assetQuery.QueryAssetFoldersAsync(requestContext, folder11.Id)) |
|||
.Returns(ResultList.CreateFrom(1, folder21)); |
|||
|
|||
var folderId2 = await sut.ResolveOrCreateAsync(requestContext, commandBus, "level1/level2"); |
|||
|
|||
Assert.Equal(folder21.Id, folderId2); |
|||
|
|||
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_recursively_and_create_folder() |
|||
{ |
|||
var folder11 = CreateFolder("level1"); |
|||
var folder21 = CreateFolder("level2"); |
|||
|
|||
A.CallTo(() => assetQuery.QueryAssetFoldersAsync(requestContext, DomainId.Empty)) |
|||
.Returns(ResultList.CreateFrom(1, folder11)); |
|||
|
|||
A.CallTo(() => assetQuery.QueryAssetFoldersAsync(requestContext, folder11.Id)) |
|||
.Returns(ResultList.CreateFrom(1, folder21)); |
|||
|
|||
await sut.ResolveOrCreateAsync(requestContext, commandBus, "level1/level2"); |
|||
|
|||
var folderId3 = await sut.ResolveOrCreateAsync(requestContext, commandBus, "level1/level2/level3"); |
|||
|
|||
Assert.NotEqual(DomainId.Empty, folderId3); |
|||
|
|||
A.CallTo(() => assetQuery.QueryAssetFoldersAsync(requestContext, DomainId.Empty)) |
|||
.MustHaveHappenedOnceExactly(); |
|||
|
|||
A.CallTo(() => assetQuery.QueryAssetFoldersAsync(requestContext, folder11.Id)) |
|||
.MustHaveHappenedOnceExactly(); |
|||
|
|||
A.CallTo(() => commandBus.PublishAsync(A<CreateAssetFolder>.That.Matches(x => x.FolderName == "level3" && x.ParentId == folder21.Id))) |
|||
.MustHaveHappenedOnceExactly(); |
|||
} |
|||
|
|||
private static IAssetFolderEntity CreateFolder(string name) |
|||
{ |
|||
var assetFolder = A.Fake<IAssetFolderEntity>(); |
|||
|
|||
A.CallTo(() => assetFolder.FolderName) |
|||
.Returns(name); |
|||
|
|||
A.CallTo(() => assetFolder.Id) |
|||
.Returns(DomainId.NewGuid()); |
|||
|
|||
return assetFolder; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
<sqx-dropdown [formControl]="control" [items]="snapshot.assetFolders" valueProperty="id" searchProperty="folderName"> |
|||
<ng-template let-assetFolder="$implicit" let-context="context"> |
|||
<span class="truncate">{{assetFolder.folderName | sqxTranslate | sqxHighlight:context}}</span> |
|||
</ng-template> |
|||
</sqx-dropdown> |
|||
@ -0,0 +1,76 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, OnInit } from '@angular/core'; |
|||
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; |
|||
import { MathHelper, StatefulControlComponent, value$ } from '@app/framework'; |
|||
import { AssetPathItem, AssetsService } from '@app/shared/internal'; |
|||
import { AppsState } from '@app/shared/state/apps.state'; |
|||
import { ROOT_ITEM } from '@app/shared/state/assets.state'; |
|||
|
|||
export const SQX_ASSETS_FOLDER_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = { |
|||
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AssetFolderDropdownComponent), multi: true |
|||
}; |
|||
|
|||
interface State { |
|||
// The asset folders.
|
|||
assetFolders: ReadonlyArray<AssetPathItem>; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'sqx-asset-folder-dropdown', |
|||
styleUrls: ['./asset-folder-dropdown.component.scss'], |
|||
templateUrl: './asset-folder-dropdown.component.html', |
|||
providers: [ |
|||
SQX_ASSETS_FOLDER_DROPDOWN_CONTROL_VALUE_ACCESSOR |
|||
], |
|||
changeDetection: ChangeDetectionStrategy.OnPush |
|||
}) |
|||
export class AssetFolderDropdownComponent extends StatefulControlComponent<State, any> implements OnInit, ControlValueAccessor { |
|||
public control = new FormControl(); |
|||
|
|||
constructor(changeDetector: ChangeDetectorRef, |
|||
private readonly appsState: AppsState, |
|||
private readonly assetsService: AssetsService |
|||
) { |
|||
super(changeDetector, { |
|||
assetFolders: [] |
|||
}); |
|||
|
|||
this.own( |
|||
value$(this.control) |
|||
.subscribe((value: any) => { |
|||
if (this.control.enabled) { |
|||
this.callChange(value); |
|||
this.callTouched(); |
|||
} |
|||
})); |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
this.assetsService.getAssetFolders(this.appsState.appName, MathHelper.EMPTY_GUID) |
|||
.subscribe(dto => { |
|||
const assetFolders = [ROOT_ITEM, ...dto.items]; |
|||
|
|||
this.next({ assetFolders }); |
|||
}); |
|||
} |
|||
|
|||
public setDisabledState(isDisabled: boolean) { |
|||
super.setDisabledState(isDisabled); |
|||
|
|||
if (isDisabled) { |
|||
this.control.disable({ emitEvent: false }); |
|||
} else { |
|||
this.control.enable({ emitEvent: false }); |
|||
} |
|||
} |
|||
|
|||
public writeValue(obj: any): void { |
|||
this.control.setValue(obj || ROOT_ITEM.id); |
|||
} |
|||
} |
|||
@ -1,3 +1,3 @@ |
|||
<sqx-checkbox-group [formControl]="selectionControl" |
|||
<sqx-checkbox-group [formControl]="control" |
|||
[values]="snapshot.converter.suggestions"> |
|||
</sqx-checkbox-group> |
|||
@ -1,4 +1,4 @@ |
|||
<sqx-dropdown [formControl]="selectionControl" [items]="snapshot.contentNames"> |
|||
<sqx-dropdown [formControl]="control" [items]="snapshot.contentNames"> |
|||
<ng-template let-content="$implicit" let-context="context"> |
|||
<span class="truncate" [innerHTML]="content.name | sqxHighlight:context"></span> |
|||
</ng-template> |
|||
|
|||
@ -1,2 +1,3 @@ |
|||
<sqx-tag-editor placeholder="{{ 'common.tagAddReference' | sqxTranslate }}" [converter]="snapshot.converter" [formControl]="selectionControl" [suggestions]="snapshot.converter.suggestions"> |
|||
<sqx-tag-editor placeholder="{{ 'common.tagAddReference' | sqxTranslate }}" |
|||
[converter]="snapshot.converter" [formControl]="control" [suggestions]="snapshot.converter.suggestions"> |
|||
</sqx-tag-editor> |
|||
Loading…
Reference in new issue