2017-05-12 14:49:44 +02:00
|
|
|
using System.Net;
|
2022-06-20 08:37:17 +02:00
|
|
|
using System.Web;
|
2020-11-19 22:17:42 +11:00
|
|
|
using Microsoft.AspNetCore.Authorization;
|
2020-05-26 13:08:20 +02:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2022-05-09 11:42:10 +02:00
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
2020-08-21 14:52:47 +01:00
|
|
|
using Microsoft.Extensions.Options;
|
2021-02-18 11:06:02 +01:00
|
|
|
using Umbraco.Cms.Core;
|
|
|
|
|
using Umbraco.Cms.Core.Configuration.Models;
|
2022-11-29 11:22:57 +00:00
|
|
|
using Umbraco.Cms.Core.DependencyInjection;
|
2021-02-18 11:06:02 +01:00
|
|
|
using Umbraco.Cms.Core.Hosting;
|
|
|
|
|
using Umbraco.Cms.Core.IO;
|
|
|
|
|
using Umbraco.Cms.Core.Mapping;
|
|
|
|
|
using Umbraco.Cms.Core.Models;
|
|
|
|
|
using Umbraco.Cms.Core.Models.ContentEditing;
|
2022-06-20 08:37:17 +02:00
|
|
|
using Umbraco.Cms.Core.Models.Membership;
|
2021-02-18 11:06:02 +01:00
|
|
|
using Umbraco.Cms.Core.Security;
|
|
|
|
|
using Umbraco.Cms.Core.Services;
|
2022-05-09 11:42:10 +02:00
|
|
|
using Umbraco.Cms.Core.Snippets;
|
2021-02-18 11:06:02 +01:00
|
|
|
using Umbraco.Cms.Core.Strings;
|
|
|
|
|
using Umbraco.Cms.Core.Strings.Css;
|
|
|
|
|
using Umbraco.Cms.Web.BackOffice.Filters;
|
|
|
|
|
using Umbraco.Cms.Web.BackOffice.Trees;
|
|
|
|
|
using Umbraco.Cms.Web.Common.ActionsResults;
|
|
|
|
|
using Umbraco.Cms.Web.Common.Attributes;
|
|
|
|
|
using Umbraco.Cms.Web.Common.Authorization;
|
2020-06-08 13:14:23 +02:00
|
|
|
using Umbraco.Extensions;
|
2021-02-18 11:06:02 +01:00
|
|
|
using Stylesheet = Umbraco.Cms.Core.Models.Stylesheet;
|
|
|
|
|
using StylesheetRule = Umbraco.Cms.Core.Models.ContentEditing.StylesheetRule;
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
namespace Umbraco.Cms.Web.BackOffice.Controllers;
|
|
|
|
|
|
|
|
|
|
// TODO: Put some exception filters in our webapi to return 404 instead of 500 when we throw ArgumentNullException
|
|
|
|
|
// ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/
|
|
|
|
|
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
|
|
|
|
//[PrefixlessBodyModelValidator]
|
|
|
|
|
[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)]
|
|
|
|
|
public class CodeFileController : BackOfficeNotificationsController
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
|
|
|
|
private readonly IFileService _fileService;
|
|
|
|
|
private readonly FileSystems _fileSystems;
|
|
|
|
|
private readonly GlobalSettings _globalSettings;
|
|
|
|
|
private readonly IHostingEnvironment _hostingEnvironment;
|
|
|
|
|
|
|
|
|
|
private readonly ILocalizedTextService _localizedTextService;
|
|
|
|
|
private readonly IShortStringHelper _shortStringHelper;
|
|
|
|
|
private readonly IUmbracoMapper _umbracoMapper;
|
2022-07-11 14:07:08 +02:00
|
|
|
private readonly PartialViewSnippetCollection _partialViewSnippetCollection;
|
|
|
|
|
private readonly PartialViewMacroSnippetCollection _partialViewMacroSnippetCollection;
|
2022-06-20 08:37:17 +02:00
|
|
|
|
|
|
|
|
[ActivatorUtilitiesConstructor]
|
|
|
|
|
public CodeFileController(
|
|
|
|
|
IHostingEnvironment hostingEnvironment,
|
|
|
|
|
FileSystems fileSystems,
|
|
|
|
|
IFileService fileService,
|
|
|
|
|
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
|
|
|
|
ILocalizedTextService localizedTextService,
|
|
|
|
|
IUmbracoMapper umbracoMapper,
|
|
|
|
|
IShortStringHelper shortStringHelper,
|
|
|
|
|
IOptionsSnapshot<GlobalSettings> globalSettings,
|
|
|
|
|
PartialViewSnippetCollection partialViewSnippetCollection,
|
|
|
|
|
PartialViewMacroSnippetCollection partialViewMacroSnippetCollection)
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
_hostingEnvironment = hostingEnvironment;
|
|
|
|
|
_fileSystems = fileSystems;
|
|
|
|
|
_fileService = fileService;
|
|
|
|
|
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
|
|
|
|
_localizedTextService = localizedTextService;
|
|
|
|
|
_umbracoMapper = umbracoMapper;
|
|
|
|
|
_shortStringHelper = shortStringHelper;
|
|
|
|
|
_globalSettings = globalSettings.Value;
|
|
|
|
|
_partialViewSnippetCollection = partialViewSnippetCollection;
|
|
|
|
|
_partialViewMacroSnippetCollection = partialViewMacroSnippetCollection;
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
[Obsolete("Use ctor will all params. Scheduled for removal in V12.")]
|
|
|
|
|
public CodeFileController(
|
|
|
|
|
IHostingEnvironment hostingEnvironment,
|
|
|
|
|
FileSystems fileSystems,
|
|
|
|
|
IFileService fileService,
|
|
|
|
|
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
|
|
|
|
ILocalizedTextService localizedTextService,
|
|
|
|
|
IUmbracoMapper umbracoMapper,
|
|
|
|
|
IShortStringHelper shortStringHelper,
|
|
|
|
|
IOptionsSnapshot<GlobalSettings> globalSettings) : this(
|
|
|
|
|
hostingEnvironment,
|
|
|
|
|
fileSystems,
|
|
|
|
|
fileService,
|
|
|
|
|
backOfficeSecurityAccessor,
|
|
|
|
|
localizedTextService,
|
|
|
|
|
umbracoMapper,
|
|
|
|
|
shortStringHelper,
|
|
|
|
|
globalSettings,
|
|
|
|
|
StaticServiceProvider.Instance.GetRequiredService<PartialViewSnippetCollection>(),
|
|
|
|
|
StaticServiceProvider.Instance.GetRequiredService<PartialViewMacroSnippetCollection>())
|
|
|
|
|
{
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Used to create a brand new file
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="type">This is a string but will be 'scripts' 'partialViews', 'partialViewMacros'</param>
|
|
|
|
|
/// <param name="display"></param>
|
|
|
|
|
/// <returns>Will return a simple 200 if file creation succeeds</returns>
|
|
|
|
|
[ValidationFilter]
|
|
|
|
|
public ActionResult<CodeFileDisplay> PostCreate(string type, CodeFileDisplay display)
|
|
|
|
|
{
|
|
|
|
|
if (display == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException("display");
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (string.IsNullOrWhiteSpace(type))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "type");
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
|
|
|
|
|
switch (type)
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
case Constants.Trees.PartialViews:
|
|
|
|
|
var view = new PartialView(PartialViewType.PartialView, display.VirtualPath ?? string.Empty)
|
|
|
|
|
{
|
|
|
|
|
Content = display.Content,
|
|
|
|
|
};
|
|
|
|
|
Attempt<IPartialView?> result = _fileService.CreatePartialView(view, display.Snippet, currentUser?.Id);
|
|
|
|
|
if (result.Success)
|
|
|
|
|
{
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return ValidationProblem(result.Exception?.Message);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
case Constants.Trees.PartialViewMacros:
|
|
|
|
|
var viewMacro = new PartialView(PartialViewType.PartialViewMacro, display.VirtualPath ?? string.Empty)
|
|
|
|
|
{
|
|
|
|
|
Content = display.Content,
|
|
|
|
|
};
|
|
|
|
|
Attempt<IPartialView?> resultMacro =
|
|
|
|
|
_fileService.CreatePartialViewMacro(viewMacro, display.Snippet, currentUser?.Id);
|
|
|
|
|
if (resultMacro.Success)
|
|
|
|
|
{
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
2022-03-31 12:52:26 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return ValidationProblem(resultMacro.Exception?.Message);
|
2022-03-31 12:52:26 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
case Constants.Trees.Scripts:
|
|
|
|
|
var script = new Script(display.VirtualPath ?? string.Empty);
|
|
|
|
|
_fileService.SaveScript(script, currentUser?.Id);
|
|
|
|
|
return Ok();
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
default:
|
|
|
|
|
return NotFound();
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-03-31 12:52:26 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Used to create a container/folder in 'partialViews', 'partialViewMacros', 'scripts' or 'stylesheets'
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="type">'partialViews', 'partialViewMacros' or 'scripts'</param>
|
|
|
|
|
/// <param name="parentId">The virtual path of the parent.</param>
|
|
|
|
|
/// <param name="name">The name of the container/folder</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
[HttpPost]
|
|
|
|
|
public ActionResult<CodeFileDisplay> PostCreateContainer(string type, string parentId, string name)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(type))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "type");
|
|
|
|
|
}
|
2022-03-31 12:52:26 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (string.IsNullOrWhiteSpace(parentId))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "parentId");
|
|
|
|
|
}
|
2022-03-31 12:52:26 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "name");
|
|
|
|
|
}
|
2022-03-31 12:52:26 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (name.ContainsAny(Path.GetInvalidPathChars()))
|
|
|
|
|
{
|
|
|
|
|
return ValidationProblem(_localizedTextService.Localize("codefile", "createFolderIllegalChars"));
|
|
|
|
|
}
|
2022-03-31 12:52:26 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
// if the parentId is root (-1) then we just need an empty string as we are
|
|
|
|
|
// creating the path below and we don't want -1 in the path
|
|
|
|
|
if (parentId == Constants.System.RootString)
|
|
|
|
|
{
|
|
|
|
|
parentId = string.Empty;
|
|
|
|
|
}
|
2022-03-31 12:52:26 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
name = HttpUtility.UrlDecode(name);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (parentId.IsNullOrWhiteSpace() == false)
|
|
|
|
|
{
|
|
|
|
|
parentId = HttpUtility.UrlDecode(parentId);
|
|
|
|
|
name = parentId.EnsureEndsWith("/") + name;
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
var virtualPath = string.Empty;
|
|
|
|
|
switch (type)
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
case Constants.Trees.PartialViews:
|
|
|
|
|
virtualPath = NormalizeVirtualPath(name, Constants.SystemDirectories.PartialViews);
|
|
|
|
|
_fileService.CreatePartialViewFolder(virtualPath);
|
|
|
|
|
break;
|
|
|
|
|
case Constants.Trees.PartialViewMacros:
|
|
|
|
|
virtualPath = NormalizeVirtualPath(name, Constants.SystemDirectories.MacroPartials);
|
|
|
|
|
_fileService.CreatePartialViewMacroFolder(virtualPath);
|
|
|
|
|
break;
|
|
|
|
|
case Constants.Trees.Scripts:
|
|
|
|
|
virtualPath = NormalizeVirtualPath(name, _globalSettings.UmbracoScriptsPath);
|
|
|
|
|
_fileService.CreateScriptFolder(virtualPath);
|
|
|
|
|
break;
|
|
|
|
|
case Constants.Trees.Stylesheets:
|
|
|
|
|
virtualPath = NormalizeVirtualPath(name, _globalSettings.UmbracoCssPath);
|
|
|
|
|
_fileService.CreateStyleSheetFolder(virtualPath);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return new CodeFileDisplay { VirtualPath = virtualPath, Path = Url.GetTreePathFromFilePath(virtualPath) };
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Used to get a specific file from disk via the FileService
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="type">This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' or 'stylesheets'</param>
|
|
|
|
|
/// <param name="virtualPath">The filename or URL encoded path of the file to open</param>
|
|
|
|
|
/// <returns>The file and its contents from the virtualPath</returns>
|
|
|
|
|
public ActionResult<CodeFileDisplay?> GetByPath(string type, string virtualPath)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(type))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "type");
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (string.IsNullOrWhiteSpace(virtualPath))
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath");
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
virtualPath = HttpUtility.UrlDecode(virtualPath);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case Constants.Trees.PartialViews:
|
|
|
|
|
IPartialView? view = _fileService.GetPartialView(virtualPath);
|
|
|
|
|
if (view != null)
|
|
|
|
|
{
|
|
|
|
|
CodeFileDisplay? display = _umbracoMapper.Map<IPartialView, CodeFileDisplay>(view);
|
|
|
|
|
|
|
|
|
|
if (display is not null)
|
2022-03-31 12:52:26 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
display.FileType = Constants.Trees.PartialViews;
|
|
|
|
|
display.Path = Url.GetTreePathFromFilePath(view.Path);
|
|
|
|
|
display.Id = HttpUtility.UrlEncode(view.Path);
|
2022-03-31 12:52:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return display;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case Constants.Trees.PartialViewMacros:
|
|
|
|
|
IPartialView? viewMacro = _fileService.GetPartialViewMacro(virtualPath);
|
|
|
|
|
if (viewMacro != null)
|
|
|
|
|
{
|
|
|
|
|
CodeFileDisplay? display = _umbracoMapper.Map<IPartialView, CodeFileDisplay>(viewMacro);
|
|
|
|
|
|
|
|
|
|
if (display is not null)
|
2022-03-31 12:52:26 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
display.FileType = Constants.Trees.PartialViewMacros;
|
|
|
|
|
display.Path = Url.GetTreePathFromFilePath(viewMacro.Path);
|
|
|
|
|
display.Id = HttpUtility.UrlEncode(viewMacro.Path);
|
2022-03-31 12:52:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return display;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case Constants.Trees.Scripts:
|
|
|
|
|
IScript? script = _fileService.GetScript(virtualPath);
|
|
|
|
|
if (script != null)
|
|
|
|
|
{
|
|
|
|
|
CodeFileDisplay? display = _umbracoMapper.Map<IScript, CodeFileDisplay>(script);
|
|
|
|
|
|
|
|
|
|
if (display is not null)
|
2022-03-31 12:52:26 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
display.FileType = Constants.Trees.Scripts;
|
|
|
|
|
display.Path = Url.GetTreePathFromFilePath(script.Path);
|
|
|
|
|
display.Id = HttpUtility.UrlEncode(script.Path);
|
2022-03-31 12:52:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return display;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case Constants.Trees.Stylesheets:
|
|
|
|
|
IStylesheet? stylesheet = _fileService.GetStylesheet(virtualPath);
|
|
|
|
|
if (stylesheet != null)
|
|
|
|
|
{
|
|
|
|
|
CodeFileDisplay? display = _umbracoMapper.Map<IStylesheet, CodeFileDisplay>(stylesheet);
|
|
|
|
|
|
|
|
|
|
if (display is not null)
|
2022-03-31 12:52:26 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
display.FileType = Constants.Trees.Stylesheets;
|
|
|
|
|
display.Path = Url.GetTreePathFromFilePath(stylesheet.Path);
|
|
|
|
|
display.Id = HttpUtility.UrlEncode(stylesheet.Path);
|
2022-03-31 12:52:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return display;
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
break;
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return NotFound();
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Used to get a list of available templates/snippets to base a new Partial View or Partial View Macro from
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="type">This is a string but will be 'partialViews', 'partialViewMacros'</param>
|
|
|
|
|
/// <returns>Returns a list of <see cref="SnippetDisplay" /> if a correct type is sent</returns>
|
|
|
|
|
public ActionResult<IEnumerable<SnippetDisplay>> GetSnippets(string type)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(type))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "type");
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
IEnumerable<string> snippets;
|
|
|
|
|
switch (type)
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
case Constants.Trees.PartialViews:
|
|
|
|
|
snippets = _partialViewSnippetCollection.GetNames();
|
|
|
|
|
break;
|
|
|
|
|
case Constants.Trees.PartialViewMacros:
|
|
|
|
|
snippets = _partialViewMacroSnippetCollection.GetNames();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return NotFound();
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return snippets.Select(snippet => new SnippetDisplay
|
|
|
|
|
{
|
|
|
|
|
Name = snippet.SplitPascalCasing(_shortStringHelper).ToFirstUpperInvariant(),
|
|
|
|
|
FileName = snippet,
|
|
|
|
|
}).ToList();
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Used to scaffold the json object for the editors for 'scripts', 'partialViews', 'partialViewMacros' and
|
|
|
|
|
/// 'stylesheets'
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="type">This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' or 'stylesheets'</param>
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
/// <param name="snippetName"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public ActionResult<CodeFileDisplay?> GetScaffold(string type, string id, string? snippetName = null)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(type))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "type");
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (string.IsNullOrWhiteSpace(id))
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "id");
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
CodeFileDisplay? codeFileDisplay;
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case Constants.Trees.PartialViews:
|
|
|
|
|
codeFileDisplay =
|
|
|
|
|
_umbracoMapper.Map<IPartialView, CodeFileDisplay>(new PartialView(PartialViewType.PartialView, string.Empty));
|
|
|
|
|
if (codeFileDisplay is not null)
|
|
|
|
|
{
|
|
|
|
|
codeFileDisplay.VirtualPath = Constants.SystemDirectories.PartialViews;
|
|
|
|
|
if (snippetName.IsNullOrWhiteSpace() == false)
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-07-11 14:07:08 +02:00
|
|
|
codeFileDisplay.Content = _partialViewSnippetCollection.GetContentFromName(snippetName!);
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
2022-06-20 08:37:17 +02:00
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
break;
|
|
|
|
|
case Constants.Trees.PartialViewMacros:
|
|
|
|
|
codeFileDisplay =
|
|
|
|
|
_umbracoMapper.Map<IPartialView, CodeFileDisplay>(new PartialView(PartialViewType.PartialViewMacro, string.Empty));
|
|
|
|
|
if (codeFileDisplay is not null)
|
|
|
|
|
{
|
|
|
|
|
codeFileDisplay.VirtualPath = Constants.SystemDirectories.MacroPartials;
|
|
|
|
|
if (snippetName.IsNullOrWhiteSpace() == false)
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
codeFileDisplay.Content = _partialViewMacroSnippetCollection.GetContentFromName(snippetName!);
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
2022-06-20 08:37:17 +02:00
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
break;
|
|
|
|
|
case Constants.Trees.Scripts:
|
|
|
|
|
codeFileDisplay = _umbracoMapper.Map<Script, CodeFileDisplay>(new Script(string.Empty));
|
|
|
|
|
if (codeFileDisplay is not null)
|
|
|
|
|
{
|
|
|
|
|
codeFileDisplay.VirtualPath = _globalSettings.UmbracoScriptsPath;
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
break;
|
|
|
|
|
case Constants.Trees.Stylesheets:
|
|
|
|
|
codeFileDisplay = _umbracoMapper.Map<Stylesheet, CodeFileDisplay>(new Stylesheet(string.Empty));
|
|
|
|
|
if (codeFileDisplay is not null)
|
|
|
|
|
{
|
|
|
|
|
codeFileDisplay.VirtualPath = _globalSettings.UmbracoCssPath;
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return new UmbracoProblemResult("Unsupported editortype", HttpStatusCode.BadRequest);
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (codeFileDisplay is null)
|
|
|
|
|
{
|
|
|
|
|
return codeFileDisplay;
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
// Make sure that the root virtual path ends with '/'
|
|
|
|
|
codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath?.EnsureEndsWith("/");
|
2017-07-20 11:21:28 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (id != Constants.System.RootString)
|
|
|
|
|
{
|
|
|
|
|
codeFileDisplay.VirtualPath += id.TrimStart(Constants.CharArrays.ForwardSlash).EnsureEndsWith("/");
|
|
|
|
|
//if it's not new then it will have a path, otherwise it won't
|
|
|
|
|
codeFileDisplay.Path = Url.GetTreePathFromFilePath(id);
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath?.TrimStart("~");
|
|
|
|
|
codeFileDisplay.FileType = type;
|
|
|
|
|
return codeFileDisplay;
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Used to delete a specific file from disk via the FileService
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="type">This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' or 'stylesheets'</param>
|
|
|
|
|
/// <param name="virtualPath">The filename or URL encoded path of the file to delete</param>
|
|
|
|
|
/// <returns>Will return a simple 200 if file deletion succeeds</returns>
|
|
|
|
|
[HttpDelete]
|
|
|
|
|
[HttpPost]
|
|
|
|
|
public IActionResult Delete(string type, string virtualPath)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(type))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "type");
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (string.IsNullOrWhiteSpace(virtualPath))
|
2018-11-01 15:49:30 +01:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath");
|
|
|
|
|
}
|
2018-11-01 15:49:30 +01:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
virtualPath = HttpUtility.UrlDecode(virtualPath);
|
|
|
|
|
IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
|
|
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case Constants.Trees.PartialViews:
|
|
|
|
|
if (IsDirectory(
|
2022-11-17 11:17:41 +01:00
|
|
|
_hostingEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.PartialViews, virtualPath)), _fileSystems.PartialViewsFileSystem!))
|
2022-06-20 08:37:17 +02:00
|
|
|
{
|
|
|
|
|
_fileService.DeletePartialViewFolder(virtualPath);
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_fileService.DeletePartialView(virtualPath, currentUser?.Id))
|
|
|
|
|
{
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
2018-11-01 15:49:30 +01:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return new UmbracoProblemResult("No Partial View or folder found with the specified path", HttpStatusCode.NotFound);
|
2018-11-01 15:49:30 +01:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
case Constants.Trees.PartialViewMacros:
|
|
|
|
|
if (IsDirectory(
|
2022-11-17 11:17:41 +01:00
|
|
|
_hostingEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.MacroPartials, virtualPath)), _fileSystems.MacroPartialsFileSystem!))
|
2018-11-01 15:49:30 +01:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
_fileService.DeletePartialViewMacroFolder(virtualPath);
|
|
|
|
|
return Ok();
|
2018-11-01 15:49:30 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (_fileService.DeletePartialViewMacro(virtualPath, currentUser?.Id))
|
|
|
|
|
{
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
2018-11-01 15:49:30 +01:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return new UmbracoProblemResult("No Partial View Macro or folder found with the specified path", HttpStatusCode.NotFound);
|
|
|
|
|
|
|
|
|
|
case Constants.Trees.Scripts:
|
|
|
|
|
if (IsDirectory(
|
2022-11-17 11:17:41 +01:00
|
|
|
_hostingEnvironment.MapPathWebRoot(Path.Combine(_globalSettings.UmbracoScriptsPath, virtualPath)), _fileSystems.ScriptsFileSystem!))
|
2022-06-20 08:37:17 +02:00
|
|
|
{
|
|
|
|
|
_fileService.DeleteScriptFolder(virtualPath);
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
2018-11-01 15:49:30 +01:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (_fileService.GetScript(virtualPath) != null)
|
|
|
|
|
{
|
|
|
|
|
_fileService.DeleteScript(virtualPath, currentUser?.Id);
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new UmbracoProblemResult("No Script or folder found with the specified path", HttpStatusCode.NotFound);
|
|
|
|
|
case Constants.Trees.Stylesheets:
|
|
|
|
|
if (IsDirectory(
|
2022-11-17 11:17:41 +01:00
|
|
|
_hostingEnvironment.MapPathWebRoot(Path.Combine(_globalSettings.UmbracoCssPath, virtualPath)), _fileSystems.StylesheetsFileSystem!))
|
2022-06-20 08:37:17 +02:00
|
|
|
{
|
|
|
|
|
_fileService.DeleteStyleSheetFolder(virtualPath);
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_fileService.GetStylesheet(virtualPath) != null)
|
|
|
|
|
{
|
|
|
|
|
_fileService.DeleteStylesheet(virtualPath, currentUser?.Id);
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new UmbracoProblemResult("No Stylesheet found with the specified path", HttpStatusCode.NotFound);
|
|
|
|
|
default:
|
|
|
|
|
return NotFound();
|
2018-11-01 15:49:30 +01:00
|
|
|
}
|
2022-06-20 08:37:17 +02:00
|
|
|
}
|
2018-11-01 15:49:30 +01:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Used to create or update a 'partialview', 'partialviewmacro', 'script' or 'stylesheets' file
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="display"></param>
|
|
|
|
|
/// <returns>The updated CodeFileDisplay model</returns>
|
|
|
|
|
public ActionResult<CodeFileDisplay> PostSave(CodeFileDisplay display)
|
|
|
|
|
{
|
|
|
|
|
if (display == null)
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
throw new ArgumentNullException("display");
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
TryValidateModel(display);
|
|
|
|
|
if (ModelState.IsValid == false)
|
2018-10-27 21:18:03 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
return ValidationProblem(ModelState);
|
2018-10-27 21:18:03 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
switch (display.FileType)
|
2018-10-27 21:18:03 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
case Constants.Trees.PartialViews:
|
|
|
|
|
Attempt<IPartialView?> partialViewResult = CreateOrUpdatePartialView(display);
|
|
|
|
|
if (partialViewResult.Success)
|
|
|
|
|
{
|
|
|
|
|
display = _umbracoMapper.Map(partialViewResult.Result, display);
|
|
|
|
|
display.Path = Url.GetTreePathFromFilePath(partialViewResult.Result?.Path);
|
|
|
|
|
display.Id = HttpUtility.UrlEncode(partialViewResult.Result?.Path);
|
|
|
|
|
return display;
|
|
|
|
|
}
|
2018-10-27 21:18:03 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
display.AddErrorNotification(
|
|
|
|
|
_localizedTextService.Localize("speechBubbles", "partialViewErrorHeader"),
|
|
|
|
|
_localizedTextService.Localize("speechBubbles", "partialViewErrorText"));
|
|
|
|
|
break;
|
2018-10-27 21:18:03 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
case Constants.Trees.PartialViewMacros:
|
|
|
|
|
Attempt<IPartialView?> partialViewMacroResult = CreateOrUpdatePartialViewMacro(display);
|
|
|
|
|
if (partialViewMacroResult.Success)
|
2022-03-31 12:52:26 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
display = _umbracoMapper.Map(partialViewMacroResult.Result, display);
|
|
|
|
|
display.Path = Url.GetTreePathFromFilePath(partialViewMacroResult.Result?.Path);
|
|
|
|
|
display.Id = HttpUtility.UrlEncode(partialViewMacroResult.Result?.Path);
|
|
|
|
|
return display;
|
2022-03-31 12:52:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
display.AddErrorNotification(
|
|
|
|
|
_localizedTextService.Localize("speechBubbles", "partialViewErrorHeader"),
|
|
|
|
|
_localizedTextService.Localize("speechBubbles", "partialViewErrorText"));
|
|
|
|
|
break;
|
2018-10-27 21:18:03 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
case Constants.Trees.Scripts:
|
2018-10-27 21:18:03 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
IScript? scriptResult = CreateOrUpdateScript(display);
|
|
|
|
|
display = _umbracoMapper.Map(scriptResult, display);
|
|
|
|
|
display.Path = Url.GetTreePathFromFilePath(scriptResult?.Path);
|
|
|
|
|
display.Id = HttpUtility.UrlEncode(scriptResult?.Path);
|
|
|
|
|
return display;
|
|
|
|
|
|
|
|
|
|
//display.AddErrorNotification(
|
|
|
|
|
// _localizedTextService.Localize("speechBubbles/partialViewErrorHeader"),
|
|
|
|
|
// _localizedTextService.Localize("speechBubbles/partialViewErrorText"));
|
|
|
|
|
|
|
|
|
|
case Constants.Trees.Stylesheets:
|
|
|
|
|
|
|
|
|
|
IStylesheet? stylesheetResult = CreateOrUpdateStylesheet(display);
|
|
|
|
|
display = _umbracoMapper.Map(stylesheetResult, display);
|
|
|
|
|
display.Path = Url.GetTreePathFromFilePath(stylesheetResult?.Path);
|
|
|
|
|
display.Id = HttpUtility.UrlEncode(stylesheetResult?.Path);
|
|
|
|
|
return display;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return NotFound();
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
2017-07-20 11:21:28 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return display;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Extracts "umbraco style rules" from a style sheet
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="data">The style sheet data</param>
|
|
|
|
|
/// <returns>The style rules</returns>
|
|
|
|
|
public StylesheetRule[]? PostExtractStylesheetRules(StylesheetData data)
|
|
|
|
|
{
|
|
|
|
|
if (data.Content.IsNullOrWhiteSpace())
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
return new StylesheetRule[0];
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return StylesheetHelper.ParseRules(data.Content)?.Select(rule => new StylesheetRule
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
Name = rule.Name,
|
|
|
|
|
Selector = rule.Selector,
|
|
|
|
|
Styles = rule.Styles
|
|
|
|
|
}).ToArray();
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a style sheet from CSS and style rules
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="data">The style sheet data</param>
|
|
|
|
|
/// <returns>The style sheet combined from the CSS and the rules</returns>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Any "umbraco style rules" in the CSS will be removed and replaced with the rules passed in <see cref="data" />
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public string? PostInterpolateStylesheetRules(StylesheetData data)
|
|
|
|
|
{
|
|
|
|
|
// first remove all existing rules
|
|
|
|
|
Core.Strings.Css.StylesheetRule[] existingRules = data.Content.IsNullOrWhiteSpace()
|
|
|
|
|
? new Core.Strings.Css.StylesheetRule[0]
|
|
|
|
|
: StylesheetHelper.ParseRules(data.Content).ToArray();
|
|
|
|
|
foreach (Core.Strings.Css.StylesheetRule rule in existingRules)
|
|
|
|
|
{
|
|
|
|
|
data.Content = StylesheetHelper.ReplaceRule(data.Content, rule.Name, null);
|
|
|
|
|
}
|
2020-05-26 13:08:20 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
data.Content = data.Content?.TrimEnd(Constants.CharArrays.LineFeedCarriageReturn);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
// now add all the posted rules
|
|
|
|
|
if (data.Rules != null && data.Rules.Any())
|
|
|
|
|
{
|
|
|
|
|
foreach (StylesheetRule rule in data.Rules)
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
data.Content = StylesheetHelper.AppendRule(
|
|
|
|
|
data.Content,
|
|
|
|
|
new Core.Strings.Css.StylesheetRule
|
|
|
|
|
{
|
|
|
|
|
Name = rule.Name,
|
|
|
|
|
Selector = rule.Selector,
|
|
|
|
|
Styles = rule.Styles
|
|
|
|
|
});
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
data.Content += Environment.NewLine;
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return data.Content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Create or Update a Script
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="display"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// It's important to note that Scripts are DIFFERENT from cshtml files since scripts use IFileSystem and cshtml files
|
|
|
|
|
/// use a normal file system because they must exist on a real file system for ASP.NET to work.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private IScript? CreateOrUpdateScript(CodeFileDisplay display) =>
|
|
|
|
|
CreateOrUpdateFile(
|
|
|
|
|
display,
|
|
|
|
|
".js",
|
|
|
|
|
_fileSystems.ScriptsFileSystem,
|
|
|
|
|
name => _fileService.GetScript(name),
|
|
|
|
|
(script, userId) => _fileService.SaveScript(script, userId),
|
|
|
|
|
name => new Script(name ?? string.Empty));
|
|
|
|
|
|
|
|
|
|
private IStylesheet? CreateOrUpdateStylesheet(CodeFileDisplay display) =>
|
|
|
|
|
CreateOrUpdateFile(
|
|
|
|
|
display,
|
|
|
|
|
".css",
|
|
|
|
|
_fileSystems.StylesheetsFileSystem,
|
|
|
|
|
name => _fileService.GetStylesheet(name),
|
|
|
|
|
(stylesheet, userId) => _fileService.SaveStylesheet(stylesheet, userId),
|
|
|
|
|
name => new Stylesheet(name ?? string.Empty));
|
|
|
|
|
|
|
|
|
|
private T CreateOrUpdateFile<T>(CodeFileDisplay display, string extension, IFileSystem? fileSystem, Func<string?, T> getFileByName, Action<T, int?> saveFile, Func<string?, T> createFile)
|
|
|
|
|
where T : IFile?
|
|
|
|
|
{
|
|
|
|
|
//must always end with the correct extension
|
|
|
|
|
display.Name = EnsureCorrectFileExtension(display.Name, extension);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
var virtualPath = display.VirtualPath ?? string.Empty;
|
|
|
|
|
// this is all weird, should be using relative paths everywhere!
|
|
|
|
|
var relPath = fileSystem?.GetRelativePath(virtualPath);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
if (relPath?.EndsWith(extension) == false)
|
|
|
|
|
{
|
|
|
|
|
//this would typically mean it's new
|
|
|
|
|
relPath = relPath.IsNullOrWhiteSpace()
|
|
|
|
|
? relPath + display.Name
|
|
|
|
|
: relPath.EnsureEndsWith('/') + display.Name;
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
|
|
|
|
|
T file = getFileByName(relPath);
|
|
|
|
|
if (file != null)
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
// might need to find the path
|
|
|
|
|
var orgPath = file.Name is null
|
|
|
|
|
? string.Empty
|
|
|
|
|
: file.OriginalPath.Substring(0, file.OriginalPath.IndexOf(file.Name));
|
|
|
|
|
file.Path = orgPath + display.Name;
|
|
|
|
|
|
|
|
|
|
file.Content = display.Content;
|
|
|
|
|
//try/catch? since this doesn't return an Attempt?
|
|
|
|
|
saveFile(file, currentUser?.Id);
|
|
|
|
|
}
|
|
|
|
|
else
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
file = createFile(relPath);
|
|
|
|
|
if (file is not null)
|
|
|
|
|
{
|
|
|
|
|
file.Content = display.Content;
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
saveFile(file, currentUser?.Id);
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Attempt<IPartialView?> CreateOrUpdatePartialView(CodeFileDisplay display) =>
|
|
|
|
|
CreateOrUpdatePartialView(
|
|
|
|
|
display,
|
|
|
|
|
Constants.SystemDirectories.PartialViews,
|
|
|
|
|
_fileService.GetPartialView,
|
|
|
|
|
_fileService.SavePartialView,
|
|
|
|
|
_fileService.CreatePartialView);
|
|
|
|
|
|
|
|
|
|
private Attempt<IPartialView?> CreateOrUpdatePartialViewMacro(CodeFileDisplay display) =>
|
|
|
|
|
CreateOrUpdatePartialView(display, Constants.SystemDirectories.MacroPartials, _fileService.GetPartialViewMacro, _fileService.SavePartialViewMacro, _fileService.CreatePartialViewMacro);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Helper method to take care of persisting partial views or partial view macros - so we're not duplicating the same
|
|
|
|
|
/// logic
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="display"></param>
|
|
|
|
|
/// <param name="systemDirectory"></param>
|
|
|
|
|
/// <param name="getView"></param>
|
|
|
|
|
/// <param name="saveView"></param>
|
|
|
|
|
/// <param name="createView"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private Attempt<IPartialView?> CreateOrUpdatePartialView(
|
|
|
|
|
CodeFileDisplay display,
|
|
|
|
|
string systemDirectory,
|
|
|
|
|
Func<string, IPartialView?> getView,
|
|
|
|
|
Func<IPartialView, int?,
|
|
|
|
|
Attempt<IPartialView?>> saveView,
|
|
|
|
|
Func<IPartialView, string?,
|
|
|
|
|
int?, Attempt<IPartialView?>> createView)
|
|
|
|
|
{
|
|
|
|
|
//must always end with the correct extension
|
|
|
|
|
display.Name = EnsureCorrectFileExtension(display.Name, ".cshtml");
|
|
|
|
|
|
|
|
|
|
Attempt<IPartialView?> partialViewResult;
|
|
|
|
|
IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
|
|
|
|
|
|
|
|
|
|
var virtualPath = NormalizeVirtualPath(display.VirtualPath, systemDirectory);
|
|
|
|
|
IPartialView? view = getView(virtualPath);
|
|
|
|
|
if (view != null)
|
2017-05-12 14:49:44 +02:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
// might need to find the path
|
|
|
|
|
var orgPath = view.OriginalPath.Substring(0, view.OriginalPath.IndexOf(view.Name ?? string.Empty));
|
|
|
|
|
view.Path = orgPath + display.Name;
|
2021-04-07 16:25:46 +02:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
view.Content = display.Content;
|
|
|
|
|
partialViewResult = saveView(view, currentUser?.Id);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
view = new PartialView(PartialViewType.PartialView, virtualPath + display.Name)
|
|
|
|
|
{
|
|
|
|
|
Content = display.Content
|
|
|
|
|
};
|
|
|
|
|
partialViewResult = createView(view, display.Snippet, currentUser?.Id);
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
2018-11-01 15:49:30 +01:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
return partialViewResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string NormalizeVirtualPath(string? virtualPath, string systemDirectory)
|
|
|
|
|
{
|
|
|
|
|
if (virtualPath.IsNullOrWhiteSpace())
|
2018-11-01 15:49:30 +01:00
|
|
|
{
|
2022-06-20 08:37:17 +02:00
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
systemDirectory = systemDirectory.TrimStart("~");
|
|
|
|
|
systemDirectory = systemDirectory.Replace('\\', '/');
|
|
|
|
|
virtualPath = virtualPath!.TrimStart("~");
|
|
|
|
|
virtualPath = virtualPath.Replace('\\', '/');
|
|
|
|
|
virtualPath = virtualPath.ReplaceFirst(systemDirectory, string.Empty);
|
|
|
|
|
|
|
|
|
|
return virtualPath;
|
|
|
|
|
}
|
2018-11-01 15:49:30 +01:00
|
|
|
|
2022-06-20 08:37:17 +02:00
|
|
|
private string? EnsureCorrectFileExtension(string? value, string extension)
|
|
|
|
|
{
|
|
|
|
|
if (value?.EndsWith(extension) == false)
|
|
|
|
|
{
|
|
|
|
|
value += extension;
|
2018-11-01 15:49:30 +01:00
|
|
|
}
|
2022-06-20 08:37:17 +02:00
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-17 11:17:41 +01:00
|
|
|
private bool IsDirectory(string path, IFileSystem fileSystem)
|
2022-06-20 08:37:17 +02:00
|
|
|
{
|
2022-11-17 11:17:41 +01:00
|
|
|
// If it's a physical filesystem check with directory info
|
|
|
|
|
if (fileSystem.CanAddPhysical)
|
|
|
|
|
{
|
|
|
|
|
var dirInfo = new DirectoryInfo(path);
|
|
|
|
|
|
|
|
|
|
// If you turn off indexing in Windows this will have the attribute:
|
|
|
|
|
// `FileAttributes.Directory | FileAttributes.NotContentIndexed`
|
|
|
|
|
return (dirInfo.Attributes & FileAttributes.Directory) != 0;
|
|
|
|
|
}
|
2022-06-20 08:37:17 +02:00
|
|
|
|
2022-11-17 11:17:41 +01:00
|
|
|
// Otherwise check the filesystem abstraction to see if the folder exists
|
|
|
|
|
// Since this is used for delete, it presumably exists if we're trying to delete it
|
|
|
|
|
return fileSystem.DirectoryExists(path);
|
2022-06-20 08:37:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this is an internal class for passing stylesheet data from the client to the controller while editing
|
|
|
|
|
public class StylesheetData
|
|
|
|
|
{
|
|
|
|
|
public string? Content { get; set; }
|
|
|
|
|
|
|
|
|
|
public StylesheetRule[]? Rules { get; set; }
|
2017-05-12 14:49:44 +02:00
|
|
|
}
|
|
|
|
|
}
|