using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Legacy;
using Umbraco.Core.IO;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Core.Strings.Css;
using Umbraco.Extensions;
using Umbraco.Web.Models.ContentEditing;
using Stylesheet = Umbraco.Core.Models.Stylesheet;
using StylesheetRule = Umbraco.Web.Models.ContentEditing.StylesheetRule;
using Umbraco.Web.BackOffice.Filters;
using Umbraco.Web.Common.ActionsResults;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Common.Exceptions;
using Umbraco.Web.Editors;
namespace Umbraco.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("UmbracoApi")]
//[PrefixlessBodyModelValidator]
[TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})]
public class CodeFileController : BackOfficeNotificationsController
{
private readonly IIOHelper _ioHelper;
private readonly IFileSystems _fileSystems;
private readonly IFileService _fileService;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly ILocalizedTextService _localizedTextService;
private readonly UmbracoMapper _umbracoMapper;
private readonly IShortStringHelper _shortStringHelper;
private readonly IGlobalSettings _globalSettings;
public CodeFileController(
IIOHelper ioHelper,
IFileSystems fileSystems,
IFileService fileService,
IUmbracoContextAccessor umbracoContextAccessor,
ILocalizedTextService localizedTextService,
UmbracoMapper umbracoMapper,
IShortStringHelper shortStringHelper,
IGlobalSettings globalSettings)
{
_ioHelper = ioHelper;
_fileSystems = fileSystems;
_fileService = fileService;
_umbracoContextAccessor = umbracoContextAccessor;
_localizedTextService = localizedTextService;
_umbracoMapper = umbracoMapper;
_shortStringHelper = shortStringHelper;
_globalSettings = globalSettings;
}
///
/// Used to create a brand new file
///
/// This is a string but will be 'scripts' 'partialViews', 'partialViewMacros'
///
/// Will return a simple 200 if file creation succeeds
[ValidationFilter]
public IActionResult PostCreate(string type, CodeFileDisplay display)
{
if (display == null) throw new ArgumentNullException("display");
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser;
switch (type)
{
case Core.Constants.Trees.PartialViews:
var view = new PartialView(PartialViewType.PartialView, display.VirtualPath);
view.Content = display.Content;
var result = _fileService.CreatePartialView(view, display.Snippet, currentUser.Id);
return result.Success == true ? Ok() : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message);
case Core.Constants.Trees.PartialViewMacros:
var viewMacro = new PartialView(PartialViewType.PartialViewMacro, display.VirtualPath);
viewMacro.Content = display.Content;
var resultMacro = _fileService.CreatePartialViewMacro(viewMacro, display.Snippet, currentUser.Id);
return resultMacro.Success == true ? Ok() : throw HttpResponseException.CreateNotificationValidationErrorResponse(resultMacro.Exception.Message);
case Core.Constants.Trees.Scripts:
var script = new Script(display.VirtualPath);
_fileService.SaveScript(script, currentUser.Id);
return Ok();
default:
return NotFound();
}
}
///
/// Used to create a container/folder in 'partialViews', 'partialViewMacros', 'scripts' or 'stylesheets'
///
/// 'partialViews', 'partialViewMacros' or 'scripts'
/// The virtual path of the parent.
/// The name of the container/folder
///
[HttpPost]
public IActionResult PostCreateContainer(string type, string parentId, string name)
{
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
if (string.IsNullOrWhiteSpace(parentId)) throw new ArgumentException("Value cannot be null or whitespace.", "parentId");
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name");
if (name.ContainsAny(Path.GetInvalidPathChars())) {
throw HttpResponseException.CreateNotificationValidationErrorResponse(_localizedTextService.Localize("codefile/createFolderIllegalChars"));
}
// 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 == Core.Constants.System.RootString)
{
parentId = string.Empty;
}
name = System.Web.HttpUtility.UrlDecode(name);
if (parentId.IsNullOrWhiteSpace() == false)
{
parentId = System.Web.HttpUtility.UrlDecode(parentId);
name = parentId.EnsureEndsWith("/") + name;
}
var virtualPath = string.Empty;
switch (type)
{
case Core.Constants.Trees.PartialViews:
virtualPath = NormalizeVirtualPath(name, Core.Constants.SystemDirectories.PartialViews);
_fileService.CreatePartialViewFolder(virtualPath);
break;
case Core.Constants.Trees.PartialViewMacros:
virtualPath = NormalizeVirtualPath(name, Core.Constants.SystemDirectories.MacroPartials);
_fileService.CreatePartialViewMacroFolder(virtualPath);
break;
case Core.Constants.Trees.Scripts:
virtualPath = NormalizeVirtualPath(name, _globalSettings.UmbracoScriptsPath);
_fileService.CreateScriptFolder(virtualPath);
break;
case Core.Constants.Trees.Stylesheets:
virtualPath = NormalizeVirtualPath(name, _globalSettings.UmbracoCssPath);
_fileService.CreateStyleSheetFolder(virtualPath);
break;
}
return Ok(new CodeFileDisplay
{
VirtualPath = virtualPath,
Path = Url.GetTreePathFromFilePath(virtualPath)
});
}
///
/// Used to get a specific file from disk via the FileService
///
/// This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' or 'stylesheets'
/// The filename or urlencoded path of the file to open
/// The file and its contents from the virtualPath
public IActionResult GetByPath(string type, string virtualPath)
{
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
if (string.IsNullOrWhiteSpace(virtualPath)) throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath");
virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath);
switch (type)
{
case Core.Constants.Trees.PartialViews:
var view = _fileService.GetPartialView(virtualPath);
if (view != null)
{
var display = _umbracoMapper.Map(view);
display.FileType = Core.Constants.Trees.PartialViews;
display.Path = Url.GetTreePathFromFilePath(view.Path);
display.Id = System.Web.HttpUtility.UrlEncode(view.Path);
return Ok(display);
}
break;
case Core.Constants.Trees.PartialViewMacros:
var viewMacro = _fileService.GetPartialViewMacro(virtualPath);
if (viewMacro != null)
{
var display = _umbracoMapper.Map(viewMacro);
display.FileType = Core.Constants.Trees.PartialViewMacros;
display.Path = Url.GetTreePathFromFilePath(viewMacro.Path);
display.Id = System.Web.HttpUtility.UrlEncode(viewMacro.Path);
return Ok(display);
}
break;
case Core.Constants.Trees.Scripts:
var script = _fileService.GetScriptByName(virtualPath);
if (script != null)
{
var display = _umbracoMapper.Map(script);
display.FileType = Core.Constants.Trees.Scripts;
display.Path = Url.GetTreePathFromFilePath(script.Path);
display.Id = System.Web.HttpUtility.UrlEncode(script.Path);
return Ok(display);
}
break;
case Core.Constants.Trees.Stylesheets:
var stylesheet = _fileService.GetStylesheetByName(virtualPath);
if (stylesheet != null)
{
var display = _umbracoMapper.Map(stylesheet);
display.FileType = Core.Constants.Trees.Stylesheets;
display.Path = Url.GetTreePathFromFilePath(stylesheet.Path);
display.Id = System.Web.HttpUtility.UrlEncode(stylesheet.Path);
return Ok(display);
}
break;
}
return NotFound();
}
///
/// Used to get a list of available templates/snippets to base a new Partial View or Partial View Macro from
///
/// This is a string but will be 'partialViews', 'partialViewMacros'
/// Returns a list of if a correct type is sent
public IEnumerable GetSnippets(string type)
{
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
IEnumerable snippets;
switch (type)
{
case Core.Constants.Trees.PartialViews:
snippets = _fileService.GetPartialViewSnippetNames(
//ignore these - (this is taken from the logic in "PartialView.ascx.cs")
"Gallery",
"ListChildPagesFromChangeableSource",
"ListChildPagesOrderedByProperty",
"ListImagesFromMediaFolder");
break;
case Core.Constants.Trees.PartialViewMacros:
snippets = _fileService.GetPartialViewSnippetNames();
break;
default:
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return snippets.Select(snippet => new SnippetDisplay() {Name = snippet.SplitPascalCasing(_shortStringHelper).ToFirstUpperInvariant(), FileName = snippet});
}
///
/// Used to scaffold the json object for the editors for 'scripts', 'partialViews', 'partialViewMacros' and 'stylesheets'
///
/// This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' or 'stylesheets'
///
///
///
public IActionResult GetScaffold(string type, string id, string snippetName = null)
{
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("Value cannot be null or whitespace.", "id");
CodeFileDisplay codeFileDisplay;
switch (type)
{
case Core.Constants.Trees.PartialViews:
codeFileDisplay = _umbracoMapper.Map(new PartialView(PartialViewType.PartialView, string.Empty));
codeFileDisplay.VirtualPath = Core.Constants.SystemDirectories.PartialViews;
if (snippetName.IsNullOrWhiteSpace() == false)
codeFileDisplay.Content = _fileService.GetPartialViewSnippetContent(snippetName);
break;
case Core.Constants.Trees.PartialViewMacros:
codeFileDisplay = _umbracoMapper.Map(new PartialView(PartialViewType.PartialViewMacro, string.Empty));
codeFileDisplay.VirtualPath = Core.Constants.SystemDirectories.MacroPartials;
if (snippetName.IsNullOrWhiteSpace() == false)
codeFileDisplay.Content = _fileService.GetPartialViewMacroSnippetContent(snippetName);
break;
case Core.Constants.Trees.Scripts:
codeFileDisplay = _umbracoMapper.Map