Skip to content

Commit

Permalink
Merge pull request #23 from prjseal/feature/add-edit-link
Browse files Browse the repository at this point in the history
Added edit link tag helper with readme description too
  • Loading branch information
Warren Buckley authored Nov 16, 2021
2 parents cccf09d + dcc7cb1 commit dee84a4
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 1 deletion.
15 changes: 15 additions & 0 deletions Our.Umbraco.TagHelpers/Composing/BackofficeUserComposer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
using Our.Umbraco.TagHelpers.Services;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;

namespace Our.Umbraco.TagHelpers.Composing
{
public class CustomComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Services.AddScoped<IBackofficeUserAccessor, BackofficeUserAccessor>();
}
}
}
153 changes: 153 additions & 0 deletions Our.Umbraco.TagHelpers/EditLinkTagHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Our.Umbraco.TagHelpers.Extensions;
using Our.Umbraco.TagHelpers.Services;
using System.Text;
using Umbraco.Cms.Core.Web;

namespace Our.Umbraco.TagHelpers
{
/// <summary>
/// If the user viewing the front end is logged in as an umbraco user
/// then an edit link will display on the front end of the site. This will
/// take you to the umbraco backoffice to edit the current page.
/// </summary>
[HtmlTargetElement("our-edit-link")]
public class EditLinkTagHelper : TagHelper
{
private readonly IBackofficeUserAccessor _backofficeUserAccessor;
private IUmbracoContextAccessor _umbracoContextAccessor;

public EditLinkTagHelper(IBackofficeUserAccessor backofficeUserAccessor, IUmbracoContextAccessor umbracoContextAccessor)
{
_backofficeUserAccessor = backofficeUserAccessor;
_umbracoContextAccessor = umbracoContextAccessor;
}

/// <summary>
/// The id of the current content item
/// </summary>
[HtmlAttributeName("content-id")]
public int ContentId { get; set; } = int.MinValue;

/// <summary>
/// Override the umbraco edit content url if yours is different
/// </summary>
[HtmlAttributeName("edit-url")]
public string EditUrl { get; set; } = "/umbraco#/content/content/edit/";

/// <summary>
/// A boolean to say whether or not you would like to use the default styling.
/// </summary>
[HtmlAttributeName("use-default-styles")]
public bool UseDefaultStyles { get; set; } = false;

public override void Process(TagHelperContext context, TagHelperOutput output)
{
// Turn <our-edit-link> into an <a> tag
output.TagName = "a";

// An outer wrapper div if we use inbuilt styling
var outerDiv = new TagBuilder("div");

// Check if the user is logged in to the backoffice
// and they have access to the content section
if (_backofficeUserAccessor.BackofficeUser.IsAllowedToSeeEditLink())
{
// Try & get Umbraco Current Node int ID (Only do this if ContentId has NOT been set)
if (_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext) && ContentId == int.MinValue)
{
ContentId = umbracoContext.PublishedRequest.PublishedContent.Id;
}

// Backoffice URL to content item
var editLinkUrl = $"{EditUrl}{ContentId}";

if (UseDefaultStyles)
{
// Wrap the <a> in a <div>
// Render the outer div with some inline styles
outerDiv.Attributes.Add("style", GetOuterElementStyles());
output.PreElement.AppendHtml(outerDiv.RenderStartTag());
}

// Set the link on the <a> tag
output.Attributes.SetAttribute("href", editLinkUrl);

if (UseDefaultStyles)
{
output.Attributes.SetAttribute("style", GetLinkStyles());
}

if (UseDefaultStyles)
{
//Add the closing outer div
output.PostElement.AppendHtml(outerDiv.RenderEndTag());
}

return;
}
else
{
output.SuppressOutput();
return;
}
}

/// <summary>
/// Helper method to get the link styles
/// </summary>
/// <param name="linkColour">The CSS colour of the link text</param>
/// <param name="linkBackgroundColour">The CSS colour of the link background</param>
/// <param name="linkPadding">The padding around the link</param>
/// <param name="fontSize">The font size of the link text</param>
/// <param name="borderRadius">The border radius of the link</param>
/// <returns></returns>
private static string GetLinkStyles(
string linkColour = "#ffffff",
string linkBackgroundColour = "#1b264f",
int linkPadding = 10,
int fontSize = 16,
int borderRadius = 6)
{
StringBuilder linkStyles = new StringBuilder();
linkStyles.Append($"color:{linkColour};");
linkStyles.Append($"background-color:{linkBackgroundColour};");
linkStyles.Append($"padding:{linkPadding}px;");
linkStyles.Append($"font-size:{fontSize}px;");
linkStyles.Append($"border-radius:{borderRadius}px;");
return linkStyles.ToString();
}

/// <summary>
/// Helper method to get the outer element styles
/// </summary>
/// <param name="outerPosition">The CSS position of the outer element</param>
/// <param name="margin">The margin around the outer element</param>
/// <param name="zindex">The z-index of the outer element</param>
/// <param name="linkPadding">The padding around the link</param>
/// <returns></returns>
private static string GetOuterElementStyles(
string outerPosition = "fixed",
int margin = 10,
int zindex = 10000,
int linkPadding = 10)
{
linkPadding /= 2;

var outerStyles = new StringBuilder();

outerStyles.Append("display:block;");
if (outerPosition == "fixed")
{
outerStyles.Append($"bottom:{margin + linkPadding}px;");
outerStyles.Append($"left:{margin}px;");
}

outerStyles.Append($"z-index:{zindex};");
outerStyles.Append($"position:{outerPosition};");

return outerStyles.ToString();
}
}
}
27 changes: 27 additions & 0 deletions Our.Umbraco.TagHelpers/Extensions/ClaimsIdentityExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Linq;
using System.Security.Claims;
using Umbraco.Cms.Core;

