<commit_before>﻿using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Xml.Linq;
using System.Xml.XPath;
using HtmlAgilityPack;
using Umbraco.Core;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Dynamics;
using Umbraco.Core.Models;
using Umbraco.Core.Xml;
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Templates;
using umbraco;
using System.Collections.Generic;
using umbraco.cms.businesslogic.member;
using umbraco.cms.businesslogic.web;
using umbraco.presentation.templateControls;

namespace Umbraco.Web
{

	/// <summary>
	/// A helper class that provides many useful methods and functionality for using Umbraco in templates
	/// </summary>
	public class UmbracoHelper
	{
		private readonly UmbracoContext _umbracoContext;
		private readonly IPublishedContent _currentPage;

		/// <summary>
		/// Custom constructor setting the current page to the parameter passed in
		/// </summary>
		/// <param name="umbracoContext"></param>
		/// <param name="content"></param>
		public UmbracoHelper(UmbracoContext umbracoContext, IPublishedContent content)
			: this(umbracoContext)
		{			
			if (content == null) throw new ArgumentNullException("content");
			_currentPage = content;
		}

		/// <summary>
		/// Standard constructor setting the current page to the page that has been routed to
		/// </summary>
		/// <param name="umbracoContext"></param>
		public UmbracoHelper(UmbracoContext umbracoContext)
		{
			if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
			if (umbracoContext.RoutingContext == null) throw new NullReferenceException("The RoutingContext on the UmbracoContext cannot be null");
			_umbracoContext = umbracoContext;
			if (_umbracoContext.IsFrontEndUmbracoRequest)
			{
				_currentPage = _umbracoContext.PublishedContentRequest.PublishedContent;
			}
		}

        /// <summary>
        /// Returns the current IPublishedContent item assigned to the UmbracoHelper
        /// </summary>
        /// <remarks>
        /// Note that this is the assigned IPublishedContent item to the UmbracoHelper, this is not necessarily the Current IPublishedContent item
        /// being rendered. This IPublishedContent object is contextual to the current UmbracoHelper instance.
        /// 
        /// In some cases accessing this property will throw an exception if there is not IPublishedContent assigned to the Helper
        /// this will only ever happen if the Helper is constructed with an UmbracoContext and it is not a front-end request
        /// </remarks>
        /// <exception cref="InvalidOperationException">Thrown if the UmbracoHelper is constructed with an UmbracoContext and it is not a front-end request</exception>
	    public IPublishedContent AssignedContentItem
	    {
	        get
	        {
	            if (_currentPage == null)
                    throw new InvalidOperationException("Cannot return the " + typeof(IPublishedContent).Name + " because the " + typeof(UmbracoHelper).Name + " was constructed with an " + typeof(UmbracoContext).Name + " and the current request is not a front-end request.");
                
                return _currentPage;
	        }
	    }

	    /// <summary>
		/// Renders the template for the specified pageId and an optional altTemplateId
		/// </summary>
		/// <param name="pageId"></param>
		/// <param name="altTemplateId">If not specified, will use the template assigned to the node</param>
		/// <returns></returns>
		public IHtmlString RenderTemplate(int pageId, int? altTemplateId = null)
		{
			var templateRenderer = new TemplateRenderer(_umbracoContext, pageId, altTemplateId);
			using (var sw = new StringWriter())
			{
				try
				{
					templateRenderer.Render(sw);					
				}
				catch(Exception ex)
				{
					sw.Write("<!-- Error rendering template with id {0}: '{1}' -->", pageId, ex);
				}
				return new HtmlString(sw.ToString());	
			}			
		}

		#region RenderMacro

		/// <summary>
		/// Renders the macro with the specified alias.
		/// </summary>
		/// <param name="alias">The alias.</param>
		/// <returns></returns>
		public IHtmlString RenderMacro(string alias)
		{
			return RenderMacro(alias, new { });
		}

		/// <summary>
		/// Renders the macro with the specified alias, passing in the specified parameters.
		/// </summary>
		/// <param name="alias">The alias.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns></returns>
		public IHtmlString RenderMacro(string alias, object parameters)
		{
			return RenderMacro(alias, parameters.ToDictionary<object>());
		}

