mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Extracting Orchard.Users email templates
The texts of the emails sent are defined in custom shapes. It's using IMessageService to send an email directly.
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Messaging.Events;
|
||||
using Orchard.Messaging.Models;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Settings;
|
||||
using Orchard.Users.Models;
|
||||
|
||||
namespace Orchard.Users.Handlers {
|
||||
public class UserMessagesAlteration : IMessageEventHandler {
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly ISiteService _siteService;
|
||||
|
||||
public UserMessagesAlteration(IContentManager contentManager, ISiteService siteService) {
|
||||
_contentManager = contentManager;
|
||||
_siteService = siteService;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public void Sending(MessageContext context) {
|
||||
if (context.MessagePrepared)
|
||||
return;
|
||||
|
||||
switch (context.Type) {
|
||||
case MessageTypes.Moderation:
|
||||
context.MailMessage.Subject = T("New account").Text;
|
||||
context.MailMessage.Body =
|
||||
T("The user <b>{0}</b> with email <b>{1}</b> has requested a new account. This user won't be able to log while his account has not been approved.",
|
||||
context.Properties["UserName"], context.Properties["Email"]).Text;
|
||||
FormatEmailBody(context);
|
||||
context.MessagePrepared = true;
|
||||
break;
|
||||
|
||||
case MessageTypes.Validation:
|
||||
var registeredWebsite = _siteService.GetSiteSettings().As<RegistrationSettingsPart>().ValidateEmailRegisteredWebsite;
|
||||
var contactEmail = _siteService.GetSiteSettings().As<RegistrationSettingsPart>().ValidateEmailContactEMail;
|
||||
context.MailMessage.Subject = T("Verification E-Mail").Text;
|
||||
context.MailMessage.Body =
|
||||
T("Thank you for registering with {0}.<br/><br/><br/><b>Final Step</b><br/>To verify that you own this e-mail address, please click the following link:<br/><a href=\"{1}\">{1}</a><br/><br/><b>Troubleshooting:</b><br/>If clicking on the link above does not work, try the following:<br/><br/>Select and copy the entire link.<br/>Open a browser window and paste the link in the address bar.<br/>Click <b>Go</b> or, on your keyboard, press <b>Enter</b> or <b>Return</b>.",
|
||||
registeredWebsite, context.Properties["ChallengeUrl"]).Text;
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(contactEmail)) {
|
||||
context.MailMessage.Body +=
|
||||
T("<br/><br/>If you continue to have access problems or want to report other issues, please <a href=\"mailto:{0}\">Contact Us</a>.",
|
||||
contactEmail).Text;
|
||||
}
|
||||
FormatEmailBody(context);
|
||||
context.MessagePrepared = true;
|
||||
break;
|
||||
|
||||
case MessageTypes.LostPassword:
|
||||
var recipient = GetRecipient(context);
|
||||
context.MailMessage.Subject = T("Lost password").Text;
|
||||
context.MailMessage.Body =
|
||||
T("Dear {0}, please <a href=\"{1}\">click here</a> to change your password.", recipient.UserName,
|
||||
context.Properties["LostPasswordUrl"]).Text;
|
||||
FormatEmailBody(context);
|
||||
context.MessagePrepared = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private UserPart GetRecipient(MessageContext context) {
|
||||
// we expect a single account to be created
|
||||
var contentItem = _contentManager.Get(context.Recipients.Single().Id);
|
||||
if (contentItem == null) {
|
||||
return null;
|
||||
}
|
||||
return contentItem.As<UserPart>();
|
||||
}
|
||||
|
||||
private static void FormatEmailBody(MessageContext context) {
|
||||
context.MailMessage.Body = "<p style=\"font-family:Arial, Helvetica; font-size:10pt;\">" + context.MailMessage.Body;
|
||||
context.MailMessage.Body += "</p>";
|
||||
}
|
||||
|
||||
public void Sent(MessageContext context) {
|
||||
}
|
||||
}
|
||||
}
|
@@ -49,6 +49,10 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\lib\newtonsoft.json\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
@@ -73,7 +77,6 @@
|
||||
<Compile Include="Drivers\UserPartDriver.cs" />
|
||||
<Compile Include="Migrations.cs" />
|
||||
<Compile Include="Events\UserContext.cs" />
|
||||
<Compile Include="Handlers\UserMessagesAlteration.cs" />
|
||||
<Compile Include="Handlers\RegistrationSettingsPartHandler.cs" />
|
||||
<Compile Include="Events\IUserEventHandler.cs" />
|
||||
<Compile Include="Models\MessageTypes.cs" />
|
||||
@@ -120,6 +123,10 @@
|
||||
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
|
||||
<Name>Orchard.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Messaging\Orchard.Messaging.csproj">
|
||||
<Project>{085948FF-0E9B-4A9A-B564-F8B8B4BDDDBC}</Project>
|
||||
<Name>Orchard.Messaging</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\Items\Content-User.Edit.cshtml" />
|
||||
@@ -151,6 +158,15 @@
|
||||
<SubType>Designer</SubType>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\Template.User.Moderated.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\Template.User.Validated.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\Template.User.LostPassword.cshtml" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
|
@@ -4,6 +4,8 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Web.Security;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using Orchard.DisplayManagement;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
using Orchard.ContentManagement;
|
||||
@@ -18,15 +20,26 @@ namespace Orchard.Users.Services {
|
||||
[UsedImplicitly]
|
||||
public class MembershipService : IMembershipService {
|
||||
private readonly IOrchardServices _orchardServices;
|
||||
private readonly IMessageManager _messageManager;
|
||||
private readonly IMessageService _messageService;
|
||||
private readonly IEnumerable<IUserEventHandler> _userEventHandlers;
|
||||
private readonly IEncryptionService _encryptionService;
|
||||
private readonly IShapeFactory _shapeFactory;
|
||||
private readonly IShapeDisplay _shapeDisplay;
|
||||
|
||||
public MembershipService(IOrchardServices orchardServices, IMessageManager messageManager, IEnumerable<IUserEventHandler> userEventHandlers, IClock clock, IEncryptionService encryptionService) {
|
||||
public MembershipService(
|
||||
IOrchardServices orchardServices,
|
||||
IMessageService messageService,
|
||||
IEnumerable<IUserEventHandler> userEventHandlers,
|
||||
IClock clock,
|
||||
IEncryptionService encryptionService,
|
||||
IShapeFactory shapeFactory,
|
||||
IShapeDisplay shapeDisplay) {
|
||||
_orchardServices = orchardServices;
|
||||
_messageManager = messageManager;
|
||||
_messageService = messageService;
|
||||
_userEventHandlers = userEventHandlers;
|
||||
_encryptionService = encryptionService;
|
||||
_shapeFactory = shapeFactory;
|
||||
_shapeDisplay = shapeDisplay;
|
||||
Logger = NullLogger.Instance;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
@@ -51,7 +64,7 @@ namespace Orchard.Users.Services {
|
||||
user.Record.Email = createUserParams.Email;
|
||||
user.Record.NormalizedUserName = createUserParams.Username.ToLowerInvariant();
|
||||
user.Record.HashAlgorithm = "SHA1";
|
||||
SetPassword(user.Record, createUserParams.Password);
|
||||
SetPassword(user, createUserParams.Password);
|
||||
|
||||
if ( registrationSettings != null ) {
|
||||
user.Record.RegistrationStatus = registrationSettings.UsersAreModerated ? UserStatus.Pending : UserStatus.Approved;
|
||||
@@ -81,7 +94,10 @@ namespace Orchard.Users.Services {
|
||||
}
|
||||
}
|
||||
|
||||
if ( registrationSettings != null && registrationSettings.UsersAreModerated && registrationSettings.NotifyModeration && !createUserParams.IsApproved ) {
|
||||
if ( registrationSettings != null
|
||||
&& registrationSettings.UsersAreModerated
|
||||
&& registrationSettings.NotifyModeration
|
||||
&& !createUserParams.IsApproved ) {
|
||||
var usernames = String.IsNullOrWhiteSpace(registrationSettings.NotificationsRecipients)
|
||||
? new string[0]
|
||||
: registrationSettings.NotificationsRecipients.Split(new[] {',', ' '}, StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -91,8 +107,15 @@ namespace Orchard.Users.Services {
|
||||
continue;
|
||||
}
|
||||
var recipient = GetUser(userName);
|
||||
if (recipient != null)
|
||||
_messageManager.Send(recipient.ContentItem.Record, MessageTypes.Moderation, "email" , new Dictionary<string, string> { { "UserName", createUserParams.Username}, { "Email" , createUserParams.Email } });
|
||||
if (recipient != null) {
|
||||
var template = _shapeFactory.Create("Template_User_Moderated", Arguments.From(createUserParams));
|
||||
var payload = new {
|
||||
Subject = T("New account").Text,
|
||||
Body = _shapeDisplay.Display(template),
|
||||
Recipients = new [] { recipient.Email }
|
||||
};
|
||||
_messageService.Send("Email", JsonConvert.SerializeObject(payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +136,7 @@ namespace Orchard.Users.Services {
|
||||
if (user == null)
|
||||
user = _orchardServices.ContentManager.Query<UserPart, UserPartRecord>().Where(u => u.Email == lowerName).List().FirstOrDefault();
|
||||
|
||||
if ( user == null || ValidatePassword(user.As<UserPart>().Record, password) == false )
|
||||
if ( user == null || ValidatePassword(user.As<UserPart>(), password) == false )
|
||||
return null;
|
||||
|
||||
if ( user.EmailStatus != UserStatus.Approved )
|
||||
@@ -129,54 +152,50 @@ namespace Orchard.Users.Services {
|
||||
if (!user.Is<UserPart>())
|
||||
throw new InvalidCastException();
|
||||
|
||||
var userRecord = user.As<UserPart>().Record;
|
||||
SetPassword(userRecord, password);
|
||||
}
|
||||
var userPart = user.As<UserPart>();
|
||||
|
||||
|
||||
void SetPassword(UserPartRecord partRecord, string password) {
|
||||
switch (GetSettings().PasswordFormat) {
|
||||
case MembershipPasswordFormat.Clear:
|
||||
SetPasswordClear(partRecord, password);
|
||||
SetPasswordClear(userPart, password);
|
||||
break;
|
||||
case MembershipPasswordFormat.Hashed:
|
||||
SetPasswordHashed(partRecord, password);
|
||||
SetPasswordHashed(userPart, password);
|
||||
break;
|
||||
case MembershipPasswordFormat.Encrypted:
|
||||
SetPasswordEncrypted(partRecord, password);
|
||||
SetPasswordEncrypted(userPart, password);
|
||||
break;
|
||||
default:
|
||||
throw new ApplicationException(T("Unexpected password format value").ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidatePassword(UserPartRecord partRecord, string password) {
|
||||
private bool ValidatePassword(UserPart userPart, string password) {
|
||||
// Note - the password format stored with the record is used
|
||||
// otherwise changing the password format on the site would invalidate
|
||||
// all logins
|
||||
switch (partRecord.PasswordFormat) {
|
||||
switch (userPart.PasswordFormat) {
|
||||
case MembershipPasswordFormat.Clear:
|
||||
return ValidatePasswordClear(partRecord, password);
|
||||
return ValidatePasswordClear(userPart, password);
|
||||
case MembershipPasswordFormat.Hashed:
|
||||
return ValidatePasswordHashed(partRecord, password);
|
||||
return ValidatePasswordHashed(userPart, password);
|
||||
case MembershipPasswordFormat.Encrypted:
|
||||
return ValidatePasswordEncrypted(partRecord, password);
|
||||
return ValidatePasswordEncrypted(userPart, password);
|
||||
default:
|
||||
throw new ApplicationException("Unexpected password format value");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPasswordClear(UserPartRecord partRecord, string password) {
|
||||
partRecord.PasswordFormat = MembershipPasswordFormat.Clear;
|
||||
partRecord.Password = password;
|
||||
partRecord.PasswordSalt = null;
|
||||
private static void SetPasswordClear(UserPart userPart, string password) {
|
||||
userPart.PasswordFormat = MembershipPasswordFormat.Clear;
|
||||
userPart.Password = password;
|
||||
userPart.PasswordSalt = null;
|
||||
}
|
||||
|
||||
private static bool ValidatePasswordClear(UserPartRecord partRecord, string password) {
|
||||
return partRecord.Password == password;
|
||||
private static bool ValidatePasswordClear(UserPart userPart, string password) {
|
||||
return userPart.Password == password;
|
||||
}
|
||||
|
||||
private static void SetPasswordHashed(UserPartRecord partRecord, string password) {
|
||||
private static void SetPasswordHashed(UserPart userPart, string password) {
|
||||
|
||||
var saltBytes = new byte[0x10];
|
||||
using (var random = new RNGCryptoServiceProvider()) {
|
||||
@@ -188,39 +207,39 @@ namespace Orchard.Users.Services {
|
||||
var combinedBytes = saltBytes.Concat(passwordBytes).ToArray();
|
||||
|
||||
byte[] hashBytes;
|
||||
using (var hashAlgorithm = HashAlgorithm.Create(partRecord.HashAlgorithm)) {
|
||||
using (var hashAlgorithm = HashAlgorithm.Create(userPart.HashAlgorithm)) {
|
||||
hashBytes = hashAlgorithm.ComputeHash(combinedBytes);
|
||||
}
|
||||
|
||||
partRecord.PasswordFormat = MembershipPasswordFormat.Hashed;
|
||||
partRecord.Password = Convert.ToBase64String(hashBytes);
|
||||
partRecord.PasswordSalt = Convert.ToBase64String(saltBytes);
|
||||
userPart.PasswordFormat = MembershipPasswordFormat.Hashed;
|
||||
userPart.Password = Convert.ToBase64String(hashBytes);
|
||||
userPart.PasswordSalt = Convert.ToBase64String(saltBytes);
|
||||
}
|
||||
|
||||
private static bool ValidatePasswordHashed(UserPartRecord partRecord, string password) {
|
||||
private static bool ValidatePasswordHashed(UserPart userPart, string password) {
|
||||
|
||||
var saltBytes = Convert.FromBase64String(partRecord.PasswordSalt);
|
||||
var saltBytes = Convert.FromBase64String(userPart.PasswordSalt);
|
||||
|
||||
var passwordBytes = Encoding.Unicode.GetBytes(password);
|
||||
|
||||
var combinedBytes = saltBytes.Concat(passwordBytes).ToArray();
|
||||
|
||||
byte[] hashBytes;
|
||||
using (var hashAlgorithm = HashAlgorithm.Create(partRecord.HashAlgorithm)) {
|
||||
using (var hashAlgorithm = HashAlgorithm.Create(userPart.HashAlgorithm)) {
|
||||
hashBytes = hashAlgorithm.ComputeHash(combinedBytes);
|
||||
}
|
||||
|
||||
return partRecord.Password == Convert.ToBase64String(hashBytes);
|
||||
|
||||
return userPart.Password == Convert.ToBase64String(hashBytes);
|
||||
}
|
||||
|
||||
private void SetPasswordEncrypted(UserPartRecord partRecord, string password) {
|
||||
partRecord.Password = Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(password)));
|
||||
partRecord.PasswordSalt = null;
|
||||
partRecord.PasswordFormat = MembershipPasswordFormat.Encrypted;
|
||||
private void SetPasswordEncrypted(UserPart userPart, string password) {
|
||||
userPart.Password = Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(password)));
|
||||
userPart.PasswordSalt = null;
|
||||
userPart.PasswordFormat = MembershipPasswordFormat.Encrypted;
|
||||
}
|
||||
|
||||
private bool ValidatePasswordEncrypted(UserPartRecord partRecord, string password) {
|
||||
return String.Equals(password, Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(partRecord.Password))), StringComparison.Ordinal);
|
||||
private bool ValidatePasswordEncrypted(UserPart userPart, string password) {
|
||||
return String.Equals(password, Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(userPart.Password))), StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using Orchard.DisplayManagement;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Settings;
|
||||
using Orchard.Users.Models;
|
||||
using Orchard.Security;
|
||||
using System.Xml.Linq;
|
||||
@@ -22,19 +25,36 @@ namespace Orchard.Users.Services {
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IMembershipService _membershipService;
|
||||
private readonly IClock _clock;
|
||||
private readonly IMessageManager _messageManager;
|
||||
private readonly IMessageService _messageService;
|
||||
private readonly IEncryptionService _encryptionService;
|
||||
private readonly IShapeFactory _shapeFactory;
|
||||
private readonly IShapeDisplay _shapeDisplay;
|
||||
private readonly ISiteService _siteService;
|
||||
|
||||
public UserService(IContentManager contentManager, IMembershipService membershipService, IClock clock, IMessageManager messageManager, ShellSettings shellSettings, IEncryptionService encryptionService) {
|
||||
public UserService(
|
||||
IContentManager contentManager,
|
||||
IMembershipService membershipService,
|
||||
IClock clock,
|
||||
IMessageService messageService,
|
||||
ShellSettings shellSettings,
|
||||
IEncryptionService encryptionService,
|
||||
IShapeFactory shapeFactory,
|
||||
IShapeDisplay shapeDisplay,
|
||||
ISiteService siteService
|
||||
) {
|
||||
_contentManager = contentManager;
|
||||
_membershipService = membershipService;
|
||||
_clock = clock;
|
||||
_messageManager = messageManager;
|
||||
_messageService = messageService;
|
||||
_encryptionService = encryptionService;
|
||||
_shapeFactory = shapeFactory;
|
||||
_shapeDisplay = shapeDisplay;
|
||||
_siteService = siteService;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public bool VerifyUserUnicity(string userName, string email) {
|
||||
string normalizedUserName = userName.ToLowerInvariant();
|
||||
@@ -111,7 +131,24 @@ namespace Orchard.Users.Services {
|
||||
public void SendChallengeEmail(IUser user, Func<string, string> createUrl) {
|
||||
string nonce = CreateNonce(user, DelayToValidate);
|
||||
string url = createUrl(nonce);
|
||||
_messageManager.Send(user.ContentItem.Record, MessageTypes.Validation, "email", new Dictionary<string, string> { { "ChallengeUrl", url } });
|
||||
|
||||
if (user != null) {
|
||||
var site = _siteService.GetSiteSettings();
|
||||
|
||||
var template = _shapeFactory.Create("Template_User_Validated", Arguments.From(new {
|
||||
RegisteredWebsite = site.As<RegistrationSettingsPart>().ValidateEmailRegisteredWebsite,
|
||||
ContactEmail = site.As<RegistrationSettingsPart>().ValidateEmailContactEMail,
|
||||
ChallengeUrl = url
|
||||
}));
|
||||
|
||||
var payload = new {
|
||||
Subject = T("Verification E-Mail").Text,
|
||||
Body = _shapeDisplay.Display(template),
|
||||
Recipients = new[] { user.Email }
|
||||
};
|
||||
|
||||
_messageService.Send("Email", JsonConvert.SerializeObject(payload));
|
||||
}
|
||||
}
|
||||
|
||||
public bool SendLostPasswordEmail(string usernameOrEmail, Func<string, string> createUrl) {
|
||||
@@ -122,7 +159,18 @@ namespace Orchard.Users.Services {
|
||||
string nonce = CreateNonce(user, DelayToResetPassword);
|
||||
string url = createUrl(nonce);
|
||||
|
||||
_messageManager.Send(user.ContentItem.Record, MessageTypes.LostPassword, "email", new Dictionary<string, string> { { "LostPasswordUrl", url } });
|
||||
var template = _shapeFactory.Create("Template_User_LostPassword", Arguments.From(new {
|
||||
User = user,
|
||||
LostPasswordUrl = url
|
||||
}));
|
||||
|
||||
var payload = new {
|
||||
Subject = T("Lost password").Text,
|
||||
Body = _shapeDisplay.Display(template),
|
||||
Recipients = new[] { user.Email }
|
||||
};
|
||||
|
||||
_messageService.Send("Email", JsonConvert.SerializeObject(payload));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1 @@
|
||||
@T("Dear {0}, please <a href=\"{1}\">click here</a> to change your password.", Model.User.UserName, Model.LostPasswordUrl)
|
@@ -0,0 +1 @@
|
||||
@T("The user <b>{0}</b> with email <b>{1}</b> has requested a new account. This user won't be able to log while his account has not been approved.", Model.Username, Model.Email)
|
@@ -0,0 +1,4 @@
|
||||
@T("Thank you for registering with {0}.<br /><br /><br /><b>Final Step</b><br />To verify that you own this e-mail address, please click the following link:<br /><a href=\"{1}\">{1}</a><br /><br /><b>Troubleshooting:</b><br />If clicking on the link above does not work, try the following:<br /><br />Select and copy the entire link.<br />Open a browser window and paste the link in the address bar.<br />Click <b>Go</b> or, on your keyboard, press <b>Enter</b> or <b>Return</b>.", Model.RegisteredWebsite, Model.ChallengeUrl)
|
||||
@if (!String.IsNullOrWhiteSpace(Model.ContactEmail)) {
|
||||
@T("<br /><br />If you continue to have access problems or want to report other issues, please <a href=\"mailto:{0}\">Contact Us</a>.",Model.ContactEmail)
|
||||
}
|
Reference in New Issue
Block a user