Lots of work on the member editor - creates new email address prop editor, allows text prop editor to be required based on config, fixes the section directive bug, creating change password prop ed, streamlines more of the services layer to ensure that the things that need to be public are public
This commit is contained in:
@@ -411,6 +411,11 @@ namespace Umbraco.Core
|
||||
/// Alias for the XPath DropDownList datatype.
|
||||
/// </summary>
|
||||
public const string XPathDropDownListAlias = "Umbraco.XPathDropDownList";
|
||||
|
||||
/// <summary>
|
||||
/// Alias for the email address property editor
|
||||
/// </summary>
|
||||
public const string EmailAddressAlias = "Umbraco.EmailAddress";
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Umbraco.Core/PropertyEditors/EmailValidator.cs
Normal file
31
src/Umbraco.Core/PropertyEditors/EmailValidator.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// A validator that validates an email address
|
||||
/// </summary>
|
||||
[ValueValidator("Email")]
|
||||
internal sealed class EmailValidator : ManifestValueValidator, IPropertyValidator
|
||||
{
|
||||
public override IEnumerable<ValidationResult> Validate(object value, string config, PreValueCollection preValues, PropertyEditor editor)
|
||||
{
|
||||
var asString = value.ToString();
|
||||
|
||||
var emailVal = new EmailAddressAttribute();
|
||||
|
||||
if (emailVal.IsValid(asString) == false)
|
||||
{
|
||||
//TODO: localize these!
|
||||
yield return new ValidationResult("Email is invalid", new[] { "value" });
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(object value, PreValueCollection preValues, PropertyEditor editor)
|
||||
{
|
||||
return Validate(value, null, preValues, editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,36 +346,7 @@ namespace Umbraco.Core.Services
|
||||
Audit.Add(AuditTypes.Delete, string.Format("Delete ContentTypes performed by user"), userId, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMemberType"/> object by its Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="IMemberType"/> to retrieve</param>
|
||||
/// <returns><see cref="IMemberType"/></returns>
|
||||
public IMemberType GetMemberType(int id)
|
||||
{
|
||||
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMemberType"/> object by its Alias
|
||||
/// </summary>
|
||||
/// <param name="alias">Alias of the <see cref="IMemberType"/> to retrieve</param>
|
||||
/// <returns><see cref="IMemberType"/></returns>
|
||||
public IMemberType GetMemberType(string alias)
|
||||
{
|
||||
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork()))
|
||||
{
|
||||
var query = Query<IMemberType>.Builder.Where(x => x.Alias == alias);
|
||||
var contentTypes = repository.GetByQuery(query);
|
||||
|
||||
return contentTypes.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMediaType"/> object by its Id
|
||||
/// </summary>
|
||||
|
||||
@@ -66,21 +66,7 @@ namespace Umbraco.Core.Services
|
||||
/// <remarks>Deleting a <see cref="IContentType"/> will delete all the <see cref="IContent"/> objects based on this <see cref="IContentType"/></remarks>
|
||||
/// <param name="userId">Optional Id of the User deleting the ContentTypes</param>
|
||||
void Delete(IEnumerable<IContentType> contentTypes, int userId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMemberType"/> object by its Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="IMediaType"/> to retrieve</param>
|
||||
/// <returns><see cref="IMediaType"/></returns>
|
||||
IMemberType GetMemberType(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMemberType"/> object by its Alias
|
||||
/// </summary>
|
||||
/// <param name="alias">Alias of the <see cref="IMediaType"/> to retrieve</param>
|
||||
/// <returns><see cref="IMediaType"/></returns>
|
||||
IMemberType GetMemberType(string alias);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMediaType"/> object by its Id
|
||||
/// </summary>
|
||||
|
||||
@@ -12,7 +12,18 @@ namespace Umbraco.Core.Services
|
||||
/// <returns>An Enumerable list of <see cref="IContentType"/> objects</returns>
|
||||
IEnumerable<IMemberType> GetAllMemberTypes(params int[] ids);
|
||||
|
||||
IMemberType GetMemberType(string alias);
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMemberType"/> object by its Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="IMediaType"/> to retrieve</param>
|
||||
/// <returns><see cref="IMediaType"/></returns>
|
||||
IMemberType GetMemberType(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMemberType"/> object by its Alias
|
||||
/// </summary>
|
||||
/// <param name="alias">Alias of the <see cref="IMediaType"/> to retrieve</param>
|
||||
/// <returns><see cref="IMediaType"/></returns>
|
||||
IMemberType GetMemberType(string alias);
|
||||
}
|
||||
}
|
||||
@@ -37,17 +37,11 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public IMemberType GetMemberType(string alias)
|
||||
{
|
||||
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork()))
|
||||
{
|
||||
var query = Query<IMemberType>.Builder.Where(x => x.Alias == alias);
|
||||
var memberTypes = repository.GetByQuery(query);
|
||||
|
||||
return memberTypes.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMemberType"/> object by its Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="IMemberType"/> to retrieve</param>
|
||||
/// <returns><see cref="IMemberType"/></returns>
|
||||
public IMemberType GetMemberType(int id)
|
||||
{
|
||||
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork()))
|
||||
@@ -56,5 +50,21 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMemberType"/> object by its Alias
|
||||
/// </summary>
|
||||
/// <param name="alias">Alias of the <see cref="IMemberType"/> to retrieve</param>
|
||||
/// <returns><see cref="IMemberType"/></returns>
|
||||
public IMemberType GetMemberType(string alias)
|
||||
{
|
||||
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork()))
|
||||
{
|
||||
var query = Query<IMemberType>.Builder.Where(x => x.Alias == alias);
|
||||
var contentTypes = repository.GetByQuery(query);
|
||||
|
||||
return contentTypes.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -358,6 +358,7 @@
|
||||
<Compile Include="Models\Membership\MembershipExtensions.cs" />
|
||||
<Compile Include="Models\Membership\UmbracoMembershipMember.cs" />
|
||||
<Compile Include="Models\Membership\UmbracoMembershipUser.cs" />
|
||||
<Compile Include="Models\PagedResult.cs" />
|
||||
<Compile Include="Models\PreValue.cs" />
|
||||
<Compile Include="Models\MemberType.cs" />
|
||||
<Compile Include="Models\PreValueCollection.cs" />
|
||||
@@ -450,6 +451,7 @@
|
||||
<Compile Include="Persistence\Repositories\TagsRepository.cs" />
|
||||
<Compile Include="PropertyEditors\BackwardsCompatibleData.cs" />
|
||||
<Compile Include="PropertyEditors\BackwardsCompatibleDataType.cs" />
|
||||
<Compile Include="PropertyEditors\EmailValidator.cs" />
|
||||
<Compile Include="PropertyEditors\IParameterEditor.cs" />
|
||||
<Compile Include="PropertyEditors\LegacyParameterEditorAliasConverter.cs" />
|
||||
<Compile Include="PropertyEditors\LegacyPropertyEditorIdToAliasConverter.cs" />
|
||||
|
||||
@@ -12,6 +12,7 @@ function sectionsDirective($timeout, $window, navigationService, sectionResource
|
||||
|
||||
scope.maxSections = 7;
|
||||
scope.overflowingSections = 0;
|
||||
scope.sections = [];
|
||||
|
||||
function loadSections(){
|
||||
sectionResource.getSections()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
angular.module('umbraco.directives.validation')
|
||||
.directive('valCompare',function () {
|
||||
return {
|
||||
require: "ngModel",
|
||||
link: function(scope, elem, attrs, ctrl) {
|
||||
require: "ngModel",
|
||||
link: function (scope, elem, attrs, ctrl) {
|
||||
|
||||
//TODO: Pretty sure this should be done using a requires ^form in the directive declaration
|
||||
var otherInput = elem.inheritedData("$formController")[attrs.valCompare];
|
||||
|
||||
ctrl.$parsers.push(function(value) {
|
||||
|
||||
@@ -6,17 +6,36 @@
|
||||
* NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string
|
||||
**/
|
||||
function valRegex() {
|
||||
|
||||
return {
|
||||
require: 'ngModel',
|
||||
restrict: "A",
|
||||
link: function (scope, elm, attrs, ctrl) {
|
||||
|
||||
var flags = "";
|
||||
if (attrs.valRegexFlags) {
|
||||
try {
|
||||
flags = scope.$eval(attrs.valRegexFlags);
|
||||
if (!flags) {
|
||||
flags = attrs.valRegexFlags;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
flags = attrs.valRegexFlags;
|
||||
}
|
||||
}
|
||||
var regex;
|
||||
try {
|
||||
regex = new RegExp(scope.$eval(attrs.valRegex));
|
||||
var resolved = scope.$eval(attrs.valRegex);
|
||||
if (resolved) {
|
||||
regex = new RegExp(resolved, flags);
|
||||
}
|
||||
else {
|
||||
regex = new RegExp(attrs.valRegex, flags);
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
regex = new RegExp(attrs.valRegex);
|
||||
regex = new RegExp(attrs.valRegex, flags);
|
||||
}
|
||||
|
||||
var patternValidator = function (viewValue) {
|
||||
|
||||
@@ -203,7 +203,7 @@ function umbDataFormatter() {
|
||||
|
||||
/** formats the display model used to display the member to the model used to save the member */
|
||||
formatMemberPostData: function(displayModel, action) {
|
||||
//this is basically the same as for media but we need to explicitly add the username,email to the save model
|
||||
//this is basically the same as for media but we need to explicitly add the username,email, password to the save model
|
||||
var saveModel = this.formatMediaPostData(displayModel, action);
|
||||
|
||||
var genericTab = _.find(displayModel.tabs, function (item) {
|
||||
@@ -216,8 +216,17 @@ function umbDataFormatter() {
|
||||
var propEmail = _.find(genericTab.properties, function (item) {
|
||||
return item.alias === "_umb_email";
|
||||
});
|
||||
var propPass = _.find(genericTab.properties, function (item) {
|
||||
return item.alias === "_umb_password";
|
||||
});
|
||||
saveModel.email = propEmail.value;
|
||||
saveModel.username = propLogin.value;
|
||||
//NOTE: This would only be set for new members!
|
||||
if (angular.isString(propPass.value)) {
|
||||
// if we are resetting or changing passwords then that data will come from the property editor and
|
||||
// it's value will be an object not just a string.
|
||||
saveModel.password = propPass.value;
|
||||
}
|
||||
|
||||
return saveModel;
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<form novalidate name="contentForm"
|
||||
ng-controller="Umbraco.Editors.DataType.EditController"
|
||||
ng-show="loaded"
|
||||
ng-submit="save()">
|
||||
ng-submit="save()"
|
||||
val-status-changed>
|
||||
<umb-panel val-show-validation>
|
||||
|
||||
<umb-header>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordController",
|
||||
function($scope) {
|
||||
|
||||
//the model config will contain an object, if it does not we'll create defaults
|
||||
//NOTE: We will not support doing the password regex on the client side because the regex on the server side
|
||||
//based on the membership provider cannot always be ported to js from .net directly.
|
||||
/*
|
||||
{
|
||||
requiresQuestionAnswer: true/false,
|
||||
enableReset: true/false,
|
||||
minPasswordLength: 10
|
||||
}
|
||||
*/
|
||||
|
||||
//set defaults if they are not available
|
||||
if (!$scope.model.config || !$scope.model.config.requiresQuestionAnswer) {
|
||||
$scope.model.config.requiresQuestionAnswer = false;
|
||||
}
|
||||
if (!$scope.model.config || !$scope.model.config.enableReset) {
|
||||
$scope.model.config.enableReset = true;
|
||||
}
|
||||
if (!$scope.model.config || !$scope.model.config.minPasswordLength) {
|
||||
$scope.model.config.minPasswordLength = 7;
|
||||
}
|
||||
|
||||
|
||||
$scope.confirm = "";
|
||||
|
||||
$scope.hasPassword = $scope.model.value !== undefined && $scope.model.value !== null && $scope.model.value !== "";
|
||||
|
||||
$scope.changing = !$scope.hasPassword;
|
||||
|
||||
$scope.doChange = function() {
|
||||
$scope.changing = true;
|
||||
};
|
||||
|
||||
$scope.cancelChange = function() {
|
||||
$scope.changing = false;
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
<div ng-controller="Umbraco.PropertyEditors.ChangePasswordController" ng-switch="changing">
|
||||
<div ng-switch-when="false">
|
||||
<!--<a href="" ng-click="doChange()">Change password</a> -->
|
||||
<div class="control-group warning">
|
||||
<span class="help-block">Password changing or resetting is currently not supported</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="true">
|
||||
<div class="control-group">
|
||||
|
||||
<input type="text" name="password" ng-model="model.value" id="{{model.alias}}"
|
||||
class="umb-editor umb-textstring textstring"
|
||||
required
|
||||
val-server
|
||||
ng-minlength="{{model.config.minPasswordLength}}" />
|
||||
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="required">Required</span>
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="minlength">Minimum {{model.config.minPasswordLength}} characters</span>
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="valServer"></span>
|
||||
|
||||
</div>
|
||||
<div class="control-group">
|
||||
|
||||
<input type="text" name="confirmpassword" ng-model="model.confirm" id="{{model.alias}}"
|
||||
class="umb-editor umb-textstring textstring"
|
||||
val-compare="password" />
|
||||
|
||||
<span class="help-inline" val-msg-for="confirmpassword" val-toggle-msg="valCompare">Passwords must match</span>
|
||||
|
||||
</div>
|
||||
<!--<a href="" ng-click="cancelChange()" ng-show="hasPassword">Cancel</a> -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
angular.module("umbraco")
|
||||
.controller("Umbraco.PropertyEditors.EmailController",
|
||||
function($rootScope, $scope, dialogService, $routeParams, contentResource, contentTypeResource, editorContextService, notificationsService) {
|
||||
|
||||
|
||||
});
|
||||
@@ -1,3 +1,13 @@
|
||||
<input type="email" name="textbox" ng-model="model.value" id="{{model.alias}}" class="umb-editor umb-textstring textstring" val-server="value" />
|
||||
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="email">Invalid email</span>
|
||||
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="valServer"></span>
|
||||
<div>
|
||||
|
||||
<input type="email" name="textbox"
|
||||
ng-model="model.value"
|
||||
id="{{model.alias}}"
|
||||
class="umb-editor umb-textstring textstring"
|
||||
ng-required="model.config.IsRequired"
|
||||
val-server="value" />
|
||||
|
||||
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="required">Required</span>
|
||||
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="email">Invalid email</span>
|
||||
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="valServer"></span>
|
||||
</div>
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
<input type="text" name="textbox" ng-model="model.value" id="{{model.alias}}" class="umb-editor umb-textstring textstring" val-server="value" />
|
||||
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="valServer"></span>
|
||||
<div>
|
||||
<input type="text" name="textbox" ng-model="model.value" id="{{model.alias}}"
|
||||
class="umb-editor umb-textstring textstring"
|
||||
val-server="value"
|
||||
ng-required="model.config.IsRequired" />
|
||||
|
||||
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="required">Required</span>
|
||||
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="valServer"></span>
|
||||
</div>
|
||||
|
||||
@@ -255,7 +255,7 @@ namespace Umbraco.Web.Editors
|
||||
else
|
||||
{
|
||||
//publish the item and check if it worked, if not we will show a diff msg below
|
||||
publishStatus = ((ContentService)Services.ContentService).SaveAndPublishInternal(contentItem.PersistedContent, (int)Security.CurrentUser.Id);
|
||||
publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, (int)Security.CurrentUser.Id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using AutoMapper;
|
||||
using Examine.LuceneEngine.SearchCriteria;
|
||||
using Examine.SearchCriteria;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
@@ -77,9 +81,8 @@ namespace Umbraco.Web.Editors
|
||||
/// Gets an empty content item for the
|
||||
/// </summary>
|
||||
/// <param name="contentTypeAlias"></param>
|
||||
/// <param name="parentId"></param>
|
||||
/// <returns></returns>
|
||||
public MemberDisplay GetEmpty(string contentTypeAlias, string username, string password)
|
||||
public MemberDisplay GetEmpty(string contentTypeAlias)
|
||||
{
|
||||
var contentType = Services.MemberTypeService.GetMemberType(contentTypeAlias);
|
||||
if (contentType == null)
|
||||
@@ -87,7 +90,7 @@ namespace Umbraco.Web.Editors
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
var emptyContent = new Core.Models.Member("", "", "", "", -1, contentType);
|
||||
var emptyContent = new Core.Models.Member("", contentType);
|
||||
return Mapper.Map<IMember, MemberDisplay>(emptyContent);
|
||||
}
|
||||
|
||||
@@ -96,6 +99,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[FileUploadCleanupFilter]
|
||||
[MembershipProviderValidationFilter]
|
||||
public MemberDisplay PostSave(
|
||||
[ModelBinder(typeof(MemberBinder))]
|
||||
MemberSave contentItem)
|
||||
@@ -109,30 +113,18 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
UpdateName(contentItem);
|
||||
|
||||
//map the custom properties
|
||||
//map the custom properties - this will already be set for new entities in our member binder
|
||||
contentItem.PersistedContent.Email = contentItem.Email;
|
||||
//TODO: If we allow changing the alias then we'll need to change URLs, etc... in the editor, would prefer to use
|
||||
// a unique id but then need to figure out how to handle that with custom membership providers - waiting on feedback from morten.
|
||||
|
||||
contentItem.PersistedContent.Username = contentItem.Username;
|
||||
|
||||
MapPropertyValues(contentItem);
|
||||
|
||||
//We need to manually check the validation results here because:
|
||||
// * We still need to save the entity even if there are validation value errors
|
||||
// * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null)
|
||||
// then we cannot continue saving, we can only display errors
|
||||
// * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display
|
||||
// a message indicating this
|
||||
//Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem)
|
||||
&& (contentItem.Action == ContentSaveAction.SaveNew))
|
||||
{
|
||||
//ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
|
||||
// add the modelstate to the outgoing object and throw validation response
|
||||
var forDisplay = Mapper.Map<IMember, MemberDisplay>(contentItem.PersistedContent);
|
||||
forDisplay.Errors = ModelState.ToErrorDictionary();
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
|
||||
}
|
||||
{
|
||||
var forDisplay = Mapper.Map<IMember, MemberDisplay>(contentItem.PersistedContent);
|
||||
forDisplay.Errors = ModelState.ToErrorDictionary();
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
|
||||
}
|
||||
|
||||
//save the item
|
||||
@@ -175,4 +167,75 @@ namespace Umbraco.Web.Editors
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This validates the submitted data in regards to the current membership provider
|
||||
/// </summary>
|
||||
internal class MembershipProviderValidationFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
base.OnActionExecuting(actionContext);
|
||||
|
||||
var membershipProvider = Membership.Providers[Constants.Conventions.Member.UmbracoMemberProviderName];
|
||||
if (membershipProvider == null)
|
||||
{
|
||||
throw new InvalidOperationException("No membership provider found with name " + Constants.Conventions.Member.UmbracoMemberProviderName);
|
||||
}
|
||||
|
||||
var contentItem = (MemberSave) actionContext.ActionArguments["contentItem"];
|
||||
|
||||
var validEmail = ValidateUniqueEmail(contentItem, membershipProvider, actionContext);
|
||||
if (validEmail == false)
|
||||
{
|
||||
actionContext.ModelState.AddPropertyError(new ValidationResult("Email address is already in use"), "umb_email");
|
||||
}
|
||||
}
|
||||
|
||||
internal bool ValidateUniqueEmail(MemberSave contentItem, MembershipProvider membershipProvider, HttpActionContext actionContext)
|
||||
{
|
||||
if (contentItem == null) throw new ArgumentNullException("contentItem");
|
||||
if (membershipProvider == null) throw new ArgumentNullException("membershipProvider");
|
||||
|
||||
if (membershipProvider.RequiresUniqueEmail == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int totalRecs;
|
||||
var existingByEmail = membershipProvider.FindUsersByEmail(contentItem.Email.Trim(), 0, int.MaxValue, out totalRecs);
|
||||
switch (contentItem.Action)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
//ok, we're updating the member, we need to check if they are changing their email and if so, does it exist already ?
|
||||
if (contentItem.PersistedContent.Email.InvariantEquals(contentItem.Email.Trim()) == false)
|
||||
{
|
||||
//they are changing their email
|
||||
if (existingByEmail.Cast<MembershipUser>().Select(x => x.Email)
|
||||
.Any(x => x.InvariantEquals(contentItem.Email.Trim())))
|
||||
{
|
||||
//the user cannot use this email
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ContentSaveAction.SaveNew:
|
||||
//check if the user's email already exists
|
||||
if (existingByEmail.Cast<MembershipUser>().Select(x => x.Email)
|
||||
.Any(x => x.InvariantEquals(contentItem.Email.Trim())))
|
||||
{
|
||||
//the user cannot use this email
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case ContentSaveAction.Publish:
|
||||
case ContentSaveAction.PublishNew:
|
||||
default:
|
||||
//we don't support this for members
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Web.Mvc;
|
||||
|
||||
@@ -48,6 +49,32 @@ namespace Umbraco.Web
|
||||
// state.AddModelError("DataValidation", errorMessage);
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the error to model state correctly for a property so we can use it on the client side.
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="propertyAlias"></param>
|
||||
internal static void AddPropertyError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, ValidationResult result, string propertyAlias)
|
||||
{
|
||||
//if there are no member names supplied then we assume that the validation message is for the overall property
|
||||
// not a sub field on the property editor
|
||||
if (!result.MemberNames.Any())
|
||||
{
|
||||
//add a model state error for the entire property
|
||||
modelState.AddModelError(string.Format("{0}.{1}", "Properties", propertyAlias), result.ErrorMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
//there's assigned field names so we'll combine the field name with the property name
|
||||
// so that we can try to match it up to a real sub field of this editor
|
||||
foreach (var field in result.MemberNames)
|
||||
{
|
||||
modelState.AddModelError(string.Format("{0}.{1}.{2}", "Properties", propertyAlias, field), result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IDictionary<string, object> ToErrorDictionary(this System.Web.Http.ModelBinding.ModelStateDictionary modelState)
|
||||
{
|
||||
var modelStateError = new Dictionary<string, object>();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using System.Collections.Generic;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Mapping;
|
||||
@@ -53,7 +54,9 @@ namespace Umbraco.Web.Models.Mapping
|
||||
config.CreateMap<IMember, ContentItemDto<IMember>>()
|
||||
.ForMember(
|
||||
dto => dto.Owner,
|
||||
expression => expression.ResolveUsing<OwnerResolver<IMember>>());
|
||||
expression => expression.ResolveUsing<OwnerResolver<IMember>>())
|
||||
//do no map the custom member properties (currently anyways, they were never there in 6.x)
|
||||
.ForMember(dto => dto.Properties, expression => expression.ResolveUsing<MemberDtoPropertiesValueResolver>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,7 +73,8 @@ namespace Umbraco.Web.Models.Mapping
|
||||
Alias = string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
|
||||
Label = ui.Text("login"),
|
||||
Value = display.Username,
|
||||
View = "textbox"
|
||||
View = "textbox",
|
||||
Config = new Dictionary<string, object> { { "IsRequired", true } }
|
||||
},
|
||||
new ContentPropertyDisplay
|
||||
{
|
||||
@@ -84,10 +88,28 @@ namespace Umbraco.Web.Models.Mapping
|
||||
Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
|
||||
Label = ui.Text("general", "email"),
|
||||
Value = display.Email,
|
||||
View = "textbox"
|
||||
View = "email",
|
||||
Config = new Dictionary<string, object> {{"IsRequired", true}}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This ensures that the custom membership provider properties are not mapped (currently since they weren't there in v6)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Because these properties don't exist on the form, if we don't remove them for this map we'll get validation errors when posting data
|
||||
/// </remarks>
|
||||
internal class MemberDtoPropertiesValueResolver : ValueResolver<IMember, IEnumerable<ContentPropertyDto>>
|
||||
{
|
||||
protected override IEnumerable<ContentPropertyDto> ResolveCore(IMember source)
|
||||
{
|
||||
var exclude = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray();
|
||||
return source.Properties
|
||||
.Where(x => exclude.Contains(x.Alias) == false)
|
||||
.Select(Mapper.Map<Property, ContentPropertyDto>);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a paged result for a model collection
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[DataContract(Name = "pagedCollection", Namespace = "")]
|
||||
public class PagedResult<T>
|
||||
{
|
||||
public PagedResult(long totalItems, long pageNumber, long pageSize)
|
||||
{
|
||||
TotalItems = totalItems;
|
||||
PageNumber = pageNumber;
|
||||
PageSize = pageSize;
|
||||
|
||||
if (pageSize > 0)
|
||||
{
|
||||
TotalPages = (long) Math.Ceiling(totalItems/(Decimal) pageSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
TotalPages = 1;
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember(Name = "pageNumber")]
|
||||
public long PageNumber { get; private set; }
|
||||
|
||||
[DataMember(Name = "pageSize")]
|
||||
public long PageSize { get; private set; }
|
||||
|
||||
[DataMember(Name = "totalPages")]
|
||||
public long TotalPages { get; private set; }
|
||||
|
||||
[DataMember(Name = "totalItems")]
|
||||
public long TotalItems { get; private set; }
|
||||
|
||||
[DataMember(Name = "items")]
|
||||
public IEnumerable<T> Items { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the skip size based on the paged parameters specified
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns 0 if the page number or page size is zero
|
||||
/// </remarks>
|
||||
internal int SkipSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PageNumber > 0 && PageSize > 0)
|
||||
{
|
||||
return Convert.ToInt32((PageNumber - 1)*PageSize);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
{
|
||||
[PropertyEditor(Constants.PropertyEditors.EmailAddressAlias, "Email address", "email")]
|
||||
public class EmailAddressPropertyEditor : PropertyEditor
|
||||
{
|
||||
protected override PropertyValueEditor CreateValueEditor()
|
||||
{
|
||||
var editor = base.CreateValueEditor();
|
||||
//add an email address validator
|
||||
editor.Validators.Add(new EmailValidator());
|
||||
return editor;
|
||||
}
|
||||
|
||||
protected override PreValueEditor CreatePreValueEditor()
|
||||
{
|
||||
return new EmailAddressePreValueEditor();
|
||||
}
|
||||
|
||||
internal class EmailAddressePreValueEditor : PreValueEditor
|
||||
{
|
||||
[PreValueField("Required?", "boolean")]
|
||||
public bool IsRequired { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -327,6 +327,7 @@
|
||||
<Compile Include="Models\Mapping\MacroModelMapper.cs" />
|
||||
<Compile Include="Models\Mapping\MemberModelMapper.cs" />
|
||||
<Compile Include="PropertyEditors\ColorListPreValueEditor.cs" />
|
||||
<Compile Include="PropertyEditors\EmailAddressPropertyEditor.cs" />
|
||||
<Compile Include="PropertyEditors\ListViewPropertyEditor.cs" />
|
||||
<Compile Include="PropertyEditors\ParameterEditors\TextAreaParameterEditor.cs" />
|
||||
<Compile Include="PropertyEditors\ParameterEditors\TextParameterEditor.cs" />
|
||||
@@ -351,7 +352,6 @@
|
||||
<Compile Include="Models\Mapping\DataTypeModelMapper.cs" />
|
||||
<Compile Include="Models\Mapping\EntityModelMapper.cs" />
|
||||
<Compile Include="Models\Mapping\PreValueDisplayResolver.cs" />
|
||||
<Compile Include="Models\PagedResult.cs" />
|
||||
<Compile Include="PropertyEditors\CheckBoxListPropertyEditor.cs" />
|
||||
<Compile Include="PropertyEditors\ColorPickerPropertyEditor.cs" />
|
||||
<Compile Include="PropertyEditors\DatePropertyEditor.cs" />
|
||||
|
||||
@@ -25,7 +25,8 @@ namespace Umbraco.Web.WebApi.Binders
|
||||
|
||||
protected override IMember GetExisting(MemberSave model)
|
||||
{
|
||||
//TODO: We're going to remove the built-in member properties from this editor - not sure if we should be persisting them elsewhere ?
|
||||
//TODO: We're going to remove the built-in member properties from this editor - We didn't support these in 6.x so
|
||||
// pretty hard to support them in 7 when the member type editor is using the old APIs!
|
||||
var toRemove = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray();
|
||||
|
||||
var member = ApplicationContext.Services.MemberService.GetByUsername(model.Username);
|
||||
@@ -38,11 +39,21 @@ namespace Umbraco.Web.WebApi.Binders
|
||||
|
||||
protected override IMember CreateNew(MemberSave model)
|
||||
{
|
||||
var contentType = ApplicationContext.Services.ContentTypeService.GetMemberType(model.ContentTypeAlias);
|
||||
var contentType = ApplicationContext.Services.MemberTypeService.GetMemberType(model.ContentTypeAlias);
|
||||
if (contentType == null)
|
||||
{
|
||||
throw new InvalidOperationException("No member type found wth alias " + model.ContentTypeAlias);
|
||||
}
|
||||
|
||||
//TODO: We're going to remove the built-in member properties from this editor - We didn't support these in 6.x so
|
||||
// pretty hard to support them in 7 when the member type editor is using the old APIs!
|
||||
var toRemove = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray();
|
||||
foreach (var remove in toRemove)
|
||||
{
|
||||
contentType.RemovePropertyType(remove);
|
||||
}
|
||||
|
||||
//return the new member with the details filled in
|
||||
return new Member(model.Name, model.Email, model.Username, model.Password, -1, contentType);
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
|
||||
foreach (var result in editor.ValueEditor.Validators.SelectMany(v => v.Validate(postedValue, preValues, editor)))
|
||||
{
|
||||
AddError(actionContext.ModelState, result, p.Alias);
|
||||
actionContext.ModelState.AddPropertyError(result, p.Alias);
|
||||
}
|
||||
|
||||
//Now we need to validate the property based on the PropertyType validation (i.e. regex and required)
|
||||
@@ -148,7 +148,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
foreach (var result in p.PropertyEditor.ValueEditor.RequiredValidator.Validate(postedValue, "", preValues, editor))
|
||||
{
|
||||
AddError(actionContext.ModelState, result, p.Alias);
|
||||
actionContext.ModelState.AddPropertyError(result, p.Alias);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
foreach (var result in p.PropertyEditor.ValueEditor.RegexValidator.Validate(postedValue, p.ValidationRegExp, preValues, editor))
|
||||
{
|
||||
AddError(actionContext.ModelState, result, p.Alias);
|
||||
actionContext.ModelState.AddPropertyError(result, p.Alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,30 +164,6 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
return actionContext.ModelState.IsValid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the error to model state correctly for a property so we can use it on the client side.
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="propertyAlias"></param>
|
||||
private void AddError(ModelStateDictionary modelState, ValidationResult result, string propertyAlias)
|
||||
{
|
||||
//if there are no member names supplied then we assume that the validation message is for the overall property
|
||||
// not a sub field on the property editor
|
||||
if (!result.MemberNames.Any())
|
||||
{
|
||||
//add a model state error for the entire property
|
||||
modelState.AddModelError(string.Format("{0}.{1}", "Properties", propertyAlias), result.ErrorMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
//there's assigned field names so we'll combine the field name with the property name
|
||||
// so that we can try to match it up to a real sub field of this editor
|
||||
foreach (var field in result.MemberNames)
|
||||
{
|
||||
modelState.AddModelError(string.Format("{0}.{1}.{2}", "Properties", propertyAlias, field), result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user