Skip to content

Commit dee84a4

Browse files
author
Warren Buckley
authored
Merge pull request #23 from prjseal/feature/add-edit-link
Added edit link tag helper with readme description too
2 parents cccf09d + dcc7cb1 commit dee84a4

File tree

7 files changed

+299
-1
lines changed

7 files changed

+299
-1
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Our.Umbraco.TagHelpers.Services;
3+
using Umbraco.Cms.Core.Composing;
4+
using Umbraco.Cms.Core.DependencyInjection;
5+
6+
namespace Our.Umbraco.TagHelpers.Composing
7+
{
8+
public class CustomComposer : IComposer
9+
{
10+
public void Compose(IUmbracoBuilder builder)
11+
{
12+
builder.Services.AddScoped<IBackofficeUserAccessor, BackofficeUserAccessor>();
13+
}
14+
}
15+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
using Microsoft.AspNetCore.Mvc.Rendering;
2+
using Microsoft.AspNetCore.Razor.TagHelpers;
3+
using Our.Umbraco.TagHelpers.Extensions;
4+
using Our.Umbraco.TagHelpers.Services;
5+
using System.Text;
6+
using Umbraco.Cms.Core.Web;
7+
8+
namespace Our.Umbraco.TagHelpers
9+
{
10+
/// <summary>
11+
/// If the user viewing the front end is logged in as an umbraco user
12+
/// then an edit link will display on the front end of the site. This will
13+
/// take you to the umbraco backoffice to edit the current page.
14+
/// </summary>
15+
[HtmlTargetElement("our-edit-link")]
16+
public class EditLinkTagHelper : TagHelper
17+
{
18+
private readonly IBackofficeUserAccessor _backofficeUserAccessor;
19+
private IUmbracoContextAccessor _umbracoContextAccessor;
20+
21+
public EditLinkTagHelper(IBackofficeUserAccessor backofficeUserAccessor, IUmbracoContextAccessor umbracoContextAccessor)
22+
{
23+
_backofficeUserAccessor = backofficeUserAccessor;
24+
_umbracoContextAccessor = umbracoContextAccessor;
25+
}
26+
27+
/// <summary>
28+
/// The id of the current content item
29+
/// </summary>
30+
[HtmlAttributeName("content-id")]
31+
public int ContentId { get; set; } = int.MinValue;
32+
33+
/// <summary>
34+
/// Override the umbraco edit content url if yours is different
35+
/// </summary>
36+
[HtmlAttributeName("edit-url")]
37+
public string EditUrl { get; set; } = "/umbraco#/content/content/edit/";
38+
39+
/// <summary>
40+
/// A boolean to say whether or not you would like to use the default styling.
41+
/// </summary>
42+
[HtmlAttributeName("use-default-styles")]
43+
public bool UseDefaultStyles { get; set; } = false;
44+
45+
public override void Process(TagHelperContext context, TagHelperOutput output)
46+
{
47+
// Turn <our-edit-link> into an <a> tag
48+
output.TagName = "a";
49+
50+
// An outer wrapper div if we use inbuilt styling
51+
var outerDiv = new TagBuilder("div");
52+
53+
// Check if the user is logged in to the backoffice
54+
// and they have access to the content section
55+
if (_backofficeUserAccessor.BackofficeUser.IsAllowedToSeeEditLink())
56+
{
57+
// Try & get Umbraco Current Node int ID (Only do this if ContentId has NOT been set)
58+
if (_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext) && ContentId == int.MinValue)
59+
{
60+
ContentId = umbracoContext.PublishedRequest.PublishedContent.Id;
61+
}
62+
63+
// Backoffice URL to content item
64+
var editLinkUrl = $"{EditUrl}{ContentId}";
65+
66+
if (UseDefaultStyles)
67+
{
68+
// Wrap the <a> in a <div>
69+
// Render the outer div with some inline styles
70+
outerDiv.Attributes.Add("style", GetOuterElementStyles());
71+
output.PreElement.AppendHtml(outerDiv.RenderStartTag());
72+
}
73+
74+
// Set the link on the <a> tag
75+
output.Attributes.SetAttribute("href", editLinkUrl);
76+
77+
if (UseDefaultStyles)
78+
{
79+
output.Attributes.SetAttribute("style", GetLinkStyles());
80+
}
81+
82+
if (UseDefaultStyles)
83+
{
84+
//Add the closing outer div
85+
output.PostElement.AppendHtml(outerDiv.RenderEndTag());
86+
}
87+
88+
return;
89+
}
90+
else
91+
{
92+
output.SuppressOutput();
93+
return;
94+
}
95+
}
96+
97+
/// <summary>
98+
/// Helper method to get the link styles
99+
/// </summary>
100+
/// <param name="linkColour">The CSS colour of the link text</param>
101+
/// <param name="linkBackgroundColour">The CSS colour of the link background</param>
102+
/// <param name="linkPadding">The padding around the link</param>
103+
/// <param name="fontSize">The font size of the link text</param>
104+
/// <param name="borderRadius">The border radius of the link</param>
105+
/// <returns></returns>
106+
private static string GetLinkStyles(
107+
string linkColour = "#ffffff",
108+
string linkBackgroundColour = "#1b264f",
109+
int linkPadding = 10,
110+
int fontSize = 16,
111+
int borderRadius = 6)
112+
{
113+
StringBuilder linkStyles = new StringBuilder();
114+
linkStyles.Append($"color:{linkColour};");
115+
linkStyles.Append($"background-color:{linkBackgroundColour};");
116+
linkStyles.Append($"padding:{linkPadding}px;");
117+
linkStyles.Append($"font-size:{fontSize}px;");
118+
linkStyles.Append($"border-radius:{borderRadius}px;");
119+
return linkStyles.ToString();
120+
}
121+
122+
/// <summary>
123+
/// Helper method to get the outer element styles
124+
/// </summary>
125+
/// <param name="outerPosition">The CSS position of the outer element</param>
126+
/// <param name="margin">The margin around the outer element</param>
127+
/// <param name="zindex">The z-index of the outer element</param>
128+
/// <param name="linkPadding">The padding around the link</param>
129+
/// <returns></returns>
130+
private static string GetOuterElementStyles(
131+
string outerPosition = "fixed",
132+
int margin = 10,
133+
int zindex = 10000,
134+
int linkPadding = 10)
135+
{
136+
linkPadding /= 2;
137+
138+
var outerStyles = new StringBuilder();
139+
140+
outerStyles.Append("display:block;");
141+
if (outerPosition == "fixed")
142+
{
143+
outerStyles.Append($"bottom:{margin + linkPadding}px;");
144+
outerStyles.Append($"left:{margin}px;");
145+
}
146+
147+
outerStyles.Append($"z-index:{zindex};");
148+
outerStyles.Append($"position:{outerPosition};");
149+
150+
return outerStyles.ToString();
151+
}
152+
}
153+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Linq;
2+
using System.Security.Claims;
3+
using Umbraco.Cms.Core;
4+
5+
namespace Our.Umbraco.TagHelpers.Extensions
6+
{
7+
public static class ClaimsIdentityExtensions
8+
{
9+
public static bool IsAllowedToSeeEditLink(this ClaimsIdentity identity)
10+
{
11+
return IsLoggedIntoUmbraco(identity) && HasAccessToContentSection(identity);
12+
}
13+
14+
public static bool IsLoggedIntoUmbraco(this ClaimsIdentity identity)
15+
{
16+
return identity?.AuthenticationType != null
17+
&& identity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType;
18+
}
19+
20+
public static bool HasAccessToContentSection(this ClaimsIdentity identity)
21+
{
22+
return identity?.Claims != null && identity.Claims.Any(x =>
23+
x.Type == Constants.Security.AllowedApplicationsClaimType
24+
&& x.Value == Constants.Conventions.PermissionCategories.ContentCategory);
25+
}
26+
}
27+
}

Our.Umbraco.TagHelpers/Our.Umbraco.TagHelpers.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
</ItemGroup>
4444

4545
<ItemGroup>
46-
<None Include="TagHelperLogo.png" Pack="true" PackagePath="\"/>
46+
<None Include="TagHelperLogo.png" Pack="true" PackagePath="\" />
4747
</ItemGroup>
4848

4949
</Project>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using Microsoft.AspNetCore.Authentication;
2+
using Microsoft.AspNetCore.Authentication.Cookies;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.Extensions.Options;
5+
using System.Security.Claims;
6+
using Umbraco.Extensions;
7+
8+
namespace Our.Umbraco.TagHelpers.Services
9+
{
10+
11+
/// <summary>
12+
/// Gets the backoffice user from the cookie
13+
/// Code from Markus Johansson on our.umbraco.com
14+
/// 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
15+
/// </summary>
16+
public class BackofficeUserAccessor : IBackofficeUserAccessor
17+
{
18+
private readonly IOptionsSnapshot<CookieAuthenticationOptions> _cookieOptionsSnapshot;
19+
private readonly IHttpContextAccessor _httpContextAccessor;
20+
21+
public BackofficeUserAccessor(
22+
IOptionsSnapshot<CookieAuthenticationOptions> cookieOptionsSnapshot,
23+
IHttpContextAccessor httpContextAccessor)
24+
{
25+
_cookieOptionsSnapshot = cookieOptionsSnapshot;
26+
_httpContextAccessor = httpContextAccessor;
27+
}
28+
29+
public ClaimsIdentity BackofficeUser
30+
{
31+
get
32+
{
33+
var httpContext = _httpContextAccessor.HttpContext;
34+
35+
if (httpContext == null)
36+
return new ClaimsIdentity();
37+
38+
CookieAuthenticationOptions cookieOptions = _cookieOptionsSnapshot.Get(global::Umbraco.Cms.Core.Constants.Security.BackOfficeAuthenticationType);
39+
string backOfficeCookie = httpContext.Request.Cookies[cookieOptions.Cookie.Name!];
40+
41+
if (string.IsNullOrEmpty(backOfficeCookie))
42+
return new ClaimsIdentity();
43+
44+
AuthenticationTicket unprotected = cookieOptions.TicketDataFormat.Unprotect(backOfficeCookie!);
45+
ClaimsIdentity backOfficeIdentity = unprotected!.Principal.GetUmbracoIdentity();
46+
47+
return backOfficeIdentity;
48+
}
49+
}
50+
}
51+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Security.Claims;
2+
3+
namespace Our.Umbraco.TagHelpers.Services
4+
{
5+
public interface IBackofficeUserAccessor
6+
{
7+
ClaimsIdentity BackofficeUser { get; }
8+
}
9+
}

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,49 @@ There are two special Member Groups you can use:
227227
<div our-member-exclude="*">Everyone except who is authenticated will see this.</div>
228228
```
229229

230+
## `<our-edit-link>`
231+
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.
232+
233+
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.
234+
235+
### Simple example
236+
This is the most basic example. The link will render wherever you put it in the HTML.
237+
238+
```html
239+
<our-edit-link>Edit</our-edit-link>
240+
```
241+
242+
It will output a link link this, where 1057 is the id of the current page:
243+
244+
```html
245+
<a href="/umbraco#/content/content/edit/1057">Edit</a>
246+
```
247+
248+
### Use Default Styles example
249+
250+
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.
251+
252+
```html
253+
<our-edit-link use-default-styles>Edit</our-edit-link>
254+
```
255+
256+
### Change the edit url
257+
258+
Perhaps you have changed your umbraco path to something different, you can use the `edit-url` attribute to change the umbraco edit content url:
259+
260+
```html
261+
<our-edit-link edit-url="/mysecretumbracopath#/content/content/edit/">Edit</our-edit-link>
262+
```
263+
264+
### Open in a new tab
265+
266+
As the edit link is just an `a` tag, you can add the usual attributes like `target` and `class` etc.
267+
If you want the edit link to open in a new tab, just add the `target="_blank"` attribute.
268+
269+
```html
270+
<our-edit-link target="_blank">Edit</our-edit-link>
271+
```
272+
230273
## Video 📺
231274
[![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)
232275

0 commit comments

Comments
 (0)