using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Models.TemplateQuery; namespace Umbraco.Web.BackOffice.Controllers { /// /// The API controller used for building content queries within the template /// [PluginController("UmbracoApi")] [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 Terms => new List { 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 Properties => new List { 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 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-file", Name = x.Name }) }; } private IEnumerable 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 contents; queryExpression.Append(indent); if (model.ContentType != null && !model.ContentType.Alias.IsNullOrWhiteSpace()) { contents = sourceDocument == null ? Enumerable.Empty() : sourceDocument.ChildrenOfType(_variationContextAccessor, model.ContentType.Alias); queryExpression.AppendFormat(".ChildrenOfType(\"{0}\")", model.ContentType.Alias); } else { contents = sourceDocument == null ? Enumerable.Empty() : 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("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 SortByDefaultPropertyValue(IEnumerable 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); } } /// /// Gets a list of all content types /// /// public IEnumerable 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; } /// /// Returns a collection of allowed properties. /// public IEnumerable GetAllowedProperties() { return Properties.OrderBy(x => x.Name); } /// /// Returns a collection of constraint conditions that can be used in the query /// public IEnumerable GetFilterConditions() { return Terms; } } }