Files
Umbraco-CMS/src/Umbraco.Infrastructure/Services/Implement/FileService.cs
Mole 738150f5ad Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/align-namespaces
# Conflicts:
#	src/Umbraco.Core/IO/ViewHelper.cs
#	src/Umbraco.Core/Models/ContentModel.cs
#	src/Umbraco.Core/Models/ContentModelOfTContent.cs
#	src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs
#	src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs
#	src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs
#	src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs
#	src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs
#	src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs
#	src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs
#	src/Umbraco.Web.Common/Controllers/RenderController.cs
#	src/Umbraco.Web.Common/Macros/PartialViewMacroPage.cs
#	src/Umbraco.Web.Common/Views/UmbracoViewPage.cs
#	src/Umbraco.Web.UI.NetCore/Views/Partials/blocklist/default.cshtml
#	src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3-fluid.cshtml
#	src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3.cshtml
#	src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/embed.cshtml
#	src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/macro.cshtml
#	src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs
#	src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs
2021-02-17 15:35:44 +01:00

1161 lines
43 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Core.Scoping;
using Umbraco.Extensions;
namespace Umbraco.Core.Services.Implement
{
/// <summary>
/// Represents the File Service, which is an easy access to operations involving <see cref="IFile"/> objects like Scripts, Stylesheets and Templates
/// </summary>
public class FileService : ScopeRepositoryService, IFileService
{
private readonly IStylesheetRepository _stylesheetRepository;
private readonly IScriptRepository _scriptRepository;
private readonly ITemplateRepository _templateRepository;
private readonly IPartialViewRepository _partialViewRepository;
private readonly IPartialViewMacroRepository _partialViewMacroRepository;
private readonly IAuditRepository _auditRepository;
private readonly IShortStringHelper _shortStringHelper;
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private const string PartialViewHeader = "@inherits Umbraco.Web.Common.Views.UmbracoViewPage";
private const string PartialViewMacroHeader = "@inherits Umbraco.Web.Common.Macros.PartialViewMacroPage";
public FileService(IScopeProvider uowProvider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
IStylesheetRepository stylesheetRepository, IScriptRepository scriptRepository, ITemplateRepository templateRepository,
IPartialViewRepository partialViewRepository, IPartialViewMacroRepository partialViewMacroRepository,
IAuditRepository auditRepository, IShortStringHelper shortStringHelper, IOptions<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment)
: base(uowProvider, loggerFactory, eventMessagesFactory)
{
_stylesheetRepository = stylesheetRepository;
_scriptRepository = scriptRepository;
_templateRepository = templateRepository;
_partialViewRepository = partialViewRepository;
_partialViewMacroRepository = partialViewMacroRepository;
_auditRepository = auditRepository;
_shortStringHelper = shortStringHelper;
_globalSettings = globalSettings.Value;
_hostingEnvironment = hostingEnvironment;
}
#region Stylesheets
/// <inheritdoc />
public IEnumerable<IStylesheet> GetStylesheets(params string[] names)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _stylesheetRepository.GetMany(names);
}
}
/// <inheritdoc />
public IStylesheet GetStylesheetByName(string name)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _stylesheetRepository.Get(name);
}
}
/// <inheritdoc />
public void SaveStylesheet(IStylesheet stylesheet, int userId = Cms.Core.Constants.Security.SuperUserId)
{
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IStylesheet>(stylesheet);
if (scope.Events.DispatchCancelable(SavingStylesheet, this, saveEventArgs))
{
scope.Complete();
return;
}
_stylesheetRepository.Save(stylesheet);
saveEventArgs.CanCancel = false;
scope.Events.Dispatch(SavedStylesheet, this, saveEventArgs);
Audit(AuditType.Save, userId, -1, "Stylesheet");
scope.Complete();
}
}
/// <inheritdoc />
public void DeleteStylesheet(string path, int userId = Cms.Core.Constants.Security.SuperUserId)
{
using (var scope = ScopeProvider.CreateScope())
{
var stylesheet = _stylesheetRepository.Get(path);
if (stylesheet == null)
{
scope.Complete();
return;
}
var deleteEventArgs = new DeleteEventArgs<IStylesheet>(stylesheet);
if (scope.Events.DispatchCancelable(DeletingStylesheet, this, deleteEventArgs))
{
scope.Complete();
return; // causes rollback // causes rollback
}
_stylesheetRepository.Delete(stylesheet);
deleteEventArgs.CanCancel = false;
scope.Events.Dispatch(DeletedStylesheet, this, deleteEventArgs);
Audit(AuditType.Delete, userId, -1, "Stylesheet");
scope.Complete();
}
}
/// <inheritdoc />
public bool ValidateStylesheet(IStylesheet stylesheet)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _stylesheetRepository.ValidateStylesheet(stylesheet);
}
}
public void CreateStyleSheetFolder(string folderPath)
{
using (var scope = ScopeProvider.CreateScope())
{
_stylesheetRepository.AddFolder(folderPath);
scope.Complete();
}
}
public void DeleteStyleSheetFolder(string folderPath)
{
using (var scope = ScopeProvider.CreateScope())
{
_stylesheetRepository.DeleteFolder(folderPath);
scope.Complete();
}
}
public Stream GetStylesheetFileContentStream(string filepath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _stylesheetRepository.GetFileContentStream(filepath);
}
}
public void SetStylesheetFileContent(string filepath, Stream content)
{
using (var scope = ScopeProvider.CreateScope())
{
_stylesheetRepository.SetFileContent(filepath, content);
scope.Complete();
}
}
public long GetStylesheetFileSize(string filepath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _stylesheetRepository.GetFileSize(filepath);
}
}
#endregion
#region Scripts
/// <inheritdoc />
public IEnumerable<IScript> GetScripts(params string[] names)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _scriptRepository.GetMany(names);
}
}
/// <inheritdoc />
public IScript GetScriptByName(string name)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _scriptRepository.Get(name);
}
}
/// <inheritdoc />
public void SaveScript(IScript script, int userId = Cms.Core.Constants.Security.SuperUserId)
{
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IScript>(script);
if (scope.Events.DispatchCancelable(SavingScript, this, saveEventArgs))
{
scope.Complete();
return;
}
_scriptRepository.Save(script);
saveEventArgs.CanCancel = false;
scope.Events.Dispatch(SavedScript, this, saveEventArgs);
Audit(AuditType.Save, userId, -1, "Script");
scope.Complete();
}
}
/// <inheritdoc />
public void DeleteScript(string path, int userId = Cms.Core.Constants.Security.SuperUserId)
{
using (var scope = ScopeProvider.CreateScope())
{
var script = _scriptRepository.Get(path);
if (script == null)
{
scope.Complete();
return;
}
var deleteEventArgs = new DeleteEventArgs<IScript>(script);
if (scope.Events.DispatchCancelable(DeletingScript, this, deleteEventArgs))
{
scope.Complete();
return;
}
_scriptRepository.Delete(script);
deleteEventArgs.CanCancel = false;
scope.Events.Dispatch(DeletedScript, this, deleteEventArgs);
Audit(AuditType.Delete, userId, -1, "Script");
scope.Complete();
}
}
/// <inheritdoc />
public bool ValidateScript(IScript script)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _scriptRepository.ValidateScript(script);
}
}
public void CreateScriptFolder(string folderPath)
{
using (var scope = ScopeProvider.CreateScope())
{
_scriptRepository.AddFolder(folderPath);
scope.Complete();
}
}
public void DeleteScriptFolder(string folderPath)
{
using (var scope = ScopeProvider.CreateScope())
{
_scriptRepository.DeleteFolder(folderPath);
scope.Complete();
}
}
public Stream GetScriptFileContentStream(string filepath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _scriptRepository.GetFileContentStream(filepath);
}
}
public void SetScriptFileContent(string filepath, Stream content)
{
using (var scope = ScopeProvider.CreateScope())
{
_scriptRepository.SetFileContent(filepath, content);
scope.Complete();
}
}
public long GetScriptFileSize(string filepath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _scriptRepository.GetFileSize(filepath);
}
}
#endregion
#region Templates
/// <summary>
/// Creates a template for a content type
/// </summary>
/// <param name="contentTypeAlias"></param>
/// <param name="contentTypeName"></param>
/// <param name="userId"></param>
/// <returns>
/// The template created
/// </returns>
public Attempt<OperationResult<OperationResultType, ITemplate>> CreateTemplateForContentType(string contentTypeAlias, string contentTypeName, int userId = Cms.Core.Constants.Security.SuperUserId)
{
var template = new Template(_shortStringHelper, contentTypeName,
//NOTE: We are NOT passing in the content type alias here, we want to use it's name since we don't
// want to save template file names as camelCase, the Template ctor will clean the alias as
// `alias.ToCleanString(CleanStringType.UnderscoreAlias)` which has been the default.
// This fixes: http://issues.umbraco.org/issue/U4-7953
contentTypeName);
var evtMsgs = EventMessagesFactory.Get();
// TODO: This isn't pretty because we we're required to maintain backwards compatibility so we could not change
// the event args here. The other option is to create a different event with different event
// args specifically for this method... which also isn't pretty. So fix this in v8!
var additionalData = new Dictionary<string, object>
{
{ "CreateTemplateForContentType", true },
{ "ContentTypeAlias", contentTypeAlias },
};
if (contentTypeAlias != null && contentTypeAlias.Length > 255)
{
throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
}
// check that the template hasn't been created on disk before creating the content type
// if it exists, set the new template content to the existing file content
string content = GetViewContent(contentTypeAlias);
if (content != null)
{
template.Content = content;
}
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<ITemplate>(template, true, evtMsgs, additionalData);
if (scope.Events.DispatchCancelable(SavingTemplate, this, saveEventArgs))
{
scope.Complete();
return OperationResult.Attempt.Fail<OperationResultType, ITemplate>(OperationResultType.FailedCancelledByEvent, evtMsgs, template);
}
_templateRepository.Save(template);
saveEventArgs.CanCancel = false;
scope.Events.Dispatch(SavedTemplate, this, saveEventArgs);
Audit(AuditType.Save, userId, template.Id, ObjectTypes.GetName(UmbracoObjectTypes.Template));
scope.Complete();
}
return OperationResult.Attempt.Succeed<OperationResultType, ITemplate>(OperationResultType.Success, evtMsgs, template);
}
/// <summary>
/// Create a new template, setting the content if a view exists in the filesystem
/// </summary>
/// <param name="name"></param>
/// <param name="alias"></param>
/// <param name="content"></param>
/// <param name="masterTemplate"></param>
/// <param name="userId"></param>
/// <returns></returns>
public ITemplate CreateTemplateWithIdentity(string name, string alias, string content, ITemplate masterTemplate = null, int userId = Cms.Core.Constants.Security.SuperUserId)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Name cannot be empty or contain only white-space characters", nameof(name));
}
if (name.Length > 255)
{
throw new ArgumentOutOfRangeException(nameof(name), "Name cannot be more than 255 characters in length.");
}
// file might already be on disk, if so grab the content to avoid overwriting
var template = new Template(_shortStringHelper, name, alias)
{
Content = GetViewContent(alias) ?? content
};
if (masterTemplate != null)
{
template.SetMasterTemplate(masterTemplate);
}
SaveTemplate(template, userId);
return template;
}
/// <summary>
/// Gets a list of all <see cref="ITemplate"/> objects
/// </summary>
/// <returns>An enumerable list of <see cref="ITemplate"/> objects</returns>
public IEnumerable<ITemplate> GetTemplates(params string[] aliases)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.GetAll(aliases).OrderBy(x => x.Name);
}
}
/// <summary>
/// Gets a list of all <see cref="ITemplate"/> objects
/// </summary>
/// <returns>An enumerable list of <see cref="ITemplate"/> objects</returns>
public IEnumerable<ITemplate> GetTemplates(int masterTemplateId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.GetChildren(masterTemplateId).OrderBy(x => x.Name);
}
}
/// <summary>
/// Gets a <see cref="ITemplate"/> object by its alias.
/// </summary>
/// <param name="alias">The alias of the template.</param>
/// <returns>The <see cref="ITemplate"/> object matching the alias, or null.</returns>
public ITemplate GetTemplate(string alias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.Get(alias);
}
}
/// <summary>
/// Gets a <see cref="ITemplate"/> object by its identifier.
/// </summary>
/// <param name="id">The identifier of the template.</param>
/// <returns>The <see cref="ITemplate"/> object matching the identifier, or null.</returns>
public ITemplate GetTemplate(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.Get(id);
}
}
/// <summary>
/// Gets a <see cref="ITemplate"/> object by its guid identifier.
/// </summary>
/// <param name="id">The guid identifier of the template.</param>
/// <returns>The <see cref="ITemplate"/> object matching the identifier, or null.</returns>
public ITemplate GetTemplate(Guid id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<ITemplate>().Where(x => x.Key == id);
return _templateRepository.Get(query).SingleOrDefault();
}
}
public IEnumerable<ITemplate> GetTemplateDescendants(string alias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.GetDescendants(alias);
}
}
/// <summary>
/// Gets the template descendants
/// </summary>
/// <param name="masterTemplateId"></param>
/// <returns></returns>
public IEnumerable<ITemplate> GetTemplateDescendants(int masterTemplateId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.GetDescendants(masterTemplateId);
}
}
/// <summary>
/// Gets the template children
/// </summary>
/// <param name="alias"></param>
/// <returns></returns>
public IEnumerable<ITemplate> GetTemplateChildren(string alias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.GetChildren(alias);
}
}
/// <summary>
/// Gets the template children
/// </summary>
/// <param name="masterTemplateId"></param>
/// <returns></returns>
public IEnumerable<ITemplate> GetTemplateChildren(int masterTemplateId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.GetChildren(masterTemplateId);
}
}
/// <summary>
/// Saves a <see cref="Template"/>
/// </summary>
/// <param name="template"><see cref="Template"/> to save</param>
/// <param name="userId"></param>
public void SaveTemplate(ITemplate template, int userId = Cms.Core.Constants.Security.SuperUserId)
{
if (template == null)
{
throw new ArgumentNullException(nameof(template));
}
if (string.IsNullOrWhiteSpace(template.Name) || template.Name.Length > 255)
{
throw new InvalidOperationException("Name cannot be null, empty, contain only white-space characters or be more than 255 characters in length.");
}
using (var scope = ScopeProvider.CreateScope())
{
if (scope.Events.DispatchCancelable(SavingTemplate, this, new SaveEventArgs<ITemplate>(template)))
{
scope.Complete();
return;
}
_templateRepository.Save(template);
scope.Events.Dispatch(SavedTemplate, this, new SaveEventArgs<ITemplate>(template, false));
Audit(AuditType.Save, userId, template.Id, UmbracoObjectTypes.Template.GetName());
scope.Complete();
}
}
/// <summary>
/// Saves a collection of <see cref="Template"/> objects
/// </summary>
/// <param name="templates">List of <see cref="Template"/> to save</param>
/// <param name="userId">Optional id of the user</param>
public void SaveTemplate(IEnumerable<ITemplate> templates, int userId = Cms.Core.Constants.Security.SuperUserId)
{
var templatesA = templates.ToArray();
using (var scope = ScopeProvider.CreateScope())
{
if (scope.Events.DispatchCancelable(SavingTemplate, this, new SaveEventArgs<ITemplate>(templatesA)))
{
scope.Complete();
return;
}
foreach (var template in templatesA)
_templateRepository.Save(template);
scope.Events.Dispatch(SavedTemplate, this, new SaveEventArgs<ITemplate>(templatesA, false));
Audit(AuditType.Save, userId, -1, UmbracoObjectTypes.Template.GetName());
scope.Complete();
}
}
/// <summary>
/// Deletes a template by its alias
/// </summary>
/// <param name="alias">Alias of the <see cref="ITemplate"/> to delete</param>
/// <param name="userId"></param>
public void DeleteTemplate(string alias, int userId = Cms.Core.Constants.Security.SuperUserId)
{
using (var scope = ScopeProvider.CreateScope())
{
var template = _templateRepository.Get(alias);
if (template == null)
{
scope.Complete();
return;
}
var args = new DeleteEventArgs<ITemplate>(template);
if (scope.Events.DispatchCancelable(DeletingTemplate, this, args))
{
scope.Complete();
return;
}
_templateRepository.Delete(template);
args.CanCancel = false;
scope.Events.Dispatch(DeletedTemplate, this, args);
Audit(AuditType.Delete, userId, template.Id, ObjectTypes.GetName(UmbracoObjectTypes.Template));
scope.Complete();
}
}
/// <summary>
/// Validates a <see cref="ITemplate"/>
/// </summary>
/// <param name="template"><see cref="ITemplate"/> to validate</param>
/// <returns>True if Script is valid, otherwise false</returns>
public bool ValidateTemplate(ITemplate template)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.ValidateTemplate(template);
}
}
public Stream GetTemplateFileContentStream(string filepath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.GetFileContentStream(filepath);
}
}
public void SetTemplateFileContent(string filepath, Stream content)
{
using (var scope = ScopeProvider.CreateScope())
{
_templateRepository.SetFileContent(filepath, content);
scope.Complete();
}
}
public long GetTemplateFileSize(string filepath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _templateRepository.GetFileSize(filepath);
}
}
private string GetViewContent(string fileName)
{
if (fileName.IsNullOrWhiteSpace())
throw new ArgumentNullException(nameof(fileName));
if (!fileName.EndsWith(".cshtml"))
fileName = $"{fileName}.cshtml";
var fs = _templateRepository.GetFileContentStream(fileName);
if (fs == null) return null;
using (var view = new StreamReader(fs))
{
return view.ReadToEnd().Trim();
}
}
#endregion
#region Partial Views
public IEnumerable<string> GetPartialViewSnippetNames(params string[] filterNames)
{
var snippetPath = _hostingEnvironment.MapPathContentRoot($"{_globalSettings.UmbracoPath}/PartialViewMacros/Templates/");
var files = Directory.GetFiles(snippetPath, "*.cshtml")
.Select(Path.GetFileNameWithoutExtension)
.Except(filterNames, StringComparer.InvariantCultureIgnoreCase)
.ToArray();
//Ensure the ones that are called 'Empty' are at the top
var empty = files.Where(x => Path.GetFileName(x).InvariantStartsWith("Empty"))
.OrderBy(x => x.Length)
.ToArray();
return empty.Union(files.Except(empty));
}
public void DeletePartialViewFolder(string folderPath)
{
using (var scope = ScopeProvider.CreateScope())
{
_partialViewRepository.DeleteFolder(folderPath);
scope.Complete();
}
}
public void DeletePartialViewMacroFolder(string folderPath)
{
using (var scope = ScopeProvider.CreateScope())
{
_partialViewMacroRepository.DeleteFolder(folderPath);
scope.Complete();
}
}
public IPartialView GetPartialView(string path)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _partialViewRepository.Get(path);
}
}
public IPartialView GetPartialViewMacro(string path)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _partialViewMacroRepository.Get(path);
}
}
public IEnumerable<IPartialView> GetPartialViewMacros(params string[] names)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _partialViewMacroRepository.GetMany(names).OrderBy(x => x.Name);
}
}
public Attempt<IPartialView> CreatePartialView(IPartialView partialView, string snippetName = null, int userId = Cms.Core.Constants.Security.SuperUserId)
{
return CreatePartialViewMacro(partialView, PartialViewType.PartialView, snippetName, userId);
}
public Attempt<IPartialView> CreatePartialViewMacro(IPartialView partialView, string snippetName = null, int userId = Cms.Core.Constants.Security.SuperUserId)
{
return CreatePartialViewMacro(partialView, PartialViewType.PartialViewMacro, snippetName, userId);
}
private Attempt<IPartialView> CreatePartialViewMacro(IPartialView partialView, PartialViewType partialViewType, string snippetName = null, int userId = Cms.Core.Constants.Security.SuperUserId)
{
string partialViewHeader;
switch (partialViewType)
{
case PartialViewType.PartialView:
partialViewHeader = PartialViewHeader;
break;
case PartialViewType.PartialViewMacro:
partialViewHeader = PartialViewMacroHeader;
break;
default:
throw new ArgumentOutOfRangeException(nameof(partialViewType));
}
string partialViewContent = null;
if (snippetName.IsNullOrWhiteSpace() == false)
{
//create the file
var snippetPathAttempt = TryGetSnippetPath(snippetName);
if (snippetPathAttempt.Success == false)
{
throw new InvalidOperationException("Could not load snippet with name " + snippetName);
}
using (var snippetFile = new StreamReader(System.IO.File.OpenRead(snippetPathAttempt.Result)))
{
var snippetContent = snippetFile.ReadToEnd().Trim();
//strip the @inherits if it's there
snippetContent = StripPartialViewHeader(snippetContent);
//Update Model.Content. to be Model. when used as PartialView
if(partialViewType == PartialViewType.PartialView)
{
snippetContent = snippetContent.Replace("Model.Content.", "Model.");
}
partialViewContent = $"{partialViewHeader}{Environment.NewLine}{snippetContent}";
}
}
using (var scope = ScopeProvider.CreateScope())
{
var newEventArgs = new NewEventArgs<IPartialView>(partialView, true, partialView.Alias, -1);
if (scope.Events.DispatchCancelable(CreatingPartialView, this, newEventArgs))
{
scope.Complete();
return Attempt<IPartialView>.Fail();
}
var repository = GetPartialViewRepository(partialViewType);
if (partialViewContent != null) partialView.Content = partialViewContent;
repository.Save(partialView);
newEventArgs.CanCancel = false;
scope.Events.Dispatch(CreatedPartialView, this, newEventArgs);
Audit(AuditType.Save, userId, -1, partialViewType.ToString());
scope.Complete();
}
return Attempt<IPartialView>.Succeed(partialView);
}
public bool DeletePartialView(string path, int userId = Cms.Core.Constants.Security.SuperUserId)
{
return DeletePartialViewMacro(path, PartialViewType.PartialView, userId);
}
public bool DeletePartialViewMacro(string path, int userId = Cms.Core.Constants.Security.SuperUserId)
{
return DeletePartialViewMacro(path, PartialViewType.PartialViewMacro, userId);
}
private bool DeletePartialViewMacro(string path, PartialViewType partialViewType, int userId = Cms.Core.Constants.Security.SuperUserId)
{
using (var scope = ScopeProvider.CreateScope())
{
var repository = GetPartialViewRepository(partialViewType);
var partialView = repository.Get(path);
if (partialView == null)
{
scope.Complete();
return true;
}
var deleteEventArgs = new DeleteEventArgs<IPartialView>(partialView);
if (scope.Events.DispatchCancelable(DeletingPartialView, this, deleteEventArgs))
{
scope.Complete();
return false;
}
repository.Delete(partialView);
deleteEventArgs.CanCancel = false;
scope.Events.Dispatch(DeletedPartialView, this, deleteEventArgs);
Audit(AuditType.Delete, userId, -1, partialViewType.ToString());
scope.Complete();
}
return true;
}
public Attempt<IPartialView> SavePartialView(IPartialView partialView, int userId = Cms.Core.Constants.Security.SuperUserId)
{
return SavePartialView(partialView, PartialViewType.PartialView, userId);
}
public Attempt<IPartialView> SavePartialViewMacro(IPartialView partialView, int userId = Cms.Core.Constants.Security.SuperUserId)
{
return SavePartialView(partialView, PartialViewType.PartialViewMacro, userId);
}
private Attempt<IPartialView> SavePartialView(IPartialView partialView, PartialViewType partialViewType, int userId = Cms.Core.Constants.Security.SuperUserId)
{
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IPartialView>(partialView);
if (scope.Events.DispatchCancelable(SavingPartialView, this, saveEventArgs))
{
scope.Complete();
return Attempt<IPartialView>.Fail();
}
var repository = GetPartialViewRepository(partialViewType);
repository.Save(partialView);
saveEventArgs.CanCancel = false;
Audit(AuditType.Save, userId, -1, partialViewType.ToString());
scope.Events.Dispatch(SavedPartialView, this, saveEventArgs);
scope.Complete();
}
return Attempt.Succeed(partialView);
}
public bool ValidatePartialView(IPartialView partialView)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _partialViewRepository.ValidatePartialView(partialView);
}
}
public bool ValidatePartialViewMacro(IPartialView partialView)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _partialViewMacroRepository.ValidatePartialView(partialView);
}
}
internal string StripPartialViewHeader(string contents)
{
var headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline);
return headerMatch.Replace(contents, string.Empty);
}
internal Attempt<string> TryGetSnippetPath(string fileName)
{
if (fileName.EndsWith(".cshtml") == false)
{
fileName += ".cshtml";
}
var snippetPath = _hostingEnvironment.MapPathContentRoot($"{_globalSettings.UmbracoPath}/PartialViewMacros/Templates/{fileName}");
return System.IO.File.Exists(snippetPath)
? Attempt<string>.Succeed(snippetPath)
: Attempt<string>.Fail();
}
public Stream GetPartialViewMacroFileContentStream(string filepath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _partialViewMacroRepository.GetFileContentStream(filepath);
}
}
public void SetPartialViewMacroFileContent(string filepath, Stream content)
{
using (var scope = ScopeProvider.CreateScope())
{
_partialViewMacroRepository.SetFileContent(filepath, content);
scope.Complete();
}
}
public Stream GetPartialViewFileContentStream(string filepath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _partialViewRepository.GetFileContentStream(filepath);
}
}
public void SetPartialViewFileContent(string filepath, Stream content)
{
using (var scope = ScopeProvider.CreateScope())
{
_partialViewRepository.SetFileContent(filepath, content);
scope.Complete();
}
}
public void CreatePartialViewFolder(string folderPath)
{
using (var scope = ScopeProvider.CreateScope())
{
_partialViewRepository.AddFolder(folderPath);
scope.Complete();
}
}
public void CreatePartialViewMacroFolder(string folderPath)
{
using (var scope = ScopeProvider.CreateScope())
{
_partialViewMacroRepository.AddFolder(folderPath);
scope.Complete();
}
}
public long GetPartialViewMacroFileSize(string filepath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _partialViewMacroRepository.GetFileSize(filepath);
}
}
public long GetPartialViewFileSize(string filepath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _partialViewRepository.GetFileSize(filepath);
}
}
private IPartialViewRepository GetPartialViewRepository(PartialViewType partialViewType)
{
switch (partialViewType)
{
case PartialViewType.PartialView:
return _partialViewRepository;
case PartialViewType.PartialViewMacro:
return _partialViewMacroRepository;
default:
throw new ArgumentOutOfRangeException(nameof(partialViewType));
}
}
#endregion
#region Snippets
public string GetPartialViewSnippetContent(string snippetName)
{
return GetPartialViewMacroSnippetContent(snippetName, PartialViewType.PartialView);
}
public string GetPartialViewMacroSnippetContent(string snippetName)
{
return GetPartialViewMacroSnippetContent(snippetName, PartialViewType.PartialViewMacro);
}
private string GetPartialViewMacroSnippetContent(string snippetName, PartialViewType partialViewType)
{
if (snippetName.IsNullOrWhiteSpace())
throw new ArgumentNullException(nameof(snippetName));
string partialViewHeader;
switch (partialViewType)
{
case PartialViewType.PartialView:
partialViewHeader = PartialViewHeader;
break;
case PartialViewType.PartialViewMacro:
partialViewHeader = PartialViewMacroHeader;
break;
default:
throw new ArgumentOutOfRangeException(nameof(partialViewType));
}
// Try and get the snippet path
var snippetPathAttempt = TryGetSnippetPath(snippetName);
if (snippetPathAttempt.Success == false)
{
throw new InvalidOperationException("Could not load snippet with name " + snippetName);
}
using (var snippetFile = new StreamReader(System.IO.File.OpenRead(snippetPathAttempt.Result)))
{
var snippetContent = snippetFile.ReadToEnd().Trim();
//strip the @inherits if it's there
snippetContent = StripPartialViewHeader(snippetContent);
//Update Model.Content to be Model when used as PartialView
if (partialViewType == PartialViewType.PartialView)
{
snippetContent = snippetContent
.Replace("Model.Content.", "Model.")
.Replace("(Model.Content)", "(Model)");
}
var content = $"{partialViewHeader}{Environment.NewLine}{snippetContent}";
return content;
}
}
#endregion
private void Audit(AuditType type, int userId, int objectId, string entityType)
{
_auditRepository.Save(new AuditItem(objectId, type, userId, entityType));
}
// TODO: Method to change name and/or alias of view template
#region Event Handlers
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IFileService, DeleteEventArgs<ITemplate>> DeletingTemplate;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IFileService, DeleteEventArgs<ITemplate>> DeletedTemplate;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IFileService, DeleteEventArgs<IScript>> DeletingScript;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IFileService, DeleteEventArgs<IScript>> DeletedScript;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IFileService, DeleteEventArgs<IStylesheet>> DeletingStylesheet;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IFileService, DeleteEventArgs<IStylesheet>> DeletedStylesheet;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IFileService, SaveEventArgs<ITemplate>> SavingTemplate;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IFileService, SaveEventArgs<ITemplate>> SavedTemplate;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IFileService, SaveEventArgs<IScript>> SavingScript;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IFileService, SaveEventArgs<IScript>> SavedScript;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IFileService, SaveEventArgs<IStylesheet>> SavingStylesheet;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IFileService, SaveEventArgs<IStylesheet>> SavedStylesheet;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IFileService, SaveEventArgs<IPartialView>> SavingPartialView;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IFileService, SaveEventArgs<IPartialView>> SavedPartialView;
/// <summary>
/// Occurs before Create
/// </summary>
public static event TypedEventHandler<IFileService, NewEventArgs<IPartialView>> CreatingPartialView;
/// <summary>
/// Occurs after Create
/// </summary>
public static event TypedEventHandler<IFileService, NewEventArgs<IPartialView>> CreatedPartialView;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IFileService, DeleteEventArgs<IPartialView>> DeletingPartialView;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IFileService, DeleteEventArgs<IPartialView>> DeletedPartialView;
#endregion
}
}