namespace Our.Umbraco.TagHelpers.Extensions
{
public static class ClaimsIdentityExtensions
{
public static bool IsAllowedToSeeEditLink(this ClaimsIdentity identity)
{
return IsLoggedIntoUmbraco(identity) && HasAccessToContentSection(identity);
}

public static bool IsLoggedIntoUmbraco(this ClaimsIdentity identity)
{
return identity?.AuthenticationType != null
&& identity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType;
}

public static bool HasAccessToContentSection(this ClaimsIdentity identity)
{
return identity?.Claims != null && identity.Claims.Any(x =>
x.Type == Constants.Security.AllowedApplicationsClaimType
&& x.Value == Constants.Conventions.PermissionCategories.ContentCategory);
}
}
}
2 changes: 1 addition & 1 deletion Our.Umbraco.TagHelpers/Our.Umbraco.TagHelpers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
</ItemGroup>

<ItemGroup>
<None Include="TagHelperLogo.png" Pack="true" PackagePath="\"/>
<None Include="TagHelperLogo.png" Pack="true" PackagePath="\" />
</ItemGroup>

</Project>
51 changes: 51 additions & 0 deletions Our.Umbraco.TagHelpers/Services/BackofficeUserAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using Umbraco.Extensions;

namespace Our.Umbraco.TagHelpers.Services
{

/// <summary>
/// Gets the backoffice user from the cookie
/// Code from Markus Johansson on our.umbraco.com
/// https://our.umbraco.com/forum/umbraco-9/106857-how-do-i-determine-if-a-backoffice-user-is-logged-in-from-a-razor-view#comment-334423
/// </summary>
public class BackofficeUserAccessor : IBackofficeUserAccessor
{
private readonly IOptionsSnapshot<CookieAuthenticationOptions> _cookieOptionsSnapshot;
private readonly IHttpContextAccessor _httpContextAccessor;

public BackofficeUserAccessor(
IOptionsSnapshot<CookieAuthenticationOptions> cookieOptionsSnapshot,
IHttpContextAccessor httpContextAccessor)
{
_cookieOptionsSnapshot = cookieOptionsSnapshot;
_httpContextAccessor = httpContextAccessor;
}

public ClaimsIdentity BackofficeUser
{
get
{
var httpContext = _httpContextAccessor.HttpContext;

if (httpContext == null)
return new ClaimsIdentity();

CookieAuthenticationOptions cookieOptions = _cookieOptionsSnapshot.Get(global::Umbraco.Cms.Core.Constants.Security.BackOfficeAuthenticationType);
string backOfficeCookie = httpContext.Request.Cookies[cookieOptions.Cookie.Name!];

if (string.IsNullOrEmpty(backOfficeCookie))
return new ClaimsIdentity();

AuthenticationTicket unprotected = cookieOptions.TicketDataFormat.Unprotect(backOfficeCookie!);
ClaimsIdentity backOfficeIdentity = unprotected!.Principal.GetUmbracoIdentity();

return backOfficeIdentity;
}
}
}
}
9 changes: 9 additions & 0 deletions Our.Umbraco.TagHelpers/Services/IBackofficeUserAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Security.Claims;

namespace Our.Umbraco.TagHelpers.Services
{
public interface IBackofficeUserAccessor
{
ClaimsIdentity BackofficeUser { get; }
}
}
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,49 @@ There are two special Member Groups you can use:
<div our-member-exclude="*">Everyone except who is authenticated will see this.</div>
```

## `<our-edit-link>`
This is a tag helper element which renders an edit link on the front end only if the current user is logged into umbraco and has access to the content section.

The link will open the current page in the umbraco backoffice. You can override the umbraco url if you are using a different url for the backoffice.

### Simple example
This is the most basic example. The link will render wherever you put it in the HTML.

```html
<our-edit-link>Edit</our-edit-link>
```

It will output a link link this, where 1057 is the id of the current page:

```html
<a href="/umbraco#/content/content/edit/1057">Edit</a>
```

### Use Default Styles example

If you add an attribute of `use-default-styles`, it will render the link fixed to the bottom left of the screen with white text and a navy blue background.

```html
<our-edit-link use-default-styles>Edit</our-edit-link>
```

### Change the edit url

Perhaps you have changed your umbraco path to something different, you can use the `edit-url` attribute to change the umbraco edit content url:

```html
<our-edit-link edit-url="/mysecretumbracopath#/content/content/edit/">Edit</our-edit-link>
```

### Open in a new tab

As the edit link is just an `a` tag, you can add the usual attributes like `target` and `class` etc.
If you want the edit link to open in a new tab, just add the `target="_blank"` attribute.

```html
<our-edit-link target="_blank">Edit</our-edit-link>
```

## Video 📺
[![How to create ASP.NET TagHelpers for Umbraco](https://user-images.githubusercontent.com/1389894/138666925-15475216-239f-439d-b989-c67995e5df71.png)](https://www.youtube.com/watch?v=3fkDs0NwIE8)

Expand Down

0 comments on commit dee84a4

Please sign in to comment.