diff --git a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/TagHelpers/GravatarTagHelper.cs b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/TagHelpers/GravatarTagHelper.cs new file mode 100644 index 0000000000..45ff7060b1 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/TagHelpers/GravatarTagHelper.cs @@ -0,0 +1,210 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Volo.Blogging.Areas.Blog.Helpers.TagHelpers +{ + /// + /// + /// Returns a Globally Recognised Avatar https://en.gravatar.com + /// + [HtmlTargetElement("img", Attributes = "gravatar-email")] + public class GravatarTagHelper : TagHelper + { + private readonly IHttpContextAccessor _contextAccessor; + + public GravatarTagHelper(IHttpContextAccessor contextAccessor) + { + _contextAccessor = contextAccessor; + } + + /// + /// Email Address for the Gravatar + /// + [HtmlAttributeName("gravatar-email")] + public string Email { get; set; } + + /// + /// Gravatar content rating (note that Gravatars are self-rated) + /// + [HtmlAttributeName("gravatar-rating")] + public GravatarRating Rating { get; set; } = GravatarRating.GeneralAudiences; + + /// + /// Size in pixels (default: 80) + /// + [HtmlAttributeName("gravatar-size")] + public int Size { get; set; } = 80; + + /// + /// URL to a custom default image (e.g: 'Url.Content("~/images/no-grvatar.png")' ) + /// + [HtmlAttributeName("default-image-url")] + public string DefaultImageUrl { get; set; } = ""; + + /// + /// Prefer the default image over the users own Gravatar + /// + [HtmlAttributeName("force-default-image")] + public bool ForceDefaultImage { get; set; } + + /// + /// Default image if user hasn't created a Gravatar + /// + [HtmlAttributeName("default-image")] + public GravatarDefaultImage DefaultImage { get; set; } = GravatarDefaultImage.Default; + + /// + /// Always do secure (https) requests + /// + [HtmlAttributeName("force-secure-request")] + public bool ForceSecureRequest { get; set; } = true; + + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + var emailAddress = Email == null ? string.Empty : Email.Trim().ToLower(); + + var url = string.Format("{0}://{1}.gravatar.com/avatar/{2}?s={3}{4}{5}{6}", + GetUrlScheme(), + GetUrlPrefix(), + GetMd5Hash(emailAddress), + Size, + GetDefaultImageParameter(), + GetForceDefaultImageParameter(), + GetRatingParameter() + ); + + output.Attributes.SetAttribute("src", url); + } + + private string GetUrlScheme() + { + return ForceSecureRequest || _contextAccessor.HttpContext.Request.IsHttps + ? "https" : "http"; + } + + private string GetUrlPrefix() + { + return ForceSecureRequest || _contextAccessor.HttpContext.Request.IsHttps ? "secure" : "www"; + } + + private string GetDefaultImageParameter() + { + return "&d=" + (!string.IsNullOrEmpty(DefaultImageUrl) + ? System.Net.WebUtility.UrlEncode(DefaultImageUrl) + : GetEnumDescription(DefaultImage)); + } + + private string GetForceDefaultImageParameter() + { + return ForceDefaultImage ? "&f=y" : ""; + } + + private string GetRatingParameter() + { + return "&r=" + GetEnumDescription(Rating); + } + + /// + /// Generates an MD5 hash of the given string + /// + /// Source: http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5.aspx + private static string GetMd5Hash(string input) + { + // Convert the input string to a byte array and compute the hash. + var data = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(input)); + + // Create a new Stringbuilder to collect the bytes + // and create a string. + var sBuilder = new StringBuilder(); + + // Loop through each byte of the hashed data + // and format each one as a hexadecimal string. + foreach (var t in data) + { + sBuilder.Append(t.ToString("x2")); + } + + // Return the hexadecimal string. + return sBuilder.ToString(); + } + + /// + /// Returns the value of a Description for a given Enum value + /// + /// Source: http://blogs.msdn.com/b/abhinaba/archive/2005/10/21/483337.aspx + /// + /// + private static string GetEnumDescription(Enum en) + { + var type = en.GetType(); + var memInfo = type.GetMember(en.ToString()); + + if (memInfo == null || memInfo.Length <= 0) + { + return en.ToString(); + } + + var attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); + + if (attrs != null && attrs.Any()) + { + return ((DescriptionAttribute)attrs.First()).Description; + } + + return en.ToString(); + } + + public enum GravatarDefaultImage + { + /// Default Gravatar logo + [Description("")] + Default, + /// 404 - do not load any image if none is associated with the email hash, instead return an HTTP 404 (File Not Found) response + [Description("404")] + Http404, + /// Mystery-Man - a simple, cartoon-style silhouetted outline of a person (does not vary by email hash) + [Description("mm")] + MysteryMan, + /// Identicon - a geometric pattern based on an email hash + [Description("identicon")] + Identicon, + /// MonsterId - a generated 'monster' with different colors, faces, etc + [Description("monsterid")] + MonsterId, + /// Wavatar - generated faces with differing features and backgrounds + [Description("wavatar")] + Wavatar, + /// Retro - awesome generated, 8-bit arcade-style pixelated faces + [Description("retro")] + Retro + } + + /// + /// Gravatar allows users to self-rate their images so that they can indicate if an image is appropriate for a certain audience. By default, only 'G' rated images are displayed unless you indicate that you would like to see higher ratings + /// + public enum GravatarRating + { + /// Suitable for display on all websites with any audience type + [Description("g")] + GeneralAudiences, + + /// May contain rude gestures, provocatively dressed individuals, the lesser swear words, or mild violence + [Description("pg")] + ParentalGuidance, + + /// May contain such things as harsh profanity, intense violence, nudity, or hard drug use + [Description("r")] + Restricted, + + /// May contain hardcore sexual imagery or extremely disturbing violence + [Description("x")] + OnlyMature + } + } +} diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml index d0f8b433bd..05c5c611b1 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml @@ -4,6 +4,7 @@ @using Microsoft.AspNetCore.Http.Extensions @using Volo.Blogging @using Volo.Blogging.Pages.Blog.Posts +@using Volo.Blogging.Areas.Blog.Helpers.TagHelpers @inject IAuthorizationService Authorization @model DetailModel @{ @@ -36,9 +37,6 @@