Skip to content

Instantly share code, notes, and snippets.

@AThraen
Created September 26, 2022 12:39
Visitor Group impersonation through Quick Navigation in Optimizely Content Cloud
public class Startup{
//...
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<IQuickNavigatorItemProvider, VisitorGroupQuickNavigationProvider>();
//...
}
}
using EPiServer;
using EPiServer.Core;
using EPiServer.Personalization.VisitorGroups;
using EPiServer.ServiceLocation;
using EPiServer.Shell.Profile.Internal;
using EPiServer.Web;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CodeArt.Optimizely.VisitorGroupManager
{
/// <summary>
/// Provides a quick navigation to impersonate the combination of visitor groups used by a page.
/// </summary>
public class VisitorGroupQuickNavigationProvider : IQuickNavigatorItemProvider
{
private readonly IContentRepository _contentRepo;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ICurrentUiCulture _currentUiCulture;
private readonly IVisitorGroupRepository _vgRepo;
public VisitorGroupQuickNavigationProvider(
IContentRepository contentRepo,
IHttpContextAccessor httpContextAccessor,
ICurrentUiCulture currentUiCulture,
IVisitorGroupRepository vgRepo)
{
this._contentRepo = contentRepo;
this._httpContextAccessor = httpContextAccessor;
this._currentUiCulture = currentUiCulture;
this._vgRepo = vgRepo;
}
public int SortOrder => 100;
internal Injected<UIPathResolver> UIUriResolver { get; set; }
private bool IsPageData(ContentReference currentContentLink) => this._contentRepo.Get<IContent>(currentContentLink) is PageData;
public IDictionary<string, QuickNavigatorMenuItem> GetMenuItems(ContentReference currentContent)
{
IContent c = _contentRepo.Get<IContent>(currentContent);
var referencedContent = _contentRepo.FetchReferencedContentRecursively(c).ToList();
var groups = referencedContent.SelectMany(rc => _vgRepo.ExtractVisitorGroups(rc)).Distinct().ToList();
QuickNavigatorMenu quickNavigatorMenu = new QuickNavigatorMenu(this._httpContextAccessor, this._currentUiCulture);
quickNavigatorMenu.CurrentContentLink = !(currentContent != (ContentReference)null) || !this.IsPageData(currentContent) ? (ContentReference)ContentReference.StartPage : currentContent;
var active = this._httpContextAccessor.HttpContext.Request.Query["visitorgroupsByID"].ToString().Split('|').Where(s => s!= string.Empty).Select(Guid.Parse).ToList();
string firstDivider = "\" style='border-top: 1px solid black;font-weight:bold' t=\"";
quickNavigatorMenu.Add("divider", new QuickNavigatorMenuItem("Preview as Visitor Groups", "#" + firstDivider, (string)null, "false", (string)null));
//List visitor groups on this page
foreach (var g in groups)
{
//Create link
var url = new UrlBuilder(_httpContextAccessor.HttpContext.Request.Path.ToString()+_httpContextAccessor.HttpContext.Request.QueryString.ToString());
if (active.Contains(g.Id))
{
var newActive=active.Except(new Guid[] { g.Id }).ToArray();
if(newActive.Any()) url.QueryCollection["visitorgroupsByID"] = string.Join("|", newActive);
else url.QueryCollection.Remove("visitorgroupsByID");
quickNavigatorMenu.Add(g.Id.ToString(), new QuickNavigatorMenuItem("&check; " + g.Name, url.ToString(), (string)null, "true", (string)null));
} else
{
var newActive = active.Union(new Guid[] { g.Id }).ToArray();
url.QueryCollection["visitorgroupsByID"]=string.Join("|", newActive);
quickNavigatorMenu.Add(g.Id.ToString(), new QuickNavigatorMenuItem("&#9744; " + g.Name, url.ToString(), (string)null, "true", (string)null));
}
}
return quickNavigatorMenu.Items;
}
}
}
using EPiServer;
using EPiServer.Core;
using EPiServer.Personalization.VisitorGroups;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CodeArt.Optimizely.VisitorGroupManager
{
public static class VisitorGroupsHelper
{
/// <summary>
/// Analyzes which visitor groups are used in a piece of content and returns a list of those groups.
/// </summary>
/// <param name="vgRepo">The VisitorGroupRepository to extend</param>
/// <param name="content">The content to analyze</param>
/// <returns></returns>
public static IEnumerable<VisitorGroup> ExtractVisitorGroups(this IVisitorGroupRepository vgRepo,IContent content)
{
foreach (var p in content.Property)
{
if (p.Value == null) continue;
if (p.PropertyValueType == typeof(ContentArea))
{
var ca = p.Value as ContentArea;
if (ca == null) continue;
foreach (var f in ca.Items.Where(l => l.AllowedRoles != null && l.AllowedRoles.Any()))
{
//Match! This page uses the visitor groups in l.AllowedRoles. Record.
foreach (var r in f.AllowedRoles)
{
yield return vgRepo.Load(Guid.Parse(r));
}
}
}
else if (p.PropertyValueType == typeof(XhtmlString))
{
var ca = p.Value as XhtmlString;
if (ca == null) continue;
foreach (var f in ca.Fragments.Where(fr => fr is EPiServer.Core.Html.StringParsing.PersonalizedContentFragment))
{
var j = f as EPiServer.Core.Html.StringParsing.PersonalizedContentFragment;
var roles = j.GetRoles();
foreach (var r in roles)
{
yield return vgRepo.Load(Guid.Parse(r));
}
}
}
}
}
/// <summary>
/// Fetches content used by a piece of content (in ContentAreas or XhtmlStrings), including the content itself.
/// </summary>
/// <param name="repo">The IContentRepository to extend on</param>
/// <param name="content">The original content</param>
/// <returns>Enumeration of IContent referenced</returns>
public static IEnumerable<IContent> FetchReferencedContentRecursively(this IContentRepository repo, IContent content)
{
yield return content;
foreach (var p in content.Property)
{
if (p.Value == null) continue;
if (p.PropertyValueType == typeof(ContentArea))
{
var ca = p.Value as ContentArea;
if (ca == null) continue;
foreach (var f in ca.Items)
{
foreach (var y in repo.FetchReferencedContentRecursively(repo.Get<IContent>(f.ContentLink)))
yield return y;
}
}
else if (p.PropertyValueType == typeof(XhtmlString))
{
var ca = p.Value as XhtmlString;
if (ca == null) continue;
foreach (var f in ca.Fragments.Where(fr => fr is EPiServer.Core.Html.StringParsing.ContentFragment))
{
var j = f as EPiServer.Core.Html.StringParsing.ContentFragment;
foreach (var y in repo.FetchReferencedContentRecursively(repo.Get<IContent>(j.ContentLink)))
yield return y;
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment