Files
Umbraco-CMS/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs
Bjarke Berg 37ed84871a Merge remote-tracking branch 'origin/v8/dev' into netcore/dev
# Conflicts:
#	build/NuSpecs/UmbracoCms.Core.nuspec
#	build/NuSpecs/UmbracoCms.Web.nuspec
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Cache/CacheKeys.cs
#	src/Umbraco.Core/Composing/TypeFinder.cs
#	src/Umbraco.Core/Configuration/GlobalSettings.cs
#	src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
#	src/Umbraco.Core/Configuration/IGlobalSettings.cs
#	src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
#	src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs
#	src/Umbraco.Core/Constants-AppSettings.cs
#	src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs
#	src/Umbraco.Core/Extensions/StringExtensions.cs
#	src/Umbraco.Core/Extensions/UriExtensions.cs
#	src/Umbraco.Core/IO/IOHelper.cs
#	src/Umbraco.Core/IO/PhysicalFileSystem.cs
#	src/Umbraco.Core/Media/Exif/MathEx.cs
#	src/Umbraco.Core/Media/UploadAutoFillProperties.cs
#	src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
#	src/Umbraco.Core/Models/Membership/User.cs
#	src/Umbraco.Core/Models/UserExtensions.cs
#	src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs
#	src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs
#	src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs
#	src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs
#	src/Umbraco.Core/Routing/AliasUrlProvider.cs
#	src/Umbraco.Core/Routing/DefaultUrlProvider.cs
#	src/Umbraco.Core/Routing/UriUtility.cs
#	src/Umbraco.Core/Routing/UrlProviderExtensions.cs
#	src/Umbraco.Core/Runtime/CoreRuntime.cs
#	src/Umbraco.Core/RuntimeOptions.cs
#	src/Umbraco.Core/RuntimeState.cs
#	src/Umbraco.Core/Security/BackOfficeUserStore.cs
#	src/Umbraco.Core/Security/ContentPermissions.cs
#	src/Umbraco.Core/Sync/ApplicationUrlHelper.cs
#	src/Umbraco.Core/Trees/TreeNode.cs
#	src/Umbraco.Core/Udi.cs
#	src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs
#	src/Umbraco.Examine/Umbraco.Examine.csproj
#	src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs
#	src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
#	src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
#	src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
#	src/Umbraco.Infrastructure/Scoping/Scope.cs
#	src/Umbraco.Infrastructure/Search/ExamineComponent.cs
#	src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs
#	src/Umbraco.Infrastructure/Services/Implement/ContentService.cs
#	src/Umbraco.Infrastructure/Services/Implement/MediaService.cs
#	src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs
#	src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs
#	src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs
#	src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs
#	src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs
#	src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs
#	src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs
#	src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config
#	src/Umbraco.Tests/TestHelpers/SettingsForTests.cs
#	src/Umbraco.Tests/Testing/TestDatabase.cs
#	src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs
#	src/Umbraco.Tests/Web/Controllers/FilterAllowedOutgoingContentAttributeTests.cs
#	src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs
#	src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
#	src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs
#	src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
#	src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
#	src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs
#	src/Umbraco.Web.BackOffice/Controllers/MediaController.cs
#	src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs
#	src/Umbraco.Web.BackOffice/Controllers/TourController.cs
#	src/Umbraco.Web.BackOffice/Controllers/UserGroupEditorAuthorizationHelper.cs
#	src/Umbraco.Web.BackOffice/Filters/FilterAllowedOutgoingContentAttribute.cs
#	src/Umbraco.Web.BackOffice/Filters/FilterAllowedOutgoingMediaAttribute.cs
#	src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs
#	src/Umbraco.Web.BackOffice/Services/IconService.cs
#	src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs
#	src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs
#	src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs
#	src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs
#	src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs
#	src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js
#	src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js
#	src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml
#	src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml
#	src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml
#	src/Umbraco.Web.UI/config/umbracoSettings.Release.config
#	src/Umbraco.Web/Cache/MemberCacheRefresher.cs
#	src/Umbraco.Web/Composing/ModuleInjector.cs
#	src/Umbraco.Web/Editors/BackOfficeController.cs
#	src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs
#	src/Umbraco.Web/Editors/ContentTypeController.cs
#	src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs
#	src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs
#	src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs
#	src/Umbraco.Web/Editors/TinyMceController.cs
#	src/Umbraco.Web/Editors/UserGroupsController.cs
#	src/Umbraco.Web/Editors/UsersController.cs
#	src/Umbraco.Web/ImageCropperTemplateExtensions.cs
#	src/Umbraco.Web/Logging/WebProfiler.cs
#	src/Umbraco.Web/Logging/WebProfilerProvider.cs
#	src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs
#	src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs
#	src/Umbraco.Web/Mvc/JsonNetResult.cs
#	src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs
#	src/Umbraco.Web/Mvc/RenderRouteHandler.cs
#	src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs
#	src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs
#	src/Umbraco.Web/RoutableDocumentFilter.cs
#	src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs
#	src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs
#	src/Umbraco.Web/Routing/PublishedRouter.cs
#	src/Umbraco.Web/Runtime/WebInitialComposer.cs
#	src/Umbraco.Web/Scheduling/KeepAlive.cs
#	src/Umbraco.Web/Security/AppBuilderExtensions.cs
#	src/Umbraco.Web/Security/BackOfficeClaimsIdentityFactory.cs
#	src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs
#	src/Umbraco.Web/Trees/DictionaryTreeController.cs
#	src/Umbraco.Web/Trees/LanguageTreeController.cs
#	src/Umbraco.Web/Trees/LogViewerTreeController.cs
#	src/Umbraco.Web/Trees/PackagesTreeController.cs
#	src/Umbraco.Web/UmbracoApplication.cs
#	src/Umbraco.Web/UmbracoApplicationBase.cs
#	src/Umbraco.Web/UmbracoInjectedModule.cs
#	src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs
#	src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs
#	src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs
#	src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs
#	src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs
2021-03-05 15:36:27 +01:00