		/// <summary>
		/// Renders the macro with the specified alias, passing in the specified parameters.
		/// </summary>
		/// <param name="alias">The alias.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns></returns>
		public IHtmlString RenderMacro(string alias, IDictionary<string, object> parameters)
		{
			if (alias == null) throw new ArgumentNullException("alias");

			var m = macro.GetMacro(alias);
			if (_umbracoContext.PageId == null)
			{
				throw new InvalidOperationException("Cannot render a macro when UmbracoContext.PageId is null.");
			}
			if (_umbracoContext.PublishedContentRequest == null)
			{
				throw new InvalidOperationException("Cannot render a macro when there is no current PublishedContentRequest.");
			}
			var macroProps = new Hashtable();
			foreach (var i in parameters)
			{
				//TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method of macro.cs 
				// looks for a lower case match. WTF. the whole macro concept needs to be rewritten.
				macroProps.Add(i.Key.ToLower(), i.Value);
			}
			var macroControl = m.renderMacro(macroProps,
				UmbracoContext.Current.PublishedContentRequest.UmbracoPage.Elements,
				_umbracoContext.PageId.Value);

			string html;
			if (macroControl is LiteralControl)
			{
				// no need to execute, we already have text
				html = (macroControl as LiteralControl).Text;
			}
			else
			{
				var containerPage = new FormlessPage();
				containerPage.Controls.Add(macroControl);

				using (var output = new StringWriter())
				{
					// .Execute() does a PushTraceContext/PopTraceContext and writes trace output straight into 'output'
					// and I do not see how we could wire the trace context to the current context... so it creates dirty
					// trace output right in the middle of the page.
					//
					// The only thing we can do is fully disable trace output while .Execute() runs and restore afterwards
					// which means trace output is lost if the macro is a control (.ascx or user control) that is invoked
					// from within Razor -- which makes sense anyway because the control can _not_ run correctly from
					// within Razor since it will never be inserted into the page pipeline (which may even not exist at all
					// if we're running MVC).
					//
					var traceIsEnabled = containerPage.Trace.IsEnabled;
					containerPage.Trace.IsEnabled = false;
					_umbracoContext.HttpContext.Server.Execute(containerPage, output, false);
					containerPage.Trace.IsEnabled = traceIsEnabled;

					//Now, we need to ensure that local links are parsed
					html = TemplateUtilities.ParseInternalLinks(output.ToString());
				}
			}<commit_msg>Fixes: U4-1599 [MVC] ChangeContentType doesn't work in macros rendered from a View -> We need to manually reset the ContentType after we execute a webforms based macro because Server.Execute resets the ContentType back to the default.<commit_after>﻿using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Xml.Linq;
using System.Xml.XPath;
using HtmlAgilityPack;
using Umbraco.Core;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Dynamics;
using Umbraco.Core.Models;
using Umbraco.Core.Xml;
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Templates;
using umbraco;
using System.Collections.Generic;
using umbraco.cms.businesslogic.member;
using umbraco.cms.businesslogic.web;
using umbraco.presentation.templateControls;

namespace Umbraco.Web
{

	/// <summary>
	/// A helper class that provides many useful methods and functionality for using Umbraco in templates
	/// </summary>
	public class UmbracoHelper
	{
		private readonly UmbracoContext _umbracoContext;
		private readonly IPublishedContent _currentPage;

		/// <summary>
		/// Custom constructor setting the current page to the parameter passed in
		/// </summary>
		/// <param name="umbracoContext"></param>
		/// <param name="content"></param>
		public UmbracoHelper(UmbracoContext umbracoContext, IPublishedContent content)
			: this(umbracoContext)
		{			
			if (content == null) throw new ArgumentNullException("content");
			_currentPage = content;
		}

		/// <summary>
		/// Standard constructor setting the current page to the page that has been routed to
		/// </summary>
		/// <param name="umbracoContext"></param>
		public UmbracoHelper(UmbracoContext umbracoContext)
		{
			if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
			if (umbracoContext.RoutingContext == null) throw new NullReferenceException("The RoutingContext on the UmbracoContext cannot be null");
			_umbracoContext = umbracoContext;
			if (_umbracoContext.IsFrontEndUmbracoRequest)
			{
				_currentPage = _umbracoContext.PublishedContentRequest.PublishedContent;
			}
		}

        /// <summary>
        /// Returns the current IPublishedContent item assigned to the UmbracoHelper
        /// </summary>
        /// <remarks>
        /// Note that this is the assigned IPublishedContent item to the UmbracoHelper, this is not necessarily the Current IPublishedContent item
        /// being rendered. This IPublishedContent object is contextual to the current UmbracoHelper instance.
        /// 
        /// In some cases accessing this property will throw an exception if there is not IPublishedContent assigned to the Helper
        /// this will only ever happen if the Helper is constructed with an UmbracoContext and it is not a front-end request
        /// </remarks>
        /// <exception cref="InvalidOperationException">Thrown if the UmbracoHelper is constructed with an UmbracoContext and it is not a front-end request</exception>
	    public IPublishedContent AssignedContentItem
	    {
	        get
	        {
	            if (_currentPage == null)
                    throw new InvalidOperationException("Cannot return the " + typeof(IPublishedContent).Name + " because the " + typeof(UmbracoHelper).Name + " was constructed with an " + typeof(UmbracoContext).Name + " and the current request is not a front-end request.");
                
                return _currentPage;
	        }
	    }

	    /// <summary>
		/// Renders the template for the specified pageId and an optional altTemplateId
		/// </summary>
		/// <param name="pageId"></param>
		/// <param name="altTemplateId">If not specified, will use the template assigned to the node</param>
		/// <returns></returns>
		public IHtmlString RenderTemplate(int pageId, int? altTemplateId = null)
		{
			var templateRenderer = new TemplateRenderer(_umbracoContext, pageId, altTemplateId);
			using (var sw = new StringWriter())
			{
				try
				{
					templateRenderer.Render(sw);					
				}
				catch(Exception ex)
				{
					sw.Write("<!-- Error rendering template with id {0}: '{1}' -->", pageId, ex);
				}
				return new HtmlString(sw.ToString());	
			}			
		}

		#region RenderMacro

		/// <summary>
		/// Renders the macro with the specified alias.
		/// </summary>
		/// <param name="alias">The alias.</param>
		/// <returns></returns>
		public IHtmlString RenderMacro(string alias)
		{
			return RenderMacro(alias, new { });
		}

		/// <summary>
		/// Renders the macro with the specified alias, passing in the specified parameters.
		/// </summary>
		/// <param name="alias">The alias.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns></returns>
		public IHtmlString RenderMacro(string alias, object parameters)
		{
			return RenderMacro(alias, parameters.ToDictionary<object>());
		}

		/// <summary>
		/// Renders the macro with the specified alias, passing in the specified parameters.
		/// </summary>
		/// <param name="alias">The alias.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns></returns>
		public IHtmlString RenderMacro(string alias, IDictionary<string, object> parameters)
		{
			if (alias == null) throw new ArgumentNullException("alias");

			var m = macro.GetMacro(alias);
			if (_umbracoContext.PageId == null)
			{
				throw new InvalidOperationException("Cannot render a macro when UmbracoContext.PageId is null.");
			}
			if (_umbracoContext.PublishedContentRequest == null)
			{
				throw new InvalidOperationException("Cannot render a macro when there is no current PublishedContentRequest.");
			}
			var macroProps = new Hashtable();
			foreach (var i in parameters)
			{
				//TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method of macro.cs 
				// looks for a lower case match. WTF. the whole macro concept needs to be rewritten.
				macroProps.Add(i.Key.ToLower(), i.Value);
			}
			var macroControl = m.renderMacro(macroProps,
				UmbracoContext.Current.PublishedContentRequest.UmbracoPage.Elements,
				_umbracoContext.PageId.Value);

			string html;
			if (macroControl is LiteralControl)
			{
				// no need to execute, we already have text
				html = (macroControl as LiteralControl).Text;
			}
			else
			{
				var containerPage = new FormlessPage();
				containerPage.Controls.Add(macroControl);

				using (var output = new StringWriter())
				{
					// .Execute() does a PushTraceContext/PopTraceContext and writes trace output straight into 'output'
					// and I do not see how we could wire the trace context to the current context... so it creates dirty
					// trace output right in the middle of the page.
					//
					// The only thing we can do is fully disable trace output while .Execute() runs and restore afterwards
					// which means trace output is lost if the macro is a control (.ascx or user control) that is invoked
					// from within Razor -- which makes sense anyway because the control can _not_ run correctly from
					// within Razor since it will never be inserted into the page pipeline (which may even not exist at all
					// if we're running MVC).
					//
                    // I'm sure there's more things that will get lost with this context changing but I guess we'll figure 
                    // those out as we go along. One thing we lose is the content type response output.
                    // http://issues.umbraco.org/issue/U4-1599 if it is setup during the macro execution. So 
                    // here we'll save the content type response and reset it after execute is called.

				    var contentType = _umbracoContext.HttpContext.Response.ContentType;
					var traceIsEnabled = containerPage.Trace.IsEnabled;
					containerPage.Trace.IsEnabled = false;
					_umbracoContext.HttpContext.Server.Execute(containerPage, output, false);
					containerPage.Trace.IsEnabled = traceIsEnabled;
                    //reset the content type
				    _umbracoContext.HttpContext.Response.ContentType = contentType;

					//Now, we need to ensure that local links are parsed
					html = TemplateUtilities.ParseInternalLinks(output.ToString());
				}
			}
{'fraction_non_alphanumeric': 0.09805302933265019, 'fraction_numerical': 0.0008966312283847829, 'mean_word_length': 3.1829627645325473, 'pattern_counts': {'":': 0, '<': 105, '<?xml version=': 0, '>': 106, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4311092', 'n_tokens_mistral': 4856, 'n_tokens_neox': 4194, 'n_words': 1463}