csharp/Adoxio/xRM-Portals-Community-Edition/Framework/Adxstudio.Xrm/Search/Index/CmsIndexHelper.cs

CmsIndexHelper.cs
/*
  Copyright (c) Microsoft Corporation. All rights reserved.
  Licensed under the MIT License. See License.txt in the project root for license information.
*/

// --------------------------------------------------------------------------------------------------------------------
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// --------------------------------------------------------------------------------------------------------------------

namespace Adxstudio.Xrm.Search.Index
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Adxstudio.Xrm.Cms;
	using Adxstudio.Xrm.Forums.Security;
	using Adxstudio.Xrm.Web.Providers;

	using Microsoft.Xrm.Client;
	using Microsoft.Xrm.Portal.Configuration;
	using Microsoft.Xrm.Portal.Web;
	using Microsoft.Xrm.Sdk;

	/// 
	/// Helps get the cms information for specific ensaties
	/// 
	public static clast CmsIndexHelper
	{
		/// 
		/// The allow access default value which is 'F1158253-71CB-4063-BBC5-B3CFE27CA3EB'.
		/// 
		private static readonly string AllowAccessDefaultValue = "F1158253-71CB-4063-BBC5-B3CFE27CA3EB";

		#region Forums

		/// 
		/// Gets the given forums web roles.
		/// 
		/// 
		/// The content map provider.
		/// 
		/// 
		/// The forumid.
		/// 
		/// 
		/// The  of web roles astociated to the forums.
		/// 
		public static IEnumerable GetForumsWebRoles(IContentMapProvider contentMapProvider, Guid forumid)
		{
			return contentMapProvider.Using(contentMap => SelectAllForumsWebRoles(forumid, contentMap));
		}

		/// 
		/// Select all the forums web roles.
		/// 
		/// 
		/// The ensaty id.
		/// 
		/// 
		/// The content map.
		/// 
		/// 
		/// The  of web roles astociated to the forum.
		/// 
		private static IEnumerable SelectAllForumsWebRoles(Guid ensatyId, ContentMap contentMap)
		{
			EnsatyNode ensaty;

			// Get the Forums from the content map
			if (!contentMap.TryGetValue(new EnsatyReference("adx_communityforum", ensatyId), out ensaty))
			{
				return Enumerable.Empty();
			}

			var forum = ensaty as ForumNode;
			if (forum == null)
			{
				return Enumerable.Empty();
			}

			var changeRules =
				forum.ForumAccessPermissions.Where(fa => fa.Right == ForumAccessPermissionNode.RightOption.GrantChange)
					.SelectMany(fa => fa.WebRoles.Select(wr => wr.Name));

			var readRules =
				forum.ForumAccessPermissions.Where(fa => fa.Right == ForumAccessPermissionNode.RightOption.RestrictRead)
					.SelectMany(fa => fa.WebRoles.Select(wr => wr.Name)).ToList();

			bool anyInheritedReadRestrictRules = false;

			// If it has a parent page we will need to inspect to see if they have different read rules.
			if (forum.ParentPage != null)
			{
				var parentPageWebRoles = GetRulesForPage(forum.ParentPage).Distinct().ToList();
				anyInheritedReadRestrictRules =
				parentPageWebRoles.Any(
					rule =>
					{
						if (rule.Right == null)
						{
							return false;
						}
						return rule.Right.Value.ToEnum()
						   == ForumAccessPermissionProvider.RightOption.RestrictRead;
					});

				// If Both the webpage tree do not have read restrict rules then give access to all.  
				var parentPageWebRoleNames = anyInheritedReadRestrictRules || readRules.Any()
						? parentPageWebRoles.SelectMany(
							webPageAccessControlRuleNode => webPageAccessControlRuleNode.WebRoles,
								(webPageAccessControlRuleNode, webRole) => webRole.Name).Distinct()
						: new[] { AllowAccessDefaultValue };

				// If there are no read restrict rules then we just follow the parents roles and change roles
				if (!readRules.Any() && !anyInheritedReadRestrictRules)
				{
					return changeRules.Concat(parentPageWebRoleNames).Distinct();
				}

				readRules = parentPageWebRoleNames.Union(readRules).ToList();
			}

			// Since it didn't have a parent page make sure there isn't a read restrict rule if no then give access to all. 
			return readRules.Any() || anyInheritedReadRestrictRules ? changeRules.Concat(readRules).Distinct() : new[] { AllowAccessDefaultValue };
		}

		/// 
		/// Checks if the forum url is defined.
		/// 
		/// 
		/// The content map provider.
		/// 
		/// 
		/// The ensaty id.
		/// 
		/// 
		/// If the forum has a URL defined or not.
		/// 
		public static bool IsForumUrlDefined(IContentMapProvider contentMapProvider, Guid ensatyId)
		{
			return contentMapProvider.Using(
				contentMap =>
					{
						EnsatyNode ensaty;
						if (!contentMap.TryGetValue(new EnsatyReference("adx_communityforum", ensatyId), out ensaty))
						{
							return false;
						}

						var forum = ensaty as ForumNode;

						if (forum == null)
						{
							return false;
						}
						
						var partialUrl = forum.PartialUrl;

						return IsWebPageUrlDefined(forum.ParentPage, partialUrl);
					});
		}

		#endregion

		#region Ideas

		/// 
		/// Gets idea forum web roles.
		/// 
		/// 
		/// The content map provider.
		/// 
		/// 
		/// The idea forum id.
		/// 
		/// 
		/// The  of web roles astociated to the Idea Forum.
		/// 
		public static IEnumerable GetIdeaForumWebRoles(IContentMapProvider contentMapProvider, Guid ideaForumId)
		{
			return contentMapProvider.Using(contentMap => SelectAllIdeaForumWebRoles(ideaForumId, contentMap));
		}

		/// 
		/// Select all idea forum web roles.
		/// 
		/// 
		/// The ensaty id.
		/// 
		/// 
		/// The content map.
		/// 
		/// 
		/// The  of web roles astociated to the Idea Forum.
		/// 
		private static IEnumerable SelectAllIdeaForumWebRoles(Guid ensatyId, ContentMap contentMap)
		{
			EnsatyNode ensaty;

			// Get the idea forum from the content map
			if (!contentMap.TryGetValue(new EnsatyReference("adx_ideaforum", ensatyId), out ensaty))
			{
				return Enumerable.Empty();
			}

			var idea = ensaty as IdeaForumNode;

			if (idea == null)
			{
				return Enumerable.Empty();
			}

			// In IdeaSecurityProvider.cs the idea is readable if the user is in the given roles or if there is no roles 
			// given then all can read. Thus if WebRolesRead has none in there we need to add all the roles. 
			if (idea.WebRolesRead.Any())
			{
				// But if there are some Read rules then add them as well as the write rules as if they have any of them then they
				// Should be allowed to read it.
				return idea.WebRolesRead.Select(w => w.Name).Concat(idea.WebRolesWrite.Select(w => w.Name));
			}

			// If it doesn't have any specific read roles then add all zero guid as a sign that everyone should have access.
			return new[] { AllowAccessDefaultValue };
		}

		#endregion

		#region Site Markers

		/// 
		/// Checks if the site maker has a url defined.
		/// 
		/// 
		/// The content map provider.
		/// 
		/// 
		/// The site maker name.
		/// 
		/// 
		/// The  if the site marker has a url defined.
		/// 
		public static bool IsSiteMakerUrlDefined(IContentMapProvider contentMapProvider, string siteMakerName)
		{

			if (string.IsNullOrEmpty(siteMakerName))
			{
				return false;
			}

			return contentMapProvider.Using(
				contentMap =>
					{
						IDictionary lookup;
						if (!contentMap.TryGetValue("adx_sitemarker", out lookup))
						{
							return false;
						}

						var siteMarker = lookup.Values.Cast().FirstOrDefault(x => x.Name == siteMakerName);
						
						if (siteMarker == null)
						{
							return false;
						}

						var webpage = siteMarker.WebPage;
						if (webpage == null)
						{
							return false;
						}

						return IsWebPageUrlDefined(webpage);
					});

		}

		#endregion

		#region WebPages

		/// 
		/// Gets the given web pages web roles.
		/// 
		/// 
		/// The content map provider.
		/// 
		/// 
		/// The webpage Id.
		/// 
		/// 
		/// The  of the astociated webroles name for the given Webpage.
		/// 
		public static IEnumerable GetWebPageWebRoles(IContentMapProvider contentMapProvider, Guid ensatyId)
		{
			return contentMapProvider.Using(contentMap => SelectAllWebRolesForWebpage(ensatyId, contentMap));
		}

		/// 
		/// Gets guids of descendant localized web pages for the given web page.
		/// 
		/// 
		/// The content map provider.
		/// 
		/// 
		/// The webpage Id.
		/// 
		/// 
		/// The lcid for the page if it's a content page.
		/// 
		/// 
		/// The  of the web page GUIDs for descendant web pages.
		/// 
		public static IEnumerable GetDescendantLocalizedWebpagesForWebpage(IContentMapProvider contentMapProvider, Guid ensatyId, int? lcid = null)
		{
			var predicates = new List() { IsContentPage };

			if (lcid.HasValue)
			{
				Predicate isLocalized = new Predicate((WebPageNode webPageNode) =>
				{
					return webPageNode.WebPageLanguage.PortalLanguage.Lcid == lcid.Value;
				});
				predicates.Add(isLocalized);
			}

			return contentMapProvider.Using(contentMap => SelectAllDescendantWebpagesWithPredicates(ensatyId, contentMap, predicates));
		}

		/// 
		/// Gets guids of root localized web pages for the given web page.
		/// 
		/// 
		/// The content map provider.
		/// 
		/// 
		/// The webpage Id.
		/// 
		/// 
		/// The  of the web page GUIDs for descendant web pages.
		/// 
		public static IEnumerable GetDescendantRootWebpagesForWebpage(IContentMapProvider contentMapProvider, Guid ensatyId)
		{
			return contentMapProvider.Using(contentMap => SelectAllDescendantWebpagesWithPredicates(ensatyId, contentMap, new List() { IsRootWebPage }));
		}

        /// 
        /// Selects all guids of web roles for the given ensaty.
        /// 
        /// 
        /// The webpage Id.
        /// 
        /// 
        /// The content map.
        /// 
        /// 
        /// The . of the WebRole name astociated to the webpage.
        /// 
        private static IEnumerable SelectAllWebRolesForWebpage(Guid ensatyId, ContentMap contentMap)
		{
			EnsatyNode ensaty;
			if (!contentMap.TryGetValue(new EnsatyReference("adx_webpage", ensatyId), out ensaty))
			{
				return Enumerable.Empty();
			}

			var webpage = ensaty as WebPageNode;
			if (webpage == null)
			{
				return Enumerable.Empty();
			}
			var webAccessRules = GetRulesForPage(webpage).ToList();

			// If the rule doesn't have a right astociated to it then allow access 
			var anyReadRestrictRules =
				webAccessRules.Any(
					rule =>
						{
							if (rule.Right == null)
							{
								return false;
							}
							return rule.Right.Value.ToEnum()
							   == ForumAccessPermissionProvider.RightOption.RestrictRead;
						});

			// If there is not read restrict rules specified then allow READ access. 
			return anyReadRestrictRules || (webpage.PublishingState.IsVisible == null || webpage.PublishingState.IsVisible.Value == false) 
					? webAccessRules.SelectMany(webPageAccessControlRuleNode => webPageAccessControlRuleNode.WebRoles,
						(webPageAccessControlRuleNode, webRole) => webRole.Name).Distinct() 
					: new[] { AllowAccessDefaultValue };
		}

		/// 
		/// Get the rules for the page.
		/// 
		/// 
		/// The web page.
		/// 
		/// 
		/// The  of .
		/// 
		private static IEnumerable GetRulesForPage(WebPageNode webPage)
		{
			if (webPage.Parent != null)
			{
				foreach (var rule in GetRulesForPage(webPage.Parent))
				{
					yield return rule;
				}
			}

			if (webPage.IsReference)
			{
				yield break;
			}

			foreach (var rule in webPage.WebPageAccessControlRules)
			{
				if (rule.PublishingStates.Any())
				{
					if (rule.PublishingStates.Any(publishingState => publishingState.Name == webPage.PublishingState.Name))
					{
						yield return rule;
					}
				}
				else
				{
					yield return rule;
				}
			}
		}

		/// 
		/// Checks if the web page url defined.
		/// 
		/// 
		/// The content map provider.
		/// 
		/// 
		/// The ensaty id.
		/// 
		/// 
		/// OPTIONAL: additional Partial Url to be added to the webpages url.
		/// 
		/// 
		/// If the webpage has a URL defined or not.
		/// 
		public static bool IsWebPageUrlDefined(IContentMapProvider contentMapProvider, Guid ensatyId, string additionalPartialUrl = null)
		{
			return contentMapProvider.Using(
				delegate(ContentMap contentMap)
					{
						EnsatyNode ensaty;
						if (!contentMap.TryGetValue(new EnsatyReference("adx_webpage", ensatyId), out ensaty))
						{
							ADXTrace.Instance.TraceWarning(TraceCategory.Monitoring, string.Format("Web Page url is not defined. EnsatyId: {0}", ensatyId));
							return false;
						}

						var webPage = ensaty as WebPageNode;

						return IsWebPageUrlDefined(webPage, additionalPartialUrl);
					});
		}

		/// 
		/// Is the web page url defined.
		/// 
		/// 
		/// The web page.
		/// 
		/// 
		/// Optional additional Partial Url to be added to the webpage url.
		/// 
		/// 
		/// If the webpage has a URL defined or not.
		/// 
		private static bool IsWebPageUrlDefined(WebPageNode webPage, string additionalPartialUrl = null)
		{
			if (webPage == null)
			{
				ADXTrace.Instance.TraceWarning(TraceCategory.Monitoring, "Web Page url is not defined. Web Page is null");
				return false;
			}
			try
			{
				var applicationPath = GetApplicationPath(webPage, additionalPartialUrl);
				var newPath = ApplicationPath.FromPartialPath(WebsitePathUtility.ToAbsolute(new Ensaty("adx_website", webPage.Website.Id), applicationPath.PartialPath));
				return newPath != null;
			}
			catch (Exception e)
			{
				// if the application path wasn't a real path then it will throw an exception so just return false
				ADXTrace.Instance.TraceWarning(TraceCategory.Monitoring, string.Format("IsWebPageUrlDefined caught exception, returning false - {0}", e));
				return false;
			}
		}

		/// 
		/// Gets the application path for the webpage.
		/// 
		/// 
		/// The web page.
		/// 
		/// 
		/// Optional additional Partial Url to be added to the webpage url.
		/// 
		/// 
		/// The .
		/// 
		private static ApplicationPath GetApplicationPath(WebPageNode webPage, string additionalPartialUrl = null)
		{
			var partialUrl = webPage.PartialUrl;
			if (!string.IsNullOrEmpty(additionalPartialUrl))
			{
				partialUrl = string.Format("{0}/{1}", partialUrl, additionalPartialUrl);
			}
			if (webPage.Parent != null)
			{
				return AdxEnsatyUrlProvider.JoinApplicationPath(GetApplicationPath(webPage.Parent).PartialPath, partialUrl);
			}
			return ApplicationPath.FromPartialPath(partialUrl);
		}

		/// 
		/// Selects all guids of descendant web pages for the given web page.
		/// 
		/// 
		/// The webpage Id.
		/// 
		/// 
		/// The content map.
		/// 
		/// 
		///  of predicates to determine whether a web page should be included in the results. Executed in order with short circuiting.
		/// 
		/// 
		/// The  of the web page GUIDs for descendant web pages.
		/// 
		private static IEnumerable SelectAllDescendantWebpagesWithPredicates(Guid ensatyId, ContentMap contentMap, IEnumerable predicates)
		{
			EnsatyNode ensaty;
			if (!contentMap.TryGetValue(new EnsatyReference("adx_webpage", ensatyId), out ensaty))
			{
				return Enumerable.Empty();
			}

			var rootWebpage = ensaty as WebPageNode;
			if (rootWebpage == null)
			{
				return Enumerable.Empty();
			}

			// if it's a content page, we want to start at it's root page so we can navigate down the web page hierarchy
			if (rootWebpage.IsRoot == false)
			{
				if (rootWebpage.RootWebPage == null)
				{
					// just return this web page, can't reach any others
					return new List() { rootWebpage.Id };
				}

				rootWebpage = rootWebpage.RootWebPage;
			}

			var unprocessedNodes = new Queue();
			var webPageGuids = new List();
			
			unprocessedNodes.Enqueue(rootWebpage);
			while (unprocessedNodes.Count > 0)
			{
				WebPageNode currWebPage = unprocessedNodes.Dequeue();

				foreach (var childWebPage in currWebPage.WebPages)
				{
					unprocessedNodes.Enqueue(childWebPage);
				}

				if (currWebPage.LanguageContentPages != null)
				{
					foreach (var contentPage in currWebPage.LanguageContentPages)
					{
						unprocessedNodes.Enqueue(contentPage);
					}
				}

				if (predicates.All(predicate => predicate(currWebPage)))
				{
					webPageGuids.Add(currWebPage.Id);
				}
			}

			return webPageGuids;
		}

		/// 
		/// Checks if a web page is localized.
		/// 
		/// The web page node.
		/// Whether the web page is localized.
        private static bool IsContentPage(WebPageNode webPageNode)
        {
			// if IsRoot == null, MLP is disabled and root pages are the same as content pages
			return webPageNode.IsRoot == false || webPageNode.IsRoot == null;
        }

		/// 
		/// Checks if the web page is a root page.
		/// 
		/// The web page node.
		/// Whether the web page is a root web page.
		private static bool IsRootWebPage(WebPageNode webPageNode)
		{
			// if IsRoot == null, MLP is disabled and root pages are the same as content pages
			return webPageNode.IsRoot == true || webPageNode.IsRoot == null;
		}

        #endregion

    }
}