422 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
/// <summary>
/// The API controller used for editing dictionary items
/// </summary>
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMacros)]
[ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))]
public class MacrosController : BackOfficeNotificationsController
{
private readonly ParameterEditorCollection _parameterEditorCollection;
private readonly IMacroService _macroService;
private readonly IShortStringHelper _shortStringHelper;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly ILogger<MacrosController> _logger;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly UmbracoMapper _umbracoMapper;
public MacrosController(
ParameterEditorCollection parameterEditorCollection,
IMacroService macroService,
IShortStringHelper shortStringHelper,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
ILogger<MacrosController> logger,
IHostingEnvironment hostingEnvironment,
UmbracoMapper umbracoMapper
)
{
_parameterEditorCollection = parameterEditorCollection ?? throw new ArgumentNullException(nameof(parameterEditorCollection));
_macroService = macroService ?? throw new ArgumentNullException(nameof(macroService));
_shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper));
_backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
_umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
}
/// <summary>
/// Creates a new macro
/// </summary>
/// <param name="name">
/// The name.
/// </param>
/// <returns>
/// The id of the created macro
/// </returns>
[HttpPost]
public ActionResult<int> Create(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Name can not be empty");
}
var alias = name.ToSafeAlias(_shortStringHelper);
if (_macroService.GetByAlias(alias) != null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Macro with this alias already exists");
}
if (name == null || name.Length > 255)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Name cannnot be more than 255 characters in length.");
}
try
{
var macro = new Macro(_shortStringHelper)
{
Alias = alias,
Name = name,
MacroSource = string.Empty
};
_macroService.Save(macro, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id);
return macro.Id;
}
catch (Exception exception)
{
const string errorMessage = "Error creating macro";
_logger.LogError(exception, errorMessage);
return ValidationErrorResult.CreateNotificationValidationErrorResult(errorMessage);
}
}
[HttpGet]
public ActionResult<MacroDisplay> GetById(int id)
{
var macro = _macroService.GetById(id);
if (macro == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist");
}
var macroDisplay = MapToDisplay(macro);
return macroDisplay;
}
[HttpGet]
public ActionResult<MacroDisplay> GetById(Guid id)
{
var macro = _macroService.GetById(id);
if (macro == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist");
}
var macroDisplay = MapToDisplay(macro);
return macroDisplay;
}
[HttpGet]
public ActionResult<MacroDisplay> GetById(Udi id)
{
var guidUdi = id as GuidUdi;
if (guidUdi == null)
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist");
var macro = _macroService.GetById(guidUdi.Guid);
if (macro == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist");
}
var macroDisplay = MapToDisplay(macro);
return macroDisplay;
}
[HttpPost]
public IActionResult DeleteById(int id)
{
var macro = _macroService.GetById(id);
if (macro == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist");
}
_macroService.Delete(macro);
return Ok();
}
[HttpPost]
public ActionResult<MacroDisplay> Save(MacroDisplay macroDisplay)
{
if (macroDisplay == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("No macro data found in request");
}
if (macroDisplay.Name == null || macroDisplay.Name.Length > 255)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Name cannnot be more than 255 characters in length.");
}
var macro = _macroService.GetById(int.Parse(macroDisplay.Id.ToString()));
if (macro == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {macroDisplay.Id} does not exist");
}
if (macroDisplay.Alias != macro.Alias)
{
var macroByAlias = _macroService.GetByAlias(macroDisplay.Alias);
if (macroByAlias != null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Macro with this alias already exists");
}
}
macro.Alias = macroDisplay.Alias;
macro.Name = macroDisplay.Name;
macro.CacheByMember = macroDisplay.CacheByUser;
macro.CacheByPage = macroDisplay.CacheByPage;
macro.CacheDuration = macroDisplay.CachePeriod;
macro.DontRender = !macroDisplay.RenderInEditor;
macro.UseInEditor = macroDisplay.UseInEditor;
macro.MacroSource = macroDisplay.View;
macro.Properties.ReplaceAll(macroDisplay.Parameters.Select((x,i) => new MacroProperty(x.Key, x.Label, i, x.Editor)));
try
{
_macroService.Save(macro, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id);
macroDisplay.Notifications.Clear();
macroDisplay.Notifications.Add(new BackOfficeNotification("Success", "Macro saved", NotificationStyle.Success));
return macroDisplay;
}
catch (Exception exception)
{
const string errorMessage = "Error creating macro";
_logger.LogError(exception, errorMessage);
return ValidationErrorResult.CreateNotificationValidationErrorResult(errorMessage);
}
}
/// <summary>
/// Gets a list of available macro partials
/// </summary>
/// <returns>
/// The <see cref="HttpResponseMessage"/>.
/// </returns>
public IEnumerable<string> GetPartialViews()
{
var views = new List<string>();
views.AddRange(this.FindPartialViewsFiles());
return views;
}
/// <summary>
/// Gets the available parameter editors
/// </summary>
/// <returns>
/// The <see cref="HttpResponseMessage"/>.
/// </returns>
public ParameterEditorCollection GetParameterEditors()
{
return _parameterEditorCollection;
}
/// <summary>
/// Gets the available parameter editors grouped by their group.
/// </summary>
/// <returns>
/// The <see cref="HttpResponseMessage"/>.
/// </returns>
public IDictionary<string, IEnumerable<IDataEditor>> GetGroupedParameterEditors()
{
var parameterEditors = _parameterEditorCollection.ToArray();
var grouped = parameterEditors
.GroupBy(x => x.Group.IsNullOrWhiteSpace() ? "" : x.Group.ToLower())
.OrderBy(x => x.Key)
.ToDictionary(group => group.Key, group => group.OrderBy(d => d.Name).AsEnumerable());
return grouped;
}
/// <summary>
/// Get parameter editor by alias.
/// </summary>
/// <returns>
/// The <see cref="HttpResponseMessage"/>.
/// </returns>
public IDataEditor GetParameterEditorByAlias(string alias)
{
var parameterEditors = _parameterEditorCollection.ToArray();
var parameterEditor = parameterEditors.FirstOrDefault(x => x.Alias.InvariantEquals(alias));
return parameterEditor;
}
/// <summary>
/// Finds all the macro partials
/// </summary>
/// <returns>
/// The <see cref="IEnumerable"/>.
/// </returns>
private IEnumerable<string> FindPartialViewsFiles()
{
var files = new List<string>();
files.AddRange(this.FindPartialViewFilesInViewsFolder());
files.AddRange(this.FindPartialViewFilesInPluginFolders());
return files;
}
/// <summary>
/// Finds all macro partials in the views folder
/// </summary>
/// <returns>
/// The <see cref="IEnumerable"/>.
/// </returns>
private IEnumerable<string> FindPartialViewFilesInViewsFolder()
{
var partialsDir = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials);
return this.FindPartialViewFilesInFolder(
partialsDir,
partialsDir,
Constants.SystemDirectories.MacroPartials);
}
/// <summary>
/// Finds partial view files in app plugin folders.
/// </summary>
/// <returns>
/// </returns>
private IEnumerable<string> FindPartialViewFilesInPluginFolders()
{
var files = new List<string>();
var appPluginsFolder = new DirectoryInfo(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins));
if (!appPluginsFolder.Exists)
{
return files;
}
foreach (var directory in appPluginsFolder.GetDirectories())
{
var viewsFolder = directory.GetDirectories("Views");
if (viewsFolder.Any())
{
var macroPartials = viewsFolder.First().GetDirectories("MacroPartials");
if (macroPartials.Any())
{
files.AddRange(this.FindPartialViewFilesInFolder(macroPartials.First().FullName, macroPartials.First().FullName, Constants.SystemDirectories.AppPlugins + "/" + directory.Name + "/Views/MacroPartials"));
}
}
}
return files;
}
/// <summary>
/// Finds all partial views in a folder and subfolders
/// </summary>
/// <param name="orgPath">
/// The org path.
/// </param>
/// <param name="path">
/// The path.
/// </param>
/// <param name="prefixVirtualPath">
/// The prefix virtual path.
/// </param>
/// <returns>
/// The <see cref="IEnumerable"/>.
/// </returns>
private IEnumerable<string> FindPartialViewFilesInFolder(string orgPath, string path, string prefixVirtualPath)
{
var files = new List<string>();
var dirInfo = new DirectoryInfo(path);
if (dirInfo.Exists)
{
foreach (var dir in dirInfo.GetDirectories())
{
files.AddRange(this.FindPartialViewFilesInFolder(orgPath, path + "/" + dir.Name, prefixVirtualPath));
}
var fileInfo = dirInfo.GetFiles("*.*");
files.AddRange(
fileInfo.Select(file =>
prefixVirtualPath.TrimEnd(Constants.CharArrays.ForwardSlash) + "/" + (path.Replace(orgPath, string.Empty).Trim(Constants.CharArrays.ForwardSlash) + "/" + file.Name).Trim(Constants.CharArrays.ForwardSlash)));
}
return files;
}
/// <summary>
/// Used to map an <see cref="IMacro"/> instance to a <see cref="MacroDisplay"/>
/// </summary>
/// <param name="macro"></param>
/// <returns></returns>
private MacroDisplay MapToDisplay(IMacro macro)
{
var display = _umbracoMapper.Map<MacroDisplay>(macro);
var parameters = macro.Properties.Values
.OrderBy(x => x.SortOrder)
.Select(x => new MacroParameterDisplay()
{
Editor = x.EditorAlias,
Key = x.Alias,
Label = x.Name,
Id = x.Id
});
display.Parameters = parameters;
return display;
}
}
}