2018-06-29 19:55:15 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Net ;
using System.Net.Http.Formatting ;
using System.Web.Http ;
using Umbraco.Core ;
using Umbraco.Core.Models ;
using Umbraco.Core.Models.Entities ;
using Umbraco.Core.Services ;
2018-10-29 17:27:33 +11:00
using Umbraco.Web.Actions ;
2018-06-29 19:55:15 +02:00
using Umbraco.Web.Composing ;
using Umbraco.Web.Models.Trees ;
using Umbraco.Web.Mvc ;
using Umbraco.Web.WebApi.Filters ;
2018-10-29 17:27:33 +11:00
2018-06-29 19:55:15 +02:00
using Umbraco.Web.Models.ContentEditing ;
using Umbraco.Web.Search ;
using Constants = Umbraco . Core . Constants ;
namespace Umbraco.Web.Trees
{
//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(
Constants.Applications.Content,
Constants.Applications.Media,
Constants.Applications.Users,
Constants.Applications.Settings,
2018-10-05 11:09:33 +02:00
Constants.Applications.Packages,
2018-06-29 19:55:15 +02:00
Constants.Applications.Members)]
[Tree(Constants.Applications.Content, Constants.Trees.Content)]
[PluginController("UmbracoTrees")]
[CoreTree]
[SearchableTree("searchResultFormatter", "configureContentResult")]
public class ContentTreeController : ContentTreeControllerBase , ISearchableTree
{
private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher ();
protected override int RecycleBinId => Constants . System . RecycleBinContent ;
protected override bool RecycleBinSmells => Services . ContentService . RecycleBinSmells ();
private int [] _userStartNodes ;
protected override int [] UserStartNodes
=> _userStartNodes ?? ( _userStartNodes = Security . CurrentUser . CalculateContentStartNodeIds ( Services . EntityService ));
/// <inheritdoc />
protected override TreeNode GetSingleTreeNode ( IEntitySlim entity , string parentId , FormDataCollection queryStrings )
{
2018-09-21 15:49:37 +10:00
var culture = queryStrings ?[ "culture" ];
2018-07-09 18:26:15 +02:00
2018-06-29 19:55:15 +02:00
var allowedUserOptions = GetAllowedUserMenuItemsForNode ( entity );
2018-09-21 15:49:37 +10:00
if ( CanUserAccessNode ( entity , allowedUserOptions , culture ))
2018-06-29 19:55:15 +02:00
{
//Special check to see if it ia a container, if so then we'll hide children.
var isContainer = entity . IsContainer ; // && (queryStrings.Get("isDialog") != "true");
2018-07-11 15:58:48 +10:00
var hasChildren = ShouldRenderChildrenOfContainer ( entity );
2018-06-29 19:55:15 +02:00
var node = CreateTreeNode (
entity ,
Constants . ObjectTypes . Document ,
parentId ,
queryStrings ,
2018-07-11 15:58:48 +10:00
hasChildren );
2018-06-29 19:55:15 +02:00
// entity is either a container, or a document
if ( isContainer )
{
node . AdditionalData . Add ( "isContainer" , true );
node . SetContainerStyle ();
}
else
{
var documentEntity = ( IDocumentEntitySlim ) entity ;
2018-05-07 23:22:52 +10:00
2018-09-21 15:49:37 +10:00
if (! documentEntity . Variations . VariesByCulture ())
{
if (! documentEntity . Published )
node . SetNotPublishedStyle ();
2018-09-27 19:30:31 +10:00
else if ( documentEntity . Edited )
node . SetHasPendingVersionStyle ();
2018-09-21 15:49:37 +10:00
}
else
{
if (! culture . IsNullOrWhiteSpace ())
{
if (! documentEntity . PublishedCultures . Contains ( culture ))
node . SetNotPublishedStyle ();
2018-09-27 19:30:31 +10:00
else if ( documentEntity . EditedCultures . Contains ( culture ))
node . SetHasPendingVersionStyle ();
2018-09-21 15:49:37 +10:00
}
}
2018-05-07 23:22:52 +10:00
2018-10-22 09:30:39 +01:00
node . AdditionalData . Add ( "variesByCulture" , documentEntity . Variations . VariesByCulture ());
2018-06-29 19:55:15 +02:00
node . AdditionalData . Add ( "contentType" , documentEntity . ContentTypeAlias );
}
if ( Services . PublicAccessService . IsProtected ( entity . Path ))
node . SetProtectedStyle ();
return node ;
}
return null ;
}
protected override MenuItemCollection PerformGetMenuForNode ( string id , FormDataCollection queryStrings )
{
if ( id == Constants . System . Root . ToInvariantString ())
{
var menu = new MenuItemCollection ();
// if the user's start node is not the root then the only menu item to display is refresh
if ( UserStartNodes . Contains ( Constants . System . Root ) == false )
{
2018-10-29 17:27:33 +11:00
menu . Items . Add ( new RefreshNode ( Services . TextService , true ));
2018-06-29 19:55:15 +02:00
return menu ;
}
//set the default to create
2018-10-29 17:27:33 +11:00
menu . DefaultMenuAlias = ActionNew . ActionAlias ;
2018-06-29 19:55:15 +02:00
// we need to get the default permissions as you can't set permissions on the very root node
var permission = Services . UserService . GetPermissions ( Security . CurrentUser , Constants . System . Root ). First ();
2018-10-29 17:27:33 +11:00
var nodeActions = Current . Actions . FromEntityPermission ( permission )
2018-06-29 19:55:15 +02:00
. Select ( x => new MenuItem ( x ));
//these two are the standard items
2018-10-29 17:27:33 +11:00
//fixme: inject
menu . Items . Add < ActionNew >( Services . TextService . Localize ( "actions" , ActionNew . ActionAlias ));
menu . Items . Add < ActionSort >( Services . TextService . Localize ( "actions" , Current . Actions . GetAction < ActionSort >(). Alias ), true );
2018-06-29 19:55:15 +02:00
//filter the standard items
FilterUserAllowedMenuItems ( menu , nodeActions );
if ( menu . Items . Any ())
{
menu . Items . Last (). SeperatorBefore = true ;
}
// add default actions for *all* users
2018-07-09 18:26:15 +02:00
// fixme - temp disable RePublish as the page itself (republish.aspx) has been temp disabled
//menu.Items.Add<ActionRePublish>(Services.TextService.Localize("actions", ActionRePublish.Instance.Alias)).ConvertLegacyMenuItem(null, "content", "content");
2018-10-29 17:27:33 +11:00
menu . Items . Add ( new RefreshNode ( Services . TextService , true ));
2018-06-29 19:55:15 +02:00
return menu ;
}
//return a normal node menu:
int iid ;
if ( int . TryParse ( id , out iid ) == false )
{
throw new HttpResponseException ( HttpStatusCode . NotFound );
}
var item = Services . EntityService . Get ( iid , UmbracoObjectTypes . Document );
if ( item == null )
{
throw new HttpResponseException ( HttpStatusCode . NotFound );
}
//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 ();
2018-10-29 17:27:33 +11:00
menu . Items . Add ( new RefreshNode ( Services . TextService , true ));
2018-06-29 19:55:15 +02:00
return menu ;
}
2018-07-11 15:58:48 +10:00
var nodeMenu = GetAllNodeMenuItems ( item );
//if the content node is in the recycle bin, don't have a default menu, just show the regular menu
2018-06-29 19:55:15 +02:00
if ( item . Path . Split ( new [] { ',' }, StringSplitOptions . RemoveEmptyEntries ). Contains ( RecycleBinId . ToInvariantString ()))
{
nodeMenu . DefaultMenuAlias = null ;
2018-07-11 15:58:48 +10:00
nodeMenu = GetNodeMenuItemsForDeletedContent ( item );
2018-06-29 19:55:15 +02:00
}
else
{
//set the default to create
2018-10-29 17:27:33 +11:00
nodeMenu . DefaultMenuAlias = ActionNew . ActionAlias ;
2018-06-29 19:55:15 +02:00
}
2018-07-11 15:58:48 +10:00
var allowedMenuItems = GetAllowedUserMenuItemsForNode ( item );
FilterUserAllowedMenuItems ( nodeMenu , allowedMenuItems );
2018-06-29 19:55:15 +02:00
return nodeMenu ;
}
2018-08-17 00:50:05 +10:00
protected override UmbracoObjectTypes UmbracoObjectType => UmbracoObjectTypes . Document ;
2018-06-29 19:55:15 +02: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 )
{
var entity = GetEntityFromId ( id );
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 >();
2018-06-29 19:55:15 +02:00
//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
2018-05-10 18:20:33 +10:00
foreach ( var entity in result )
EnsureName ( entity , cultureVal );
2018-07-09 18:26:15 +02:00
return result ;
2018-06-29 19:55:15 +02:00
}
2018-05-10 18:20:33 +10:00
2018-06-29 19:55:15 +02: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 )
{
var menu = new MenuItemCollection ();
AddActionNode < ActionNew >( item , menu );
AddActionNode < ActionDelete >( item , menu );
AddActionNode < ActionCreateBlueprintFromContent >( item , menu );
//need to ensure some of these are converted to the legacy system - until we upgrade them all to be angularized.
AddActionNode < ActionMove >( item , menu , true );
AddActionNode < ActionCopy >( item , menu );
2018-08-02 13:52:13 +01:00
AddActionNode < ActionSort >( item , menu , true );
2018-06-29 19:55:15 +02:00
2018-08-02 10:27:37 +01:00
AddActionNode < ActionAssignDomain >( item , menu );
2018-10-29 17:27:33 +11:00
AddActionNode < ActionRights >( item , menu );
2018-06-29 19:55:15 +02:00
AddActionNode < ActionProtect >( item , menu , true , true );
2018-10-29 23:23:21 +11:00
menu . Items . Add ( new MenuItem ( "notify" , Services . TextService )
{
Icon = "megaphone" ,
SeperatorBefore = true
});
2018-06-29 19:55:15 +02:00
2018-10-29 17:27:33 +11:00
menu . Items . Add ( new RefreshNode ( Services . TextService , true ));
2018-06-29 19:55:15 +02:00
return menu ;
}
2018-07-11 15:58:48 +10:00
/// <summary>
/// Returns a collection of all menu items that can be on a deleted (in recycle bin) content node
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected MenuItemCollection GetNodeMenuItemsForDeletedContent ( IUmbracoEntity item )
{
var menu = new MenuItemCollection ();
2018-10-29 17:27:33 +11:00
menu . Items . Add < ActionRestore >( Services . TextService . Localize ( "actions" , ActionRestore . ActionAlias ));
menu . Items . Add < ActionDelete >( Services . TextService . Localize ( "actions" , ActionDelete . ActionAlias ));
2018-07-11 15:58:48 +10:00
2018-10-29 17:27:33 +11:00
menu . Items . Add ( new RefreshNode ( Services . TextService , true ));
2018-07-11 15:58:48 +10:00
return menu ;
}
2018-06-29 19:55:15 +02: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 ;
}
2018-05-10 18:20:33 +10:00
2018-05-10 19:16:46 +10:00
if (!( entity is IDocumentEntitySlim docEntity ))
throw new InvalidOperationException ( $"Cannot render a tree node for a culture when the entity isn't {typeof(IDocumentEntitySlim)}, instead it is {entity.GetType()}" );
// 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
2018-08-17 00:50:05 +10:00
// otherwise, invariant is fine (with brackets)
2018-05-10 19:16:46 +10:00
2018-08-17 00:50:05 +10:00
if ( docEntity . Variations . VariesByCulture ())
2018-05-10 18:20:33 +10:00
{
2018-08-17 00:50:05 +10:00
if ( docEntity . CultureNames . TryGetValue ( culture , out var name ) &&
! string . IsNullOrWhiteSpace ( name ))
{
entity . Name = name ;
}
else
{
entity . Name = "(" + entity . Name + ")" ;
}
2018-05-10 18:20:33 +10:00
}
2018-05-10 19:16:46 +10:00
if ( string . IsNullOrWhiteSpace ( entity . Name ))
2018-06-29 19:55:15 +02:00
entity . Name = "[[" + entity . Id + "]]" ;
2018-05-10 18:20:33 +10:00
}
2018-06-29 19:55:15 +02:00
2018-10-29 17:27:33 +11:00
//fixme: Remove the need for converting to legacy
2018-06-29 19:55:15 +02:00
private void AddActionNode < TAction >( IUmbracoEntity item , MenuItemCollection menu , bool hasSeparator = false , bool convert = false )
where TAction : IAction
{
2018-10-29 17:27:33 +11:00
//fixme: Inject
2018-06-29 19:55:15 +02:00
var menuItem = menu . Items . Add < TAction >( Services . TextService . Localize ( "actions" , Current . Actions . GetAction < TAction >(). Alias ), hasSeparator );
if ( convert ) menuItem . ConvertLegacyMenuItem ( item , "content" , "content" );
}
2018-10-29 17:27:33 +11:00
////fixme: Remove the need for converting to legacy
//private void AddActionNode<TItem, TAction>(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool convert = false)
// where TItem : MenuItem, new()
// where TAction : IAction
//{
// //fixme: Inject
// var menuItem = menu.Items.Add<TItem, TAction>(Services.TextService.Localize("actions", Current.Actions.GetAction<TAction>().Alias), hasSeparator);
// if (convert) menuItem.ConvertLegacyMenuItem(item, "content", "content");
//}
2018-06-29 19:55:15 +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 );
}
}
}