using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Xml.XPath; using Umbraco.Core; using Umbraco.Core.Dictionary; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; using Umbraco.Web.Composing; using Umbraco.Web.Mvc; using Umbraco.Web.Security; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web { /// /// A helper class that provides many useful methods and functionality for using Umbraco in templates /// /// /// This object is a request based lifetime /// public class UmbracoHelper { private readonly IPublishedContentQuery _publishedContentQuery; private readonly ITagQuery _tagQuery; private readonly MembershipHelper _membershipHelper; private readonly IUmbracoComponentRenderer _componentRenderer; private readonly ICultureDictionaryFactory _cultureDictionaryFactory; private IPublishedContent _currentPage; private ICultureDictionary _cultureDictionary; #region Constructors /// /// Initializes a new instance of . /// /// The item assigned to the helper. /// /// /// /// /// /// Sets the current page to the context's published content request's content item. public UmbracoHelper(IPublishedContent currentPage, ITagQuery tagQuery, ICultureDictionaryFactory cultureDictionary, IUmbracoComponentRenderer componentRenderer, IPublishedContentQuery publishedContentQuery, MembershipHelper membershipHelper) { _tagQuery = tagQuery ?? throw new ArgumentNullException(nameof(tagQuery)); _cultureDictionaryFactory = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary)); _componentRenderer = componentRenderer ?? throw new ArgumentNullException(nameof(componentRenderer)); _membershipHelper = membershipHelper ?? throw new ArgumentNullException(nameof(membershipHelper)); _publishedContentQuery = publishedContentQuery ?? throw new ArgumentNullException(nameof(publishedContentQuery)); _currentPage = currentPage; } /// /// Initializes a new empty instance of . /// /// For tests - nothing is initialized. internal UmbracoHelper() { } #endregion // ensures that we can return the specified value T Ensure(T o) where T : class => o ?? throw new InvalidOperationException("This UmbracoHelper instance has not been initialized."); private IUmbracoComponentRenderer ComponentRenderer => Ensure(_componentRenderer); private ICultureDictionaryFactory CultureDictionaryFactory => Ensure(_cultureDictionaryFactory); /// /// Gets the tag context. /// public ITagQuery TagQuery => Ensure(_tagQuery); /// /// Gets the query context. /// public IPublishedContentQuery ContentQuery => Ensure(_publishedContentQuery); /// /// Gets the membership helper. /// public MembershipHelper MembershipHelper => Ensure(_membershipHelper); /// /// Gets (or sets) the current item assigned to the UmbracoHelper. /// /// /// /// Note that this is the assigned IPublishedContent item to the /// UmbracoHelper, this is not necessarily the Current IPublishedContent /// item being rendered that is assigned to the UmbracoContext. /// 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 via DI during a non front-end request. /// /// /// Thrown if the /// UmbracoHelper is constructed with an UmbracoContext and it is not a /// front-end request. public IPublishedContent AssignedContentItem { get { if (_currentPage != null) { return _currentPage; } throw new InvalidOperationException( $"Cannot return the {nameof(IPublishedContent)} because the {nameof(UmbracoHelper)} was not constructed with an {nameof(IPublishedContent)}." ); } set => _currentPage = value; } /// /// Renders the template for the specified pageId and an optional altTemplateId /// /// /// If not specified, will use the template assigned to the node /// public IHtmlString RenderTemplate(int contentId, int? altTemplateId = null) { return ComponentRenderer.RenderTemplate(contentId, altTemplateId); } #region RenderMacro /// /// Renders the macro with the specified alias. /// /// The alias. /// public IHtmlString RenderMacro(string alias) { return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, new { }); } /// /// Renders the macro with the specified alias, passing in the specified parameters. /// /// The alias. /// The parameters. /// public IHtmlString RenderMacro(string alias, object parameters) { return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters.ToDictionary()); } /// /// Renders the macro with the specified alias, passing in the specified parameters. /// /// The alias. /// The parameters. /// public IHtmlString RenderMacro(string alias, IDictionary parameters) { return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters); } #endregion #region Dictionary /// /// Returns the dictionary value for the key specified /// /// /// public string GetDictionaryValue(string key) { return CultureDictionary[key]; } /// /// Returns the dictionary value for the key specified, and if empty returns the specified default fall back value /// /// key of dictionary item /// fall back text if dictionary item is empty - Name altText to match Umbraco.Field /// public string GetDictionaryValue(string key, string altText) { var dictionaryValue = GetDictionaryValue(key); if (String.IsNullOrWhiteSpace(dictionaryValue)) { dictionaryValue = altText; } return dictionaryValue; } /// /// Returns the ICultureDictionary for access to dictionary items /// public ICultureDictionary CultureDictionary => _cultureDictionary ?? (_cultureDictionary = CultureDictionaryFactory.CreateDictionary()); #endregion #region Membership /// /// Check if the current user has access to a document /// /// The full path of the document object to check /// True if the current user has access or if the current document isn't protected public bool MemberHasAccess(string path) { return MembershipHelper.MemberHasAccess(path); } /// /// Whether or not the current member is logged in (based on the membership provider) /// /// True is the current user is logged in public bool MemberIsLoggedOn() { return MembershipHelper.IsLoggedIn(); } #endregion #region Member/Content/Media from Udi public IPublishedContent PublishedContent(Udi udi) { var guidUdi = udi as GuidUdi; if (guidUdi == null) return null; var umbracoType = Constants.UdiEntityType.ToUmbracoObjectType(udi.EntityType); switch (umbracoType) { case UmbracoObjectTypes.Document: return Content(guidUdi.Guid); case UmbracoObjectTypes.Media: return Media(guidUdi.Guid); case UmbracoObjectTypes.Member: return Member(guidUdi.Guid); } return null; } #endregion #region Members public IPublishedContent Member(Udi id) { var guidUdi = id as GuidUdi; return guidUdi == null ? null : Member(guidUdi.Guid); } public IPublishedContent Member(Guid id) { return MembershipHelper.GetById(id); } public IPublishedContent Member(object id) { if (ConvertIdObjectToInt(id, out var intId)) return Member(intId); if (ConvertIdObjectToGuid(id, out var guidId)) return Member(guidId); if (ConvertIdObjectToUdi(id, out var udiId)) return Member(udiId); return null; } public IPublishedContent Member(int id) { return MembershipHelper.GetById(id); } public IPublishedContent Member(string id) { var asInt = id.TryConvertTo(); return asInt ? MembershipHelper.GetById(asInt.Result) : MembershipHelper.GetByProviderKey(id); } public IEnumerable Members(IEnumerable ids) { return MembershipHelper.GetByIds(ids); } public IEnumerable Members(IEnumerable ids) { return ids.Select(Member).WhereNotNull(); } public IEnumerable Members(IEnumerable ids) { return MembershipHelper.GetByIds(ids); } public IEnumerable Members(IEnumerable ids) { return ids.Select(Member).WhereNotNull(); } public IEnumerable Members(IEnumerable ids) { return ids.Select(Member).WhereNotNull(); } public IEnumerable Members(params int[] ids) { return ids.Select(Member).WhereNotNull(); } public IEnumerable Members(params string[] ids) { return ids.Select(Member).WhereNotNull(); } public IEnumerable Members(params Guid[] ids) { return MembershipHelper.GetByIds(ids); } public IEnumerable Members(params Udi[] ids) { return ids.Select(Member).WhereNotNull(); } public IEnumerable Members(params object[] ids) { return ids.Select(Member).WhereNotNull(); } #endregion #region Content /// /// Gets a content item from the cache. /// /// The unique identifier, or the key, of the content item. /// The content, or null of the content item is not in the cache. public IPublishedContent Content(object id) { return ContentForObject(id); } private IPublishedContent ContentForObject(object id) { if (ConvertIdObjectToInt(id, out var intId)) return ContentQuery.Content(intId); if (ConvertIdObjectToGuid(id, out var guidId)) return ContentQuery.Content(guidId); if (ConvertIdObjectToUdi(id, out var udiId)) return ContentQuery.Content(udiId); return null; } /// /// Gets a content item from the cache. /// /// The unique identifier of the content item. /// The content, or null of the content item is not in the cache. public IPublishedContent Content(int id) { return ContentQuery.Content(id); } /// /// Gets a content item from the cache. /// /// The key of the content item. /// The content, or null of the content item is not in the cache. public IPublishedContent Content(Guid id) { return ContentQuery.Content(id); } /// /// Gets a content item from the cache. /// /// The unique identifier, or the key, of the content item. /// The content, or null of the content item is not in the cache. public IPublishedContent Content(string id) { return ContentForObject(id); } public IPublishedContent Content(Udi id) { return ContentQuery.Content(id); } public IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars) { return ContentQuery.ContentSingleAtXPath(xpath, vars); } /// /// Gets content items from the cache. /// /// The unique identifiers, or the keys, of the content items. /// The content items that were found in the cache. /// Does not support mixing identifiers and keys. public IEnumerable Content(params object[] ids) { return ContentForObjects(ids); } /// /// Gets the contents corresponding to the identifiers. /// /// The content identifiers. /// The existing contents corresponding to the identifiers. /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable Content(params Udi[] ids) { return ids.Select(id => ContentQuery.Content(id)).WhereNotNull(); } /// /// Gets the contents corresponding to the identifiers. /// /// The content identifiers. /// The existing contents corresponding to the identifiers. /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable Content(params GuidUdi[] ids) { return ids.Select(id => ContentQuery.Content(id)); } private IEnumerable ContentForObjects(IEnumerable ids) { var idsA = ids.ToArray(); if (ConvertIdsObjectToInts(idsA, out var intIds)) return ContentQuery.Content(intIds); if (ConvertIdsObjectToGuids(idsA, out var guidIds)) return ContentQuery.Content(guidIds); return Enumerable.Empty(); } /// /// Gets content items from the cache. /// /// The unique identifiers of the content items. /// The content items that were found in the cache. public IEnumerable Content(params int[] ids) { return ContentQuery.Content(ids); } /// /// Gets content items from the cache. /// /// The keys of the content items. /// The content items that were found in the cache. public IEnumerable Content(params Guid[] ids) { return ContentQuery.Content(ids); } /// /// Gets content items from the cache. /// /// The unique identifiers, or the keys, of the content items. /// The content items that were found in the cache. /// Does not support mixing identifiers and keys. public IEnumerable Content(params string[] ids) { return ContentForObjects(ids); } /// /// Gets the contents corresponding to the identifiers. /// /// The content identifiers. /// The existing contents corresponding to the identifiers. /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable Content(IEnumerable ids) { return ContentForObjects(ids); } /// /// Gets the contents corresponding to the identifiers. /// /// The content identifiers. /// The existing contents corresponding to the identifiers. /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable Content(IEnumerable ids) { return ids.Select(id => ContentQuery.Content(id)).WhereNotNull(); } /// /// Gets the contents corresponding to the identifiers. /// /// The content identifiers. /// The existing contents corresponding to the identifiers. /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable Content(IEnumerable ids) { return ids.Select(id => ContentQuery.Content(id)); } /// /// Gets the contents corresponding to the identifiers. /// /// The content identifiers. /// The existing contents corresponding to the identifiers. /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable Content(IEnumerable ids) { return ContentForObjects(ids); } /// /// Gets the contents corresponding to the identifiers. /// /// The content identifiers. /// The existing contents corresponding to the identifiers. /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable Content(IEnumerable ids) { return ContentQuery.Content(ids); } public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars) { return ContentQuery.ContentAtXPath(xpath, vars); } public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) { return ContentQuery.ContentAtXPath(xpath, vars); } public IEnumerable ContentAtRoot() { return ContentQuery.ContentAtRoot(); } internal static bool ConvertIdObjectToInt(object id, out int intId) { switch (id) { case string s: return int.TryParse(s, out intId); case int i: intId = i; return true; default: intId = default; return false; } } internal static bool ConvertIdObjectToGuid(object id, out Guid guidId) { switch (id) { case string s: return Guid.TryParse(s, out guidId); case Guid g: guidId = g; return true; default: guidId = default; return false; } } private static bool ConvertIdsObjectToInts(IEnumerable ids, out IEnumerable intIds) { var list = new List(); intIds = null; foreach (var id in ids) { if (ConvertIdObjectToInt(id, out var intId)) list.Add(intId); else return false; // if one of them is not an int, fail } intIds = list; return true; } private static bool ConvertIdsObjectToGuids(IEnumerable ids, out IEnumerable guidIds) { var list = new List(); guidIds = null; foreach (var id in ids) { Guid guidId; if (ConvertIdObjectToGuid(id, out guidId)) list.Add(guidId); else return false; // if one of them is not a guid, fail } guidIds = list; return true; } /// Had to change to internal for testing. internal static bool ConvertIdObjectToUdi(object id, out Udi guidId) { switch (id) { case string s: return Udi.TryParse(s, out guidId); case Udi u: guidId = u; return true; default: guidId = default; return false; } } #endregion #region Media public IPublishedContent Media(Udi id) { var guidUdi = id as GuidUdi; return guidUdi == null ? null : Media(guidUdi.Guid); } public IPublishedContent Media(Guid id) { return ContentQuery.Media(id); } /// /// Overloaded method accepting an 'object' type /// /// /// /// /// We accept an object type because GetPropertyValue now returns an 'object', we still want to allow people to pass /// this result in to this method. /// This method will throw an exception if the value is not of type int or string. /// public IPublishedContent Media(object id) { return MediaForObject(id); } private IPublishedContent MediaForObject(object id) { if (ConvertIdObjectToInt(id, out var intId)) return ContentQuery.Media(intId); if (ConvertIdObjectToGuid(id, out var guidId)) return ContentQuery.Media(guidId); if (ConvertIdObjectToUdi(id, out var udiId)) return ContentQuery.Media(udiId); return null; } public IPublishedContent Media(int id) { return ContentQuery.Media(id); } public IPublishedContent Media(string id) { return MediaForObject(id); } /// /// Gets the medias corresponding to the identifiers. /// /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable Media(params object[] ids) { return MediaForObjects(ids); } private IEnumerable MediaForObjects(IEnumerable ids) { var idsA = ids.ToArray(); if (ConvertIdsObjectToInts(idsA, out var intIds)) return ContentQuery.Media(intIds); if (ConvertIdsObjectToGuids(idsA, out var guidIds)) return ContentQuery.Media(guidIds); return Enumerable.Empty(); } /// /// Gets the medias corresponding to the identifiers. /// /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable Media(params int[] ids) { return ContentQuery.Media(ids); } /// /// Gets the medias corresponding to the identifiers. /// /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable Media(params string[] ids) { return MediaForObjects(ids); } /// /// Gets the medias corresponding to the identifiers. /// /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable Media(params Udi[] ids) { return ids.Select(id => ContentQuery.Media(id)).WhereNotNull(); } /// /// Gets the medias corresponding to the identifiers. /// /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable Media(params GuidUdi[] ids) { return ids.Select(id => ContentQuery.Media(id)); } /// /// Gets the medias corresponding to the identifiers. /// /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable Media(IEnumerable ids) { return MediaForObjects(ids); } /// /// Gets the medias corresponding to the identifiers. /// /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable Media(IEnumerable ids) { return ContentQuery.Media(ids); } /// /// Gets the medias corresponding to the identifiers. /// /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable Media(IEnumerable ids) { return ids.Select(id => ContentQuery.Media(id)).WhereNotNull(); } /// /// Gets the medias corresponding to the identifiers. /// /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable Media(IEnumerable ids) { return ids.Select(id => ContentQuery.Media(id)); } /// /// Gets the medias corresponding to the identifiers. /// /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable Media(IEnumerable ids) { return MediaForObjects(ids); } public IEnumerable MediaAtRoot() { return ContentQuery.MediaAtRoot(); } #endregion internal static bool DecryptAndValidateEncryptedRouteString(string ufprt, out IDictionary parts) { string decryptedString; try { decryptedString = ufprt.DecryptWithMachineKey(); } catch (FormatException) { Current.Logger.Warn(typeof(UmbracoHelper), "A value was detected in the ufprt parameter but Umbraco could not decrypt the string"); parts = null; return false; } var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); parts = new Dictionary(); foreach (var key in parsedQueryString.AllKeys) { parts[key] = parsedQueryString[key]; } //validate all required keys exist //the controller if (parts.All(x => x.Key != RenderRouteHandler.ReservedAdditionalKeys.Controller)) return false; //the action if (parts.All(x => x.Key != RenderRouteHandler.ReservedAdditionalKeys.Action)) return false; //the area if (parts.All(x => x.Key != RenderRouteHandler.ReservedAdditionalKeys.Area)) return false; return true; } } }