2020-12-29 12:55:48 +01:00
using System ;
2018-06-29 19:52:40 +02:00
using System.Collections.Generic ;
2018-07-11 15:58:48 +10:00
using System.Globalization ;
2018-06-29 19:52:40 +02:00
using System.Linq ;
using System.Text ;
2018-07-11 15:58:48 +10:00
using System.Threading ;
2021-04-20 14:26:25 +02:00
using System.Threading.Tasks ;
2020-06-04 12:53:08 +02:00
using Microsoft.AspNetCore.Mvc ;
2021-04-20 14:26:25 +02:00
using Umbraco.Cms.Core ;
2021-02-18 11:06:02 +01:00
using Umbraco.Cms.Core.Mapping ;
using Umbraco.Cms.Core.Models ;
using Umbraco.Cms.Core.Models.ContentEditing ;
using Umbraco.Cms.Core.Models.PublishedContent ;
using Umbraco.Cms.Core.Routing ;
using Umbraco.Cms.Core.Services ;
using Umbraco.Cms.Core.Strings ;
using Umbraco.Cms.Core.Templates ;
using Umbraco.Cms.Core.Web ;
using Umbraco.Cms.Web.Common.Attributes ;
using Umbraco.Extensions ;
2018-06-29 19:52:40 +02:00
2021-02-18 11:06:02 +01:00
namespace Umbraco.Cms.Web.BackOffice.Controllers
2018-06-29 19:52:40 +02:00
{
/// <summary>
/// API controller to deal with Macro data
/// </summary>
2020-08-04 12:27:21 +10:00
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
2020-06-04 12:53:08 +02:00
public class MacroRenderingController : UmbracoAuthorizedJsonController
2018-06-29 19:52:40 +02:00
{
2019-01-21 10:55:48 +01:00
private readonly IMacroService _macroService ;
2020-06-04 12:53:08 +02:00
private readonly IUmbracoContextAccessor _umbracoContextAccessor ;
private readonly IShortStringHelper _shortStringHelper ;
2021-04-22 20:25:25 +10:00
private readonly ISiteDomainMapper _siteDomainHelper ;
2021-04-20 19:34:18 +02:00
private readonly IUmbracoMapper _umbracoMapper ;
2019-01-31 15:09:31 +11:00
private readonly IUmbracoComponentRenderer _componentRenderer ;
2018-06-29 19:52:40 +02:00
private readonly IVariationContextAccessor _variationContextAccessor ;
2019-01-31 15:09:31 +11:00
2020-02-10 19:59:47 +01:00
public MacroRenderingController (
2021-04-20 19:34:18 +02:00
IUmbracoMapper umbracoMapper ,
2020-02-10 19:59:47 +01:00
IUmbracoComponentRenderer componentRenderer ,
IVariationContextAccessor variationContextAccessor ,
2020-02-14 13:04:49 +01:00
IMacroService macroService ,
2020-06-04 12:53:08 +02:00
IUmbracoContextAccessor umbracoContextAccessor ,
IShortStringHelper shortStringHelper ,
2021-04-22 20:25:25 +10:00
ISiteDomainMapper siteDomainHelper )
2020-06-04 12:53:08 +02:00
2018-06-29 19:52:40 +02:00
{
2020-06-04 12:53:08 +02:00
_umbracoMapper = umbracoMapper ? ? throw new ArgumentNullException ( nameof ( umbracoMapper ) ) ;
_componentRenderer = componentRenderer ? ? throw new ArgumentNullException ( nameof ( componentRenderer ) ) ;
_variationContextAccessor = variationContextAccessor ? ? throw new ArgumentNullException ( nameof ( variationContextAccessor ) ) ;
_macroService = macroService ? ? throw new ArgumentNullException ( nameof ( macroService ) ) ;
_umbracoContextAccessor = umbracoContextAccessor ? ? throw new ArgumentNullException ( nameof ( umbracoContextAccessor ) ) ;
_shortStringHelper = shortStringHelper ? ? throw new ArgumentNullException ( nameof ( shortStringHelper ) ) ;
_siteDomainHelper = siteDomainHelper ? ? throw new ArgumentNullException ( nameof ( siteDomainHelper ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gets the macro parameters to be filled in for a particular macro
/// </summary>
/// <returns></returns>
/// <remarks>
2019-01-21 10:55:48 +01:00
/// Note that ALL logged in users have access to this method because editors will need to insert macros into rte (content/media/members) and it's used for
2018-06-29 19:52:40 +02:00
/// inserting into templates/views/etc... it doesn't expose any sensitive data.
/// </remarks>
2020-12-29 12:55:48 +01:00
public ActionResult < IEnumerable < MacroParameter > > GetMacroParameters ( int macroId )
2018-06-29 19:52:40 +02:00
{
2019-01-21 10:55:48 +01:00
var macro = _macroService . GetById ( macroId ) ;
2018-06-29 19:52:40 +02:00
if ( macro = = null )
{
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
2020-12-29 12:55:48 +01:00
return new ActionResult < IEnumerable < MacroParameter > > ( _umbracoMapper . Map < IEnumerable < MacroParameter > > ( macro ) . OrderBy ( x = > x . SortOrder ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
2019-01-26 10:52:19 -05:00
/// Gets a rendered macro as HTML for rendering in the rich text editor
2018-06-29 19:52:40 +02:00
/// </summary>
/// <param name="macroAlias"></param>
/// <param name="pageId"></param>
/// <param name="macroParams">
/// To send a dictionary as a GET parameter the query should be structured like:
///
/// ?macroAlias=Test&pageId=3634¯oParams[0].key=myKey¯oParams[0].value=myVal¯oParams[1].key=anotherKey¯oParams[1].value=anotherVal
///
/// </param>
/// <returns></returns>
[HttpGet]
2021-04-20 14:26:25 +02:00
public async Task < IActionResult > GetMacroResultAsHtmlForEditor ( string macroAlias , int pageId , [ FromQuery ] IDictionary < string , object > macroParams )
2018-06-29 19:52:40 +02:00
{
2021-04-20 14:26:25 +02:00
return await GetMacroResultAsHtml ( macroAlias , pageId , macroParams ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
2019-01-26 10:52:19 -05:00
/// Gets a rendered macro as HTML for rendering in the rich text editor.
/// Using HTTP POST instead of GET allows for more parameters to be passed as it's not dependent on URL-length limitations like GET.
2018-06-29 19:52:40 +02:00
/// The method using GET is kept to maintain backwards compatibility
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
2021-04-20 14:26:25 +02:00
public async Task < IActionResult > GetMacroResultAsHtmlForEditor ( MacroParameterModel model )
2018-06-29 19:52:40 +02:00
{
2021-04-20 14:26:25 +02:00
return await GetMacroResultAsHtml ( model . MacroAlias , model . PageId , model . MacroParams ) ;
2018-06-29 19:52:40 +02:00
}
public class MacroParameterModel
{
public string MacroAlias { get ; set ; }
public int PageId { get ; set ; }
public IDictionary < string , object > MacroParams { get ; set ; }
}
2021-04-20 14:26:25 +02:00
private async Task < IActionResult > GetMacroResultAsHtml ( string macroAlias , int pageId , IDictionary < string , object > macroParams )
2018-06-29 19:52:40 +02:00
{
2019-01-21 10:55:48 +01:00
var m = _macroService . GetByAlias ( macroAlias ) ;
2018-06-29 19:52:40 +02:00
if ( m = = null )
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
2020-06-04 12:53:08 +02:00
var umbracoContext = _umbracoContextAccessor . GetRequiredUmbracoContext ( ) ;
var publishedContent = umbracoContext . Content . GetById ( true , pageId ) ;
2019-02-04 13:48:53 +11:00
2018-06-29 19:52:40 +02:00
//if it isn't supposed to be rendered in the editor then return an empty string
2019-02-04 13:42:07 +11:00
//currently we cannot render a macro if the page doesn't yet exist
2019-06-25 12:36:03 +02:00
if ( pageId = = - 1 | | publishedContent = = null | | m . DontRender )
2018-06-29 19:52:40 +02:00
{
2019-01-26 10:52:19 -05:00
//need to create a specific content result formatted as HTML since this controller has been configured
2018-06-29 19:52:40 +02:00
//with only json formatters.
2020-06-04 12:53:08 +02:00
return Content ( string . Empty , "text/html" , Encoding . UTF8 ) ;
2018-06-29 19:52:40 +02:00
}
2018-07-11 15:58:48 +10:00
// When rendering the macro in the backoffice the default setting would be to use the Culture of the logged in user.
// Since a Macro might contain thing thats related to the culture of the "IPublishedContent" (ie Dictionary keys) we want
// to set the current culture to the culture related to the content item. This is hacky but it works.
2019-02-11 07:54:39 +01:00
2019-04-17 14:41:54 +02:00
// fixme
// in a 1:1 situation we do not handle the language being edited
// so the macro renders in the wrong language
2019-04-17 10:03:49 +02:00
2020-06-04 12:53:08 +02:00
var culture = DomainUtilities . GetCultureFromDomains ( publishedContent . Id , publishedContent . Path , null , umbracoContext , _siteDomainHelper ) ;
2019-04-17 10:03:49 +02:00
2018-07-11 15:58:48 +10:00
if ( culture ! = null )
2019-04-17 10:03:49 +02:00
Thread . CurrentThread . CurrentCulture = Thread . CurrentThread . CurrentUICulture = CultureInfo . GetCultureInfo ( culture ) ;
2019-04-17 14:41:54 +02:00
// must have an active variation context!
_variationContextAccessor . VariationContext = new VariationContext ( culture ) ;
2018-07-11 15:58:48 +10:00
2020-06-04 12:53:08 +02:00
using ( umbracoContext . ForcedPreview ( true ) )
2019-10-09 00:39:28 +01:00
{
//need to create a specific content result formatted as HTML since this controller has been configured
//with only json formatters.
2021-04-20 14:26:25 +02:00
return Content ( ( await _componentRenderer . RenderMacroForContent ( publishedContent , m . Alias , macroParams ) ) . ToString ( ) , "text/html" ,
2020-06-04 12:53:08 +02:00
Encoding . UTF8 ) ;
2019-10-09 00:39:28 +01:00
}
2018-06-29 19:52:40 +02:00
}
[HttpPost]
2020-11-23 08:11:01 +01:00
public IActionResult CreatePartialViewMacroWithFile ( CreatePartialViewMacroWithFileModel model )
2018-06-29 19:52:40 +02:00
{
if ( model = = null ) throw new ArgumentNullException ( "model" ) ;
if ( string . IsNullOrWhiteSpace ( model . Filename ) ) throw new ArgumentException ( "Filename cannot be null or whitespace" , "model.Filename" ) ;
if ( string . IsNullOrWhiteSpace ( model . VirtualPath ) ) throw new ArgumentException ( "VirtualPath cannot be null or whitespace" , "model.VirtualPath" ) ;
var macroName = model . Filename . TrimEnd ( ".cshtml" ) ;
2020-06-04 12:53:08 +02:00
var macro = new Macro ( _shortStringHelper )
2018-06-29 19:52:40 +02:00
{
2020-06-04 12:53:08 +02:00
Alias = macroName . ToSafeAlias ( _shortStringHelper ) ,
2018-06-29 19:52:40 +02:00
Name = macroName ,
2020-02-25 13:38:36 +01:00
MacroSource = model . VirtualPath . EnsureStartsWith ( "~" )
2018-06-29 19:52:40 +02:00
} ;
2019-01-21 10:55:48 +01:00
_macroService . Save ( macro ) ; // may throw
2020-11-23 08:11:01 +01:00
return Ok ( ) ;
2018-06-29 19:52:40 +02:00
}
public class CreatePartialViewMacroWithFileModel
{
public string Filename { get ; set ; }
public string VirtualPath { get ; set ; }
}
}
}