Files
Umbraco-CMS/src/Umbraco.Web.BackOffice/Controllers/TemplateQueryController.cs
Bjarke Berg 23293b77f6 Merge remote-tracking branch 'origin/v8/dev' into v9/feature/merge-v8-05072021
# Conflicts:
#	build/NuSpecs/UmbracoCms.Web.nuspec
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Compose/RelateOnTrashComponent.cs
#	src/Umbraco.Core/Composing/Current.cs
#	src/Umbraco.Core/Constants-AppSettings.cs
#	src/Umbraco.Core/Constants-SqlTemplates.cs
#	src/Umbraco.Core/Dashboards/ContentDashboardSettings.cs
#	src/Umbraco.Core/Dashboards/IContentDashboardSettings.cs
#	src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs
#	src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs
#	src/Umbraco.Core/Models/IReadOnlyContentBase.cs
#	src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs
#	src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
#	src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs
#	src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs
#	src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs
#	src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs
#	src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs
#	src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs
#	src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs
#	src/Umbraco.Core/Routing/UrlProviderExtensions.cs
#	src/Umbraco.Core/Runtime/CoreRuntime.cs
#	src/Umbraco.Core/Services/ILocalizedTextService.cs
#	src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs
#	src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs
#	src/Umbraco.Examine/UmbracoContentIndex.cs
#	src/Umbraco.Infrastructure/Examine/GenericIndexDiagnostics.cs
#	src/Umbraco.Infrastructure/IPublishedContentQuery.cs
#	src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
#	src/Umbraco.Infrastructure/Models/MediaWithCrops.cs
#	src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs
#	src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs
#	src/Umbraco.Infrastructure/PropertyEditors/ImageCropperConfiguration.cs
#	src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs
#	src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs
#	src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs
#	src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs
#	src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MediaPickerWithCropsValueConverter.cs
#	src/Umbraco.Infrastructure/PublishedContentQuery.cs
#	src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs
#	src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs
#	src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/ContentSourceDto.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs
#	src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs
#	src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs
#	src/Umbraco.Tests/App.config
#	src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs
#	src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
#	src/Umbraco.Tests/PublishedContent/NuCacheTests.cs
#	src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs
#	src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs
#	src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
#	src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs
#	src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs
#	src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs
#	src/Umbraco.Web.BackOffice/Controllers/MediaController.cs
#	src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs
#	src/Umbraco.Web.BackOffice/Controllers/TemplateQueryController.cs
#	src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs
#	src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs
#	src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs
#	src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs
#	src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs
#	src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs
#	src/Umbraco.Web.Common/Macros/MacroRenderer.cs
#	src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml
#	src/Umbraco.Web.UI/Umbraco/config/lang/it.xml
#	src/Umbraco.Web.UI/web.Template.Debug.config
#	src/Umbraco.Web.UI/web.Template.config
#	src/Umbraco.Web/Compose/NotificationsComponent.cs
#	src/Umbraco.Web/Composing/ModuleInjector.cs
#	src/Umbraco.Web/Editors/AuthenticationController.cs
#	src/Umbraco.Web/Editors/BackOfficeController.cs
#	src/Umbraco.Web/Editors/ContentTypeController.cs
#	src/Umbraco.Web/Editors/CurrentUserController.cs
#	src/Umbraco.Web/Editors/DictionaryController.cs
#	src/Umbraco.Web/Editors/MediaTypeController.cs
#	src/Umbraco.Web/Editors/MemberController.cs
#	src/Umbraco.Web/Editors/MemberGroupController.cs
#	src/Umbraco.Web/Editors/MemberTypeController.cs
#	src/Umbraco.Web/Editors/NuCacheStatusController.cs
#	src/Umbraco.Web/Editors/UserGroupsController.cs
#	src/Umbraco.Web/Editors/UsersController.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs
#	src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs
#	src/Umbraco.Web/Models/Trees/MenuItemList.cs
#	src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentCacheDataModel.cs
#	src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs
#	src/Umbraco.Web/PublishedCache/NuCache/DataSource/SerializerBase.cs
#	src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs
#	src/Umbraco.Web/Runtime/WebRuntime.cs
#	src/Umbraco.Web/Search/ExamineComponent.cs
#	src/Umbraco.Web/Trees/ApplicationTreeController.cs
#	src/Umbraco.Web/Trees/MemberTreeController.cs
#	src/Umbraco.Web/UrlHelperRenderExtensions.cs
2021-07-05 20:58:04 +02:00

