2020-12-08 15:36:37 +01:00
using System ;
2018-06-29 19:52:40 +02:00
using System.Collections.Generic ;
2018-10-01 14:32:46 +02:00
using System.IO ;
2018-06-29 19:52:40 +02:00
using System.Linq ;
2020-06-12 22:13:43 +02:00
using System.Net.Mime ;
2018-06-29 19:52:40 +02:00
using System.Text ;
2020-12-22 16:36:07 +01:00
using System.Threading.Tasks ;
using Microsoft.AspNetCore.Authorization ;
2020-06-09 13:48:50 +02:00
using Microsoft.AspNetCore.Http.Extensions ;
using Microsoft.AspNetCore.Mvc ;
2020-09-21 09:52:58 +02:00
using Microsoft.Extensions.Logging ;
2018-06-29 19:52:40 +02:00
using Umbraco.Core ;
2019-12-20 17:36:44 +01:00
using Umbraco.Core.Dictionary ;
using Umbraco.Core.Events ;
2020-06-09 13:48:50 +02:00
using Umbraco.Core.Mapping ;
2018-06-29 19:52:40 +02:00
using Umbraco.Core.Models ;
2018-09-20 17:22:39 +02:00
using Umbraco.Core.Models.ContentEditing ;
2019-12-20 17:36:44 +01:00
using Umbraco.Core.Models.Membership ;
2018-06-29 19:52:40 +02:00
using Umbraco.Core.Models.Validation ;
2019-12-20 17:36:44 +01:00
using Umbraco.Core.Persistence ;
using Umbraco.Core.Persistence.Querying ;
2018-07-17 14:23:07 +10:00
using Umbraco.Core.PropertyEditors ;
2019-12-20 17:36:44 +01:00
using Umbraco.Core.Security ;
2020-11-17 20:27:10 +01:00
using Umbraco.Core.Serialization ;
2019-12-20 17:36:44 +01:00
using Umbraco.Core.Services ;
using Umbraco.Core.Strings ;
2020-06-09 13:48:50 +02:00
using Umbraco.Extensions ;
2020-12-22 16:36:07 +01:00
using Umbraco.Web.Actions ;
using Umbraco.Web.BackOffice.ActionResults ;
using Umbraco.Web.BackOffice.Authorization ;
2020-06-09 13:48:50 +02:00
using Umbraco.Web.BackOffice.Filters ;
2020-06-12 22:13:43 +02:00
using Umbraco.Web.BackOffice.ModelBinders ;
2020-12-22 16:36:07 +01:00
using Umbraco.Web.Common.ActionsResults ;
2020-06-09 13:48:50 +02:00
using Umbraco.Web.Common.Attributes ;
2020-12-22 16:36:07 +01:00
using Umbraco.Web.Common.Authorization ;
using Umbraco.Web.ContentApps ;
using Umbraco.Web.Models.ContentEditing ;
2020-06-09 13:48:50 +02:00
using Umbraco.Web.Models.Mapping ;
2020-12-22 16:36:07 +01:00
using Umbraco.Web.Routing ;
2018-08-07 15:48:21 +01:00
2020-09-22 11:07:01 +02:00
namespace Umbraco.Web.BackOffice.Controllers
2018-06-29 19:52:40 +02:00
{
/// <summary>
/// The API controller used for editing content
/// </summary>
2020-08-04 12:27:21 +10:00
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
2020-11-19 22:17:42 +11:00
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
2021-01-29 10:30:28 +01:00
[ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))]
[ParameterSwapControllerActionSelector(nameof(GetNiceUrl), "id", typeof(int), typeof(Guid), typeof(Udi))]
2018-06-29 19:52:40 +02:00
public class ContentController : ContentControllerBase
{
2018-07-17 14:23:07 +10:00
private readonly PropertyEditorCollection _propertyEditors ;
2020-06-09 13:48:50 +02:00
private readonly IContentService _contentService ;
private readonly ILocalizedTextService _localizedTextService ;
private readonly IUserService _userService ;
2020-10-21 16:51:00 +11:00
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor ;
2020-06-09 13:48:50 +02:00
private readonly IEntityService _entityService ;
private readonly IContentTypeService _contentTypeService ;
private readonly UmbracoMapper _umbracoMapper ;
private readonly IPublishedUrlProvider _publishedUrlProvider ;
private readonly IPublicAccessService _publicAccessService ;
private readonly IDomainService _domainService ;
private readonly IDataTypeService _dataTypeService ;
private readonly ILocalizationService _localizationService ;
private readonly IMemberService _memberService ;
private readonly IFileService _fileService ;
private readonly INotificationService _notificationService ;
private readonly ActionCollection _actionCollection ;
private readonly IMemberGroupService _memberGroupService ;
private readonly ISqlContext _sqlContext ;
2020-11-23 22:43:41 +11:00
private readonly IAuthorizationService _authorizationService ;
2018-09-04 01:13:26 +10:00
private readonly Lazy < IDictionary < string , ILanguage > > _allLangs ;
2020-09-21 09:52:58 +02:00
private readonly ILogger < ContentController > _logger ;
2018-06-29 19:52:40 +02:00
2018-09-30 15:02:09 +01:00
public object Domains { get ; private set ; }
2019-12-18 18:55:00 +01:00
public ContentController (
ICultureDictionary cultureDictionary ,
2020-09-21 09:52:58 +02:00
ILoggerFactory loggerFactory ,
2020-02-14 13:04:49 +01:00
IShortStringHelper shortStringHelper ,
2020-06-12 11:14:06 +02:00
IEventMessagesFactory eventMessages ,
2020-06-09 13:48:50 +02:00
ILocalizedTextService localizedTextService ,
PropertyEditorCollection propertyEditors ,
IContentService contentService ,
IUserService userService ,
2020-10-21 16:51:00 +11:00
IBackOfficeSecurityAccessor backofficeSecurityAccessor ,
2020-06-09 13:48:50 +02:00
IEntityService entityService ,
IContentTypeService contentTypeService ,
2020-02-14 13:04:49 +01:00
UmbracoMapper umbracoMapper ,
2020-06-09 13:48:50 +02:00
IPublishedUrlProvider publishedUrlProvider ,
IPublicAccessService publicAccessService ,
IDomainService domainService ,
IDataTypeService dataTypeService ,
ILocalizationService localizationService ,
IMemberService memberService ,
IFileService fileService ,
INotificationService notificationService ,
ActionCollection actionCollection ,
IMemberGroupService memberGroupService ,
2020-11-17 20:27:10 +01:00
ISqlContext sqlContext ,
2020-11-23 22:43:41 +11:00
IJsonSerializer serializer ,
IAuthorizationService authorizationService )
2020-11-17 20:27:10 +01:00
: base ( cultureDictionary , loggerFactory , shortStringHelper , eventMessages , localizedTextService , serializer )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
_propertyEditors = propertyEditors ;
_contentService = contentService ;
_localizedTextService = localizedTextService ;
_userService = userService ;
2020-09-22 10:01:00 +02:00
_backofficeSecurityAccessor = backofficeSecurityAccessor ;
2020-06-09 13:48:50 +02:00
_entityService = entityService ;
_contentTypeService = contentTypeService ;
_umbracoMapper = umbracoMapper ;
_publishedUrlProvider = publishedUrlProvider ;
_publicAccessService = publicAccessService ;
_domainService = domainService ;
_dataTypeService = dataTypeService ;
_localizationService = localizationService ;
_memberService = memberService ;
_fileService = fileService ;
_notificationService = notificationService ;
_actionCollection = actionCollection ;
_memberGroupService = memberGroupService ;
_sqlContext = sqlContext ;
2020-11-23 22:43:41 +11:00
_authorizationService = authorizationService ;
2020-09-21 09:52:58 +02:00
_logger = loggerFactory . CreateLogger < ContentController > ( ) ;
2020-06-12 11:14:06 +02:00
_allLangs = new Lazy < IDictionary < string , ILanguage > > ( ( ) = > _localizationService . GetAllLanguages ( ) . ToDictionary ( x = > x . IsoCode , x = > x , StringComparer . InvariantCultureIgnoreCase ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Return content for the specified ids
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[FilterAllowedOutgoingContent(typeof(IEnumerable<ContentItemDisplay>))]
2020-12-02 14:21:05 +00:00
public IEnumerable < ContentItemDisplay > GetByIds ( [ FromQuery ] int [ ] ids )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var foundContent = _contentService . GetByIds ( ids ) ;
2019-01-21 15:57:48 +01:00
return foundContent . Select ( MapToDisplay ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Updates the permissions for a content item for a particular user group
/// </summary>
/// <param name="saveModel"></param>
/// <returns></returns>
/// <remarks>
2019-01-26 10:52:19 -05:00
/// Permission check is done for letter 'R' which is for <see cref="ActionRights"/> which the user must have access to update
2018-06-29 19:52:40 +02:00
/// </remarks>
2020-11-23 22:43:41 +11:00
public async Task < ActionResult < IEnumerable < AssignedUserGroupPermissions > > > PostSaveUserGroupPermissions ( UserGroupPermissionsSave saveModel )
2020-12-02 14:21:05 +00:00
{
if ( saveModel . ContentId < = 0 ) return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
2019-01-26 10:52:19 -05:00
// TODO: Should non-admins be allowed to set granular permissions?
2018-06-29 19:52:40 +02:00
2020-06-09 13:48:50 +02:00
var content = _contentService . GetById ( saveModel . ContentId ) ;
if ( content = = null ) return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
2020-11-23 22:43:41 +11:00
// Authorize...
2020-11-30 19:09:14 +11:00
var resource = new ContentPermissionsResource ( content , ActionRights . ActionLetter ) ;
var authorizationResult = await _authorizationService . AuthorizeAsync ( User , content , AuthorizationPolicies . ContentPermissionByResource ) ;
2020-11-23 22:43:41 +11:00
if ( ! authorizationResult . Succeeded )
{
return Forbid ( ) ;
}
2018-06-29 19:52:40 +02:00
//current permissions explicitly assigned to this content item
2020-06-09 13:48:50 +02:00
var contentPermissions = _contentService . GetPermissions ( content )
2018-06-29 19:52:40 +02:00
. ToDictionary ( x = > x . UserGroupId , x = > x ) ;
2020-06-09 13:48:50 +02:00
var allUserGroups = _userService . GetAllUserGroups ( ) . ToArray ( ) ;
2018-06-29 19:52:40 +02:00
//loop through each user group
foreach ( var userGroup in allUserGroups )
{
//check if there's a permission set posted up for this user group
IEnumerable < string > groupPermissions ;
if ( saveModel . AssignedPermissions . TryGetValue ( userGroup . Id , out groupPermissions ) )
{
//create a string collection of the assigned letters
var groupPermissionCodes = groupPermissions . ToArray ( ) ;
//check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions
//for this group/node which will go back to the defaults
if ( groupPermissionCodes . Length = = 0 )
{
2020-06-09 13:48:50 +02:00
_userService . RemoveUserGroupPermissions ( userGroup . Id , content . Id ) ;
2018-06-29 19:52:40 +02:00
}
//check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored
else if ( userGroup . Permissions . UnsortedSequenceEqual ( groupPermissionCodes ) )
{
//only remove them if they are actually currently assigned
if ( contentPermissions . ContainsKey ( userGroup . Id ) )
{
//remove these permissions from this node for this group since the ones being assigned are the same as the defaults
2020-06-09 13:48:50 +02:00
_userService . RemoveUserGroupPermissions ( userGroup . Id , content . Id ) ;
2018-06-29 19:52:40 +02:00
}
}
//if they are different we need to update, otherwise there's nothing to update
else if ( contentPermissions . ContainsKey ( userGroup . Id ) = = false | | contentPermissions [ userGroup . Id ] . AssignedPermissions . UnsortedSequenceEqual ( groupPermissionCodes ) = = false )
{
2020-06-09 13:48:50 +02:00
_userService . ReplaceUserGroupPermissions ( userGroup . Id , groupPermissionCodes . Select ( x = > x [ 0 ] ) , content . Id ) ;
2018-06-29 19:52:40 +02:00
}
}
}
return GetDetailedPermissions ( content , allUserGroups ) ;
}
/// <summary>
/// Returns the user group permissions for user groups assigned to this node
/// </summary>
/// <param name="contentId"></param>
/// <returns></returns>
/// <remarks>
2019-01-26 10:52:19 -05:00
/// Permission check is done for letter 'R' which is for <see cref="ActionRights"/> which the user must have access to view
2018-06-29 19:52:40 +02:00
/// </remarks>
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionAdministrationById)]
2020-06-09 13:48:50 +02:00
public ActionResult < IEnumerable < AssignedUserGroupPermissions > > GetDetailedPermissions ( int contentId )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
if ( contentId < = 0 ) return NotFound ( ) ;
var content = _contentService . GetById ( contentId ) ;
if ( content = = null ) return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
2019-01-27 01:17:32 -05:00
// TODO: Should non-admins be able to see detailed permissions?
2018-06-29 19:52:40 +02:00
2020-06-09 13:48:50 +02:00
var allUserGroups = _userService . GetAllUserGroups ( ) ;
2018-06-29 19:52:40 +02:00
return GetDetailedPermissions ( content , allUserGroups ) ;
2018-05-10 19:16:46 +10:00
}
2018-06-29 19:52:40 +02:00
2020-06-09 13:48:50 +02:00
private ActionResult < IEnumerable < AssignedUserGroupPermissions > > GetDetailedPermissions ( IContent content , IEnumerable < IUserGroup > allUserGroups )
2018-06-29 19:52:40 +02:00
{
//get all user groups and map their default permissions to the AssignedUserGroupPermissions model.
//we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults.
2020-06-09 13:48:50 +02:00
var defaultPermissionsByGroup = _umbracoMapper . MapEnumerable < IUserGroup , AssignedUserGroupPermissions > ( allUserGroups ) ;
2018-06-29 19:52:40 +02:00
var defaultPermissionsAsDictionary = defaultPermissionsByGroup
. ToDictionary ( x = > Convert . ToInt32 ( x . Id ) , x = > x ) ;
//get the actual assigned permissions
2020-06-09 13:48:50 +02:00
var assignedPermissionsByGroup = _contentService . GetPermissions ( content ) . ToArray ( ) ;
2018-06-29 19:52:40 +02:00
//iterate over assigned and update the defaults with the real values
foreach ( var assignedGroupPermission in assignedPermissionsByGroup )
{
var defaultUserGroupPermissions = defaultPermissionsAsDictionary [ assignedGroupPermission . UserGroupId ] ;
//clone the default permissions model to the assigned ones
defaultUserGroupPermissions . AssignedPermissions = AssignedUserGroupPermissions . ClonePermissions ( defaultUserGroupPermissions . DefaultPermissions ) ;
//since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions
//and we'll re-check it if it's one of the explicitly assigned ones
foreach ( var permission in defaultUserGroupPermissions . AssignedPermissions . SelectMany ( x = > x . Value ) )
{
permission . Checked = false ;
permission . Checked = assignedGroupPermission . AssignedPermissions . Contains ( permission . PermissionCode , StringComparer . InvariantCulture ) ;
}
}
return defaultPermissionsByGroup ;
}
/// <summary>
/// Returns an item to be used to display the recycle bin for content
/// </summary>
/// <returns></returns>
2020-06-09 13:48:50 +02:00
public ActionResult < ContentItemDisplay > GetRecycleBin ( )
2018-06-29 19:52:40 +02:00
{
2018-07-17 14:23:07 +10:00
var apps = new List < ContentApp > ( ) ;
2020-06-09 13:48:50 +02:00
apps . Add ( ListViewContentAppFactory . CreateContentApp ( _dataTypeService , _propertyEditors , "recycleBin" , "content" , Core . Constants . DataTypes . DefaultMembersListView ) ) ;
2018-07-17 14:23:07 +10:00
apps [ 0 ] . Active = true ;
2018-06-29 19:52:40 +02:00
var display = new ContentItemDisplay
{
2019-11-05 13:45:42 +01:00
Id = Constants . System . RecycleBinContent ,
2018-06-29 19:52:40 +02:00
ParentId = - 1 ,
ContentTypeAlias = "recycleBin" ,
IsContainer = true ,
2019-11-05 13:45:42 +01:00
Path = "-1," + Constants . System . RecycleBinContent ,
2018-07-19 19:32:07 +10:00
Variants = new List < ContentVariantDisplay >
2018-07-13 12:45:04 +10:00
{
new ContentVariantDisplay
{
CreateDate = DateTime . Now ,
2020-06-09 13:48:50 +02:00
Name = _localizedTextService . Localize ( "general/recycleBin" )
2018-07-13 12:45:04 +10:00
}
2018-07-17 14:23:07 +10:00
} ,
ContentApps = apps
2018-06-29 19:52:40 +02:00
} ;
return display ;
}
2020-06-09 13:48:50 +02:00
public ActionResult < ContentItemDisplay > GetBlueprintById ( int id )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var foundContent = _contentService . GetBlueprintById ( id ) ;
2018-06-29 19:52:40 +02:00
if ( foundContent = = null )
{
2020-06-09 13:48:50 +02:00
return HandleContentNotFound ( id ) ;
2018-06-29 19:52:40 +02:00
}
var content = MapToDisplay ( foundContent ) ;
SetupBlueprint ( content , foundContent ) ;
return content ;
}
private static void SetupBlueprint ( ContentItemDisplay content , IContent persistedContent )
{
content . AllowPreview = false ;
//set a custom path since the tree that renders this has the content type id as the parent
content . Path = string . Format ( "-1,{0},{1}" , persistedContent . ContentTypeId , content . Id ) ;
content . AllowedActions = new [ ] { "A" } ;
content . IsBlueprint = true ;
2019-01-27 01:17:32 -05:00
// TODO: exclude the content apps here
2018-07-13 12:45:04 +10:00
//var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" };
//var propsTab = content.Tabs.Last();
//propsTab.Properties = propsTab.Properties
// .Where(p => excludeProps.Contains(p.Alias) == false);
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gets the content json for the content id
/// </summary>
/// <param name="id"></param>
/// <param name="culture"></param>
/// <returns></returns>
2020-12-02 14:21:05 +00:00
[OutgoingEditorModelEvent]
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
2021-01-14 19:41:32 +01:00
public ActionResult < ContentItemDisplay > GetById ( int id )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var foundContent = GetObjectFromRequest ( ( ) = > _contentService . GetById ( id ) ) ;
2018-06-29 19:52:40 +02:00
if ( foundContent = = null )
{
2021-01-14 19:41:32 +01:00
return HandleContentNotFound ( id ) ;
2018-07-18 13:19:48 +10:00
}
2018-07-19 19:32:07 +10:00
var content = MapToDisplay ( foundContent ) ;
2018-07-18 13:19:48 +10:00
return content ;
}
2018-07-18 13:19:14 +10:00
/// <summary>
2020-01-08 19:02:16 +01:00
/// Gets the content json for the content guid
2018-07-18 13:19:14 +10:00
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
2020-12-02 14:21:05 +00:00
[OutgoingEditorModelEvent]
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
2021-01-14 19:41:32 +01:00
public ActionResult < ContentItemDisplay > GetById ( Guid id )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var foundContent = GetObjectFromRequest ( ( ) = > _contentService . GetById ( id ) ) ;
2018-06-29 19:52:40 +02:00
if ( foundContent = = null )
{
2021-01-14 19:41:32 +01:00
return HandleContentNotFound ( id ) ;
2018-06-29 19:52:40 +02:00
}
2018-07-13 12:45:04 +10:00
var content = MapToDisplay ( foundContent ) ;
2018-06-29 19:52:40 +02:00
return content ;
}
2018-07-18 13:19:14 +10:00
/// <summary>
2020-01-08 19:02:16 +01:00
/// Gets the content json for the content udi
2018-07-18 13:19:14 +10:00
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
2020-12-02 14:21:05 +00:00
[OutgoingEditorModelEvent]
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
2020-12-29 12:55:48 +01:00
public ActionResult < ContentItemDisplay > GetById ( Udi id )
2018-07-18 13:19:14 +10:00
{
var guidUdi = id as GuidUdi ;
if ( guidUdi ! = null )
{
2018-07-19 19:32:07 +10:00
return GetById ( guidUdi . Guid ) ;
2018-07-18 13:19:14 +10:00
}
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
2018-11-12 17:29:38 +11:00
}
2018-06-29 19:52:40 +02:00
/// <summary>
2020-01-08 19:02:16 +01:00
/// Gets an empty content item for the document type.
2018-06-29 19:52:40 +02:00
/// </summary>
/// <param name="contentTypeAlias"></param>
2018-12-07 13:44:41 +01:00
/// <param name="parentId"></param>
2020-12-02 14:21:05 +00:00
[OutgoingEditorModelEvent]
2020-12-29 12:55:48 +01:00
public ActionResult < ContentItemDisplay > GetEmpty ( string contentTypeAlias , int parentId )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var contentType = _contentTypeService . Get ( contentTypeAlias ) ;
2018-06-29 19:52:40 +02:00
if ( contentType = = null )
{
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
2021-01-29 10:30:28 +01:00
return GetEmptyInner ( contentType , parentId ) ;
Block Editor List (#8273)
* add style to create-option in itempicker + removing overflow hidden
* style adjustment
* clean up of html
* correct sentence to use the number 7
* correct overlays, so they can use size
* numberrange prevalue editor
* add confirmRemove overlay
* correcting primary btn colors
* move confirmMessage below content of overlay.
* min max validation for numberrange
* remove comment
* improved actions for block list
* use file tree for view picker
* style adjustment to border of creator item in item-picker
* vertical align
* clean up + validation limit range
* rename ElementTypes to Blocks
* implement block list editor
* renaming
* use Chrome Headless for unit tests
* test for blockEditorService
* safer code
* block list editor
* rename view to overlayView
* block editor work
* Revert "rename view to overlayView"
This reverts commit 5b910c178a4f193d190367c4f1da3402aa8c4d0e.
* block editor implementation
* sync models
* block list editor copy paste feature
* order var declarations
* remove unused paste function
* block list editor better naming
* simpler label generation
* clean up
* compile config for test mode
* Chrome Debug for VS code
* promise test working
* space change
* another two tests
* more tests for block list editor
* provide key on blockModel for angularJS performance optimization
* syncronization from infinite editing layers
* use an isolated scope
* more tests
* fix C# test
* remove unused block watcher component
* clean css
* only show on hover or focus for block-actions
* clean up and prepare for implementing settings
* remove console
* Add ability to render block list editor using Grid style rendering extension
* Enable Block List Editor settings editing
* Add Stacked Content to Block List migration
* Block Editor: Clean-up, refactoring, one step closer being ready for Content-Apps
* changes naming to Submit, to avoid misunderstanding.
* use a common variable on the block model for isOpen, to be able to make Actions show when open.
* NotSupported property editor, to be used when an editor is not supported in the given context.
* remove unused controller
* Hide group header if only one group is presented
* rename notsupport property editor css class
* smaller header for property group
* hide description if no description is presented
* css adjustments
* Inline create button styling: Better spacing, darker blue color for Focus Outline, moving the plus icon to mouse position for better visual appearance.
* css correction
* Add references for picked items
* Revert commit 45e892f3505059674779c6e1a43084a367c2862f - Changes api to GetData
* Use the .Data propertry as opposed to GetData in this PartialView
* Fix block list test failures
* Just parsing layout as model for partial views.
* minor adjustments
* Remove DB migrations so that they can be reviewed as a block
* Add migrations for new block editor
* Update default rendering partial view
* Add error handling to default template
* Handle color picker data in stacked content
* BlockList PreValue Editor opens configurations as overlay
* translation for prevalue editor property group headlines
* blockcard corrections
* block list prevalue corrections
* revert agressive clean up
* Block Picker
* MaxPropertyWidth PreValue + Implementation
* Incorporate latest block list editor changes, update migration for changed configuration
* Change declared converter type
* Handle invalid data type references
* Remove code duplicated from PR #7957
* use ElementModel for the ContentModel of an ElementType. So we can use ElementTypeModel for the ModelDefinition aka. the Type.
* do still show itempicker for BlockConfiguration even though there is no ElementTypes to pick. This enables the option to create a new ElementType to be used.
* use the right wrapper, for correct spacing
* parse item
* correct fallback for label
* removed unused callback
* paste feature for block-picker
* localize block-picker tabs
* Slightly change for shadow on block-picker item hover
* Localization of BlockEditor Settings Tab
* localizationService
* only filter when more than 8 items available
* Add multiple blocks if hold down CTRL or SuperKey
* adds notes
* ability to add a scoped stylesheet for block view
* make scoped block draggable + style adjustments
* provide index for custom view
* rename contentModel to data + rename layoutModel to layout
* clean up
* more localization
* openSettings option for block editor
* minor changes for a better developer experience
* paste mistake corrected
* only manipulate content-content-app if its present
* make small overlays slightly bigger
* moved block list entry editor into block list editor folder
* limit labelinterpretator to only runs ones pr. edit. and lets make sure to have a label thought we dont have any properties.
* fixed inline views gulp watcher
* changed vm to a better controller instance name
* make watch for views work again.
* able to re run watch
* make js up to date
* fix white background of image-picker
* media-picker container class
* loading indication
* adjust unit tests to latest interface
* getting started on JS Docs
* converting code to use contentTypeKey instead of contentTypeAlias, still missing a few TODOs.
* revert change
* add todo
* use Guid for Key
* use key
* Updates the caching layer to handle GUID keys for content types while preserving backwards compat, fixes unit tests, removes the strongly typed lists for the block editor value since it's unecessary
* Reverts the nested content changes, fixes up the GetEmptyByKey
* Returns ContentTypeKey from the server, updates js to use this everywhere and fix all js tests.
* Allows key in SimpleContentType
* correct for the new spelling
* appended this. since the method is a non-static class method.
* only add background-image if value isnt null
* simplifyed limits validation
* clean up
* no need to execute a digest.
* define the full model for new configurations
* removed setDataFromBlockModel and general clean up and added documentation
* default size should be medium
* use retriveValuesFrom method to transfer data.
* ability to disable an navigation item
* createElementTypeAndCallback working again for settings.
* still have the ability to retrive a scaffold model by alias, since we are still using alias in clipboard service.
* disable clipboard tab if no available paste options
* ups, should stay as alias
* disable clipboard when empty
* use property alias for content add button
* use a grey that can be seen on top of grey and on top of white.
* some accessibility improvements
* rename entry to block
* appended ' and added space in Element Type
* use background color for hover to corospond with active state
* make nested content unsupported
* Moving BlockEditorModelObject into its own file for Documentation purpose. Same goes for renaming BlockModel to BlockObject. and a lot of documentation written.
* fix links in js docs
* added a blocklistentryeditor for unsupported blocks
* ability to retrive the $scope from UmbVariantContentEditors, needed for Block Editor to get a scope existing across variants and splitview.
* Appending the block objects to layout, to share it across variants and in split-view.
* removed trailing comma
* Unsupported block
* Dont copy unsupported blocks
* use grey color for unsupported block
* text correction
* we dont have this fallback anymore
* sort properties
* Text change
* css correction
* use active to determine if an inline editor is expanded. To enable the inline editor to be expanded on creation.
* using udi instead of key.
* bringing the runtime block key back
* live editing ability
* live editing for settings data
* call formSubmit before property editor are removed from DOM. Both for overlay-editing and inline-editing. Fire an postFormSubmitting event after formSubmitting event to let editors know that data could have been updated.
* make sure settings object exists
* only set active to false if it was false before opening the editor.
* update test with new scope parameter
* move destroy responsibility to blockObject
* rename onDestroy to destroy
* added some JS-Docs
* correction jsDocs
* Update ElementType Resource to not use hardcoded URL but to use the Umbraco.Sys.ServerVariables.umbracoUrls instead
* Remove partially completed ConvertToElement migration, fixed in issue 7939 instead.
* Remove external property editor migration
* corrected naming of umbBlockListScopedBlock and umbBlockListBlock
* correct ngdoc type
* removed vscode specific configuration of karma
* Finished Todo, gets name of documentType if copying all entities in an infinite editor
* changed comment from TODO to something that explains the state.
* stop tracking build output files.
* rename files to match file name conventions
* this should not happen.
* remove dublicated code
* rename requestCopyBlock to copyBlock
* make sure images does not repeat.
* scale thumbnail for block showcase
* renamed blockcard.component to umb-block-card and moved it.
* removed inline style
* correct style location
* corrected filepath
* corrected file path
* keep elementTypes up to date through the EventService.
* mark Umbraco.BlockList as unsupported inside Nested Content
* correct js docs name
* remove comment
* remove comment
* remove unused controller
* rename inline method name
* corrected spelling mistake
* remove not very used vars
* make binding one-way
* split in multiple lines
* corrected default rendering
* removing documentation that is relevant for developers of new block editors. this documentation will be transfered to Our documentation.
* added danish translation
* corrected blog to blok
* Remove invalid using statement
* use native forEach
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Benjamin Carleski <benjamin@proworks.com>
Co-authored-by: Warren Buckley <warren@umbraco.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Claus <claus@claus.nu>
2020-06-30 19:52:42 +10:00
}
/// <summary>
/// Gets an empty content item for the document type.
/// </summary>
/// <param name="contentTypeKey"></param>
/// <param name="parentId"></param>
2020-12-02 14:21:05 +00:00
[OutgoingEditorModelEvent]
2020-12-29 12:55:48 +01:00
public ActionResult < ContentItemDisplay > GetEmptyByKey ( Guid contentTypeKey , int parentId )
Block Editor List (#8273)
* add style to create-option in itempicker + removing overflow hidden
* style adjustment
* clean up of html
* correct sentence to use the number 7
* correct overlays, so they can use size
* numberrange prevalue editor
* add confirmRemove overlay
* correcting primary btn colors
* move confirmMessage below content of overlay.
* min max validation for numberrange
* remove comment
* improved actions for block list
* use file tree for view picker
* style adjustment to border of creator item in item-picker
* vertical align
* clean up + validation limit range
* rename ElementTypes to Blocks
* implement block list editor
* renaming
* use Chrome Headless for unit tests
* test for blockEditorService
* safer code
* block list editor
* rename view to overlayView
* block editor work
* Revert "rename view to overlayView"
This reverts commit 5b910c178a4f193d190367c4f1da3402aa8c4d0e.
* block editor implementation
* sync models
* block list editor copy paste feature
* order var declarations
* remove unused paste function
* block list editor better naming
* simpler label generation
* clean up
* compile config for test mode
* Chrome Debug for VS code
* promise test working
* space change
* another two tests
* more tests for block list editor
* provide key on blockModel for angularJS performance optimization
* syncronization from infinite editing layers
* use an isolated scope
* more tests
* fix C# test
* remove unused block watcher component
* clean css
* only show on hover or focus for block-actions
* clean up and prepare for implementing settings
* remove console
* Add ability to render block list editor using Grid style rendering extension
* Enable Block List Editor settings editing
* Add Stacked Content to Block List migration
* Block Editor: Clean-up, refactoring, one step closer being ready for Content-Apps
* changes naming to Submit, to avoid misunderstanding.
* use a common variable on the block model for isOpen, to be able to make Actions show when open.
* NotSupported property editor, to be used when an editor is not supported in the given context.
* remove unused controller
* Hide group header if only one group is presented
* rename notsupport property editor css class
* smaller header for property group
* hide description if no description is presented
* css adjustments
* Inline create button styling: Better spacing, darker blue color for Focus Outline, moving the plus icon to mouse position for better visual appearance.
* css correction
* Add references for picked items
* Revert commit 45e892f3505059674779c6e1a43084a367c2862f - Changes api to GetData
* Use the .Data propertry as opposed to GetData in this PartialView
* Fix block list test failures
* Just parsing layout as model for partial views.
* minor adjustments
* Remove DB migrations so that they can be reviewed as a block
* Add migrations for new block editor
* Update default rendering partial view
* Add error handling to default template
* Handle color picker data in stacked content
* BlockList PreValue Editor opens configurations as overlay
* translation for prevalue editor property group headlines
* blockcard corrections
* block list prevalue corrections
* revert agressive clean up
* Block Picker
* MaxPropertyWidth PreValue + Implementation
* Incorporate latest block list editor changes, update migration for changed configuration
* Change declared converter type
* Handle invalid data type references
* Remove code duplicated from PR #7957
* use ElementModel for the ContentModel of an ElementType. So we can use ElementTypeModel for the ModelDefinition aka. the Type.
* do still show itempicker for BlockConfiguration even though there is no ElementTypes to pick. This enables the option to create a new ElementType to be used.
* use the right wrapper, for correct spacing
* parse item
* correct fallback for label
* removed unused callback
* paste feature for block-picker
* localize block-picker tabs
* Slightly change for shadow on block-picker item hover
* Localization of BlockEditor Settings Tab
* localizationService
* only filter when more than 8 items available
* Add multiple blocks if hold down CTRL or SuperKey
* adds notes
* ability to add a scoped stylesheet for block view
* make scoped block draggable + style adjustments
* provide index for custom view
* rename contentModel to data + rename layoutModel to layout
* clean up
* more localization
* openSettings option for block editor
* minor changes for a better developer experience
* paste mistake corrected
* only manipulate content-content-app if its present
* make small overlays slightly bigger
* moved block list entry editor into block list editor folder
* limit labelinterpretator to only runs ones pr. edit. and lets make sure to have a label thought we dont have any properties.
* fixed inline views gulp watcher
* changed vm to a better controller instance name
* make watch for views work again.
* able to re run watch
* make js up to date
* fix white background of image-picker
* media-picker container class
* loading indication
* adjust unit tests to latest interface
* getting started on JS Docs
* converting code to use contentTypeKey instead of contentTypeAlias, still missing a few TODOs.
* revert change
* add todo
* use Guid for Key
* use key
* Updates the caching layer to handle GUID keys for content types while preserving backwards compat, fixes unit tests, removes the strongly typed lists for the block editor value since it's unecessary
* Reverts the nested content changes, fixes up the GetEmptyByKey
* Returns ContentTypeKey from the server, updates js to use this everywhere and fix all js tests.
* Allows key in SimpleContentType
* correct for the new spelling
* appended this. since the method is a non-static class method.
* only add background-image if value isnt null
* simplifyed limits validation
* clean up
* no need to execute a digest.
* define the full model for new configurations
* removed setDataFromBlockModel and general clean up and added documentation
* default size should be medium
* use retriveValuesFrom method to transfer data.
* ability to disable an navigation item
* createElementTypeAndCallback working again for settings.
* still have the ability to retrive a scaffold model by alias, since we are still using alias in clipboard service.
* disable clipboard tab if no available paste options
* ups, should stay as alias
* disable clipboard when empty
* use property alias for content add button
* use a grey that can be seen on top of grey and on top of white.
* some accessibility improvements
* rename entry to block
* appended ' and added space in Element Type
* use background color for hover to corospond with active state
* make nested content unsupported
* Moving BlockEditorModelObject into its own file for Documentation purpose. Same goes for renaming BlockModel to BlockObject. and a lot of documentation written.
* fix links in js docs
* added a blocklistentryeditor for unsupported blocks
* ability to retrive the $scope from UmbVariantContentEditors, needed for Block Editor to get a scope existing across variants and splitview.
* Appending the block objects to layout, to share it across variants and in split-view.
* removed trailing comma
* Unsupported block
* Dont copy unsupported blocks
* use grey color for unsupported block
* text correction
* we dont have this fallback anymore
* sort properties
* Text change
* css correction
* use active to determine if an inline editor is expanded. To enable the inline editor to be expanded on creation.
* using udi instead of key.
* bringing the runtime block key back
* live editing ability
* live editing for settings data
* call formSubmit before property editor are removed from DOM. Both for overlay-editing and inline-editing. Fire an postFormSubmitting event after formSubmitting event to let editors know that data could have been updated.
* make sure settings object exists
* only set active to false if it was false before opening the editor.
* update test with new scope parameter
* move destroy responsibility to blockObject
* rename onDestroy to destroy
* added some JS-Docs
* correction jsDocs
* Update ElementType Resource to not use hardcoded URL but to use the Umbraco.Sys.ServerVariables.umbracoUrls instead
* Remove partially completed ConvertToElement migration, fixed in issue 7939 instead.
* Remove external property editor migration
* corrected naming of umbBlockListScopedBlock and umbBlockListBlock
* correct ngdoc type
* removed vscode specific configuration of karma
* Finished Todo, gets name of documentType if copying all entities in an infinite editor
* changed comment from TODO to something that explains the state.
* stop tracking build output files.
* rename files to match file name conventions
* this should not happen.
* remove dublicated code
* rename requestCopyBlock to copyBlock
* make sure images does not repeat.
* scale thumbnail for block showcase
* renamed blockcard.component to umb-block-card and moved it.
* removed inline style
* correct style location
* corrected filepath
* corrected file path
* keep elementTypes up to date through the EventService.
* mark Umbraco.BlockList as unsupported inside Nested Content
* correct js docs name
* remove comment
* remove comment
* remove unused controller
* rename inline method name
* corrected spelling mistake
* remove not very used vars
* make binding one-way
* split in multiple lines
* corrected default rendering
* removing documentation that is relevant for developers of new block editors. this documentation will be transfered to Our documentation.
* added danish translation
* corrected blog to blok
* Remove invalid using statement
* use native forEach
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Benjamin Carleski <benjamin@proworks.com>
Co-authored-by: Warren Buckley <warren@umbraco.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Claus <claus@claus.nu>
2020-06-30 19:52:42 +10:00
{
2020-08-07 14:00:56 +02:00
var contentType = _contentTypeService . Get ( contentTypeKey ) ;
Block Editor List (#8273)
* add style to create-option in itempicker + removing overflow hidden
* style adjustment
* clean up of html
* correct sentence to use the number 7
* correct overlays, so they can use size
* numberrange prevalue editor
* add confirmRemove overlay
* correcting primary btn colors
* move confirmMessage below content of overlay.
* min max validation for numberrange
* remove comment
* improved actions for block list
* use file tree for view picker
* style adjustment to border of creator item in item-picker
* vertical align
* clean up + validation limit range
* rename ElementTypes to Blocks
* implement block list editor
* renaming
* use Chrome Headless for unit tests
* test for blockEditorService
* safer code
* block list editor
* rename view to overlayView
* block editor work
* Revert "rename view to overlayView"
This reverts commit 5b910c178a4f193d190367c4f1da3402aa8c4d0e.
* block editor implementation
* sync models
* block list editor copy paste feature
* order var declarations
* remove unused paste function
* block list editor better naming
* simpler label generation
* clean up
* compile config for test mode
* Chrome Debug for VS code
* promise test working
* space change
* another two tests
* more tests for block list editor
* provide key on blockModel for angularJS performance optimization
* syncronization from infinite editing layers
* use an isolated scope
* more tests
* fix C# test
* remove unused block watcher component
* clean css
* only show on hover or focus for block-actions
* clean up and prepare for implementing settings
* remove console
* Add ability to render block list editor using Grid style rendering extension
* Enable Block List Editor settings editing
* Add Stacked Content to Block List migration
* Block Editor: Clean-up, refactoring, one step closer being ready for Content-Apps
* changes naming to Submit, to avoid misunderstanding.
* use a common variable on the block model for isOpen, to be able to make Actions show when open.
* NotSupported property editor, to be used when an editor is not supported in the given context.
* remove unused controller
* Hide group header if only one group is presented
* rename notsupport property editor css class
* smaller header for property group
* hide description if no description is presented
* css adjustments
* Inline create button styling: Better spacing, darker blue color for Focus Outline, moving the plus icon to mouse position for better visual appearance.
* css correction
* Add references for picked items
* Revert commit 45e892f3505059674779c6e1a43084a367c2862f - Changes api to GetData
* Use the .Data propertry as opposed to GetData in this PartialView
* Fix block list test failures
* Just parsing layout as model for partial views.
* minor adjustments
* Remove DB migrations so that they can be reviewed as a block
* Add migrations for new block editor
* Update default rendering partial view
* Add error handling to default template
* Handle color picker data in stacked content
* BlockList PreValue Editor opens configurations as overlay
* translation for prevalue editor property group headlines
* blockcard corrections
* block list prevalue corrections
* revert agressive clean up
* Block Picker
* MaxPropertyWidth PreValue + Implementation
* Incorporate latest block list editor changes, update migration for changed configuration
* Change declared converter type
* Handle invalid data type references
* Remove code duplicated from PR #7957
* use ElementModel for the ContentModel of an ElementType. So we can use ElementTypeModel for the ModelDefinition aka. the Type.
* do still show itempicker for BlockConfiguration even though there is no ElementTypes to pick. This enables the option to create a new ElementType to be used.
* use the right wrapper, for correct spacing
* parse item
* correct fallback for label
* removed unused callback
* paste feature for block-picker
* localize block-picker tabs
* Slightly change for shadow on block-picker item hover
* Localization of BlockEditor Settings Tab
* localizationService
* only filter when more than 8 items available
* Add multiple blocks if hold down CTRL or SuperKey
* adds notes
* ability to add a scoped stylesheet for block view
* make scoped block draggable + style adjustments
* provide index for custom view
* rename contentModel to data + rename layoutModel to layout
* clean up
* more localization
* openSettings option for block editor
* minor changes for a better developer experience
* paste mistake corrected
* only manipulate content-content-app if its present
* make small overlays slightly bigger
* moved block list entry editor into block list editor folder
* limit labelinterpretator to only runs ones pr. edit. and lets make sure to have a label thought we dont have any properties.
* fixed inline views gulp watcher
* changed vm to a better controller instance name
* make watch for views work again.
* able to re run watch
* make js up to date
* fix white background of image-picker
* media-picker container class
* loading indication
* adjust unit tests to latest interface
* getting started on JS Docs
* converting code to use contentTypeKey instead of contentTypeAlias, still missing a few TODOs.
* revert change
* add todo
* use Guid for Key
* use key
* Updates the caching layer to handle GUID keys for content types while preserving backwards compat, fixes unit tests, removes the strongly typed lists for the block editor value since it's unecessary
* Reverts the nested content changes, fixes up the GetEmptyByKey
* Returns ContentTypeKey from the server, updates js to use this everywhere and fix all js tests.
* Allows key in SimpleContentType
* correct for the new spelling
* appended this. since the method is a non-static class method.
* only add background-image if value isnt null
* simplifyed limits validation
* clean up
* no need to execute a digest.
* define the full model for new configurations
* removed setDataFromBlockModel and general clean up and added documentation
* default size should be medium
* use retriveValuesFrom method to transfer data.
* ability to disable an navigation item
* createElementTypeAndCallback working again for settings.
* still have the ability to retrive a scaffold model by alias, since we are still using alias in clipboard service.
* disable clipboard tab if no available paste options
* ups, should stay as alias
* disable clipboard when empty
* use property alias for content add button
* use a grey that can be seen on top of grey and on top of white.
* some accessibility improvements
* rename entry to block
* appended ' and added space in Element Type
* use background color for hover to corospond with active state
* make nested content unsupported
* Moving BlockEditorModelObject into its own file for Documentation purpose. Same goes for renaming BlockModel to BlockObject. and a lot of documentation written.
* fix links in js docs
* added a blocklistentryeditor for unsupported blocks
* ability to retrive the $scope from UmbVariantContentEditors, needed for Block Editor to get a scope existing across variants and splitview.
* Appending the block objects to layout, to share it across variants and in split-view.
* removed trailing comma
* Unsupported block
* Dont copy unsupported blocks
* use grey color for unsupported block
* text correction
* we dont have this fallback anymore
* sort properties
* Text change
* css correction
* use active to determine if an inline editor is expanded. To enable the inline editor to be expanded on creation.
* using udi instead of key.
* bringing the runtime block key back
* live editing ability
* live editing for settings data
* call formSubmit before property editor are removed from DOM. Both for overlay-editing and inline-editing. Fire an postFormSubmitting event after formSubmitting event to let editors know that data could have been updated.
* make sure settings object exists
* only set active to false if it was false before opening the editor.
* update test with new scope parameter
* move destroy responsibility to blockObject
* rename onDestroy to destroy
* added some JS-Docs
* correction jsDocs
* Update ElementType Resource to not use hardcoded URL but to use the Umbraco.Sys.ServerVariables.umbracoUrls instead
* Remove partially completed ConvertToElement migration, fixed in issue 7939 instead.
* Remove external property editor migration
* corrected naming of umbBlockListScopedBlock and umbBlockListBlock
* correct ngdoc type
* removed vscode specific configuration of karma
* Finished Todo, gets name of documentType if copying all entities in an infinite editor
* changed comment from TODO to something that explains the state.
* stop tracking build output files.
* rename files to match file name conventions
* this should not happen.
* remove dublicated code
* rename requestCopyBlock to copyBlock
* make sure images does not repeat.
* scale thumbnail for block showcase
* renamed blockcard.component to umb-block-card and moved it.
* removed inline style
* correct style location
* corrected filepath
* corrected file path
* keep elementTypes up to date through the EventService.
* mark Umbraco.BlockList as unsupported inside Nested Content
* correct js docs name
* remove comment
* remove comment
* remove unused controller
* rename inline method name
* corrected spelling mistake
* remove not very used vars
* make binding one-way
* split in multiple lines
* corrected default rendering
* removing documentation that is relevant for developers of new block editors. this documentation will be transfered to Our documentation.
* added danish translation
* corrected blog to blok
* Remove invalid using statement
* use native forEach
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Benjamin Carleski <benjamin@proworks.com>
Co-authored-by: Warren Buckley <warren@umbraco.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Claus <claus@claus.nu>
2020-06-30 19:52:42 +10:00
if ( contentType = = null )
{
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
Block Editor List (#8273)
* add style to create-option in itempicker + removing overflow hidden
* style adjustment
* clean up of html
* correct sentence to use the number 7
* correct overlays, so they can use size
* numberrange prevalue editor
* add confirmRemove overlay
* correcting primary btn colors
* move confirmMessage below content of overlay.
* min max validation for numberrange
* remove comment
* improved actions for block list
* use file tree for view picker
* style adjustment to border of creator item in item-picker
* vertical align
* clean up + validation limit range
* rename ElementTypes to Blocks
* implement block list editor
* renaming
* use Chrome Headless for unit tests
* test for blockEditorService
* safer code
* block list editor
* rename view to overlayView
* block editor work
* Revert "rename view to overlayView"
This reverts commit 5b910c178a4f193d190367c4f1da3402aa8c4d0e.
* block editor implementation
* sync models
* block list editor copy paste feature
* order var declarations
* remove unused paste function
* block list editor better naming
* simpler label generation
* clean up
* compile config for test mode
* Chrome Debug for VS code
* promise test working
* space change
* another two tests
* more tests for block list editor
* provide key on blockModel for angularJS performance optimization
* syncronization from infinite editing layers
* use an isolated scope
* more tests
* fix C# test
* remove unused block watcher component
* clean css
* only show on hover or focus for block-actions
* clean up and prepare for implementing settings
* remove console
* Add ability to render block list editor using Grid style rendering extension
* Enable Block List Editor settings editing
* Add Stacked Content to Block List migration
* Block Editor: Clean-up, refactoring, one step closer being ready for Content-Apps
* changes naming to Submit, to avoid misunderstanding.
* use a common variable on the block model for isOpen, to be able to make Actions show when open.
* NotSupported property editor, to be used when an editor is not supported in the given context.
* remove unused controller
* Hide group header if only one group is presented
* rename notsupport property editor css class
* smaller header for property group
* hide description if no description is presented
* css adjustments
* Inline create button styling: Better spacing, darker blue color for Focus Outline, moving the plus icon to mouse position for better visual appearance.
* css correction
* Add references for picked items
* Revert commit 45e892f3505059674779c6e1a43084a367c2862f - Changes api to GetData
* Use the .Data propertry as opposed to GetData in this PartialView
* Fix block list test failures
* Just parsing layout as model for partial views.
* minor adjustments
* Remove DB migrations so that they can be reviewed as a block
* Add migrations for new block editor
* Update default rendering partial view
* Add error handling to default template
* Handle color picker data in stacked content
* BlockList PreValue Editor opens configurations as overlay
* translation for prevalue editor property group headlines
* blockcard corrections
* block list prevalue corrections
* revert agressive clean up
* Block Picker
* MaxPropertyWidth PreValue + Implementation
* Incorporate latest block list editor changes, update migration for changed configuration
* Change declared converter type
* Handle invalid data type references
* Remove code duplicated from PR #7957
* use ElementModel for the ContentModel of an ElementType. So we can use ElementTypeModel for the ModelDefinition aka. the Type.
* do still show itempicker for BlockConfiguration even though there is no ElementTypes to pick. This enables the option to create a new ElementType to be used.
* use the right wrapper, for correct spacing
* parse item
* correct fallback for label
* removed unused callback
* paste feature for block-picker
* localize block-picker tabs
* Slightly change for shadow on block-picker item hover
* Localization of BlockEditor Settings Tab
* localizationService
* only filter when more than 8 items available
* Add multiple blocks if hold down CTRL or SuperKey
* adds notes
* ability to add a scoped stylesheet for block view
* make scoped block draggable + style adjustments
* provide index for custom view
* rename contentModel to data + rename layoutModel to layout
* clean up
* more localization
* openSettings option for block editor
* minor changes for a better developer experience
* paste mistake corrected
* only manipulate content-content-app if its present
* make small overlays slightly bigger
* moved block list entry editor into block list editor folder
* limit labelinterpretator to only runs ones pr. edit. and lets make sure to have a label thought we dont have any properties.
* fixed inline views gulp watcher
* changed vm to a better controller instance name
* make watch for views work again.
* able to re run watch
* make js up to date
* fix white background of image-picker
* media-picker container class
* loading indication
* adjust unit tests to latest interface
* getting started on JS Docs
* converting code to use contentTypeKey instead of contentTypeAlias, still missing a few TODOs.
* revert change
* add todo
* use Guid for Key
* use key
* Updates the caching layer to handle GUID keys for content types while preserving backwards compat, fixes unit tests, removes the strongly typed lists for the block editor value since it's unecessary
* Reverts the nested content changes, fixes up the GetEmptyByKey
* Returns ContentTypeKey from the server, updates js to use this everywhere and fix all js tests.
* Allows key in SimpleContentType
* correct for the new spelling
* appended this. since the method is a non-static class method.
* only add background-image if value isnt null
* simplifyed limits validation
* clean up
* no need to execute a digest.
* define the full model for new configurations
* removed setDataFromBlockModel and general clean up and added documentation
* default size should be medium
* use retriveValuesFrom method to transfer data.
* ability to disable an navigation item
* createElementTypeAndCallback working again for settings.
* still have the ability to retrive a scaffold model by alias, since we are still using alias in clipboard service.
* disable clipboard tab if no available paste options
* ups, should stay as alias
* disable clipboard when empty
* use property alias for content add button
* use a grey that can be seen on top of grey and on top of white.
* some accessibility improvements
* rename entry to block
* appended ' and added space in Element Type
* use background color for hover to corospond with active state
* make nested content unsupported
* Moving BlockEditorModelObject into its own file for Documentation purpose. Same goes for renaming BlockModel to BlockObject. and a lot of documentation written.
* fix links in js docs
* added a blocklistentryeditor for unsupported blocks
* ability to retrive the $scope from UmbVariantContentEditors, needed for Block Editor to get a scope existing across variants and splitview.
* Appending the block objects to layout, to share it across variants and in split-view.
* removed trailing comma
* Unsupported block
* Dont copy unsupported blocks
* use grey color for unsupported block
* text correction
* we dont have this fallback anymore
* sort properties
* Text change
* css correction
* use active to determine if an inline editor is expanded. To enable the inline editor to be expanded on creation.
* using udi instead of key.
* bringing the runtime block key back
* live editing ability
* live editing for settings data
* call formSubmit before property editor are removed from DOM. Both for overlay-editing and inline-editing. Fire an postFormSubmitting event after formSubmitting event to let editors know that data could have been updated.
* make sure settings object exists
* only set active to false if it was false before opening the editor.
* update test with new scope parameter
* move destroy responsibility to blockObject
* rename onDestroy to destroy
* added some JS-Docs
* correction jsDocs
* Update ElementType Resource to not use hardcoded URL but to use the Umbraco.Sys.ServerVariables.umbracoUrls instead
* Remove partially completed ConvertToElement migration, fixed in issue 7939 instead.
* Remove external property editor migration
* corrected naming of umbBlockListScopedBlock and umbBlockListBlock
* correct ngdoc type
* removed vscode specific configuration of karma
* Finished Todo, gets name of documentType if copying all entities in an infinite editor
* changed comment from TODO to something that explains the state.
* stop tracking build output files.
* rename files to match file name conventions
* this should not happen.
* remove dublicated code
* rename requestCopyBlock to copyBlock
* make sure images does not repeat.
* scale thumbnail for block showcase
* renamed blockcard.component to umb-block-card and moved it.
* removed inline style
* correct style location
* corrected filepath
* corrected file path
* keep elementTypes up to date through the EventService.
* mark Umbraco.BlockList as unsupported inside Nested Content
* correct js docs name
* remove comment
* remove comment
* remove unused controller
* rename inline method name
* corrected spelling mistake
* remove not very used vars
* make binding one-way
* split in multiple lines
* corrected default rendering
* removing documentation that is relevant for developers of new block editors. this documentation will be transfered to Our documentation.
* added danish translation
* corrected blog to blok
* Remove invalid using statement
* use native forEach
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Benjamin Carleski <benjamin@proworks.com>
Co-authored-by: Warren Buckley <warren@umbraco.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Claus <claus@claus.nu>
2020-06-30 19:52:42 +10:00
}
2021-01-29 10:30:28 +01:00
return GetEmptyInner ( contentType , parentId ) ;
Block Editor List (#8273)
* add style to create-option in itempicker + removing overflow hidden
* style adjustment
* clean up of html
* correct sentence to use the number 7
* correct overlays, so they can use size
* numberrange prevalue editor
* add confirmRemove overlay
* correcting primary btn colors
* move confirmMessage below content of overlay.
* min max validation for numberrange
* remove comment
* improved actions for block list
* use file tree for view picker
* style adjustment to border of creator item in item-picker
* vertical align
* clean up + validation limit range
* rename ElementTypes to Blocks
* implement block list editor
* renaming
* use Chrome Headless for unit tests
* test for blockEditorService
* safer code
* block list editor
* rename view to overlayView
* block editor work
* Revert "rename view to overlayView"
This reverts commit 5b910c178a4f193d190367c4f1da3402aa8c4d0e.
* block editor implementation
* sync models
* block list editor copy paste feature
* order var declarations
* remove unused paste function
* block list editor better naming
* simpler label generation
* clean up
* compile config for test mode
* Chrome Debug for VS code
* promise test working
* space change
* another two tests
* more tests for block list editor
* provide key on blockModel for angularJS performance optimization
* syncronization from infinite editing layers
* use an isolated scope
* more tests
* fix C# test
* remove unused block watcher component
* clean css
* only show on hover or focus for block-actions
* clean up and prepare for implementing settings
* remove console
* Add ability to render block list editor using Grid style rendering extension
* Enable Block List Editor settings editing
* Add Stacked Content to Block List migration
* Block Editor: Clean-up, refactoring, one step closer being ready for Content-Apps
* changes naming to Submit, to avoid misunderstanding.
* use a common variable on the block model for isOpen, to be able to make Actions show when open.
* NotSupported property editor, to be used when an editor is not supported in the given context.
* remove unused controller
* Hide group header if only one group is presented
* rename notsupport property editor css class
* smaller header for property group
* hide description if no description is presented
* css adjustments
* Inline create button styling: Better spacing, darker blue color for Focus Outline, moving the plus icon to mouse position for better visual appearance.
* css correction
* Add references for picked items
* Revert commit 45e892f3505059674779c6e1a43084a367c2862f - Changes api to GetData
* Use the .Data propertry as opposed to GetData in this PartialView
* Fix block list test failures
* Just parsing layout as model for partial views.
* minor adjustments
* Remove DB migrations so that they can be reviewed as a block
* Add migrations for new block editor
* Update default rendering partial view
* Add error handling to default template
* Handle color picker data in stacked content
* BlockList PreValue Editor opens configurations as overlay
* translation for prevalue editor property group headlines
* blockcard corrections
* block list prevalue corrections
* revert agressive clean up
* Block Picker
* MaxPropertyWidth PreValue + Implementation
* Incorporate latest block list editor changes, update migration for changed configuration
* Change declared converter type
* Handle invalid data type references
* Remove code duplicated from PR #7957
* use ElementModel for the ContentModel of an ElementType. So we can use ElementTypeModel for the ModelDefinition aka. the Type.
* do still show itempicker for BlockConfiguration even though there is no ElementTypes to pick. This enables the option to create a new ElementType to be used.
* use the right wrapper, for correct spacing
* parse item
* correct fallback for label
* removed unused callback
* paste feature for block-picker
* localize block-picker tabs
* Slightly change for shadow on block-picker item hover
* Localization of BlockEditor Settings Tab
* localizationService
* only filter when more than 8 items available
* Add multiple blocks if hold down CTRL or SuperKey
* adds notes
* ability to add a scoped stylesheet for block view
* make scoped block draggable + style adjustments
* provide index for custom view
* rename contentModel to data + rename layoutModel to layout
* clean up
* more localization
* openSettings option for block editor
* minor changes for a better developer experience
* paste mistake corrected
* only manipulate content-content-app if its present
* make small overlays slightly bigger
* moved block list entry editor into block list editor folder
* limit labelinterpretator to only runs ones pr. edit. and lets make sure to have a label thought we dont have any properties.
* fixed inline views gulp watcher
* changed vm to a better controller instance name
* make watch for views work again.
* able to re run watch
* make js up to date
* fix white background of image-picker
* media-picker container class
* loading indication
* adjust unit tests to latest interface
* getting started on JS Docs
* converting code to use contentTypeKey instead of contentTypeAlias, still missing a few TODOs.
* revert change
* add todo
* use Guid for Key
* use key
* Updates the caching layer to handle GUID keys for content types while preserving backwards compat, fixes unit tests, removes the strongly typed lists for the block editor value since it's unecessary
* Reverts the nested content changes, fixes up the GetEmptyByKey
* Returns ContentTypeKey from the server, updates js to use this everywhere and fix all js tests.
* Allows key in SimpleContentType
* correct for the new spelling
* appended this. since the method is a non-static class method.
* only add background-image if value isnt null
* simplifyed limits validation
* clean up
* no need to execute a digest.
* define the full model for new configurations
* removed setDataFromBlockModel and general clean up and added documentation
* default size should be medium
* use retriveValuesFrom method to transfer data.
* ability to disable an navigation item
* createElementTypeAndCallback working again for settings.
* still have the ability to retrive a scaffold model by alias, since we are still using alias in clipboard service.
* disable clipboard tab if no available paste options
* ups, should stay as alias
* disable clipboard when empty
* use property alias for content add button
* use a grey that can be seen on top of grey and on top of white.
* some accessibility improvements
* rename entry to block
* appended ' and added space in Element Type
* use background color for hover to corospond with active state
* make nested content unsupported
* Moving BlockEditorModelObject into its own file for Documentation purpose. Same goes for renaming BlockModel to BlockObject. and a lot of documentation written.
* fix links in js docs
* added a blocklistentryeditor for unsupported blocks
* ability to retrive the $scope from UmbVariantContentEditors, needed for Block Editor to get a scope existing across variants and splitview.
* Appending the block objects to layout, to share it across variants and in split-view.
* removed trailing comma
* Unsupported block
* Dont copy unsupported blocks
* use grey color for unsupported block
* text correction
* we dont have this fallback anymore
* sort properties
* Text change
* css correction
* use active to determine if an inline editor is expanded. To enable the inline editor to be expanded on creation.
* using udi instead of key.
* bringing the runtime block key back
* live editing ability
* live editing for settings data
* call formSubmit before property editor are removed from DOM. Both for overlay-editing and inline-editing. Fire an postFormSubmitting event after formSubmitting event to let editors know that data could have been updated.
* make sure settings object exists
* only set active to false if it was false before opening the editor.
* update test with new scope parameter
* move destroy responsibility to blockObject
* rename onDestroy to destroy
* added some JS-Docs
* correction jsDocs
* Update ElementType Resource to not use hardcoded URL but to use the Umbraco.Sys.ServerVariables.umbracoUrls instead
* Remove partially completed ConvertToElement migration, fixed in issue 7939 instead.
* Remove external property editor migration
* corrected naming of umbBlockListScopedBlock and umbBlockListBlock
* correct ngdoc type
* removed vscode specific configuration of karma
* Finished Todo, gets name of documentType if copying all entities in an infinite editor
* changed comment from TODO to something that explains the state.
* stop tracking build output files.
* rename files to match file name conventions
* this should not happen.
* remove dublicated code
* rename requestCopyBlock to copyBlock
* make sure images does not repeat.
* scale thumbnail for block showcase
* renamed blockcard.component to umb-block-card and moved it.
* removed inline style
* correct style location
* corrected filepath
* corrected file path
* keep elementTypes up to date through the EventService.
* mark Umbraco.BlockList as unsupported inside Nested Content
* correct js docs name
* remove comment
* remove comment
* remove unused controller
* rename inline method name
* corrected spelling mistake
* remove not very used vars
* make binding one-way
* split in multiple lines
* corrected default rendering
* removing documentation that is relevant for developers of new block editors. this documentation will be transfered to Our documentation.
* added danish translation
* corrected blog to blok
* Remove invalid using statement
* use native forEach
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Benjamin Carleski <benjamin@proworks.com>
Co-authored-by: Warren Buckley <warren@umbraco.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Claus <claus@claus.nu>
2020-06-30 19:52:42 +10:00
}
2021-01-29 10:30:28 +01:00
private ContentItemDisplay GetEmptyInner ( IContentType contentType , int parentId )
Block Editor List (#8273)
* add style to create-option in itempicker + removing overflow hidden
* style adjustment
* clean up of html
* correct sentence to use the number 7
* correct overlays, so they can use size
* numberrange prevalue editor
* add confirmRemove overlay
* correcting primary btn colors
* move confirmMessage below content of overlay.
* min max validation for numberrange
* remove comment
* improved actions for block list
* use file tree for view picker
* style adjustment to border of creator item in item-picker
* vertical align
* clean up + validation limit range
* rename ElementTypes to Blocks
* implement block list editor
* renaming
* use Chrome Headless for unit tests
* test for blockEditorService
* safer code
* block list editor
* rename view to overlayView
* block editor work
* Revert "rename view to overlayView"
This reverts commit 5b910c178a4f193d190367c4f1da3402aa8c4d0e.
* block editor implementation
* sync models
* block list editor copy paste feature
* order var declarations
* remove unused paste function
* block list editor better naming
* simpler label generation
* clean up
* compile config for test mode
* Chrome Debug for VS code
* promise test working
* space change
* another two tests
* more tests for block list editor
* provide key on blockModel for angularJS performance optimization
* syncronization from infinite editing layers
* use an isolated scope
* more tests
* fix C# test
* remove unused block watcher component
* clean css
* only show on hover or focus for block-actions
* clean up and prepare for implementing settings
* remove console
* Add ability to render block list editor using Grid style rendering extension
* Enable Block List Editor settings editing
* Add Stacked Content to Block List migration
* Block Editor: Clean-up, refactoring, one step closer being ready for Content-Apps
* changes naming to Submit, to avoid misunderstanding.
* use a common variable on the block model for isOpen, to be able to make Actions show when open.
* NotSupported property editor, to be used when an editor is not supported in the given context.
* remove unused controller
* Hide group header if only one group is presented
* rename notsupport property editor css class
* smaller header for property group
* hide description if no description is presented
* css adjustments
* Inline create button styling: Better spacing, darker blue color for Focus Outline, moving the plus icon to mouse position for better visual appearance.
* css correction
* Add references for picked items
* Revert commit 45e892f3505059674779c6e1a43084a367c2862f - Changes api to GetData
* Use the .Data propertry as opposed to GetData in this PartialView
* Fix block list test failures
* Just parsing layout as model for partial views.
* minor adjustments
* Remove DB migrations so that they can be reviewed as a block
* Add migrations for new block editor
* Update default rendering partial view
* Add error handling to default template
* Handle color picker data in stacked content
* BlockList PreValue Editor opens configurations as overlay
* translation for prevalue editor property group headlines
* blockcard corrections
* block list prevalue corrections
* revert agressive clean up
* Block Picker
* MaxPropertyWidth PreValue + Implementation
* Incorporate latest block list editor changes, update migration for changed configuration
* Change declared converter type
* Handle invalid data type references
* Remove code duplicated from PR #7957
* use ElementModel for the ContentModel of an ElementType. So we can use ElementTypeModel for the ModelDefinition aka. the Type.
* do still show itempicker for BlockConfiguration even though there is no ElementTypes to pick. This enables the option to create a new ElementType to be used.
* use the right wrapper, for correct spacing
* parse item
* correct fallback for label
* removed unused callback
* paste feature for block-picker
* localize block-picker tabs
* Slightly change for shadow on block-picker item hover
* Localization of BlockEditor Settings Tab
* localizationService
* only filter when more than 8 items available
* Add multiple blocks if hold down CTRL or SuperKey
* adds notes
* ability to add a scoped stylesheet for block view
* make scoped block draggable + style adjustments
* provide index for custom view
* rename contentModel to data + rename layoutModel to layout
* clean up
* more localization
* openSettings option for block editor
* minor changes for a better developer experience
* paste mistake corrected
* only manipulate content-content-app if its present
* make small overlays slightly bigger
* moved block list entry editor into block list editor folder
* limit labelinterpretator to only runs ones pr. edit. and lets make sure to have a label thought we dont have any properties.
* fixed inline views gulp watcher
* changed vm to a better controller instance name
* make watch for views work again.
* able to re run watch
* make js up to date
* fix white background of image-picker
* media-picker container class
* loading indication
* adjust unit tests to latest interface
* getting started on JS Docs
* converting code to use contentTypeKey instead of contentTypeAlias, still missing a few TODOs.
* revert change
* add todo
* use Guid for Key
* use key
* Updates the caching layer to handle GUID keys for content types while preserving backwards compat, fixes unit tests, removes the strongly typed lists for the block editor value since it's unecessary
* Reverts the nested content changes, fixes up the GetEmptyByKey
* Returns ContentTypeKey from the server, updates js to use this everywhere and fix all js tests.
* Allows key in SimpleContentType
* correct for the new spelling
* appended this. since the method is a non-static class method.
* only add background-image if value isnt null
* simplifyed limits validation
* clean up
* no need to execute a digest.
* define the full model for new configurations
* removed setDataFromBlockModel and general clean up and added documentation
* default size should be medium
* use retriveValuesFrom method to transfer data.
* ability to disable an navigation item
* createElementTypeAndCallback working again for settings.
* still have the ability to retrive a scaffold model by alias, since we are still using alias in clipboard service.
* disable clipboard tab if no available paste options
* ups, should stay as alias
* disable clipboard when empty
* use property alias for content add button
* use a grey that can be seen on top of grey and on top of white.
* some accessibility improvements
* rename entry to block
* appended ' and added space in Element Type
* use background color for hover to corospond with active state
* make nested content unsupported
* Moving BlockEditorModelObject into its own file for Documentation purpose. Same goes for renaming BlockModel to BlockObject. and a lot of documentation written.
* fix links in js docs
* added a blocklistentryeditor for unsupported blocks
* ability to retrive the $scope from UmbVariantContentEditors, needed for Block Editor to get a scope existing across variants and splitview.
* Appending the block objects to layout, to share it across variants and in split-view.
* removed trailing comma
* Unsupported block
* Dont copy unsupported blocks
* use grey color for unsupported block
* text correction
* we dont have this fallback anymore
* sort properties
* Text change
* css correction
* use active to determine if an inline editor is expanded. To enable the inline editor to be expanded on creation.
* using udi instead of key.
* bringing the runtime block key back
* live editing ability
* live editing for settings data
* call formSubmit before property editor are removed from DOM. Both for overlay-editing and inline-editing. Fire an postFormSubmitting event after formSubmitting event to let editors know that data could have been updated.
* make sure settings object exists
* only set active to false if it was false before opening the editor.
* update test with new scope parameter
* move destroy responsibility to blockObject
* rename onDestroy to destroy
* added some JS-Docs
* correction jsDocs
* Update ElementType Resource to not use hardcoded URL but to use the Umbraco.Sys.ServerVariables.umbracoUrls instead
* Remove partially completed ConvertToElement migration, fixed in issue 7939 instead.
* Remove external property editor migration
* corrected naming of umbBlockListScopedBlock and umbBlockListBlock
* correct ngdoc type
* removed vscode specific configuration of karma
* Finished Todo, gets name of documentType if copying all entities in an infinite editor
* changed comment from TODO to something that explains the state.
* stop tracking build output files.
* rename files to match file name conventions
* this should not happen.
* remove dublicated code
* rename requestCopyBlock to copyBlock
* make sure images does not repeat.
* scale thumbnail for block showcase
* renamed blockcard.component to umb-block-card and moved it.
* removed inline style
* correct style location
* corrected filepath
* corrected file path
* keep elementTypes up to date through the EventService.
* mark Umbraco.BlockList as unsupported inside Nested Content
* correct js docs name
* remove comment
* remove comment
* remove unused controller
* rename inline method name
* corrected spelling mistake
* remove not very used vars
* make binding one-way
* split in multiple lines
* corrected default rendering
* removing documentation that is relevant for developers of new block editors. this documentation will be transfered to Our documentation.
* added danish translation
* corrected blog to blok
* Remove invalid using statement
* use native forEach
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Benjamin Carleski <benjamin@proworks.com>
Co-authored-by: Warren Buckley <warren@umbraco.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Claus <claus@claus.nu>
2020-06-30 19:52:42 +10:00
{
2020-10-21 16:51:00 +11:00
var emptyContent = _contentService . Create ( "" , parentId , contentType . Alias , _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-06-29 19:52:40 +02:00
var mapped = MapToDisplay ( emptyContent ) ;
2018-12-20 16:58:01 +11:00
// translate the content type name if applicable
2020-06-09 13:48:50 +02:00
mapped . ContentTypeName = _localizedTextService . UmbracoDictionaryTranslate ( CultureDictionary , mapped . ContentTypeName ) ;
2019-01-11 10:23:27 +01:00
// if your user type doesn't have access to the Settings section it would not get this property mapped
2019-03-12 16:10:35 +11:00
if ( mapped . DocumentType ! = null )
2020-06-09 13:48:50 +02:00
mapped . DocumentType . Name = _localizedTextService . UmbracoDictionaryTranslate ( CultureDictionary , mapped . DocumentType . Name ) ;
2018-06-29 19:52:40 +02:00
2018-07-13 12:45:04 +10:00
//remove the listview app if it exists
2018-09-14 10:53:59 +01:00
mapped . ContentApps = mapped . ContentApps . Where ( x = > x . Alias ! = "umbListView" ) . ToList ( ) ;
2018-11-12 17:29:38 +11:00
2018-06-29 19:52:40 +02:00
return mapped ;
}
2020-12-02 14:21:05 +00:00
[OutgoingEditorModelEvent]
2021-01-29 10:30:28 +01:00
public ActionResult < ContentItemDisplay > GetEmptyBlueprint ( int blueprintId , int parentId )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var blueprint = _contentService . GetBlueprintById ( blueprintId ) ;
2018-06-29 19:52:40 +02:00
if ( blueprint = = null )
{
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
blueprint . Id = 0 ;
blueprint . Name = string . Empty ;
blueprint . ParentId = parentId ;
2020-06-09 13:48:50 +02:00
var mapped = _umbracoMapper . Map < ContentItemDisplay > ( blueprint ) ;
2018-06-29 19:52:40 +02:00
2018-07-13 12:45:04 +10:00
//remove the listview app if it exists
2018-09-14 10:53:59 +01:00
mapped . ContentApps = mapped . ContentApps . Where ( x = > x . Alias ! = "umbListView" ) . ToList ( ) ;
2018-07-13 12:45:04 +10:00
2018-06-29 19:52:40 +02:00
return mapped ;
}
/// <summary>
/// Gets the Url for a given node ID
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
2020-06-09 13:48:50 +02:00
public IActionResult GetNiceUrl ( int id )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var url = _publishedUrlProvider . GetUrl ( id ) ;
2020-06-12 22:13:43 +02:00
return Content ( url , MediaTypeNames . Text . Plain , Encoding . UTF8 ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gets the Url for a given node ID
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
2020-06-09 13:48:50 +02:00
public IActionResult GetNiceUrl ( Guid id )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var url = _publishedUrlProvider . GetUrl ( id ) ;
2020-06-12 22:13:43 +02:00
return Content ( url , MediaTypeNames . Text . Plain , Encoding . UTF8 ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gets the Url for a given node ID
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
2020-06-09 13:48:50 +02:00
public IActionResult GetNiceUrl ( Udi id )
2018-06-29 19:52:40 +02:00
{
var guidUdi = id as GuidUdi ;
if ( guidUdi ! = null )
{
return GetNiceUrl ( guidUdi . Guid ) ;
2020-06-09 13:48:50 +02:00
2018-06-29 19:52:40 +02:00
}
2020-06-09 13:48:50 +02:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gets the children for the content id passed in
/// </summary>
/// <returns></returns>
2018-08-01 15:40:54 +10:00
[FilterAllowedOutgoingContent(typeof(IEnumerable<ContentItemBasic<ContentPropertyBasic>>), "Items")]
public PagedResult < ContentItemBasic < ContentPropertyBasic > > GetChildren (
2018-06-29 19:52:40 +02:00
int id ,
string includeProperties ,
2018-11-12 17:29:38 +11:00
int pageNumber = 0 ,
2018-06-29 19:52:40 +02:00
int pageSize = 0 ,
string orderBy = "SortOrder" ,
Direction orderDirection = Direction . Ascending ,
bool orderBySystemField = true ,
2018-09-07 15:02:44 +01:00
string filter = "" ,
2019-01-26 10:52:19 -05:00
string cultureName = "" ) // TODO: it's not a NAME it's the ISO CODE
2018-06-29 19:52:40 +02:00
{
long totalChildren ;
2018-09-18 11:53:33 +02:00
List < IContent > children ;
2019-02-12 11:58:38 +01:00
// Sets the culture to the only existing culture if we only have one culture.
if ( string . IsNullOrWhiteSpace ( cultureName ) )
{
if ( _allLangs . Value . Count = = 1 )
{
cultureName = _allLangs . Value . First ( ) . Key ;
}
}
2018-06-29 19:52:40 +02:00
if ( pageNumber > 0 & & pageSize > 0 )
{
IQuery < IContent > queryFilter = null ;
if ( filter . IsNullOrWhiteSpace ( ) = = false )
{
//add the default text filter
2020-06-09 13:48:50 +02:00
queryFilter = _sqlContext . Query < IContent > ( )
2018-06-29 19:52:40 +02:00
. Where ( x = > x . Name . Contains ( filter ) ) ;
}
2020-06-09 13:48:50 +02:00
children = _contentService
2018-09-18 11:53:33 +02:00
. GetPagedChildren ( id , pageNumber - 1 , pageSize , out totalChildren ,
queryFilter ,
Ordering . By ( orderBy , orderDirection , cultureName , ! orderBySystemField ) ) . ToList ( ) ;
2018-06-29 19:52:40 +02:00
}
else
{
2018-10-31 18:01:39 +11:00
//better to not use this without paging where possible, currently only the sort dialog does
2020-06-09 13:48:50 +02:00
children = _contentService . GetPagedChildren ( id , 0 , int . MaxValue , out var total ) . ToList ( ) ;
2018-09-18 11:53:33 +02:00
totalChildren = children . Count ;
2018-06-29 19:52:40 +02:00
}
if ( totalChildren = = 0 )
{
2018-08-01 15:40:54 +10:00
return new PagedResult < ContentItemBasic < ContentPropertyBasic > > ( 0 , 0 , 0 ) ;
2018-06-29 19:52:40 +02:00
}
2018-08-01 15:40:54 +10:00
var pagedResult = new PagedResult < ContentItemBasic < ContentPropertyBasic > > ( totalChildren , pageNumber , pageSize ) ;
2018-06-29 19:52:40 +02:00
pagedResult . Items = children . Select ( content = >
2020-06-09 13:48:50 +02:00
_umbracoMapper . Map < IContent , ContentItemBasic < ContentPropertyBasic > > ( content ,
2019-03-26 10:39:50 +01:00
context = >
2018-06-29 19:52:40 +02:00
{
2018-09-07 15:02:44 +01:00
2019-03-26 10:39:50 +01:00
context . SetCulture ( cultureName ) ;
2018-06-29 19:52:40 +02:00
2018-09-17 13:19:24 +02:00
// if there's a list of property aliases to map - we will make sure to store this in the mapping context.
if ( ! includeProperties . IsNullOrWhiteSpace ( ) )
2019-03-26 10:39:50 +01:00
context . SetIncludedProperties ( includeProperties . Split ( new [ ] { ", " , "," } , StringSplitOptions . RemoveEmptyEntries ) ) ;
2018-09-17 13:19:24 +02:00
} ) )
. ToList ( ) ; // evaluate now
2018-09-07 15:02:44 +01:00
2018-06-29 19:52:40 +02:00
return pagedResult ;
}
/// <summary>
/// Creates a blueprint from a content item
/// </summary>
/// <param name="contentId">The content id to copy</param>
/// <param name="name">The name of the blueprint</param>
/// <returns></returns>
[HttpPost]
2020-12-02 14:21:05 +00:00
public ActionResult < SimpleNotificationModel > CreateBlueprintFromContent ( [ FromQuery ] int contentId , [ FromQuery ] string name )
2018-06-29 19:52:40 +02:00
{
2019-01-08 22:30:12 +01:00
if ( string . IsNullOrWhiteSpace ( name ) )
2019-01-10 07:03:08 +01:00
throw new ArgumentException ( "Value cannot be null or whitespace." , nameof ( name ) ) ;
2018-06-29 19:52:40 +02:00
2020-06-09 13:48:50 +02:00
var content = _contentService . GetById ( contentId ) ;
2018-06-29 19:52:40 +02:00
if ( content = = null )
2021-01-14 19:41:32 +01:00
{
2020-06-09 13:48:50 +02:00
return NotFound ( ) ;
2021-01-14 19:41:32 +01:00
}
2018-06-29 19:52:40 +02:00
2021-01-14 19:41:32 +01:00
if ( ! EnsureUniqueName ( name , content , nameof ( name ) ) )
{
return new ValidationErrorResult ( ModelState . ToErrorDictionary ( ) ) ;
}
2018-06-29 19:52:40 +02:00
2020-10-21 16:51:00 +11:00
var blueprint = _contentService . CreateContentFromBlueprint ( content , name , _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-06-29 19:52:40 +02:00
2020-10-21 16:51:00 +11:00
_contentService . SaveBlueprint ( blueprint , _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-06-29 19:52:40 +02:00
var notificationModel = new SimpleNotificationModel ( ) ;
notificationModel . AddSuccessNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "blueprints/createdBlueprintHeading" ) ,
_localizedTextService . Localize ( "blueprints/createdBlueprintMessage" , new [ ] { content . Name } )
2018-06-29 19:52:40 +02:00
) ;
return notificationModel ;
}
2021-01-13 16:25:21 +01:00
private bool EnsureUniqueName ( string name , IContent content , string modelName )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var existing = _contentService . GetBlueprintsForContentTypes ( content . ContentTypeId ) ;
2018-06-29 19:52:40 +02:00
if ( existing . Any ( x = > x . Name = = name & & x . Id ! = content . Id ) )
{
2020-06-09 13:48:50 +02:00
ModelState . AddModelError ( modelName , _localizedTextService . Localize ( "blueprints/duplicateBlueprintMessage" ) ) ;
2021-01-13 16:25:21 +01:00
return false ;
2018-06-29 19:52:40 +02:00
}
2021-01-13 16:25:21 +01:00
return true ;
2018-06-29 19:52:40 +02:00
}
2020-12-02 14:21:05 +00:00
/// <summary>
/// Saves content
/// </summary>
[FileUploadCleanupFilter]
[ContentSaveValidation]
2021-01-12 16:01:09 +01:00
public async Task < ActionResult < ContentItemDisplay > > PostSaveBlueprint ( [ ModelBinder ( typeof ( BlueprintItemBinder ) ) ] ContentItemSave contentItem )
2020-12-02 14:21:05 +00:00
{
2020-12-21 15:58:47 +11:00
var contentItemDisplay = await PostSaveInternal (
contentItem ,
2020-12-02 14:21:05 +00:00
content = >
{
2021-01-13 16:25:21 +01:00
if ( ! EnsureUniqueName ( content . Name , content , "Name" ) )
{
return OperationResult . Cancel ( new EventMessages ( ) ) ;
}
2020-12-02 14:21:05 +00:00
_contentService . SaveBlueprint ( contentItem . PersistedContent , _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Id ) ;
2020-12-21 15:58:47 +11:00
// we need to reuse the underlying logic so return the result that it wants
return OperationResult . Succeed ( new EventMessages ( ) ) ;
2020-12-02 14:21:05 +00:00
} ,
content = >
{
var display = MapToDisplay ( content ) ;
SetupBlueprint ( display , content ) ;
return display ;
} ) ;
2020-06-12 11:14:06 +02:00
2021-01-12 16:01:09 +01:00
return contentItemDisplay ;
2020-12-02 14:21:05 +00:00
}
2018-06-29 19:52:40 +02:00
/// <summary>
/// Saves content
/// </summary>
[FileUploadCleanupFilter]
2018-08-01 16:46:13 +10:00
[ContentSaveValidation]
2020-12-02 14:21:05 +00:00
[OutgoingEditorModelEvent]
2021-01-06 08:49:02 +01:00
public async Task < ActionResult < ContentItemDisplay > > PostSave ( [ ModelBinder ( typeof ( ContentItemBinder ) ) ] ContentItemSave contentItem )
2018-06-29 19:52:40 +02:00
{
2020-11-24 00:37:26 +11:00
var contentItemDisplay = await PostSaveInternal (
2019-06-12 12:22:14 +10:00
contentItem ,
2020-10-21 16:51:00 +11:00
content = > _contentService . Save ( contentItem . PersistedContent , _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Id ) ,
2019-06-12 12:22:14 +10:00
MapToDisplay ) ;
2021-01-06 08:49:02 +01:00
return contentItemDisplay ;
2018-06-29 19:52:40 +02:00
}
2020-12-29 12:55:48 +01:00
private async Task < ActionResult < ContentItemDisplay > > PostSaveInternal ( ContentItemSave contentItem , Func < IContent , OperationResult > saveMethod , Func < IContent , ContentItemDisplay > mapToDisplay )
2018-06-29 19:52:40 +02:00
{
2020-12-21 15:58:47 +11:00
// Recent versions of IE/Edge may send in the full client side file path instead of just the file name.
// To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all
// uploaded files to being *only* the actual file name (as it should be).
2018-10-01 14:32:46 +02:00
if ( contentItem . UploadedFiles ! = null & & contentItem . UploadedFiles . Any ( ) )
{
foreach ( var file in contentItem . UploadedFiles )
{
file . FileName = Path . GetFileName ( file . FileName ) ;
}
}
2020-12-21 15:58:47 +11:00
// If we've reached here it means:
2018-07-19 19:32:07 +10:00
// * Our model has been bound
// * and validated
// * any file attachments have been saved to their temporary location for us to use
// * we have a reference to the DTO object and the persisted object
// * Permissions are valid
2018-08-14 20:21:33 +10:00
MapValuesForPersistence ( contentItem ) ;
2019-03-12 16:10:35 +11:00
var passesCriticalValidationRules = ValidateCriticalData ( contentItem , out var variantCount ) ;
2018-07-19 19:32:07 +10:00
2020-12-21 15:58:47 +11:00
// we will continue to save if model state is invalid, however we cannot save if critical data is missing.
2019-03-13 18:12:48 +11:00
if ( ! ModelState . IsValid )
2018-07-19 19:32:07 +10:00
{
2020-12-21 15:58:47 +11:00
// check for critical data validation issues, we can't continue saving if this data is invalid
2019-03-12 16:10:35 +11:00
if ( ! passesCriticalValidationRules )
2018-09-06 20:33:32 +10:00
{
2020-12-21 15:58:47 +11:00
// ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
2019-03-12 16:10:35 +11:00
// add the model state to the outgoing object and throw a validation message
2019-06-12 12:22:14 +10:00
var forDisplay = mapToDisplay ( contentItem . PersistedContent ) ;
2019-03-12 16:10:35 +11:00
forDisplay . Errors = ModelState . ToErrorDictionary ( ) ;
2020-12-29 12:55:48 +01:00
return new ValidationErrorResult ( forDisplay ) ;
2018-07-19 19:32:07 +10:00
}
2018-07-13 12:45:04 +10:00
2020-12-21 15:58:47 +11:00
// if there's only one variant and the model state is not valid we cannot publish so change it to save
2018-09-03 22:46:24 +10:00
if ( variantCount = = 1 )
2018-07-19 19:32:07 +10:00
{
2018-09-03 22:46:24 +10:00
switch ( contentItem . Action )
{
case ContentSaveAction . Publish :
2018-11-15 00:09:57 +11:00
case ContentSaveAction . PublishWithDescendants :
case ContentSaveAction . PublishWithDescendantsForce :
2018-11-09 16:12:08 +11:00
case ContentSaveAction . SendPublish :
case ContentSaveAction . Schedule :
2018-09-03 22:46:24 +10:00
contentItem . Action = ContentSaveAction . Save ;
break ;
case ContentSaveAction . PublishNew :
2018-11-15 00:09:57 +11:00
case ContentSaveAction . PublishWithDescendantsNew :
case ContentSaveAction . PublishWithDescendantsForceNew :
2018-11-09 16:12:08 +11:00
case ContentSaveAction . SendPublishNew :
case ContentSaveAction . ScheduleNew :
2018-09-03 22:46:24 +10:00
contentItem . Action = ContentSaveAction . SaveNew ;
break ;
}
2018-07-19 19:32:07 +10:00
}
}
2018-07-13 12:45:04 +10:00
2018-09-04 01:13:26 +10:00
bool wasCancelled ;
2018-07-13 12:45:04 +10:00
2018-09-04 01:13:26 +10:00
//used to track successful notifications
2018-09-04 16:16:11 +10:00
var globalNotifications = new SimpleNotificationModel ( ) ;
var notifications = new Dictionary < string , SimpleNotificationModel >
{
//global (non variant specific) notifications
[string.Empty] = globalNotifications
} ;
2018-07-13 12:45:04 +10:00
2019-04-04 21:57:49 +11:00
//The default validation language will be either: The default languauge, else if the content is brand new and the default culture is
// not marked to be saved, it will be the first culture in the list marked for saving.
2020-10-19 09:16:20 -07:00
var defaultCulture = _allLangs . Value . Values . FirstOrDefault ( x = > x . IsDefault ) ? . IsoCode ;
2019-04-04 21:57:49 +11:00
var cultureForInvariantErrors = CultureImpact . GetCultureForInvariantErrors (
contentItem . PersistedContent ,
contentItem . Variants . Where ( x = > x . Save ) . Select ( x = > x . Culture ) . ToArray ( ) ,
defaultCulture ) ;
2018-07-19 19:32:07 +10:00
switch ( contentItem . Action )
{
case ContentSaveAction . Save :
case ContentSaveAction . SaveNew :
2019-04-04 21:57:49 +11:00
SaveAndNotify ( contentItem , saveMethod , variantCount , notifications , globalNotifications , "editContentSavedText" , "editVariantSavedText" , cultureForInvariantErrors , out wasCancelled ) ;
2018-07-19 19:32:07 +10:00
break ;
2018-11-09 16:12:08 +11:00
case ContentSaveAction . Schedule :
case ContentSaveAction . ScheduleNew :
2018-11-13 14:31:37 +11:00
if ( ! SaveSchedule ( contentItem , globalNotifications ) )
2018-11-09 16:12:08 +11:00
{
wasCancelled = false ;
break ;
}
2019-04-04 21:57:49 +11:00
SaveAndNotify ( contentItem , saveMethod , variantCount , notifications , globalNotifications , "editContentScheduledSavedText" , "editVariantSavedText" , cultureForInvariantErrors , out wasCancelled ) ;
2018-11-13 14:31:37 +11:00
break ;
2018-11-12 17:29:38 +11:00
2018-07-19 19:32:07 +10:00
case ContentSaveAction . SendPublish :
case ContentSaveAction . SendPublishNew :
2020-10-21 16:51:00 +11:00
var sendResult = _contentService . SendToPublication ( contentItem . PersistedContent , _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Id ) ;
2018-09-04 01:13:26 +10:00
wasCancelled = sendResult = = false ;
if ( sendResult )
2018-07-19 19:32:07 +10:00
{
2018-09-04 01:13:26 +10:00
if ( variantCount > 1 )
{
2020-05-14 17:03:33 +02:00
var variantErrors = ModelState . GetVariantsWithErrors ( cultureForInvariantErrors ) ;
var validVariants = contentItem . Variants
. Where ( x = > x . Save & & ! variantErrors . Contains ( ( x . Culture , x . Segment ) ) )
. Select ( x = > ( culture : x . Culture , segment : x . Segment ) ) ;
foreach ( var ( culture , segment ) in validVariants )
2018-09-04 01:13:26 +10:00
{
2020-05-14 17:03:33 +02:00
var variantName = GetVariantName ( culture , segment ) ;
AddSuccessNotification ( notifications , culture , segment ,
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles/editContentSendToPublish" ) ,
_localizedTextService . Localize ( "speechBubbles/editVariantSendToPublishText" , new [ ] { variantName } ) ) ;
2018-09-04 01:13:26 +10:00
}
}
else if ( ModelState . IsValid )
{
2018-09-04 16:16:11 +10:00
globalNotifications . AddSuccessNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles/editContentSendToPublish" ) ,
_localizedTextService . Localize ( "speechBubbles/editContentSendToPublishText" ) ) ;
2018-09-04 01:13:26 +10:00
}
2018-07-19 19:32:07 +10:00
}
break ;
case ContentSaveAction . Publish :
case ContentSaveAction . PublishNew :
2018-09-04 16:16:11 +10:00
{
2019-04-04 21:57:49 +11:00
var publishStatus = PublishInternal ( contentItem , defaultCulture , cultureForInvariantErrors , out wasCancelled , out var successfulCultures ) ;
2019-06-12 10:11:27 +10:00
AddPublishStatusNotifications ( new [ ] { publishStatus } , globalNotifications , notifications , successfulCultures ) ;
2018-11-15 00:09:57 +11:00
}
break ;
case ContentSaveAction . PublishWithDescendants :
case ContentSaveAction . PublishWithDescendantsNew :
{
2020-11-24 00:37:26 +11:00
if ( ! await ValidatePublishBranchPermissionsAsync ( contentItem ) )
2018-11-15 15:24:09 +11:00
{
globalNotifications . AddErrorNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "publish" ) ,
_localizedTextService . Localize ( "publish/invalidPublishBranchPermissions" ) ) ;
2018-11-15 15:24:09 +11:00
wasCancelled = false ;
break ;
}
2019-04-04 21:57:49 +11:00
var publishStatus = PublishBranchInternal ( contentItem , false , cultureForInvariantErrors , out wasCancelled , out var successfulCultures ) . ToList ( ) ;
2019-06-12 10:11:27 +10:00
AddPublishStatusNotifications ( publishStatus , globalNotifications , notifications , successfulCultures ) ;
2018-11-15 00:09:57 +11:00
}
break ;
case ContentSaveAction . PublishWithDescendantsForce :
case ContentSaveAction . PublishWithDescendantsForceNew :
{
2020-11-24 00:37:26 +11:00
if ( ! await ValidatePublishBranchPermissionsAsync ( contentItem ) )
2018-11-15 15:24:09 +11:00
{
globalNotifications . AddErrorNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "publish" ) ,
_localizedTextService . Localize ( "publish/invalidPublishBranchPermissions" ) ) ;
2018-11-15 15:24:09 +11:00
wasCancelled = false ;
break ;
}
2019-04-04 21:57:49 +11:00
var publishStatus = PublishBranchInternal ( contentItem , true , cultureForInvariantErrors , out wasCancelled , out var successfulCultures ) . ToList ( ) ;
2019-06-12 10:11:27 +10:00
AddPublishStatusNotifications ( publishStatus , globalNotifications , notifications , successfulCultures ) ;
2018-09-04 16:16:11 +10:00
}
2018-07-19 19:32:07 +10:00
break ;
2018-09-04 01:13:26 +10:00
default :
throw new ArgumentOutOfRangeException ( ) ;
2018-07-19 19:32:07 +10:00
}
2018-07-13 12:45:04 +10:00
2018-09-04 01:13:26 +10:00
//get the updated model
2019-06-12 12:22:14 +10:00
var display = mapToDisplay ( contentItem . PersistedContent ) ;
2018-09-04 01:13:26 +10:00
//merge the tracked success messages with the outgoing model
2018-09-04 16:16:11 +10:00
display . Notifications . AddRange ( globalNotifications . Notifications ) ;
2018-09-06 15:21:02 +10:00
foreach ( var v in display . Variants . Where ( x = > x . Language ! = null ) )
2018-09-04 16:16:11 +10:00
{
if ( notifications . TryGetValue ( v . Language . IsoCode , out var n ) )
v . Notifications . AddRange ( n . Notifications ) ;
}
2018-09-04 01:13:26 +10:00
2019-04-04 21:57:49 +11:00
HandleInvalidModelState ( display , cultureForInvariantErrors ) ;
2018-09-04 01:13:26 +10:00
2021-01-13 16:25:21 +01:00
//lastly, if it is not valid, add the model state to the outgoing object and throw a 400
if ( ! ModelState . IsValid )
{
display . Errors = ModelState . ToErrorDictionary ( ) ;
return new ValidationErrorResult ( display ) ;
}
2018-09-04 01:13:26 +10:00
if ( wasCancelled )
2018-07-19 19:32:07 +10:00
{
2018-09-04 01:13:26 +10:00
AddCancelMessage ( display ) ;
if ( IsCreatingAction ( contentItem . Action ) )
{
//If the item is new and the operation was cancelled, we need to return a different
// status code so the UI can handle it since it won't be able to redirect since there
// is no Id to redirect to!
2020-12-29 12:55:48 +01:00
return new ValidationErrorResult ( display ) ;
2018-09-04 01:13:26 +10:00
}
2018-07-19 19:32:07 +10:00
}
2018-11-12 17:29:38 +11:00
2018-07-19 19:32:07 +10:00
display . PersistedContent = contentItem . PersistedContent ;
2018-07-13 12:45:04 +10:00
2018-07-19 19:32:07 +10:00
return display ;
2018-06-29 19:52:40 +02:00
}
2019-06-12 10:11:27 +10:00
private void AddPublishStatusNotifications ( IReadOnlyCollection < PublishResult > publishStatus , SimpleNotificationModel globalNotifications , Dictionary < string , SimpleNotificationModel > variantNotifications , string [ ] successfulCultures )
{
//global notifications
AddMessageForPublishStatus ( publishStatus , globalNotifications , successfulCultures ) ;
//variant specific notifications
foreach ( var c in successfulCultures ? ? Array . Empty < string > ( ) )
AddMessageForPublishStatus ( publishStatus , variantNotifications . GetOrCreate ( c ) , successfulCultures ) ;
}
2019-03-13 18:12:48 +11:00
/// <summary>
/// Validates critical data for persistence and updates the ModelState and result accordingly
/// </summary>
/// <param name="contentItem"></param>
/// <param name="variantCount">Returns the total number of variants (will be one if it's an invariant content item)</param>
/// <returns></returns>
/// <remarks>
/// For invariant, the variants collection count will be 1 and this will check if that invariant item has the critical values for persistence (i.e. Name)
2019-11-05 12:54:22 +01:00
///
2019-03-13 18:12:48 +11:00
/// For variant, each variant will be checked for critical data for persistence and if it's not there then it's flags will be reset and it will not
/// be persisted. However, we also need to deal with the case where all variants don't pass this check and then there is nothing to save. This also deals
/// with removing the Name validation keys based on data annotations validation for items that haven't been marked to be saved.
/// </remarks>
/// <returns>
/// returns false if persistence cannot take place, returns true if persistence can take place even if there are validation errors
/// </returns>
private bool ValidateCriticalData ( ContentItemSave contentItem , out int variantCount )
{
var variants = contentItem . Variants . ToList ( ) ;
variantCount = variants . Count ;
var savedCount = 0 ;
var variantCriticalValidationErrors = new List < string > ( ) ;
for ( var i = 0 ; i < variants . Count ; i + + )
{
var variant = variants [ i ] ;
if ( variant . Save )
{
//ensure the variant has all critical required data to be persisted
if ( ! RequiredForPersistenceAttribute . HasRequiredValuesForPersistence ( variant ) )
{
variantCriticalValidationErrors . Add ( variant . Culture ) ;
//if there's no Name, it cannot be persisted at all reset the flags, this cannot be saved or published
variant . Save = variant . Publish = false ;
//if there's more than 1 variant, then we need to add the culture specific error
//messages based on the variants in error so that the messages show in the publish/save dialog
if ( variants . Count > 1 )
2020-05-14 17:03:33 +02:00
AddVariantValidationError ( variant . Culture , variant . Segment , "publish/contentPublishedFailedByMissingName" ) ;
2019-03-13 18:12:48 +11:00
else
return false ; //It's invariant and is missing critical data, it cannot be saved
}
savedCount + + ;
}
else
{
var msKey = $"Variants[{i}].Name" ;
if ( ModelState . ContainsKey ( msKey ) )
{
//if it's not being saved, remove the validation key
if ( ! variant . Save ) ModelState . Remove ( msKey ) ;
}
}
}
if ( savedCount = = variantCriticalValidationErrors . Count )
{
//in this case there can be nothing saved since all variants marked to be saved haven't passed critical validation rules
return false ;
}
return true ;
}
2019-11-05 12:54:22 +01:00
2019-04-04 21:57:49 +11:00
2018-11-13 14:31:37 +11:00
/// <summary>
/// Helper method to perform the saving of the content and add the notifications to the result
/// </summary>
/// <param name="contentItem"></param>
/// <param name="saveMethod"></param>
/// <param name="variantCount"></param>
/// <param name="notifications"></param>
/// <param name="globalNotifications"></param>
/// <param name="invariantSavedLocalizationKey"></param>
/// <param name="variantSavedLocalizationKey"></param>
/// <param name="wasCancelled"></param>
/// <remarks>
/// Method is used for normal Saving and Scheduled Publishing
/// </remarks>
private void SaveAndNotify ( ContentItemSave contentItem , Func < IContent , OperationResult > saveMethod , int variantCount ,
Dictionary < string , SimpleNotificationModel > notifications , SimpleNotificationModel globalNotifications ,
2019-04-04 21:57:49 +11:00
string invariantSavedLocalizationKey , string variantSavedLocalizationKey , string cultureForInvariantErrors ,
2018-11-13 14:31:37 +11:00
out bool wasCancelled )
{
var saveResult = saveMethod ( contentItem . PersistedContent ) ;
wasCancelled = saveResult . Success = = false & & saveResult . Result = = OperationResultType . FailedCancelledByEvent ;
if ( saveResult . Success )
{
if ( variantCount > 1 )
{
2020-05-14 17:03:33 +02:00
var variantErrors = ModelState . GetVariantsWithErrors ( cultureForInvariantErrors ) ;
2020-02-18 11:46:31 +01:00
var savedWithoutErrors = contentItem . Variants
2020-05-14 17:03:33 +02:00
. Where ( x = > x . Save & & ! variantErrors . Contains ( ( x . Culture , x . Segment ) ) )
2020-05-18 10:37:28 +02:00
. Select ( x = > ( culture : x . Culture , segment : x . Segment ) ) ;
2020-02-18 11:46:31 +01:00
2020-05-14 17:03:33 +02:00
foreach ( var ( culture , segment ) in savedWithoutErrors )
2018-11-13 14:31:37 +11:00
{
2020-05-14 17:03:33 +02:00
var variantName = GetVariantName ( culture , segment ) ;
AddSuccessNotification ( notifications , culture , segment ,
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles/editContentSavedHeader" ) ,
_localizedTextService . Localize ( variantSavedLocalizationKey , new [ ] { variantName } ) ) ;
2018-11-13 14:31:37 +11:00
}
}
else if ( ModelState . IsValid )
{
globalNotifications . AddSuccessNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles/editContentSavedHeader" ) ,
_localizedTextService . Localize ( invariantSavedLocalizationKey ) ) ;
2018-11-13 14:31:37 +11:00
}
}
}
2018-11-09 16:12:08 +11:00
/// <summary>
/// Validates the incoming schedule and update the model
/// </summary>
/// <param name="contentItem"></param>
/// <param name="globalNotifications"></param>
private bool SaveSchedule ( ContentItemSave contentItem , SimpleNotificationModel globalNotifications )
{
if ( ! contentItem . PersistedContent . ContentType . VariesByCulture ( ) )
return SaveScheduleInvariant ( contentItem , globalNotifications ) ;
else
return SaveScheduleVariant ( contentItem ) ;
}
private bool SaveScheduleInvariant ( ContentItemSave contentItem , SimpleNotificationModel globalNotifications )
{
2018-11-12 17:29:38 +11:00
var variant = contentItem . Variants . First ( ) ;
2018-11-09 16:12:08 +11:00
2018-11-14 22:31:51 +11:00
var currRelease = contentItem . PersistedContent . ContentSchedule . GetSchedule ( ContentScheduleAction . Release ) . ToList ( ) ;
var currExpire = contentItem . PersistedContent . ContentSchedule . GetSchedule ( ContentScheduleAction . Expire ) . ToList ( ) ;
2018-11-09 16:12:08 +11:00
2018-11-12 17:29:38 +11:00
//Do all validation of data first
2018-11-09 16:12:08 +11:00
2018-11-12 17:29:38 +11:00
//1) release date cannot be less than now
if ( variant . ReleaseDate . HasValue & & variant . ReleaseDate < DateTime . Now )
2018-11-09 16:12:08 +11:00
{
2018-11-12 17:29:38 +11:00
globalNotifications . AddErrorNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles" , "validationFailedHeader" ) ,
_localizedTextService . Localize ( "speechBubbles" , "scheduleErrReleaseDate1" ) ) ;
2018-11-12 17:29:38 +11:00
return false ;
2018-11-09 16:12:08 +11:00
}
2018-11-12 17:29:38 +11:00
//2) expire date cannot be less than now
if ( variant . ExpireDate . HasValue & & variant . ExpireDate < DateTime . Now )
2018-11-09 16:12:08 +11:00
{
2018-11-12 17:29:38 +11:00
globalNotifications . AddErrorNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles" , "validationFailedHeader" ) ,
_localizedTextService . Localize ( "speechBubbles" , "scheduleErrExpireDate1" ) ) ;
2018-11-12 17:29:38 +11:00
return false ;
2018-11-09 16:12:08 +11:00
}
2018-11-12 17:29:38 +11:00
//3) expire date cannot be less than release date
if ( variant . ExpireDate . HasValue & & variant . ReleaseDate . HasValue & & variant . ExpireDate < = variant . ReleaseDate )
2018-11-09 16:12:08 +11:00
{
2018-11-12 17:29:38 +11:00
globalNotifications . AddErrorNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles" , "validationFailedHeader" ) ,
_localizedTextService . Localize ( "speechBubbles" , "scheduleErrExpireDate2" ) ) ;
2018-11-12 17:29:38 +11:00
return false ;
2018-11-09 16:12:08 +11:00
}
2018-11-12 17:29:38 +11:00
//Now we can do the data updates
//remove any existing release dates so we can replace it
//if there is a release date in the request or if there was previously a release and the request value is null then we are clearing the schedule
if ( variant . ReleaseDate . HasValue | | currRelease . Count > 0 )
2018-11-14 22:31:51 +11:00
contentItem . PersistedContent . ContentSchedule . Clear ( ContentScheduleAction . Release ) ;
2018-11-12 17:29:38 +11:00
//remove any existing expire dates so we can replace it
//if there is an expiry date in the request or if there was a previous expiry and the request value is null then we are clearing the schedule
if ( variant . ExpireDate . HasValue | | currExpire . Count > 0 )
2018-11-14 22:31:51 +11:00
contentItem . PersistedContent . ContentSchedule . Clear ( ContentScheduleAction . Expire ) ;
2018-11-12 17:29:38 +11:00
2018-11-09 16:12:08 +11:00
//add the new schedule
2018-11-12 17:29:38 +11:00
contentItem . PersistedContent . ContentSchedule . Add ( variant . ReleaseDate , variant . ExpireDate ) ;
2018-11-09 16:12:08 +11:00
return true ;
}
private bool SaveScheduleVariant ( ContentItemSave contentItem )
{
2018-11-13 00:46:13 +11:00
//All variants in this collection should have a culture if we get here but we'll double check and filter here)
var cultureVariants = contentItem . Variants . Where ( x = > ! x . Culture . IsNullOrWhiteSpace ( ) ) . ToList ( ) ;
var mandatoryCultures = _allLangs . Value . Values . Where ( x = > x . IsMandatory ) . Select ( x = > x . IsoCode ) . ToList ( ) ;
2018-11-12 17:29:38 +11:00
2018-11-13 14:31:37 +11:00
//Make a copy of the current schedule and apply updates to it
var schedCopy = ( ContentScheduleCollection ) contentItem . PersistedContent . ContentSchedule . DeepClone ( ) ;
foreach ( var variant in cultureVariants . Where ( x = > x . Save ) )
{
2018-11-14 22:31:51 +11:00
var currRelease = schedCopy . GetSchedule ( variant . Culture , ContentScheduleAction . Release ) . ToList ( ) ;
var currExpire = schedCopy . GetSchedule ( variant . Culture , ContentScheduleAction . Expire ) . ToList ( ) ;
2018-11-13 14:31:37 +11:00
//remove any existing release dates so we can replace it
//if there is a release date in the request or if there was previously a release and the request value is null then we are clearing the schedule
if ( variant . ReleaseDate . HasValue | | currRelease . Count > 0 )
2018-11-14 22:31:51 +11:00
schedCopy . Clear ( variant . Culture , ContentScheduleAction . Release ) ;
2018-11-13 14:31:37 +11:00
//remove any existing expire dates so we can replace it
//if there is an expiry date in the request or if there was a previous expiry and the request value is null then we are clearing the schedule
if ( variant . ExpireDate . HasValue | | currExpire . Count > 0 )
2018-11-14 22:31:51 +11:00
schedCopy . Clear ( variant . Culture , ContentScheduleAction . Expire ) ;
2018-11-13 14:31:37 +11:00
//add the new schedule
schedCopy . Add ( variant . Culture , variant . ReleaseDate , variant . ExpireDate ) ;
}
//now validate the new schedule to make sure it passes all of the rules
2018-11-09 16:12:08 +11:00
var isValid = true ;
2018-11-13 00:46:13 +11:00
2018-11-13 14:31:37 +11:00
//create lists of mandatory/non-mandatory states
var mandatoryVariants = new List < ( string culture , bool isPublished , List < DateTime > releaseDates ) > ( ) ;
var nonMandatoryVariants = new List < ( string culture , bool isPublished , List < DateTime > releaseDates ) > ( ) ;
foreach ( var groupedSched in schedCopy . FullSchedule . GroupBy ( x = > x . Culture ) )
{
var isPublished = contentItem . PersistedContent . Published & & contentItem . PersistedContent . IsCulturePublished ( groupedSched . Key ) ;
2018-11-14 22:31:51 +11:00
var releaseDates = groupedSched . Where ( x = > x . Action = = ContentScheduleAction . Release ) . Select ( x = > x . Date ) . ToList ( ) ;
2018-11-13 14:31:37 +11:00
if ( mandatoryCultures . Contains ( groupedSched . Key , StringComparer . InvariantCultureIgnoreCase ) )
mandatoryVariants . Add ( ( groupedSched . Key , isPublished , releaseDates ) ) ;
else
nonMandatoryVariants . Add ( ( groupedSched . Key , isPublished , releaseDates ) ) ;
}
var nonMandatoryVariantReleaseDates = nonMandatoryVariants . SelectMany ( x = > x . releaseDates ) . ToList ( ) ;
2018-11-13 00:46:13 +11:00
2018-11-13 14:31:37 +11:00
//validate that the mandatory languages have the right data
foreach ( var ( culture , isPublished , releaseDates ) in mandatoryVariants )
2018-11-13 00:46:13 +11:00
{
2018-11-13 14:31:37 +11:00
if ( ! isPublished & & releaseDates . Count = = 0 )
2018-11-13 00:46:13 +11:00
{
2018-11-13 14:31:37 +11:00
//can't continue, a mandatory variant is not published and not scheduled for publishing
2020-05-14 17:03:33 +02:00
// TODO: Add segment
AddVariantValidationError ( culture , null , "speechBubbles/scheduleErrReleaseDate2" ) ;
2018-11-13 00:46:13 +11:00
isValid = false ;
2018-11-13 14:31:37 +11:00
continue ;
}
if ( ! isPublished & & releaseDates . Any ( x = > nonMandatoryVariantReleaseDates . Any ( r = > x . Date > r . Date ) ) )
{
//can't continue, a mandatory variant is not published and it's scheduled for publishing after a non-mandatory
2020-05-14 17:03:33 +02:00
// TODO: Add segment
AddVariantValidationError ( culture , null , "speechBubbles/scheduleErrReleaseDate3" ) ;
2018-11-13 14:31:37 +11:00
isValid = false ;
continue ;
2018-11-13 00:46:13 +11:00
}
}
if ( ! isValid ) return false ;
2018-11-13 14:31:37 +11:00
//now we can validate the more basic rules for individual variants
2018-11-13 00:46:13 +11:00
foreach ( var variant in cultureVariants . Where ( x = > x . ReleaseDate . HasValue | | x . ExpireDate . HasValue ) )
2018-11-09 16:12:08 +11:00
{
2018-11-12 17:29:38 +11:00
//1) release date cannot be less than now
if ( variant . ReleaseDate . HasValue & & variant . ReleaseDate < DateTime . Now )
2018-11-09 16:12:08 +11:00
{
2020-05-14 17:03:33 +02:00
AddVariantValidationError ( variant . Culture , variant . Segment , "speechBubbles/scheduleErrReleaseDate1" ) ;
2018-11-09 16:12:08 +11:00
isValid = false ;
continue ;
}
2018-11-12 17:29:38 +11:00
//2) expire date cannot be less than now
if ( variant . ExpireDate . HasValue & & variant . ExpireDate < DateTime . Now )
2018-11-09 16:12:08 +11:00
{
2020-05-14 17:03:33 +02:00
AddVariantValidationError ( variant . Culture , variant . Segment , "speechBubbles/scheduleErrExpireDate1" ) ;
2018-11-12 17:29:38 +11:00
isValid = false ;
continue ;
2018-11-09 16:12:08 +11:00
}
2018-11-12 17:29:38 +11:00
//3) expire date cannot be less than release date
if ( variant . ExpireDate . HasValue & & variant . ReleaseDate . HasValue & & variant . ExpireDate < = variant . ReleaseDate )
2018-11-09 16:12:08 +11:00
{
2020-05-14 17:03:33 +02:00
AddVariantValidationError ( variant . Culture , variant . Segment , "speechBubbles/scheduleErrExpireDate2" ) ;
2018-11-12 17:29:38 +11:00
isValid = false ;
continue ;
2018-11-09 16:12:08 +11:00
}
2018-11-12 17:29:38 +11:00
}
2018-11-09 16:12:08 +11:00
2018-11-12 17:29:38 +11:00
if ( ! isValid ) return false ;
2018-11-13 14:31:37 +11:00
//now that we are validated, we can assign the copied schedule back to the model
contentItem . PersistedContent . ContentSchedule = schedCopy ;
return true ;
2018-11-09 16:12:08 +11:00
}
2018-09-04 16:16:11 +10:00
/// <summary>
/// Used to add success notifications globally and for the culture
/// </summary>
/// <param name="notifications"></param>
/// <param name="culture"></param>
/// <param name="header"></param>
/// <param name="msg"></param>
/// <remarks>
/// global notifications will be shown if all variant processing is successful and the save/publish dialog is closed, otherwise
2019-01-26 10:52:19 -05:00
/// variant specific notifications are used to show success messages in the save/publish dialog.
2018-09-04 16:16:11 +10:00
/// </remarks>
2020-05-14 17:03:33 +02:00
private static void AddSuccessNotification ( IDictionary < string , SimpleNotificationModel > notifications , string culture , string segment , string header , string msg )
2018-09-04 16:16:11 +10:00
{
//add the global notification (which will display globally if all variants are successfully processed)
notifications [ string . Empty ] . AddSuccessNotification ( header , msg ) ;
//add the variant specific notification (which will display in the dialog if all variants are not successfully processed)
2020-05-14 17:03:33 +02:00
var key = culture + "_" + segment ;
notifications . GetOrCreate ( key ) . AddSuccessNotification ( header , msg ) ;
2018-09-04 16:16:11 +10:00
}
2018-11-15 15:24:09 +11:00
/// <summary>
/// The user must have publish access to all descendant nodes of the content item in order to continue
/// </summary>
/// <param name="contentItem"></param>
/// <returns></returns>
2020-11-24 00:37:26 +11:00
private async Task < bool > ValidatePublishBranchPermissionsAsync ( ContentItemSave contentItem )
2018-11-15 15:24:09 +11:00
{
2020-11-24 00:37:26 +11:00
// Authorize...
var requirement = new ContentPermissionsPublishBranchRequirement ( ActionPublish . ActionLetter ) ;
var authorizationResult = await _authorizationService . AuthorizeAsync ( User , contentItem . PersistedContent , requirement ) ;
return authorizationResult . Succeeded ;
2018-11-15 15:24:09 +11:00
}
2019-04-04 21:57:49 +11:00
private IEnumerable < PublishResult > PublishBranchInternal ( ContentItemSave contentItem , bool force , string cultureForInvariantErrors ,
2018-11-15 00:09:57 +11:00
out bool wasCancelled , out string [ ] successfulCultures )
{
if ( ! contentItem . PersistedContent . ContentType . VariesByCulture ( ) )
{
//its invariant, proceed normally
2020-10-21 16:51:00 +11:00
var publishStatus = _contentService . SaveAndPublishBranch ( contentItem . PersistedContent , force , userId : _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Id ) ;
2019-01-27 01:17:32 -05:00
// TODO: Deal with multiple cancellations
2018-11-15 00:09:57 +11:00
wasCancelled = publishStatus . Any ( x = > x . Result = = PublishResultType . FailedPublishCancelledByEvent ) ;
2019-06-07 13:13:52 +10:00
successfulCultures = null ; //must be null! this implies invariant
2019-01-04 08:16:20 +01:00
return publishStatus ;
2018-11-15 00:09:57 +11:00
}
var mandatoryCultures = _allLangs . Value . Values . Where ( x = > x . IsMandatory ) . Select ( x = > x . IsoCode ) . ToList ( ) ;
2020-05-14 17:03:33 +02:00
var variantErrors = ModelState . GetVariantsWithErrors ( cultureForInvariantErrors ) ;
var variants = contentItem . Variants . ToList ( ) ;
2019-03-14 14:18:42 +11:00
2018-11-15 00:09:57 +11:00
//validate if we can publish based on the mandatory language requirements
var canPublish = ValidatePublishingMandatoryLanguages (
2020-05-14 17:03:33 +02:00
variantErrors ,
contentItem , variants , mandatoryCultures ,
2019-03-14 14:18:42 +11:00
mandatoryVariant = > mandatoryVariant . Publish ) ;
2018-11-15 00:09:57 +11:00
//Now check if there are validation errors on each variant.
//If validation errors are detected on a variant and it's state is set to 'publish', then we
//need to change it to 'save'.
//It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages.
2019-11-05 12:54:22 +01:00
2018-11-15 00:09:57 +11:00
foreach ( var variant in contentItem . Variants )
{
2020-05-14 17:03:33 +02:00
if ( variantErrors . Contains ( ( variant . Culture , variant . Segment ) ) )
2018-11-15 00:09:57 +11:00
variant . Publish = false ;
}
2020-05-14 17:03:33 +02:00
var culturesToPublish = variants . Where ( x = > x . Publish ) . Select ( x = > x . Culture ) . ToArray ( ) ;
2018-11-15 00:09:57 +11:00
if ( canPublish )
{
//proceed to publish if all validation still succeeds
2020-10-21 16:51:00 +11:00
var publishStatus = _contentService . SaveAndPublishBranch ( contentItem . PersistedContent , force , culturesToPublish , _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Id ) ;
2019-01-27 01:17:32 -05:00
// TODO: Deal with multiple cancellations
2018-11-15 00:09:57 +11:00
wasCancelled = publishStatus . Any ( x = > x . Result = = PublishResultType . FailedPublishCancelledByEvent ) ;
successfulCultures = contentItem . Variants . Where ( x = > x . Publish ) . Select ( x = > x . Culture ) . ToArray ( ) ;
return publishStatus ;
}
else
{
//can only save
2020-10-21 16:51:00 +11:00
var saveResult = _contentService . Save ( contentItem . PersistedContent , _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Id ) ;
2018-11-15 00:09:57 +11:00
var publishStatus = new [ ]
{
new PublishResult ( PublishResultType . FailedPublishMandatoryCultureMissing , null , contentItem . PersistedContent )
} ;
wasCancelled = saveResult . Result = = OperationResultType . FailedCancelledByEvent ;
successfulCultures = Array . Empty < string > ( ) ;
return publishStatus ;
}
}
2018-06-29 19:52:40 +02:00
/// <summary>
/// Performs the publishing operation for a content item
/// </summary>
/// <param name="contentItem"></param>
2018-05-08 01:16:32 +10:00
/// <param name="wasCancelled"></param>
2018-09-04 01:13:26 +10:00
/// <param name="successfulCultures">
/// if the content is variant this will return an array of cultures that will be published (passed validation rules)
/// </param>
2018-05-08 01:16:32 +10:00
/// <remarks>
/// If this is a culture variant than we need to do some validation, if it's not we'll publish as normal
/// </remarks>
2019-04-04 21:57:49 +11:00
private PublishResult PublishInternal ( ContentItemSave contentItem , string defaultCulture , string cultureForInvariantErrors , out bool wasCancelled , out string [ ] successfulCultures )
2018-05-08 01:16:32 +10:00
{
2018-06-20 14:18:57 +02:00
if ( ! contentItem . PersistedContent . ContentType . VariesByCulture ( ) )
2018-05-08 01:16:32 +10:00
{
//its invariant, proceed normally
2020-10-21 16:51:00 +11:00
var publishStatus = _contentService . SaveAndPublish ( contentItem . PersistedContent , userId : _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Id ) ;
2018-11-07 19:42:49 +11:00
wasCancelled = publishStatus . Result = = PublishResultType . FailedPublishCancelledByEvent ;
2019-06-12 10:11:27 +10:00
successfulCultures = null ; //must be null! this implies invariant
2018-11-15 00:09:57 +11:00
return publishStatus ;
2018-05-08 01:16:32 +10:00
}
2018-11-15 00:09:57 +11:00
var mandatoryCultures = _allLangs . Value . Values . Where ( x = > x . IsMandatory ) . Select ( x = > x . IsoCode ) . ToList ( ) ;
2020-05-14 17:03:33 +02:00
var variantErrors = ModelState . GetVariantsWithErrors ( cultureForInvariantErrors ) ;
var variants = contentItem . Variants . ToList ( ) ;
2019-03-14 14:18:42 +11:00
//validate if we can publish based on the mandatory languages selected
2018-11-15 00:09:57 +11:00
var canPublish = ValidatePublishingMandatoryLanguages (
2020-05-14 17:03:33 +02:00
variantErrors ,
contentItem , variants , mandatoryCultures ,
2019-03-14 14:18:42 +11:00
mandatoryVariant = > mandatoryVariant . Publish ) ;
//if none are published and there are validation errors for mandatory cultures, then we can't publish anything
2018-11-15 00:09:57 +11:00
//Now check if there are validation errors on each variant.
//If validation errors are detected on a variant and it's state is set to 'publish', then we
//need to change it to 'save'.
2019-11-05 12:54:22 +01:00
//It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages.
2018-11-15 00:09:57 +11:00
foreach ( var variant in contentItem . Variants )
2018-05-08 01:16:32 +10:00
{
2020-05-14 17:03:33 +02:00
if ( variantErrors . Contains ( ( variant . Culture , variant . Segment ) ) )
2018-11-15 00:09:57 +11:00
variant . Publish = false ;
}
2018-09-03 22:46:24 +10:00
2019-02-19 02:51:05 +11:00
//At this stage all variants might have failed validation which means there are no cultures flagged for publishing!
2020-05-14 17:03:33 +02:00
var culturesToPublish = variants . Where ( x = > x . Publish ) . Select ( x = > x . Culture ) . ToArray ( ) ;
2019-02-19 02:51:05 +11:00
canPublish = canPublish & & culturesToPublish . Length > 0 ;
2018-11-15 00:09:57 +11:00
if ( canPublish )
{
//try to publish all the values on the model - this will generally only fail if someone is tampering with the request
//since there's no reason variant rules would be violated in normal cases.
2020-05-14 17:03:33 +02:00
canPublish = PublishCulture ( contentItem . PersistedContent , variants , defaultCulture ) ;
2018-11-15 00:09:57 +11:00
}
2018-05-08 01:16:32 +10:00
2018-11-15 00:09:57 +11:00
if ( canPublish )
{
//proceed to publish if all validation still succeeds
2020-10-21 16:51:00 +11:00
var publishStatus = _contentService . SaveAndPublish ( contentItem . PersistedContent , culturesToPublish , _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Id ) ;
2018-11-15 00:09:57 +11:00
wasCancelled = publishStatus . Result = = PublishResultType . FailedPublishCancelledByEvent ;
2019-02-04 16:55:35 +11:00
successfulCultures = culturesToPublish ;
2018-11-15 00:09:57 +11:00
return publishStatus ;
}
else
{
//can only save
2020-10-21 16:51:00 +11:00
var saveResult = _contentService . Save ( contentItem . PersistedContent , _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Id ) ;
2018-11-15 00:09:57 +11:00
var publishStatus = new PublishResult ( PublishResultType . FailedPublishMandatoryCultureMissing , null , contentItem . PersistedContent ) ;
wasCancelled = saveResult . Result = = OperationResultType . FailedCancelledByEvent ;
successfulCultures = Array . Empty < string > ( ) ;
return publishStatus ;
2018-05-08 01:16:32 +10:00
}
}
2018-05-10 18:01:41 +10:00
2018-09-03 22:46:24 +10:00
/// <summary>
/// Validate if publishing is possible based on the mandatory language requirements
/// </summary>
2020-05-14 17:03:33 +02:00
/// <param name="variantsWithValidationErrors"></param>
2018-09-03 22:46:24 +10:00
/// <param name="contentItem"></param>
2020-05-14 17:03:33 +02:00
/// <param name="variants"></param>
2019-02-04 16:55:35 +11:00
/// <param name="mandatoryCultures"></param>
/// <param name="publishingCheck"></param>
2018-09-03 22:46:24 +10:00
/// <returns></returns>
2018-11-09 16:12:08 +11:00
private bool ValidatePublishingMandatoryLanguages (
2020-05-14 17:03:33 +02:00
IReadOnlyCollection < ( string culture , string segment ) > variantsWithValidationErrors ,
2018-11-09 16:12:08 +11:00
ContentItemSave contentItem ,
2020-05-14 17:03:33 +02:00
IReadOnlyCollection < ContentVariantSave > variants ,
2018-11-13 00:46:13 +11:00
IReadOnlyList < string > mandatoryCultures ,
2019-03-14 14:18:42 +11:00
Func < ContentVariantSave , bool > publishingCheck )
2018-09-03 22:46:24 +10:00
{
var canPublish = true ;
2019-03-14 14:18:42 +11:00
var result = new List < ( ContentVariantSave model , bool publishing , bool isValid ) > ( ) ;
2018-09-03 22:46:24 +10:00
2018-11-13 00:46:13 +11:00
foreach ( var culture in mandatoryCultures )
2018-09-03 22:46:24 +10:00
{
//Check if a mandatory language is missing from being published
2020-05-14 17:03:33 +02:00
var mandatoryVariant = variants . First ( x = > x . Culture . InvariantEquals ( culture ) ) ;
2018-12-07 13:44:41 +01:00
2018-11-13 00:46:13 +11:00
var isPublished = contentItem . PersistedContent . Published & & contentItem . PersistedContent . IsCulturePublished ( culture ) ;
2019-02-04 16:55:35 +11:00
var isPublishing = isPublished | | publishingCheck ( mandatoryVariant ) ;
2020-05-14 17:03:33 +02:00
var isValid = ! variantsWithValidationErrors . Select ( v = > v . culture ) . InvariantContains ( culture ) ;
2018-09-03 22:46:24 +10:00
2019-03-14 14:18:42 +11:00
result . Add ( ( mandatoryVariant , isPublished | | isPublishing , isValid ) ) ;
}
2018-09-03 22:46:24 +10:00
2019-03-14 14:18:42 +11:00
//iterate over the results by invalid first
string firstInvalidMandatoryCulture = null ;
foreach ( var r in result . OrderBy ( x = > x . isValid ) )
{
if ( ! r . isValid )
firstInvalidMandatoryCulture = r . model . Culture ;
2018-09-03 22:46:24 +10:00
2019-03-14 14:18:42 +11:00
if ( r . publishing & & ! r . isValid )
{
//flagged for publishing but the mandatory culture is invalid
2020-05-14 17:03:33 +02:00
AddVariantValidationError ( r . model . Culture , r . model . Segment , "publish/contentPublishedFailedReqCultureValidationError" ) ;
2019-03-14 14:18:42 +11:00
canPublish = false ;
}
else if ( r . publishing & & r . isValid & & firstInvalidMandatoryCulture ! = null )
{
//in this case this culture also cannot be published because another mandatory culture is invalid
2020-05-14 17:03:33 +02:00
AddVariantValidationError ( r . model . Culture , r . model . Segment , "publish/contentPublishedFailedReqCultureValidationError" , firstInvalidMandatoryCulture ) ;
2019-03-14 14:18:42 +11:00
canPublish = false ;
}
else if ( ! r . publishing )
{
//cannot continue publishing since a required culture that is not currently being published isn't published
2020-05-14 17:03:33 +02:00
AddVariantValidationError ( r . model . Culture , r . model . Segment , "speechBubbles/contentReqCulturePublishError" ) ;
2019-03-14 14:18:42 +11:00
canPublish = false ;
}
2018-09-03 22:46:24 +10:00
}
return canPublish ;
}
2018-06-29 19:52:40 +02:00
/// <summary>
2019-02-04 16:55:35 +11:00
/// Call PublishCulture on the content item for each culture to get a validation result for each culture
2018-06-29 19:52:40 +02:00
/// </summary>
2018-07-31 17:50:24 +10:00
/// <param name="persistentContent"></param>
/// <param name="cultureVariants"></param>
2018-05-10 18:01:41 +10:00
/// <returns></returns>
2018-11-06 21:33:24 +11:00
/// <remarks>
/// This would generally never fail unless someone is tampering with the request
/// </remarks>
2019-04-04 21:57:49 +11:00
private bool PublishCulture ( IContent persistentContent , IEnumerable < ContentVariantSave > cultureVariants , string defaultCulture )
2018-05-10 18:01:41 +10:00
{
2018-11-12 17:29:38 +11:00
foreach ( var variant in cultureVariants . Where ( x = > x . Publish ) )
2018-05-10 18:01:41 +10:00
{
2018-06-22 21:03:47 +02:00
// publishing any culture, implies the invariant culture
2019-04-04 21:57:49 +11:00
var valid = persistentContent . PublishCulture ( CultureImpact . Explicit ( variant . Culture , defaultCulture . InvariantEquals ( variant . Culture ) ) ) ;
2018-05-10 18:01:41 +10:00
if ( ! valid )
{
2020-05-14 17:03:33 +02:00
AddVariantValidationError ( variant . Culture , variant . Segment , "speechBubbles/contentCultureValidationError" ) ;
2018-05-10 18:01:41 +10:00
return false ;
}
}
return true ;
}
2018-06-29 19:52:40 +02:00
2018-08-06 16:50:40 +10:00
/// <summary>
2018-11-09 16:12:08 +11:00
/// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs
2018-08-06 16:50:40 +10:00
/// </summary>
2019-03-14 14:18:42 +11:00
/// <param name="culture">Culture to assign the error to</param>
2020-05-14 17:03:33 +02:00
/// <param name="segment">Segment to assign the error to</param>
2018-08-14 16:29:14 +10:00
/// <param name="localizationKey"></param>
2019-03-14 14:18:42 +11:00
/// <param name="cultureToken">
2019-11-05 12:54:22 +01:00
/// The culture used in the localization message, null by default which means <see cref="culture"/> will be used.
2019-03-14 14:18:42 +11:00
/// </param>
2020-05-14 17:03:33 +02:00
private void AddVariantValidationError ( string culture , string segment , string localizationKey , string cultureToken = null )
2018-08-06 16:50:40 +10:00
{
2020-05-14 17:03:33 +02:00
var cultureToUse = cultureToken ? ? culture ;
var variantName = GetVariantName ( cultureToUse , segment ) ;
2020-06-09 13:48:50 +02:00
var errMsg = _localizedTextService . Localize ( localizationKey , new [ ] { variantName } ) ;
2020-05-14 17:03:33 +02:00
ModelState . AddVariantValidationError ( culture , segment , errMsg ) ;
}
/// <summary>
2020-05-18 10:37:28 +02:00
/// Creates the human readable variant name based on culture and segment
2020-05-14 17:03:33 +02:00
/// </summary>
/// <param name="culture">Culture</param>
/// <param name="segment">Segment</param>
/// <returns></returns>
private string GetVariantName ( string culture , string segment )
{
2020-12-02 14:21:05 +00:00
if ( culture . IsNullOrWhiteSpace ( ) & & segment . IsNullOrWhiteSpace ( ) )
2020-05-14 17:03:33 +02:00
{
// TODO: Get name for default variant from somewhere?
return "Default" ;
}
var cultureName = culture = = null ? null : _allLangs . Value [ culture ] . CultureName ;
var variantName = string . Join ( " — " , new [ ] { segment , cultureName } . Where ( x = > ! x . IsNullOrWhiteSpace ( ) ) ) ;
// Format: <segment> [—] <culture name>
return variantName ;
2018-08-06 16:50:40 +10:00
}
2018-10-29 17:27:33 +11:00
2018-06-29 19:52:40 +02:00
/// <summary>
/// Publishes a document with a given ID
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
/// <remarks>
2018-10-29 17:27:33 +11:00
/// The EnsureUserPermissionForContent attribute will deny access to this method if the current user
2018-06-29 19:52:40 +02:00
/// does not have Publish access to this node.
/// </remarks>
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionPublishById)]
2020-06-09 13:48:50 +02:00
public IActionResult PostPublishById ( int id )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var foundContent = GetObjectFromRequest ( ( ) = > _contentService . GetById ( id ) ) ;
2018-06-29 19:52:40 +02:00
if ( foundContent = = null )
{
2021-01-14 19:41:32 +01:00
return HandleContentNotFound ( id ) ;
2018-06-29 19:52:40 +02:00
}
2020-10-21 16:51:00 +11:00
var publishResult = _contentService . SaveAndPublish ( foundContent , userId : _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-06-29 19:52:40 +02:00
if ( publishResult . Success = = false )
{
var notificationModel = new SimpleNotificationModel ( ) ;
2019-03-12 16:10:35 +11:00
AddMessageForPublishStatus ( new [ ] { publishResult } , notificationModel ) ;
2020-12-29 12:55:48 +01:00
return new ValidationErrorResult ( notificationModel ) ;
2018-06-29 19:52:40 +02:00
}
2020-06-09 13:48:50 +02:00
return Ok ( ) ;
2018-06-29 19:52:40 +02:00
}
[HttpDelete]
[HttpPost]
2020-06-09 13:48:50 +02:00
public IActionResult DeleteBlueprint ( int id )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var found = _contentService . GetBlueprintById ( id ) ;
2018-06-29 19:52:40 +02:00
if ( found = = null )
{
2021-01-14 19:41:32 +01:00
return HandleContentNotFound ( id ) ;
2018-06-29 19:52:40 +02:00
}
2020-06-09 13:48:50 +02:00
_contentService . DeleteBlueprint ( found ) ;
2018-06-29 19:52:40 +02:00
2020-06-09 13:48:50 +02:00
return Ok ( ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Moves an item to the recycle bin, if it is already there then it will permanently delete it
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
/// <remarks>
/// The CanAccessContentAuthorize attribute will deny access to this method if the current user
/// does not have Delete access to this node.
/// </remarks>
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionDeleteById)]
2018-06-29 19:52:40 +02:00
[HttpDelete]
[HttpPost]
2020-06-09 13:48:50 +02:00
public IActionResult DeleteById ( int id )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var foundContent = GetObjectFromRequest ( ( ) = > _contentService . GetById ( id ) ) ;
2018-06-29 19:52:40 +02:00
if ( foundContent = = null )
{
2021-01-14 19:41:32 +01:00
return HandleContentNotFound ( id ) ;
2018-06-29 19:52:40 +02:00
}
//if the current item is in the recycle bin
if ( foundContent . Trashed = = false )
{
2020-10-21 16:51:00 +11:00
var moveResult = _contentService . MoveToRecycleBin ( foundContent , _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-06-29 19:52:40 +02:00
if ( moveResult . Success = = false )
{
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
2020-12-29 12:55:48 +01:00
return new ValidationErrorResult ( new SimpleNotificationModel ( ) ) ;
2018-06-29 19:52:40 +02:00
}
}
else
{
2020-10-21 16:51:00 +11:00
var deleteResult = _contentService . Delete ( foundContent , _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-06-29 19:52:40 +02:00
if ( deleteResult . Success = = false )
{
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
2020-12-29 12:55:48 +01:00
return new ValidationErrorResult ( new SimpleNotificationModel ( ) ) ;
2018-06-29 19:52:40 +02:00
}
}
2020-06-09 13:48:50 +02:00
return Ok ( ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Empties the recycle bin
/// </summary>
/// <returns></returns>
/// <remarks>
/// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin
/// </remarks>
[HttpDelete]
[HttpPost]
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionEmptyRecycleBin)]
2020-06-12 11:14:06 +02:00
public IActionResult EmptyRecycleBin ( )
2018-06-29 19:52:40 +02:00
{
2020-10-21 16:51:00 +11:00
_contentService . EmptyRecycleBin ( _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( Constants . Security . SuperUserId ) ) ;
2018-06-29 19:52:40 +02:00
2020-06-12 11:14:06 +02:00
return new UmbracoNotificationSuccessResponse ( _localizedTextService . Localize ( "defaultdialogs/recycleBinIsEmpty" ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Change the sort order for content
/// </summary>
/// <param name="sorted"></param>
/// <returns></returns>
2020-11-23 22:43:41 +11:00
public async Task < IActionResult > PostSort ( ContentSortOrder sorted )
2018-06-29 19:52:40 +02:00
{
if ( sorted = = null )
{
2020-06-09 13:48:50 +02:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
//if there's nothing to sort just return ok
if ( sorted . IdSortOrder . Length = = 0 )
{
2020-06-09 13:48:50 +02:00
return Ok ( ) ;
2018-06-29 19:52:40 +02:00
}
2020-11-23 22:43:41 +11:00
// Authorize...
2020-11-30 19:09:14 +11:00
var resource = new ContentPermissionsResource ( _contentService . GetById ( sorted . ParentId ) , ActionSort . ActionLetter ) ;
var authorizationResult = await _authorizationService . AuthorizeAsync ( User , resource , AuthorizationPolicies . ContentPermissionByResource ) ;
2020-11-23 22:43:41 +11:00
if ( ! authorizationResult . Succeeded )
2018-06-29 19:52:40 +02:00
{
2020-11-23 22:43:41 +11:00
return Forbid ( ) ;
}
2018-06-29 19:52:40 +02:00
2020-11-23 22:43:41 +11:00
try
{
2018-06-29 19:52:40 +02:00
// Save content with new sort order and update content xml in db accordingly
2020-11-23 22:43:41 +11:00
var sortResult = _contentService . Sort ( sorted . IdSortOrder , _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Id ) ;
2018-10-24 23:55:55 +11:00
if ( ! sortResult . Success )
2018-06-29 19:52:40 +02:00
{
2020-09-21 09:52:58 +02:00
_logger . LogWarning ( "Content sorting failed, this was probably caused by an event being cancelled" ) ;
2019-01-27 01:17:32 -05:00
// TODO: Now you can cancel sorting, does the event messages bubble up automatically?
2020-12-22 16:36:07 +01:00
return new ValidationErrorResult ( "Content sorting failed, this was probably caused by an event being cancelled" ) ;
2018-06-29 19:52:40 +02:00
}
2018-08-03 08:35:01 +01:00
2020-06-09 13:48:50 +02:00
return Ok ( ) ;
2018-06-29 19:52:40 +02:00
}
catch ( Exception ex )
{
2020-09-21 09:52:58 +02:00
_logger . LogError ( ex , "Could not update content sort order" ) ;
2018-06-29 19:52:40 +02:00
throw ;
}
}
/// <summary>
/// Change the sort order for media
/// </summary>
/// <param name="move"></param>
/// <returns></returns>
2020-11-23 22:43:41 +11:00
public async Task < IActionResult > PostMove ( MoveOrCopy move )
2018-06-29 19:52:40 +02:00
{
2020-11-23 22:43:41 +11:00
// Authorize...
2020-11-30 19:09:14 +11:00
var resource = new ContentPermissionsResource ( _contentService . GetById ( move . ParentId ) , ActionMove . ActionLetter ) ;
var authorizationResult = await _authorizationService . AuthorizeAsync ( User , resource , AuthorizationPolicies . ContentPermissionByResource ) ;
2020-11-23 22:43:41 +11:00
if ( ! authorizationResult . Succeeded )
{
return Forbid ( ) ;
}
2021-01-14 19:41:32 +01:00
var toMoveResult = ValidateMoveOrCopy ( move ) ;
2021-01-29 10:30:28 +01:00
if ( ! ( toMoveResult . Result is null ) )
2021-01-14 19:41:32 +01:00
{
return toMoveResult . Result ;
}
var toMove = toMoveResult . Value ;
2018-06-29 19:52:40 +02:00
2020-10-21 16:51:00 +11:00
_contentService . Move ( toMove , move . ParentId , _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-06-29 19:52:40 +02:00
2020-06-12 22:13:43 +02:00
return Content ( toMove . Path , MediaTypeNames . Text . Plain , Encoding . UTF8 ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Copies a content item and places the copy as a child of a given parent Id
/// </summary>
/// <param name="copy"></param>
/// <returns></returns>
2021-01-14 19:41:32 +01:00
public async Task < ActionResult < IContent > > PostCopy ( MoveOrCopy copy )
2018-06-29 19:52:40 +02:00
{
2020-11-23 22:43:41 +11:00
// Authorize...
2020-11-30 19:09:14 +11:00
var resource = new ContentPermissionsResource ( _contentService . GetById ( copy . ParentId ) , ActionCopy . ActionLetter ) ;
var authorizationResult = await _authorizationService . AuthorizeAsync ( User , resource , AuthorizationPolicies . ContentPermissionByResource ) ;
2020-11-23 22:43:41 +11:00
if ( ! authorizationResult . Succeeded )
{
return Forbid ( ) ;
}
2021-01-14 19:41:32 +01:00
var toCopyResult = ValidateMoveOrCopy ( copy ) ;
2021-02-05 19:23:58 +01:00
if ( ! ( toCopyResult . Result is null ) )
2021-01-14 19:41:32 +01:00
{
return toCopyResult . Result ;
}
var toCopy = toCopyResult . Value ;
2020-10-21 16:51:00 +11:00
var c = _contentService . Copy ( toCopy , copy . ParentId , copy . RelateToOriginal , copy . Recursive , _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-06-29 19:52:40 +02:00
2020-06-12 22:13:43 +02:00
return Content ( c . Path , MediaTypeNames . Text . Plain , Encoding . UTF8 ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Unpublishes a node with a given Id and returns the unpublished entity
/// </summary>
2018-10-03 14:27:48 +02:00
/// <param name="model">The content and variants to unpublish</param>
2018-06-29 19:52:40 +02:00
/// <returns></returns>
2020-12-02 14:21:05 +00:00
[OutgoingEditorModelEvent]
2020-11-23 22:43:41 +11:00
public async Task < ActionResult < ContentItemDisplay > > PostUnpublish ( UnpublishContent model )
2018-06-29 19:52:40 +02:00
{
2020-11-23 22:43:41 +11:00
var foundContent = _contentService . GetById ( model . Id ) ;
2018-06-29 19:52:40 +02:00
if ( foundContent = = null )
2020-11-23 22:43:41 +11:00
{
2021-01-14 19:41:32 +01:00
return HandleContentNotFound ( model . Id ) ;
2020-12-02 14:21:05 +00:00
}
2020-11-23 22:43:41 +11:00
// Authorize...
2020-11-30 19:09:14 +11:00
var resource = new ContentPermissionsResource ( foundContent , ActionUnpublish . ActionLetter ) ;
var authorizationResult = await _authorizationService . AuthorizeAsync ( User , resource , AuthorizationPolicies . ContentPermissionByResource ) ;
2020-11-23 22:43:41 +11:00
if ( ! authorizationResult . Succeeded )
{
return Forbid ( ) ;
}
2018-07-05 17:14:11 +02:00
2018-10-03 14:27:48 +02:00
var languageCount = _allLangs . Value . Count ( ) ;
if ( model . Cultures . Length = = 0 | | model . Cultures . Length = = languageCount )
{
//this means that the entire content item will be unpublished
2020-10-21 16:51:00 +11:00
var unpublishResult = _contentService . Unpublish ( foundContent , userId : _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-06-29 19:52:40 +02:00
2018-10-03 14:27:48 +02:00
var content = MapToDisplay ( foundContent ) ;
2018-06-29 19:52:40 +02:00
2018-11-12 17:29:38 +11:00
if ( ! unpublishResult . Success )
2018-10-03 14:27:48 +02:00
{
AddCancelMessage ( content ) ;
2020-12-29 12:55:48 +01:00
return new ValidationErrorResult ( content ) ;
2018-10-03 14:27:48 +02:00
}
else
{
content . AddSuccessNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "content/unpublish" ) ,
_localizedTextService . Localize ( "speechBubbles/contentUnpublished" ) ) ;
2018-10-03 14:27:48 +02:00
return content ;
}
2018-06-29 19:52:40 +02:00
}
else
2018-05-08 00:37:41 +10:00
{
2018-10-03 14:27:48 +02:00
//we only want to unpublish some of the variants
2018-11-07 19:42:49 +11:00
var results = new Dictionary < string , PublishResult > ( ) ;
2018-11-12 17:29:38 +11:00
foreach ( var c in model . Cultures )
2018-10-03 14:27:48 +02:00
{
2020-10-21 16:51:00 +11:00
var result = _contentService . Unpublish ( foundContent , culture : c , userId : _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-10-03 14:27:48 +02:00
results [ c ] = result ;
2018-11-07 19:42:49 +11:00
if ( result . Result = = PublishResultType . SuccessUnpublishMandatoryCulture )
2018-10-03 14:27:48 +02:00
{
//if this happens, it means they are all unpublished, we don't need to continue
break ;
}
}
2018-06-29 19:52:40 +02:00
2018-10-03 14:27:48 +02:00
var content = MapToDisplay ( foundContent ) ;
2018-06-29 19:52:40 +02:00
2018-10-03 14:27:48 +02:00
//check for this status and return the correct message
2018-11-07 19:42:49 +11:00
if ( results . Any ( x = > x . Value . Result = = PublishResultType . SuccessUnpublishMandatoryCulture ) )
2018-10-03 14:27:48 +02:00
{
content . AddSuccessNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "content/unpublish" ) ,
_localizedTextService . Localize ( "speechBubbles/contentMandatoryCultureUnpublished" ) ) ;
2018-10-03 14:27:48 +02:00
return content ;
}
//otherwise add a message for each one unpublished
foreach ( var r in results )
{
content . AddSuccessNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "content/unpublish" ) ,
_localizedTextService . Localize ( "speechBubbles/contentCultureUnpublished" , new [ ] { _allLangs . Value [ r . Key ] . CultureName } ) ) ;
2018-10-03 14:27:48 +02:00
}
2018-06-29 19:52:40 +02:00
return content ;
2018-10-03 14:27:48 +02:00
2018-06-29 19:52:40 +02:00
}
2018-10-03 14:27:48 +02:00
2018-05-08 00:37:41 +10:00
}
2018-09-30 15:02:09 +01:00
public ContentDomainsAndCulture GetCultureAndDomains ( int id )
{
2020-06-09 13:48:50 +02:00
var nodeDomains = _domainService . GetAssignedDomains ( id , true ) . ToArray ( ) ;
2018-09-30 15:02:09 +01:00
var wildcard = nodeDomains . FirstOrDefault ( d = > d . IsWildcard ) ;
var domains = nodeDomains . Where ( d = > ! d . IsWildcard ) . Select ( d = > new DomainDisplay ( d . DomainName , d . LanguageId . GetValueOrDefault ( 0 ) ) ) ;
return new ContentDomainsAndCulture
{
Domains = domains ,
Language = wildcard = = null | | ! wildcard . LanguageId . HasValue ? "undefined" : wildcard . LanguageId . ToString ( )
} ;
}
2018-07-31 15:56:04 +10:00
[HttpPost]
2020-06-09 13:48:50 +02:00
public ActionResult < DomainSave > PostSaveLanguageAndDomains ( DomainSave model )
2018-07-31 15:56:04 +10:00
{
2020-02-12 13:47:48 +11:00
foreach ( var domain in model . Domains )
2019-02-24 09:37:22 +01:00
{
try
{
2020-07-06 12:58:11 +02:00
var uri = DomainUtilities . ParseUriFromDomainName ( domain . Name , new Uri ( Request . GetEncodedUrl ( ) ) ) ;
2019-02-24 09:37:22 +01:00
}
catch ( UriFormatException )
2019-11-05 12:54:22 +01:00
{
2020-12-29 12:55:48 +01:00
return new ValidationErrorResult ( _localizedTextService . Localize ( "assignDomain/invalidDomain" ) ) ;
2019-02-24 09:37:22 +01:00
}
}
2020-06-09 13:48:50 +02:00
var node = _contentService . GetById ( model . NodeId ) ;
2018-07-31 15:56:04 +10:00
if ( node = = null )
{
2020-06-09 13:48:50 +02:00
HttpContext . SetReasonPhrase ( "Node Not Found." ) ;
return NotFound ( "There is no content node with id {model.NodeId}." ) ;
2018-07-31 15:56:04 +10:00
}
2020-10-21 16:51:00 +11:00
var permission = _userService . GetPermissions ( _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser , node . Path ) ;
2018-07-31 15:56:04 +10:00
2020-01-06 14:59:17 +01:00
2018-10-29 17:27:33 +11:00
if ( permission . AssignedPermissions . Contains ( ActionAssignDomain . ActionLetter . ToString ( ) , StringComparer . Ordinal ) = = false )
2018-07-31 15:56:04 +10:00
{
2020-06-09 13:48:50 +02:00
HttpContext . SetReasonPhrase ( "Permission Denied." ) ;
return BadRequest ( "You do not have permission to assign domains on that node." ) ;
2018-07-31 15:56:04 +10:00
}
model . Valid = true ;
2020-06-09 13:48:50 +02:00
var domains = _domainService . GetAssignedDomains ( model . NodeId , true ) . ToArray ( ) ;
var languages = _localizationService . GetAllLanguages ( ) . ToArray ( ) ;
2018-07-31 15:56:04 +10:00
var language = model . Language > 0 ? languages . FirstOrDefault ( l = > l . Id = = model . Language ) : null ;
// process wildcard
if ( language ! = null )
{
// yet there is a race condition here...
var wildcard = domains . FirstOrDefault ( d = > d . IsWildcard ) ;
if ( wildcard ! = null )
{
wildcard . LanguageId = language . Id ;
}
else
{
wildcard = new UmbracoDomain ( "*" + model . NodeId )
{
LanguageId = model . Language ,
RootContentId = model . NodeId
} ;
}
2020-06-09 13:48:50 +02:00
var saveAttempt = _domainService . Save ( wildcard ) ;
2018-07-31 15:56:04 +10:00
if ( saveAttempt = = false )
{
2020-06-09 13:48:50 +02:00
HttpContext . SetReasonPhrase ( saveAttempt . Result . Result . ToString ( ) ) ;
return BadRequest ( "Saving domain failed" ) ;
2018-07-31 15:56:04 +10:00
}
}
else
{
var wildcard = domains . FirstOrDefault ( d = > d . IsWildcard ) ;
if ( wildcard ! = null )
{
2020-06-09 13:48:50 +02:00
_domainService . Delete ( wildcard ) ;
2018-07-31 15:56:04 +10:00
}
}
// process domains
// delete every (non-wildcard) domain, that exists in the DB yet is not in the model
foreach ( var domain in domains . Where ( d = > d . IsWildcard = = false & & model . Domains . All ( m = > m . Name . InvariantEquals ( d . DomainName ) = = false ) ) )
{
2020-06-09 13:48:50 +02:00
_domainService . Delete ( domain ) ;
2018-07-31 15:56:04 +10:00
}
2018-11-12 17:29:38 +11:00
2018-07-31 15:56:04 +10:00
var names = new List < string > ( ) ;
// create or update domains in the model
foreach ( var domainModel in model . Domains . Where ( m = > string . IsNullOrWhiteSpace ( m . Name ) = = false ) )
{
language = languages . FirstOrDefault ( l = > l . Id = = domainModel . Lang ) ;
if ( language = = null )
{
continue ;
}
var name = domainModel . Name . ToLowerInvariant ( ) ;
if ( names . Contains ( name ) )
{
domainModel . Duplicate = true ;
continue ;
}
names . Add ( name ) ;
var domain = domains . FirstOrDefault ( d = > d . DomainName . InvariantEquals ( domainModel . Name ) ) ;
if ( domain ! = null )
{
domain . LanguageId = language . Id ;
2020-06-09 13:48:50 +02:00
_domainService . Save ( domain ) ;
2018-07-31 15:56:04 +10:00
}
2020-06-09 13:48:50 +02:00
else if ( _domainService . Exists ( domainModel . Name ) )
2018-07-31 15:56:04 +10:00
{
domainModel . Duplicate = true ;
2020-06-09 13:48:50 +02:00
var xdomain = _domainService . GetByName ( domainModel . Name ) ;
2018-07-31 15:56:04 +10:00
var xrcid = xdomain . RootContentId ;
if ( xrcid . HasValue )
{
2020-06-09 13:48:50 +02:00
var xcontent = _contentService . GetById ( xrcid . Value ) ;
2018-07-31 15:56:04 +10:00
var xnames = new List < string > ( ) ;
while ( xcontent ! = null )
{
xnames . Add ( xcontent . Name ) ;
if ( xcontent . ParentId < - 1 )
xnames . Add ( "Recycle Bin" ) ;
2020-06-09 13:48:50 +02:00
xcontent = _contentService . GetParent ( xcontent ) ;
2018-07-31 15:56:04 +10:00
}
xnames . Reverse ( ) ;
domainModel . Other = "/" + string . Join ( "/" , xnames ) ;
}
}
else
{
// yet there is a race condition here...
var newDomain = new UmbracoDomain ( name )
{
LanguageId = domainModel . Lang ,
RootContentId = model . NodeId
} ;
2020-06-09 13:48:50 +02:00
var saveAttempt = _domainService . Save ( newDomain ) ;
2018-07-31 15:56:04 +10:00
if ( saveAttempt = = false )
{
2020-06-09 13:48:50 +02:00
HttpContext . SetReasonPhrase ( saveAttempt . Result . Result . ToString ( ) ) ;
return BadRequest ( "Saving new domain failed" ) ;
2018-07-31 15:56:04 +10:00
}
}
}
model . Valid = model . Domains . All ( m = > m . Duplicate = = false ) ;
return model ;
}
2018-08-06 16:50:40 +10:00
/// <summary>
2019-03-14 00:59:51 +11:00
/// Ensure there is culture specific errors in the result if any errors are for culture properties
/// and we're dealing with variant content, then call the base class HandleInvalidModelState
2018-08-06 16:50:40 +10:00
/// </summary>
/// <param name="display"></param>
/// <remarks>
/// This is required to wire up the validation in the save/publish dialog
/// </remarks>
2019-04-04 21:57:49 +11:00
private void HandleInvalidModelState ( ContentItemDisplay display , string cultureForInvariantErrors )
2018-08-06 16:50:40 +10:00
{
2019-03-14 00:59:51 +11:00
if ( ! ModelState . IsValid & & display . Variants . Count ( ) > 1 )
2018-08-06 16:50:40 +10:00
{
//Add any culture specific errors here
2020-05-14 17:03:33 +02:00
var variantErrors = ModelState . GetVariantsWithErrors ( cultureForInvariantErrors ) ;
2018-08-06 16:50:40 +10:00
2020-05-14 17:03:33 +02:00
foreach ( var ( culture , segment ) in variantErrors )
2018-08-06 16:50:40 +10:00
{
2020-05-14 17:03:33 +02:00
AddVariantValidationError ( culture , segment , "speechBubbles/contentCultureValidationError" ) ;
2018-08-06 16:50:40 +10:00
}
}
2019-03-14 00:59:51 +11:00
}
2019-11-05 12:54:22 +01:00
2018-06-29 19:52:40 +02:00
/// <summary>
2018-08-14 20:21:33 +10:00
/// Maps the dto property values and names to the persisted model
2018-06-29 19:52:40 +02:00
/// </summary>
2018-07-19 19:32:07 +10:00
/// <param name="contentSave"></param>
2018-08-14 20:21:33 +10:00
private void MapValuesForPersistence ( ContentItemSave contentSave )
2018-06-29 19:52:40 +02:00
{
2019-10-11 14:13:49 +02:00
// inline method to determine the culture and segment to persist the property
2020-01-21 10:18:03 +01:00
( string culture , string segment ) PropertyCultureAndSegment ( IProperty property , ContentVariantSave variant )
2019-10-11 14:13:49 +02:00
{
var culture = property . PropertyType . VariesByCulture ( ) ? variant . Culture : null ;
var segment = property . PropertyType . VariesBySegment ( ) ? variant . Segment : null ;
return ( culture , segment ) ;
2020-01-21 10:18:03 +01:00
}
2018-07-31 17:50:24 +10:00
2018-08-10 15:39:57 +10:00
var variantIndex = 0 ;
2019-01-10 07:58:45 +01:00
2018-07-31 17:50:24 +10:00
//loop through each variant, set the correct name and property values
foreach ( var variant in contentSave . Variants )
2018-06-29 19:52:40 +02:00
{
2018-08-06 18:05:04 +10:00
//Don't update anything for this variant if Save is not true
2019-01-10 10:06:42 +01:00
if ( ! variant . Save ) continue ;
2018-08-06 18:05:04 +10:00
2018-07-19 19:32:07 +10:00
//Don't update the name if it is empty
if ( ! variant . Name . IsNullOrWhiteSpace ( ) )
2018-06-29 19:52:40 +02:00
{
2018-07-19 19:32:07 +10:00
if ( contentSave . PersistedContent . ContentType . VariesByCulture ( ) )
{
if ( variant . Culture . IsNullOrWhiteSpace ( ) )
throw new InvalidOperationException ( $"Cannot set culture name without a culture." ) ;
contentSave . PersistedContent . SetCultureName ( variant . Name , variant . Culture ) ;
}
else
{
contentSave . PersistedContent . Name = variant . Name ;
}
2018-06-29 19:52:40 +02:00
}
2018-07-31 17:50:24 +10:00
2018-08-10 15:39:57 +10:00
//This is important! We only want to process invariant properties with the first variant, for any other variant
// we need to exclude invariant properties from being processed, otherwise they will be double processed for the
// same value which can cause some problems with things such as file uploads.
var propertyCollection = variantIndex = = 0
? variant . PropertyCollectionDto
: new ContentPropertyCollectionDto
{
2020-02-10 09:16:03 +01:00
Properties = variant . PropertyCollectionDto . Properties . Where (
x = > ! x . Culture . IsNullOrWhiteSpace ( ) | | ! x . Segment . IsNullOrWhiteSpace ( ) )
2018-08-10 15:39:57 +10:00
} ;
2018-07-31 17:50:24 +10:00
//for each variant, map the property values
2018-08-02 15:12:26 +10:00
MapPropertyValuesForPersistence < IContent , ContentItemSave > (
2018-07-31 17:50:24 +10:00
contentSave ,
2018-08-10 15:39:57 +10:00
propertyCollection ,
2019-10-11 14:13:49 +02:00
( save , property ) = >
{
// Get property value
2020-01-21 10:18:03 +01:00
( var culture , var segment ) = PropertyCultureAndSegment ( property , variant ) ;
return property . GetValue ( culture , segment ) ;
2019-10-11 14:13:49 +02:00
} ,
( save , property , v ) = >
{
// Set property value
( var culture , var segment ) = PropertyCultureAndSegment ( property , variant ) ;
2020-01-21 10:18:03 +01:00
property . SetValue ( v , culture , segment ) ;
} ,
2018-11-20 13:24:06 +01:00
variant . Culture ) ;
2018-08-10 15:39:57 +10:00
variantIndex + + ;
2018-06-29 19:52:40 +02:00
}
2018-11-12 17:29:38 +11:00
2019-01-10 18:31:13 +01:00
// handle template
if ( string . IsNullOrWhiteSpace ( contentSave . TemplateAlias ) ) // cleared: clear if not already null
{
if ( contentSave . PersistedContent . TemplateId ! = null )
{
contentSave . PersistedContent . TemplateId = null ;
}
}
else // set: update if different
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var template = _fileService . GetTemplate ( contentSave . TemplateAlias ) ;
2019-01-10 18:31:13 +01:00
if ( template = = null )
2018-06-29 19:52:40 +02:00
{
2019-01-10 18:31:13 +01:00
//ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias);
2020-09-21 09:52:58 +02:00
_logger . LogWarning ( "No template exists with the specified alias: {TemplateAlias}" , contentSave . TemplateAlias ) ;
2019-01-10 18:31:13 +01:00
}
else if ( template . Id ! = contentSave . PersistedContent . TemplateId )
{
contentSave . PersistedContent . TemplateId = template . Id ;
2018-06-29 19:52:40 +02:00
}
}
}
/// <summary>
/// Ensures the item can be moved/copied to the new location
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
2020-12-29 12:55:48 +01:00
private ActionResult < IContent > ValidateMoveOrCopy ( MoveOrCopy model )
2018-06-29 19:52:40 +02:00
{
if ( model = = null )
{
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
2020-06-09 13:48:50 +02:00
var contentService = _contentService ;
2018-06-29 19:52:40 +02:00
var toMove = contentService . GetById ( model . Id ) ;
if ( toMove = = null )
{
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
if ( model . ParentId < 0 )
{
2019-10-15 17:57:06 +02:00
//cannot move if the content item is not allowed at the root
if ( toMove . ContentType . AllowedAsRoot = = false )
2018-06-29 19:52:40 +02:00
{
2020-12-29 12:55:48 +01:00
return ValidationErrorResult . CreateNotificationValidationErrorResult (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "moveOrCopy/notAllowedAtRoot" ) ) ;
2018-06-29 19:52:40 +02:00
}
}
else
{
var parent = contentService . GetById ( model . ParentId ) ;
if ( parent = = null )
{
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
2020-06-09 13:48:50 +02:00
var parentContentType = _contentTypeService . Get ( parent . ContentTypeId ) ;
2018-06-29 19:52:40 +02:00
//check if the item is allowed under this one
2019-01-03 09:27:52 +01:00
if ( parentContentType . AllowedContentTypes . Select ( x = > x . Id ) . ToArray ( )
2018-06-29 19:52:40 +02:00
. Any ( x = > x . Value = = toMove . ContentType . Id ) = = false )
{
2020-12-29 12:55:48 +01:00
return ValidationErrorResult . CreateNotificationValidationErrorResult (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "moveOrCopy/notAllowedByContentType" ) ) ;
2018-06-29 19:52:40 +02:00
}
// Check on paths
2020-12-29 12:55:48 +01:00
if ( $",{parent.Path}," . IndexOf ( $",{toMove.Id}," , StringComparison . Ordinal ) > - 1 )
2018-06-29 19:52:40 +02:00
{
2020-12-29 12:55:48 +01:00
return ValidationErrorResult . CreateNotificationValidationErrorResult (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "moveOrCopy/notAllowedByPath" ) ) ;
2018-06-29 19:52:40 +02:00
}
}
2020-12-29 12:55:48 +01:00
return new ActionResult < IContent > ( toMove ) ;
2018-06-29 19:52:40 +02:00
}
2018-09-04 01:13:26 +10:00
/// <summary>
/// Adds notification messages to the outbound display model for a given published status
/// </summary>
2019-03-27 12:41:02 +11:00
/// <param name="statuses"></param>
2018-09-04 01:13:26 +10:00
/// <param name="display"></param>
/// <param name="successfulCultures">
2019-01-26 10:52:19 -05:00
/// This is null when dealing with invariant content, else it's the cultures that were successfully published
2018-09-04 01:13:26 +10:00
/// </param>
2019-03-27 12:41:02 +11:00
private void AddMessageForPublishStatus ( IReadOnlyCollection < PublishResult > statuses , INotificationModel display , string [ ] successfulCultures = null )
2018-06-29 19:52:40 +02:00
{
2018-11-15 17:20:00 +11:00
var totalStatusCount = statuses . Count ( ) ;
2018-11-15 15:24:09 +11:00
//Put the statuses into groups, each group results in a different message
var statusGroup = statuses . GroupBy ( x = >
2018-06-29 19:52:40 +02:00
{
2018-11-15 15:24:09 +11:00
switch ( x . Result )
{
case PublishResultType . SuccessPublish :
case PublishResultType . SuccessPublishCulture :
2018-11-15 17:20:00 +11:00
//these 2 belong to a single group
2018-12-07 13:44:41 +01:00
return PublishResultType . SuccessPublish ;
2018-11-15 15:24:09 +11:00
case PublishResultType . FailedPublishAwaitingRelease :
case PublishResultType . FailedPublishCultureAwaitingRelease :
//these 2 belong to a single group
return PublishResultType . FailedPublishAwaitingRelease ;
case PublishResultType . FailedPublishHasExpired :
case PublishResultType . FailedPublishCultureHasExpired :
//these 2 belong to a single group
return PublishResultType . FailedPublishHasExpired ;
2018-11-15 17:20:00 +11:00
case PublishResultType . SuccessPublishAlready :
2018-11-15 15:24:09 +11:00
case PublishResultType . FailedPublishPathNotPublished :
case PublishResultType . FailedPublishCancelledByEvent :
case PublishResultType . FailedPublishIsTrashed :
case PublishResultType . FailedPublishContentInvalid :
case PublishResultType . FailedPublishMandatoryCultureMissing :
//the rest that we are looking for each belong in their own group
return x . Result ;
default :
throw new IndexOutOfRangeException ( $"{x.Result}\" was not expected . ");
}
} ) ;
foreach ( var status in statusGroup )
2018-12-07 13:44:41 +01:00
{
2018-11-15 15:24:09 +11:00
switch ( status . Key )
{
2018-11-15 17:20:00 +11:00
case PublishResultType . SuccessPublishAlready :
2018-09-04 01:13:26 +10:00
{
2019-01-27 01:17:32 -05:00
// TODO: Here we should have messaging for when there are release dates specified like https://github.com/umbraco/Umbraco-CMS/pull/3507
2018-12-07 13:13:00 +11:00
// but this will take a bit of effort because we need to deal with variants, different messaging, etc... A quick attempt was made here:
// http://github.com/umbraco/Umbraco-CMS/commit/9b3de7b655e07c612c824699b48a533c0448131a
2018-11-15 17:20:00 +11:00
//special case, we will only show messages for this if:
// * it's not a bulk publish operation
// * it's a bulk publish operation and all successful statuses are this one
var itemCount = status . Count ( ) ;
if ( totalStatusCount = = 1 | | totalStatusCount = = itemCount )
{
if ( successfulCultures = = null | | totalStatusCount = = itemCount )
{
//either invariant single publish, or bulk publish where all statuses are already published
display . AddSuccessNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles/editContentPublishedHeader" ) ,
_localizedTextService . Localize ( "speechBubbles/editContentPublishedText" ) ) ;
2018-11-15 17:20:00 +11:00
}
else
{
foreach ( var c in successfulCultures )
{
display . AddSuccessNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles/editContentPublishedHeader" ) ,
_localizedTextService . Localize ( "speechBubbles/editVariantPublishedText" , new [ ] { _allLangs . Value [ c ] . CultureName } ) ) ;
2018-11-15 17:20:00 +11:00
}
}
}
2018-09-04 01:13:26 +10:00
}
2018-11-15 17:20:00 +11:00
break ;
case PublishResultType . SuccessPublish :
2018-11-15 15:24:09 +11:00
{
2019-01-27 01:17:32 -05:00
// TODO: Here we should have messaging for when there are release dates specified like https://github.com/umbraco/Umbraco-CMS/pull/3507
2018-12-07 13:13:00 +11:00
// but this will take a bit of effort because we need to deal with variants, different messaging, etc... A quick attempt was made here:
// http://github.com/umbraco/Umbraco-CMS/commit/9b3de7b655e07c612c824699b48a533c0448131a
2018-11-15 17:20:00 +11:00
var itemCount = status . Count ( ) ;
if ( successfulCultures = = null )
2018-11-15 15:24:09 +11:00
{
display . AddSuccessNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles/editContentPublishedHeader" ) ,
2018-11-15 17:20:00 +11:00
totalStatusCount > 1
2020-06-09 13:48:50 +02:00
? _localizedTextService . Localize ( "speechBubbles/editMultiContentPublishedText" , new [ ] { itemCount . ToInvariantString ( ) } )
: _localizedTextService . Localize ( "speechBubbles/editContentPublishedText" ) ) ;
2018-11-15 17:20:00 +11:00
}
else
{
foreach ( var c in successfulCultures )
{
display . AddSuccessNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles/editContentPublishedHeader" ) ,
2018-11-15 17:20:00 +11:00
totalStatusCount > 1
2020-06-09 13:48:50 +02:00
? _localizedTextService . Localize ( "speechBubbles/editMultiVariantPublishedText" , new [ ] { itemCount . ToInvariantString ( ) , _allLangs . Value [ c ] . CultureName } )
: _localizedTextService . Localize ( "speechBubbles/editVariantPublishedText" , new [ ] { _allLangs . Value [ c ] . CultureName } ) ) ;
2018-11-15 17:20:00 +11:00
}
2018-11-15 15:24:09 +11:00
}
}
break ;
case PublishResultType . FailedPublishPathNotPublished :
{
2019-03-27 16:31:53 +11:00
//TODO: This doesn't take into account variations with the successfulCultures param
var names = string . Join ( ", " , status . Select ( x = > $"'{x.Content.Name}'" ) ) ;
2018-11-15 15:24:09 +11:00
display . AddWarningNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "publish" ) ,
_localizedTextService . Localize ( "publish/contentPublishedFailedByParent" ,
2018-11-15 15:24:09 +11:00
new [ ] { names } ) . Trim ( ) ) ;
}
break ;
case PublishResultType . FailedPublishCancelledByEvent :
{
2019-03-27 16:31:53 +11:00
//TODO: This doesn't take into account variations with the successfulCultures param
var names = string . Join ( ", " , status . Select ( x = > $"'{x.Content.Name}'" ) ) ;
2018-11-15 15:24:09 +11:00
AddCancelMessage ( display , message : "publish/contentPublishedFailedByEvent" , messageParams : new [ ] { names } ) ;
}
break ;
case PublishResultType . FailedPublishAwaitingRelease :
{
2019-03-27 16:31:53 +11:00
//TODO: This doesn't take into account variations with the successfulCultures param
var names = string . Join ( ", " , status . Select ( x = > $"'{x.Content.Name}'" ) ) ;
2018-11-15 15:24:09 +11:00
display . AddWarningNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "publish" ) ,
_localizedTextService . Localize ( "publish/contentPublishedFailedAwaitingRelease" ,
2018-11-15 15:24:09 +11:00
new [ ] { names } ) . Trim ( ) ) ;
}
break ;
case PublishResultType . FailedPublishHasExpired :
{
2019-03-27 16:31:53 +11:00
//TODO: This doesn't take into account variations with the successfulCultures param
var names = string . Join ( ", " , status . Select ( x = > $"'{x.Content.Name}'" ) ) ;
2018-11-15 15:24:09 +11:00
display . AddWarningNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "publish" ) ,
_localizedTextService . Localize ( "publish/contentPublishedFailedExpired" ,
2018-11-15 15:24:09 +11:00
new [ ] { names } ) . Trim ( ) ) ;
}
break ;
case PublishResultType . FailedPublishIsTrashed :
{
2019-03-27 16:31:53 +11:00
//TODO: This doesn't take into account variations with the successfulCultures param
var names = string . Join ( ", " , status . Select ( x = > $"'{x.Content.Name}'" ) ) ;
2018-11-15 15:24:09 +11:00
display . AddWarningNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "publish" ) ,
_localizedTextService . Localize ( "publish/contentPublishedFailedIsTrashed" ,
2018-11-15 15:24:09 +11:00
new [ ] { names } ) . Trim ( ) ) ;
}
break ;
case PublishResultType . FailedPublishContentInvalid :
{
2019-03-27 16:31:53 +11:00
if ( successfulCultures = = null )
{
var names = string . Join ( ", " , status . Select ( x = > $"'{x.Content.Name}'" ) ) ;
display . AddWarningNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "publish" ) ,
_localizedTextService . Localize ( "publish/contentPublishedFailedInvalid" ,
2019-03-27 16:31:53 +11:00
new [ ] { names } ) . Trim ( ) ) ;
}
else
{
foreach ( var c in successfulCultures )
{
2019-06-07 14:20:44 +10:00
var names = string . Join ( ", " , status . Select ( x = > $"'{(x.Content.ContentType.VariesByCulture() ? x.Content.GetCultureName(c) : x.Content.Name)}'" ) ) ;
2019-03-27 16:31:53 +11:00
display . AddWarningNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "publish" ) ,
_localizedTextService . Localize ( "publish/contentPublishedFailedInvalid" ,
2019-03-27 16:31:53 +11:00
new [ ] { names } ) . Trim ( ) ) ;
}
}
2018-11-15 15:24:09 +11:00
}
break ;
case PublishResultType . FailedPublishMandatoryCultureMissing :
display . AddWarningNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "publish" ) ,
2019-01-21 15:57:48 +01:00
"publish/contentPublishedFailedByCulture" ) ;
2018-11-15 15:24:09 +11:00
break ;
default :
throw new IndexOutOfRangeException ( $"PublishedResultType \" { status . Key } \ " was not expected." ) ;
2018-06-29 19:52:40 +02:00
}
}
}
/// <summary>
/// Used to map an <see cref="IContent"/> instance to a <see cref="ContentItemDisplay"/> and ensuring a language is present if required
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
2018-07-13 12:45:04 +10:00
private ContentItemDisplay MapToDisplay ( IContent content )
2018-06-29 19:52:40 +02:00
{
2020-06-09 13:48:50 +02:00
var display = _umbracoMapper . Map < ContentItemDisplay > ( content , context = >
2020-02-12 13:47:48 +11:00
{
2020-10-21 16:51:00 +11:00
context . Items [ "CurrentUser" ] = _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser ;
2020-02-12 13:47:48 +11:00
} ) ;
2019-02-14 13:13:19 +01:00
display . AllowPreview = display . AllowPreview & & content . Trashed = = false & & content . ContentType . IsElement = = false ;
2018-06-29 19:52:40 +02:00
return display ;
2018-05-08 00:37:41 +10:00
}
2018-11-12 17:29:38 +11:00
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
2020-06-09 13:48:50 +02:00
public ActionResult < IEnumerable < NotifySetting > > GetNotificationOptions ( int contentId )
2018-08-07 15:48:21 +01:00
{
var notifications = new List < NotifySetting > ( ) ;
2020-06-09 13:48:50 +02:00
if ( contentId < = 0 ) return NotFound ( ) ;
2018-08-07 15:48:21 +01:00
2020-06-09 13:48:50 +02:00
var content = _contentService . GetById ( contentId ) ;
if ( content = = null ) return NotFound ( ) ;
2018-08-07 15:48:21 +01:00
2020-10-21 16:51:00 +11:00
var userNotifications = _notificationService . GetUserNotifications ( _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser , content . Path ) . ToList ( ) ;
2018-08-07 15:48:21 +01:00
2020-06-09 13:48:50 +02:00
foreach ( var a in _actionCollection . Where ( x = > x . ShowInNotifier ) )
2018-08-07 15:48:21 +01:00
{
var n = new NotifySetting
{
2020-06-09 13:48:50 +02:00
Name = _localizedTextService . Localize ( "actions" , a . Alias ) ,
2018-11-12 17:29:38 +11:00
Checked = userNotifications . FirstOrDefault ( x = > x . Action = = a . Letter . ToString ( ) ) ! = null ,
2018-08-07 15:48:21 +01:00
NotifyCode = a . Letter . ToString ( )
} ;
notifications . Add ( n ) ;
}
return notifications ;
}
2020-12-02 14:21:05 +00:00
public IActionResult PostNotificationOptions ( int contentId , [ FromQuery ( Name = "notifyOptions[]" ) ] string [ ] notifyOptions )
2018-08-07 15:48:21 +01:00
{
2020-06-09 13:48:50 +02:00
if ( contentId < = 0 ) return NotFound ( ) ;
var content = _contentService . GetById ( contentId ) ;
if ( content = = null ) return NotFound ( ) ;
2020-10-21 16:51:00 +11:00
_notificationService . SetNotifications ( _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser , content , notifyOptions ) ;
2018-08-07 15:48:21 +01:00
2020-06-09 13:48:50 +02:00
return NoContent ( ) ;
2018-08-07 15:48:21 +01:00
}
2018-10-17 12:15:57 +01:00
[HttpGet]
2018-10-17 14:39:11 +01:00
public IEnumerable < RollbackVersion > GetRollbackVersions ( int contentId , string culture = null )
2018-10-17 12:15:57 +01:00
{
var rollbackVersions = new List < RollbackVersion > ( ) ;
2018-10-22 08:45:30 +02:00
var writerIds = new HashSet < int > ( ) ;
2018-10-17 12:15:57 +01:00
2020-06-09 13:48:50 +02:00
var versions = _contentService . GetVersionsSlim ( contentId , 0 , 50 ) ;
2018-10-18 13:13:11 +01:00
2018-10-17 12:15:57 +01:00
//Not all nodes are variants & thus culture can be null
2018-10-18 13:13:11 +01:00
if ( culture ! = null )
{
2018-10-22 08:45:30 +02:00
//Get cultures that were published with the version = their update date is equal to the version's
2018-10-18 13:13:11 +01:00
versions = versions . Where ( x = > x . UpdateDate = = x . GetUpdateDate ( culture ) ) ;
}
2018-10-17 12:15:57 +01:00
2018-10-18 14:24:32 +01:00
//First item is our current item/state (cant rollback to ourselves)
versions = versions . Skip ( 1 ) ;
2018-10-18 13:13:11 +01:00
foreach ( var version in versions )
2018-10-17 12:15:57 +01:00
{
2018-10-22 08:45:30 +02:00
var rollbackVersion = new RollbackVersion
{
VersionId = version . VersionId ,
VersionDate = version . UpdateDate ,
VersionAuthorId = version . WriterId
} ;
2018-10-17 12:15:57 +01:00
2018-10-22 08:45:30 +02:00
rollbackVersions . Add ( rollbackVersion ) ;
2018-10-17 12:15:57 +01:00
2018-10-22 08:45:30 +02:00
writerIds . Add ( version . WriterId ) ;
}
2018-10-17 12:15:57 +01:00
2020-06-09 13:48:50 +02:00
var users = _userService
2018-10-22 08:45:30 +02:00
. GetUsersById ( writerIds . ToArray ( ) )
. ToDictionary ( x = > x . Id , x = > x . Name ) ;
foreach ( var rollbackVersion in rollbackVersions )
{
if ( users . TryGetValue ( rollbackVersion . VersionAuthorId , out var userName ) )
rollbackVersion . VersionAuthorName = userName ;
2018-10-17 12:15:57 +01:00
}
2018-11-12 17:29:38 +11:00
2018-10-17 12:15:57 +01:00
return rollbackVersions ;
}
2018-10-17 14:39:11 +01:00
[HttpGet]
2018-10-17 15:09:29 +01:00
public ContentVariantDisplay GetRollbackVersion ( int versionId , string culture = null )
2018-10-17 14:39:11 +01:00
{
2020-06-09 13:48:50 +02:00
var version = _contentService . GetVersion ( versionId ) ;
2018-10-17 14:39:11 +01:00
var content = MapToDisplay ( version ) ;
2018-11-12 17:29:38 +11:00
return culture = = null
? content . Variants . FirstOrDefault ( ) //No culture set - so this is an invariant node - so just list me the first item in here
2018-10-18 14:24:32 +01:00
: content . Variants . FirstOrDefault ( x = > x . Language . IsoCode = = culture ) ;
2018-10-17 14:39:11 +01:00
}
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionRollbackById)]
2018-10-17 12:15:57 +01:00
[HttpPost]
2020-06-09 13:48:50 +02:00
public IActionResult PostRollbackContent ( int contentId , int versionId , string culture = "*" )
2018-10-17 12:15:57 +01:00
{
2020-10-21 16:51:00 +11:00
var rollbackResult = _contentService . Rollback ( contentId , versionId , culture , _backofficeSecurityAccessor . BackOfficeSecurity . GetUserId ( ) . ResultOr ( 0 ) ) ;
2018-10-18 13:13:11 +01:00
2018-11-12 17:29:38 +11:00
if ( rollbackResult . Success )
2020-06-09 13:48:50 +02:00
return Ok ( ) ;
2018-10-17 14:39:11 +01:00
2018-10-18 14:24:32 +01:00
var notificationModel = new SimpleNotificationModel ( ) ;
2018-10-18 13:13:11 +01:00
2018-10-18 14:24:32 +01:00
switch ( rollbackResult . Result )
2018-10-17 14:39:11 +01:00
{
2018-10-18 14:24:32 +01:00
case OperationResultType . Failed :
case OperationResultType . FailedCannot :
case OperationResultType . FailedExceptionThrown :
case OperationResultType . NoOperation :
default :
notificationModel . AddErrorNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles/operationFailedHeader" ) ,
2019-01-27 01:17:32 -05:00
null ) ; // TODO: There is no specific failed to save error message AFAIK
2018-10-18 14:24:32 +01:00
break ;
case OperationResultType . FailedCancelledByEvent :
notificationModel . AddErrorNotification (
2020-06-09 13:48:50 +02:00
_localizedTextService . Localize ( "speechBubbles/operationCancelledHeader" ) ,
_localizedTextService . Localize ( "speechBubbles/operationCancelledText" ) ) ;
2018-10-18 14:24:32 +01:00
break ;
2018-10-17 14:39:11 +01:00
}
2020-12-29 12:55:48 +01:00
return new ValidationErrorResult ( notificationModel ) ;
2018-10-17 14:39:11 +01:00
}
2018-11-24 19:39:15 +01:00
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionProtectById)]
2018-11-24 19:39:15 +01:00
[HttpGet]
2020-06-09 13:48:50 +02:00
public IActionResult GetPublicAccess ( int contentId )
2018-11-24 19:39:15 +01:00
{
2020-06-09 13:48:50 +02:00
var content = _contentService . GetById ( contentId ) ;
2018-11-24 19:39:15 +01:00
if ( content = = null )
{
2020-06-09 13:48:50 +02:00
return NotFound ( ) ;
2018-11-24 19:39:15 +01:00
}
2020-06-09 13:48:50 +02:00
var entry = _publicAccessService . GetEntryForContent ( content ) ;
2019-08-13 21:14:28 +02:00
if ( entry = = null | | entry . ProtectedNodeId ! = content . Id )
2018-11-24 19:39:15 +01:00
{
2020-06-09 13:48:50 +02:00
return Ok ( ) ;
2018-11-24 19:39:15 +01:00
}
2020-06-09 13:48:50 +02:00
var loginPageEntity = _entityService . Get ( entry . LoginNodeId , UmbracoObjectTypes . Document ) ;
var errorPageEntity = _entityService . Get ( entry . NoAccessNodeId , UmbracoObjectTypes . Document ) ;
2018-11-24 19:39:15 +01:00
// unwrap the current public access setup for the client
// - this API method is the single point of entry for both "modes" of public access (single user and role based)
2018-12-12 09:55:35 +01:00
var usernames = entry . Rules
2018-12-09 14:58:52 +01:00
. Where ( rule = > rule . RuleType = = Constants . Conventions . PublicAccess . MemberUsernameRuleType )
2018-12-12 09:55:35 +01:00
. Select ( rule = > rule . RuleValue ) . ToArray ( ) ;
2019-11-25 21:20:00 +11:00
var members = usernames
2020-06-09 13:48:50 +02:00
. Select ( username = > _memberService . GetByUsername ( username ) )
2019-11-25 21:20:00 +11:00
. Where ( member = > member ! = null )
2020-06-09 13:48:50 +02:00
. Select ( _umbracoMapper . Map < MemberDisplay > )
2019-11-25 21:20:00 +11:00
. ToArray ( ) ;
2018-12-12 09:55:35 +01:00
2020-06-09 13:48:50 +02:00
var allGroups = _memberGroupService . GetAll ( ) . ToArray ( ) ;
2018-12-12 14:27:20 +01:00
var groups = entry . Rules
2018-11-24 19:39:15 +01:00
. Where ( rule = > rule . RuleType = = Constants . Conventions . PublicAccess . MemberRoleRuleType )
2018-12-12 14:27:20 +01:00
. Select ( rule = > allGroups . FirstOrDefault ( g = > g . Name = = rule . RuleValue ) )
. Where ( memberGroup = > memberGroup ! = null )
2020-06-09 13:48:50 +02:00
. Select ( _umbracoMapper . Map < MemberGroupDisplay > )
2018-11-24 19:39:15 +01:00
. ToArray ( ) ;
2020-06-09 13:48:50 +02:00
return Ok ( new PublicAccess
2018-11-24 19:39:15 +01:00
{
2018-12-09 14:58:52 +01:00
Members = members ,
2018-12-12 14:27:20 +01:00
Groups = groups ,
2020-06-09 13:48:50 +02:00
LoginPage = loginPageEntity ! = null ? _umbracoMapper . Map < EntityBasic > ( loginPageEntity ) : null ,
ErrorPage = errorPageEntity ! = null ? _umbracoMapper . Map < EntityBasic > ( errorPageEntity ) : null
2018-11-24 19:39:15 +01:00
} ) ;
}
// set up public access using role based access
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionProtectById)]
2018-11-24 19:39:15 +01:00
[HttpPost]
2020-12-02 14:21:05 +00:00
public IActionResult PostPublicAccess ( int contentId , [ FromQuery ( Name = "groups[]" ) ] string [ ] groups , [ FromQuery ( Name = "usernames[]" ) ] string [ ] usernames , int loginPageId , int errorPageId )
2018-11-24 19:39:15 +01:00
{
2018-12-12 14:27:20 +01:00
if ( ( groups = = null | | groups . Any ( ) = = false ) & & ( usernames = = null | | usernames . Any ( ) = = false ) )
2018-11-24 19:39:15 +01:00
{
2020-06-09 13:48:50 +02:00
return BadRequest ( ) ;
2018-11-24 19:39:15 +01:00
}
2020-06-09 13:48:50 +02:00
var content = _contentService . GetById ( contentId ) ;
var loginPage = _contentService . GetById ( loginPageId ) ;
var errorPage = _contentService . GetById ( errorPageId ) ;
2018-11-24 19:39:15 +01:00
if ( content = = null | | loginPage = = null | | errorPage = = null )
{
2020-06-09 13:48:50 +02:00
return BadRequest ( ) ;
2018-11-24 19:39:15 +01:00
}
2018-12-12 14:27:20 +01:00
var isGroupBased = groups ! = null & & groups . Any ( ) ;
var candidateRuleValues = isGroupBased
? groups
2018-12-12 09:27:03 +01:00
: usernames ;
2018-12-12 14:27:20 +01:00
var newRuleType = isGroupBased
2018-12-09 14:58:52 +01:00
? Constants . Conventions . PublicAccess . MemberRoleRuleType
: Constants . Conventions . PublicAccess . MemberUsernameRuleType ;
2020-06-09 13:48:50 +02:00
var entry = _publicAccessService . GetEntryForContent ( content ) ;
2018-11-24 19:39:15 +01:00
2019-08-13 21:14:28 +02:00
if ( entry = = null | | entry . ProtectedNodeId ! = content . Id )
2018-11-24 19:39:15 +01:00
{
entry = new PublicAccessEntry ( content , loginPage , errorPage , new List < PublicAccessRule > ( ) ) ;
2018-12-09 14:58:52 +01:00
2018-12-12 14:27:20 +01:00
foreach ( var ruleValue in candidateRuleValues )
2018-11-24 19:39:15 +01:00
{
2018-12-12 14:27:20 +01:00
entry . AddRule ( ruleValue , newRuleType ) ;
2018-11-24 19:39:15 +01:00
}
}
else
{
entry . LoginNodeId = loginPage . Id ;
entry . NoAccessNodeId = errorPage . Id ;
var currentRules = entry . Rules . ToArray ( ) ;
var obsoleteRules = currentRules . Where ( rule = >
2018-12-09 14:58:52 +01:00
rule . RuleType ! = newRuleType
| | candidateRuleValues . Contains ( rule . RuleValue ) = = false
2018-11-24 19:39:15 +01:00
) ;
2018-12-09 14:58:52 +01:00
var newRuleValues = candidateRuleValues . Where ( group = >
2018-11-24 19:39:15 +01:00
currentRules . Any ( rule = >
2018-12-09 14:58:52 +01:00
rule . RuleType = = newRuleType
2018-11-24 19:39:15 +01:00
& & rule . RuleValue = = group
) = = false
) ;
foreach ( var rule in obsoleteRules )
{
entry . RemoveRule ( rule ) ;
}
2018-12-12 14:27:20 +01:00
foreach ( var ruleValue in newRuleValues )
2018-11-24 19:39:15 +01:00
{
2018-12-12 14:27:20 +01:00
entry . AddRule ( ruleValue , newRuleType ) ;
2018-11-24 19:39:15 +01:00
}
}
2020-06-09 13:48:50 +02:00
return _publicAccessService . Save ( entry ) . Success
2020-12-02 14:21:05 +00:00
? ( IActionResult ) Ok ( )
2020-06-09 13:48:50 +02:00
: Problem ( ) ;
2018-11-24 19:39:15 +01:00
}
2020-11-23 22:43:41 +11:00
[Authorize(Policy = AuthorizationPolicies.ContentPermissionProtectById)]
2018-11-25 09:17:04 +01:00
[HttpPost]
2020-06-09 13:48:50 +02:00
public IActionResult RemovePublicAccess ( int contentId )
2018-11-25 09:17:04 +01:00
{
2020-06-09 13:48:50 +02:00
var content = _contentService . GetById ( contentId ) ;
2018-11-25 09:17:04 +01:00
if ( content = = null )
{
2020-06-09 13:48:50 +02:00
return NotFound ( ) ;
2018-11-25 09:17:04 +01:00
}
2020-06-09 13:48:50 +02:00
var entry = _publicAccessService . GetEntryForContent ( content ) ;
2018-11-25 09:17:04 +01:00
if ( entry = = null )
{
2020-06-09 13:48:50 +02:00
return Ok ( ) ;
2018-11-25 09:17:04 +01:00
}
2020-06-09 13:48:50 +02:00
return _publicAccessService . Delete ( entry ) . Success
2020-12-02 14:21:05 +00:00
? ( IActionResult ) Ok ( )
2020-06-09 13:48:50 +02:00
: Problem ( ) ;
2018-11-25 09:17:04 +01:00
}
2018-06-29 19:52:40 +02:00
}
}