2013-08-12 15:06:12 +02:00
using System ;
2017-09-13 17:35:20 +02:00
using System.Collections.Generic ;
2013-08-12 15:06:12 +02:00
using System.Linq ;
using System.Net ;
using System.Net.Http.Formatting ;
using System.Web.Http ;
using Umbraco.Core ;
using Umbraco.Core.Models ;
2018-01-15 11:32:30 +01:00
using Umbraco.Core.Models.Entities ;
2013-08-12 15:06:12 +02:00
using Umbraco.Core.Services ;
2017-05-30 18:13:11 +02:00
using Umbraco.Web.Composing ;
2013-10-08 12:38:27 +11:00
using Umbraco.Web.Models.Trees ;
2013-08-16 17:25:52 +10:00
using Umbraco.Web.Mvc ;
2013-11-20 14:18:03 +11:00
using Umbraco.Web.WebApi.Filters ;
2016-03-17 15:28:46 +01:00
using Umbraco.Web._Legacy.Actions ;
2017-09-12 16:22:16 +02:00
using Umbraco.Web.Models.ContentEditing ;
using Umbraco.Web.Search ;
2013-08-16 17:25:52 +10:00
using Constants = Umbraco . Core . Constants ;
2013-08-12 15:06:12 +02:00
namespace Umbraco.Web.Trees
{
2013-11-20 14:18:03 +11:00
//We will not allow the tree to render unless the user has access to any of the sections that the tree gets rendered
// this is not ideal but until we change permissions to be tree based (not section) there's not much else we can do here.
[UmbracoApplicationAuthorize(
2016-07-29 15:48:32 +02:00
Constants.Applications.Content,
Constants.Applications.Media,
2013-11-20 14:18:03 +11:00
Constants.Applications.Users,
Constants.Applications.Settings,
Constants.Applications.Developer,
Constants.Applications.Members)]
2015-09-28 08:54:00 +02:00
[Tree(Constants.Applications.Content, Constants.Trees.Content)]
2013-08-16 17:25:52 +10:00
[PluginController("UmbracoTrees")]
2013-10-03 11:25:26 +10:00
[CoreTree]
2017-09-12 16:22:16 +02:00
[SearchableTree("searchResultFormatter", "configureContentResult")]
public class ContentTreeController : ContentTreeControllerBase , ISearchableTree
2013-08-12 15:06:12 +02:00
{
2017-09-12 16:22:16 +02:00
private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher ();
2017-09-23 10:08:18 +02:00
2017-05-12 14:49:44 +02:00
protected override int RecycleBinId => Constants . System . RecycleBinContent ;
2013-08-12 15:06:12 +02:00
2017-05-12 14:49:44 +02:00
protected override bool RecycleBinSmells => Services . ContentService . RecycleBinSmells ();
2013-08-12 15:06:12 +02:00
2017-09-12 16:22:16 +02:00
private int [] _userStartNodes ;
protected override int [] UserStartNodes
=> _userStartNodes ?? ( _userStartNodes = Security . CurrentUser . CalculateContentStartNodeIds ( Services . EntityService ));
2016-07-29 15:48:32 +02:00
2018-01-15 11:32:30 +01:00
/// <inheritdoc />
protected override TreeNode GetSingleTreeNode ( IEntitySlim entity , string parentId , FormDataCollection queryStrings )
2013-12-12 13:27:33 +11:00
{
2018-05-01 18:17:07 +10:00
var langId = queryStrings [ "culture" ]. TryConvertTo < string >();
2018-04-17 01:37:35 +10:00
2018-01-15 11:32:30 +01:00
var allowedUserOptions = GetAllowedUserMenuItemsForNode ( entity );
2018-04-17 01:37:35 +10:00
if ( CanUserAccessNode ( entity , allowedUserOptions , langId . Success ? langId . Result : null ))
2013-12-12 13:27:33 +11:00
{
//Special check to see if it ia a container, if so then we'll hide children.
2018-01-15 11:32:30 +01:00
var isContainer = entity . IsContainer ; // && (queryStrings.Get("isDialog") != "true");
2013-12-12 13:27:33 +11:00
var node = CreateTreeNode (
2017-05-12 14:49:44 +02:00
entity ,
2017-09-19 18:19:05 +02:00
Constants . ObjectTypes . Document ,
2013-12-12 13:27:33 +11:00
parentId ,
queryStrings ,
2018-01-15 11:32:30 +01:00
entity . HasChildren && ! isContainer );
2013-12-12 13:27:33 +11:00
2018-01-15 11:32:30 +01:00
// entity is either a container, or a document
2013-12-12 13:27:33 +11:00
if ( isContainer )
2014-10-08 14:26:18 +11:00
{
node . AdditionalData . Add ( "isContainer" , true );
2013-12-12 13:27:33 +11:00
node . SetContainerStyle ();
2014-10-08 14:26:18 +11:00
}
2018-01-15 11:32:30 +01:00
else
{
var documentEntity = ( IDocumentEntitySlim ) entity ;
2018-05-07 23:22:52 +10:00
//fixme we need these statuses per variant but to do that we need to fix the issues listed in IDocumentEntitySlim
if (! documentEntity . Published )
2018-01-15 11:32:30 +01:00
node . SetNotPublishedStyle ();
2018-05-07 23:22:52 +10:00
//if (documentEntity.Edited)
// node.SetHasUnpublishedVersionStyle();
2018-01-15 11:32:30 +01:00
node . AdditionalData . Add ( "contentType" , documentEntity . ContentTypeAlias );
}
2016-07-29 15:48:32 +02:00
2018-01-15 11:32:30 +01:00
if ( Services . PublicAccessService . IsProtected ( entity . Path ))
2013-12-12 13:27:33 +11:00
node . SetProtectedStyle ();
return node ;
}
return null ;
}
2013-08-12 15:06:12 +02:00
protected override MenuItemCollection PerformGetMenuForNode ( string id , FormDataCollection queryStrings )
{
if ( id == Constants . System . Root . ToInvariantString ())
{
2013-09-26 15:55:38 +10:00
var menu = new MenuItemCollection ();
2017-09-23 10:08:18 +02:00
2017-09-19 15:51:47 +02:00
// if the user's start node is not the root then the only menu item to display is refresh
2017-09-12 16:22:16 +02:00
if ( UserStartNodes . Contains ( Constants . System . Root ) == false )
2013-10-01 16:38:10 +10:00
{
2017-09-19 15:51:47 +02:00
menu . Items . Add < RefreshNode , ActionRefresh >(
Services . TextService . Localize ( string . Concat ( "actions/" , ActionRefresh . Instance . Alias )),
true );
2013-10-01 16:38:10 +10:00
return menu ;
}
2013-09-26 15:55:38 +10:00
//set the default to create
menu . DefaultMenuAlias = ActionNew . Instance . Alias ;
2013-08-12 15:06:12 +02:00
// we need to get the default permissions as you can't set permissions on the very root node
2016-03-16 17:52:08 +01:00
var permission = Services . UserService . GetPermissions ( Security . CurrentUser , Constants . System . Root ). First ();
2016-03-17 15:28:46 +01:00
var nodeActions = global :: Umbraco . Web . _Legacy . Actions . Action . FromEntityPermission ( permission )
2016-03-16 17:52:08 +01:00
. Select ( x => new MenuItem ( x ));
2013-08-12 15:06:12 +02:00
//these two are the standard items
2016-03-15 16:58:13 +01:00
menu . Items . Add < ActionNew >( Services . TextService . Localize ( "actions" , ActionNew . Instance . Alias ));
menu . Items . Add < ActionSort >( Services . TextService . Localize ( "actions" , ActionSort . Instance . Alias ), true ). ConvertLegacyMenuItem ( null , "content" , "content" );
2013-08-12 15:06:12 +02:00
//filter the standard items
2013-09-26 15:55:38 +10:00
FilterUserAllowedMenuItems ( menu , nodeActions );
2013-08-12 15:06:12 +02:00
2013-10-03 15:05:48 +10:00
if ( menu . Items . Any ())
2013-08-12 15:06:12 +02:00
{
2013-10-03 15:05:48 +10:00
menu . Items . Last (). SeperatorBefore = true ;
2013-08-12 15:06:12 +02:00
}
// add default actions for *all* users
2016-03-15 16:58:13 +01:00
menu . Items . Add < ActionRePublish >( Services . TextService . Localize ( "actions" , ActionRePublish . Instance . Alias )). ConvertLegacyMenuItem ( null , "content" , "content" );
menu . Items . Add < RefreshNode , ActionRefresh >( Services . TextService . Localize ( "actions" , ActionRefresh . Instance . Alias ), true );
2016-07-29 15:48:32 +02:00
2013-09-26 15:55:38 +10:00
return menu ;
2013-08-12 15:06:12 +02:00
}
2013-09-26 15:55:38 +10:00
2013-08-12 15:06:12 +02:00
//return a normal node menu:
int iid ;
if ( int . TryParse ( id , out iid ) == false )
{
2013-09-03 12:27:48 +10:00
throw new HttpResponseException ( HttpStatusCode . NotFound );
2013-08-12 15:06:12 +02:00
}
var item = Services . EntityService . Get ( iid , UmbracoObjectTypes . Document );
if ( item == null )
{
throw new HttpResponseException ( HttpStatusCode . NotFound );
}
2017-09-19 15:51:47 +02:00
//if the user has no path access for this node, all they can do is refresh
if ( Security . CurrentUser . HasPathAccess ( item , Services . EntityService , RecycleBinId ) == false )
{
var menu = new MenuItemCollection ();
menu . Items . Add < RefreshNode , ActionRefresh >(
Services . TextService . Localize ( string . Concat ( "actions/" , ActionRefresh . Instance . Alias )),
true );
return menu ;
}
2013-09-26 15:55:38 +10:00
var nodeMenu = GetAllNodeMenuItems ( item );
var allowedMenuItems = GetAllowedUserMenuItemsForNode ( item );
2016-07-29 15:48:32 +02:00
2013-09-26 15:55:38 +10:00
FilterUserAllowedMenuItems ( nodeMenu , allowedMenuItems );
2013-10-24 16:54:17 +11:00
//if the media item is in the recycle bin, don't have a default menu, just show the regular menu
2017-09-12 16:22:16 +02:00
if ( item . Path . Split ( new [] { ',' }, StringSplitOptions . RemoveEmptyEntries ). Contains ( RecycleBinId . ToInvariantString ()))
2013-10-24 16:54:17 +11:00
{
nodeMenu . DefaultMenuAlias = null ;
2016-03-15 16:58:13 +01:00
nodeMenu . Items . Insert ( 2 , new MenuItem ( ActionRestore . Instance , Services . TextService . Localize ( "actions" , ActionRestore . Instance . Alias )));
2013-10-24 16:54:17 +11:00
}
else
{
//set the default to create
2016-07-29 15:48:32 +02:00
nodeMenu . DefaultMenuAlias = ActionNew . Instance . Alias ;
2013-10-24 16:54:17 +11:00
}
2016-07-29 15:48:32 +02:00
2013-09-26 15:55:38 +10:00
return nodeMenu ;
2013-08-12 15:06:12 +02:00
}
protected override UmbracoObjectTypes UmbracoObjectType
{
get { return UmbracoObjectTypes . Document ; }
}
2013-11-19 12:28:50 +11:00
/// <summary>
/// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
protected override bool HasPathAccess ( string id , FormDataCollection queryStrings )
{
2017-05-12 14:49:44 +02:00
var entity = GetEntityFromId ( id );
2017-09-19 15:51:47 +02:00
return HasPathAccess ( entity , queryStrings );
2018-05-10 18:20:33 +10:00
}
protected override IEnumerable < IEntitySlim > GetChildEntities ( string id , FormDataCollection queryStrings )
{
var result = base . GetChildEntities ( id , queryStrings );
var culture = queryStrings [ "culture" ]. TryConvertTo < string >();
//if this is null we'll set it to the default.
var cultureVal = ( culture . Success ? culture . Result : null ) ?? Services . LocalizationService . GetDefaultLanguageIsoCode ();
// set names according to variations
foreach ( var entity in result )
EnsureName ( entity , cultureVal );
return result ;
2013-11-19 12:28:50 +11:00
}
2013-09-26 15:55:38 +10:00
/// <summary>
/// Returns a collection of all menu items that can be on a content node
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected MenuItemCollection GetAllNodeMenuItems ( IUmbracoEntity item )
2013-08-12 15:06:12 +02:00
{
var menu = new MenuItemCollection ();
2016-07-29 15:48:32 +02:00
AddActionNode < ActionNew >( item , menu );
AddActionNode < ActionDelete >( item , menu );
2017-09-12 16:22:16 +02:00
AddActionNode < ActionCreateBlueprintFromContent >( item , menu );
2013-09-03 12:27:48 +10:00
//need to ensure some of these are converted to the legacy system - until we upgrade them all to be angularized.
2016-07-29 15:48:32 +02:00
AddActionNode < ActionMove >( item , menu , true );
AddActionNode < ActionCopy >( item , menu );
AddActionNode < ActionChangeDocType >( item , menu , convert : true );
2013-09-03 12:27:48 +10:00
2016-07-29 15:48:32 +02:00
AddActionNode < ActionSort >( item , menu , true , true );
2013-08-12 15:06:12 +02:00
2016-07-29 15:48:32 +02:00
AddActionNode < ActionRollback >( item , menu , convert : true );
AddActionNode < ActionAudit >( item , menu , convert : true );
AddActionNode < ActionToPublish >( item , menu , convert : true );
AddActionNode < ActionAssignDomain >( item , menu , convert : true );
AddActionNode < ActionRights >( item , menu , convert : true );
AddActionNode < ActionProtect >( item , menu , true , true );
2013-10-02 20:00:01 +10:00
2016-07-29 15:48:32 +02:00
AddActionNode < ActionNotify >( item , menu , true , true );
AddActionNode < ActionSendToTranslate >( item , menu , convert : true );
AddActionNode < RefreshNode , ActionRefresh >( item , menu , true );
2013-08-12 15:06:12 +02:00
return menu ;
}
2018-05-10 18:20:33 +10:00
/// <summary>
/// set name according to variations
/// </summary>
/// <param name="entity"></param>
/// <param name="culture"></param>
private void EnsureName ( IEntitySlim entity , string culture )
{
if ( culture == null )
{
if ( string . IsNullOrWhiteSpace ( entity . Name ))
entity . Name = "[[" + entity . Id + "]]" ;
return ;
}
if ( entity is IDocumentEntitySlim docEntity )
{
// we are getting the tree for a given culture,
// for those items that DO support cultures, we need to get the proper name, IF it exists
// otherwise, invariant is fine
if ( docEntity . Variations . Has ( Core . Models . ContentVariation . CultureNeutral ) &&
docEntity . CultureNames . TryGetValue ( culture , out var name ) &&
! string . IsNullOrWhiteSpace ( name ))
{
entity . Name = name ;
}
if ( string . IsNullOrWhiteSpace ( entity . Name ))
entity . Name = "[[" + entity . Id + "]]" ;
}
throw new InvalidOperationException ( $"Cannot render a tree node for a culture when the entity isn't {typeof(IDocumentEntitySlim)}, instead it is {entity.GetType()}" );
}
2013-08-12 15:06:12 +02:00
2016-07-29 15:48:32 +02:00
private void AddActionNode < TAction >( IUmbracoEntity item , MenuItemCollection menu , bool hasSeparator = false , bool convert = false )
where TAction : IAction
{
var menuItem = menu . Items . Add < TAction >( Services . TextService . Localize ( "actions" , Current . Actions . GetAction < TAction >(). Alias ), hasSeparator );
if ( convert ) menuItem . ConvertLegacyMenuItem ( item , "content" , "content" );
}
private void AddActionNode < TItem , TAction >( IUmbracoEntity item , MenuItemCollection menu , bool hasSeparator = false , bool convert = false )
where TItem : MenuItem , new ()
where TAction : IAction
{
var menuItem = menu . Items . Add < TItem , TAction >( Services . TextService . Localize ( "actions" , Current . Actions . GetAction < TAction >(). Alias ), hasSeparator );
if ( convert ) menuItem . ConvertLegacyMenuItem ( item , "content" , "content" );
}
2017-09-23 10:08:18 +02:00
2017-09-12 16:22:16 +02:00
public IEnumerable < SearchResultItem > Search ( string query , int pageSize , long pageIndex , out long totalFound , string searchFrom = null )
{
return _treeSearcher . ExamineSearch ( Umbraco , query , UmbracoEntityTypes . Document , pageSize , pageIndex , out totalFound , searchFrom );
}
2013-08-12 15:06:12 +02:00
}
2017-07-20 11:21:28 +02:00
}