260 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Models.TemplateQuery;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
/// <summary>
/// The API controller used for building content queries within the template
/// </summary>
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[JsonCamelCaseFormatter]
public class TemplateQueryController : UmbracoAuthorizedJsonController
{
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IPublishedContentQuery _publishedContentQuery;
private readonly ILocalizedTextService _localizedTextService;
private readonly IPublishedValueFallback _publishedValueFallback;
private readonly IContentTypeService _contentTypeService;
public TemplateQueryController(
IVariationContextAccessor variationContextAccessor,
IPublishedContentQuery publishedContentQuery,
ILocalizedTextService localizedTextService,
IPublishedValueFallback publishedValueFallback,
IContentTypeService contentTypeService)
{
_variationContextAccessor = variationContextAccessor ??
throw new ArgumentNullException(nameof(variationContextAccessor));
_publishedContentQuery =
publishedContentQuery ?? throw new ArgumentNullException(nameof(publishedContentQuery));
_localizedTextService =
localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
_publishedValueFallback =
publishedValueFallback ?? throw new ArgumentNullException(nameof(publishedValueFallback));
_contentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService));
}
private IEnumerable<OperatorTerm> Terms => new List<OperatorTerm>
{
new OperatorTerm(_localizedTextService.Localize("template","is"), Operator.Equals, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template","isNot"), Operator.NotEquals, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template","before"), Operator.LessThan, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template","beforeIncDate"), Operator.LessThanEqualTo, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template","after"), Operator.GreaterThan, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template","afterIncDate"), Operator.GreaterThanEqualTo, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template","equals"), Operator.Equals, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template","doesNotEqual"), Operator.NotEquals, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template","contains"), Operator.Contains, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template","doesNotContain"), Operator.NotContains, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template","greaterThan"), Operator.GreaterThan, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template","greaterThanEqual"), Operator.GreaterThanEqualTo, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template","lessThan"), Operator.LessThan, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template","lessThanEqual"), Operator.LessThanEqualTo, new [] {"int"})
};
private IEnumerable<PropertyModel> Properties => new List<PropertyModel>
{
new PropertyModel { Name = _localizedTextService.Localize("template","id"), Alias = "Id", Type = "int" },
new PropertyModel { Name = _localizedTextService.Localize("template","name"), Alias = "Name", Type = "string" },
new PropertyModel { Name = _localizedTextService.Localize("template","createdDate"), Alias = "CreateDate", Type = "datetime" },
new PropertyModel { Name = _localizedTextService.Localize("template","lastUpdatedDate"), Alias = "UpdateDate", Type = "datetime" }
};
public QueryResultModel PostTemplateQuery(QueryModel model)
{
var queryExpression = new StringBuilder();
IEnumerable<IPublishedContent> contents;
if (model == null)
{
contents = _publishedContentQuery.ContentAtRoot().FirstOrDefault().Children(_variationContextAccessor);
queryExpression.Append("Umbraco.ContentAtRoot().FirstOrDefault().Children()");
}
else
{
contents = PostTemplateValue(model, queryExpression);
}
// timing should be fairly correct, due to the fact that all the linq statements are yield returned.
var timer = new Stopwatch();
timer.Start();
var results = contents.ToList();
timer.Stop();
return new QueryResultModel
{
QueryExpression = queryExpression.ToString(),
ResultCount = results.Count,
ExecutionTime = timer.ElapsedMilliseconds,
SampleResults = results.Take(20).Select(x => new TemplateQueryResult
{
Icon = "icon-document",
Name = x.Name
})
};
}
private IEnumerable<IPublishedContent> PostTemplateValue(QueryModel model, StringBuilder queryExpression)
{
var indent = Environment.NewLine + " ";
// set the source
IPublishedContent sourceDocument;
if (model.Source != null && model.Source.Id > 0)
{
sourceDocument = _publishedContentQuery.Content(model.Source.Id);
if (sourceDocument == null)
queryExpression.AppendFormat("Umbraco.Content({0})", model.Source.Id);
else
queryExpression.AppendFormat("Umbraco.Content(Guid.Parse(\"{0}\"))", sourceDocument.Key);
}
else
{
sourceDocument = _publishedContentQuery.ContentAtRoot().FirstOrDefault();
queryExpression.Append("Umbraco.ContentAtRoot().FirstOrDefault()");
}
// get children, optionally filtered by type
IEnumerable<IPublishedContent> contents;
queryExpression.Append(indent);
if (model.ContentType != null && !model.ContentType.Alias.IsNullOrWhiteSpace())
{
contents = sourceDocument == null
? Enumerable.Empty<IPublishedContent>()
: sourceDocument.ChildrenOfType(_variationContextAccessor, model.ContentType.Alias);
queryExpression.AppendFormat(".ChildrenOfType(\"{0}\")", model.ContentType.Alias);
}
else
{
contents = sourceDocument == null
? Enumerable.Empty<IPublishedContent>()
: sourceDocument.Children(_variationContextAccessor);
queryExpression.Append(".Children()");
}
// apply filters
foreach (var condition in model.Filters.Where(x => !x.ConstraintValue.IsNullOrWhiteSpace()))
{
//x is passed in as the parameter alias for the linq where statement clause
var operation = condition.BuildCondition<IPublishedContent>("x");
//for review - this uses a tonized query rather then the normal linq query.
contents = contents.Where(operation.Compile());
queryExpression.Append(indent);
queryExpression.AppendFormat(".Where({0})", operation);
}
// always add IsVisible() to the query
contents = contents.Where(x => x.IsVisible(_publishedValueFallback));
queryExpression.Append(indent);
queryExpression.Append(".Where(x => x.IsVisible())");
// apply sort
if (model.Sort != null && !model.Sort.Property.Alias.IsNullOrWhiteSpace())
{
contents = SortByDefaultPropertyValue(contents, model.Sort);
queryExpression.Append(indent);
queryExpression.AppendFormat(model.Sort.Direction == "ascending"
? ".OrderBy(x => x.{0})"
: ".OrderByDescending(x => x.{0})"
, model.Sort.Property.Alias);
}
// take
if (model.Take > 0)
{
contents = contents.Take(model.Take);
queryExpression.Append(indent);
queryExpression.AppendFormat(".Take({0})", model.Take);
}
return contents;
}
private object GetConstraintValue(QueryCondition condition)
{
switch (condition.Property.Type)
{
case "int":
return int.Parse(condition.ConstraintValue);
case "datetime":
DateTime dt;
return DateTime.TryParse(condition.ConstraintValue, out dt) ? dt : DateTime.Today;
default:
return condition.ConstraintValue;
}
}
private IEnumerable<IPublishedContent> SortByDefaultPropertyValue(IEnumerable<IPublishedContent> contents, SortExpression sortExpression)
{
switch (sortExpression.Property.Alias)
{
case "id":
return sortExpression.Direction == "ascending"
? contents.OrderBy(x => x.Id)
: contents.OrderByDescending(x => x.Id);
case "createDate":
return sortExpression.Direction == "ascending"
? contents.OrderBy(x => x.CreateDate)
: contents.OrderByDescending(x => x.CreateDate);
case "publishDate":
return sortExpression.Direction == "ascending"
? contents.OrderBy(x => x.UpdateDate)
: contents.OrderByDescending(x => x.UpdateDate);
case "name":
return sortExpression.Direction == "ascending"
? contents.OrderBy(x => x.Name)
: contents.OrderByDescending(x => x.Name);
default:
return sortExpression.Direction == "ascending"
? contents.OrderBy(x => x.Name)
: contents.OrderByDescending(x => x.Name);
}
}
/// <summary>
/// Gets a list of all content types
/// </summary>
/// <returns></returns>
public IEnumerable<ContentTypeModel> GetContentTypes()
{
var contentTypes = _contentTypeService.GetAll()
.Select(x => new ContentTypeModel { Alias = x.Alias, Name = _localizedTextService.Localize("template", "contentOfType", tokens: new string[] { x.Name }) })
.OrderBy(x => x.Name).ToList();
contentTypes.Insert(0, new ContentTypeModel { Alias = string.Empty, Name = _localizedTextService.Localize("template", "allContent") });
return contentTypes;
}
/// <summary>
/// Returns a collection of allowed properties.
/// </summary>
public IEnumerable<PropertyModel> GetAllowedProperties()
{
return Properties.OrderBy(x => x.Name);
}
/// <summary>
/// Returns a collection of constraint conditions that can be used in the query
/// </summary>
public IEnumerable<object> GetFilterConditions()
{
return Terms;
}
}
}