2007-07-19 12:40:05 +02:00
< ? php
2016-06-16 06:57:19 +02:00
2016-07-22 01:32:32 +02:00
namespace SilverStripe\CMS\Controllers ;
2017-08-21 02:45:56 +02:00
use InvalidArgumentException ;
use Psr\SimpleCache\CacheInterface ;
2016-08-23 04:36:06 +02:00
use SilverStripe\Admin\AdminRootController ;
use SilverStripe\Admin\CMSBatchActionHandler ;
use SilverStripe\Admin\LeftAndMain ;
2017-08-21 02:45:56 +02:00
use SilverStripe\Admin\LeftAndMainFormRequestHandler ;
2022-08-06 09:07:24 +02:00
use SilverStripe\Admin\Navigator\SilverStripeNavigator ;
2016-10-25 02:22:31 +02:00
use SilverStripe\CMS\BatchActions\CMSBatchAction_Archive ;
use SilverStripe\CMS\BatchActions\CMSBatchAction_Publish ;
use SilverStripe\CMS\BatchActions\CMSBatchAction_Restore ;
use SilverStripe\CMS\BatchActions\CMSBatchAction_Unpublish ;
2018-09-04 03:13:33 +02:00
use SilverStripe\CMS\Controllers\CMSSiteTreeFilter_Search ;
2016-08-23 04:36:06 +02:00
use SilverStripe\CMS\Model\CurrentPageIdentifier ;
use SilverStripe\CMS\Model\RedirectorPage ;
use SilverStripe\CMS\Model\SiteTree ;
2017-08-21 02:45:56 +02:00
use SilverStripe\CMS\Model\VirtualPage ;
2016-08-23 04:36:06 +02:00
use SilverStripe\Control\Controller ;
use SilverStripe\Control\Director ;
2016-09-09 01:26:24 +02:00
use SilverStripe\Control\HTTPRequest ;
use SilverStripe\Control\HTTPResponse ;
use SilverStripe\Control\HTTPResponse_Exception ;
2022-10-18 07:21:09 +02:00
use SilverStripe\Control\PjaxResponseNegotiator ;
2018-09-04 03:13:33 +02:00
use SilverStripe\Core\Cache\MemberCacheFlusher ;
2017-11-27 22:09:27 +01:00
use SilverStripe\Core\Config\Config ;
2016-08-23 04:36:06 +02:00
use SilverStripe\Core\Convert ;
2017-08-21 02:45:56 +02:00
use SilverStripe\Core\Environment ;
2018-09-04 03:13:33 +02:00
use SilverStripe\Core\Flushable ;
2016-08-23 04:36:06 +02:00
use SilverStripe\Core\Injector\Injector ;
use SilverStripe\Forms\DateField ;
use SilverStripe\Forms\DropdownField ;
use SilverStripe\Forms\FieldGroup ;
use SilverStripe\Forms\FieldList ;
use SilverStripe\Forms\Form ;
use SilverStripe\Forms\FormAction ;
2016-09-08 09:33:32 +02:00
use SilverStripe\Forms\FormField ;
2016-08-23 04:36:06 +02:00
use SilverStripe\Forms\GridField\GridField ;
use SilverStripe\Forms\GridField\GridFieldConfig ;
use SilverStripe\Forms\GridField\GridFieldDataColumns ;
use SilverStripe\Forms\GridField\GridFieldLevelup ;
use SilverStripe\Forms\GridField\GridFieldPaginator ;
use SilverStripe\Forms\GridField\GridFieldSortableHeader ;
use SilverStripe\Forms\HiddenField ;
use SilverStripe\Forms\LabelField ;
use SilverStripe\Forms\LiteralField ;
2017-08-21 02:45:56 +02:00
use SilverStripe\Forms\Tab ;
2016-09-08 09:33:32 +02:00
use SilverStripe\Forms\TabSet ;
2016-08-23 04:36:06 +02:00
use SilverStripe\Forms\TextField ;
2016-06-16 06:57:19 +02:00
use SilverStripe\ORM\ArrayList ;
2017-08-21 02:45:56 +02:00
use SilverStripe\ORM\CMSPreviewable ;
2016-06-16 06:57:19 +02:00
use SilverStripe\ORM\DataList ;
2016-08-23 04:36:06 +02:00
use SilverStripe\ORM\DataObject ;
2016-06-16 06:57:19 +02:00
use SilverStripe\ORM\DB ;
2016-10-21 05:15:34 +02:00
use SilverStripe\ORM\FieldType\DBHTMLText ;
2016-08-23 04:36:06 +02:00
use SilverStripe\ORM\HiddenClass ;
2018-09-25 05:14:16 +02:00
use SilverStripe\ORM\Hierarchy\Hierarchy ;
2017-04-06 07:38:15 +02:00
use SilverStripe\ORM\Hierarchy\MarkedSet ;
2016-08-23 04:36:06 +02:00
use SilverStripe\ORM\SS_List ;
2016-12-09 04:00:46 +01:00
use SilverStripe\ORM\ValidationResult ;
2017-05-12 02:47:46 +02:00
use SilverStripe\Security\InheritedPermissions ;
2016-06-23 01:51:20 +02:00
use SilverStripe\Security\Member ;
use SilverStripe\Security\Permission ;
use SilverStripe\Security\PermissionProvider ;
2016-08-23 04:36:06 +02:00
use SilverStripe\Security\Security ;
use SilverStripe\Security\SecurityToken ;
2017-08-21 02:45:56 +02:00
use SilverStripe\SiteConfig\SiteConfig ;
use SilverStripe\Versioned\ChangeSet ;
use SilverStripe\Versioned\ChangeSetItem ;
use SilverStripe\Versioned\Versioned ;
2019-07-12 05:31:17 +02:00
use SilverStripe\VersionedAdmin\Controllers\CMSPageHistoryViewerController ;
2016-08-23 04:36:06 +02:00
use SilverStripe\View\ArrayData ;
use SilverStripe\View\Requirements ;
2016-07-22 01:32:32 +02:00
use Translatable ;
2016-08-23 04:36:06 +02:00
2007-07-19 12:40:05 +02:00
/**
* The main " content " area of the CMS .
2011-08-19 02:32:31 +02:00
*
2007-07-19 12:40:05 +02:00
* This class creates a 2 - frame layout - left - tree and right - form - to sit beneath the main
* admin menu .
2015-08-14 02:39:45 +02:00
*
2007-07-19 12:40:05 +02:00
* @ todo Create some base classes to contain the generic functionality that will be replicated .
2016-08-11 04:10:51 +02:00
*
* @ mixin LeftAndMainPageIconsExtension
2007-07-19 12:40:05 +02:00
*/
2017-11-30 03:56:16 +01:00
class CMSMain extends LeftAndMain implements CurrentPageIdentifier , PermissionProvider , Flushable , MemberCacheFlusher
2017-01-25 21:59:25 +01:00
{
2017-09-07 02:36:26 +02:00
/**
* Unique ID for page icons CSS block
*/
const PAGE_ICONS_ID = 'PageIcons' ;
2017-01-25 21:59:25 +01:00
private static $url_segment = 'pages' ;
private static $url_rule = '/$Action/$ID/$OtherID' ;
// Maintain a lower priority than other administration sections
// so that Director does not think they are actions of CMSMain
private static $url_priority = 39 ;
private static $menu_title = 'Edit Page' ;
private static $menu_icon_class = 'font-icon-sitemap' ;
private static $menu_priority = 10 ;
private static $tree_class = SiteTree :: class ;
private static $subitem_class = Member :: class ;
2017-03-29 06:19:57 +02:00
private static $session_namespace = self :: class ;
2017-01-25 21:59:25 +01:00
private static $required_permission_codes = 'CMS_ACCESS_CMSMain' ;
2018-09-28 03:43:46 +02:00
/**
* Should the archive warning message be dynamic based on the specific content ? This is slow on larger sites and can be disabled .
*
* @ config
* @ var bool
*/
private static $enable_dynamic_archive_warning_message = true ;
2017-01-25 21:59:25 +01:00
/**
* Amount of results showing on a single page .
*
* @ config
* @ var int
*/
private static $page_length = 15 ;
2020-04-19 06:18:01 +02:00
private static $allowed_actions = [
2017-01-25 21:59:25 +01:00
'archive' ,
'deleteitems' ,
'DeleteItemsForm' ,
'dialog' ,
'duplicate' ,
'duplicatewithchildren' ,
'publishall' ,
'publishitems' ,
'PublishItemsForm' ,
'submit' ,
'EditForm' ,
2018-09-04 03:13:33 +02:00
'schema' ,
2017-01-25 21:59:25 +01:00
'SearchForm' ,
'SiteTreeAsUL' ,
'getshowdeletedsubtree' ,
2017-03-29 06:19:57 +02:00
'savetreenode' ,
'getsubtree' ,
'updatetreenodes' ,
2017-01-25 21:59:25 +01:00
'batchactions' ,
'treeview' ,
'listview' ,
'ListViewForm' ,
'childfilter' ,
2020-04-19 06:18:01 +02:00
];
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
private static $url_handlers = [
2017-12-13 02:35:21 +01:00
'EditForm/$ID' => 'EditForm' ,
2017-03-29 06:19:57 +02:00
];
2020-04-19 06:18:01 +02:00
private static $casting = [
2017-01-25 21:59:25 +01:00
'TreeIsFiltered' => 'Boolean' ,
'AddForm' => 'HTMLFragment' ,
'LinkPages' => 'Text' ,
'Link' => 'Text' ,
'ListViewForm' => 'HTMLFragment' ,
'ExtraTreeTools' => 'HTMLFragment' ,
'PageList' => 'HTMLFragment' ,
'PageListSidebar' => 'HTMLFragment' ,
'SiteTreeHints' => 'HTMLFragment' ,
'SecurityID' => 'Text' ,
'SiteTreeAsUL' => 'HTMLFragment' ,
2020-04-19 06:18:01 +02:00
];
2017-01-25 21:59:25 +01:00
2017-11-30 03:56:16 +01:00
private static $dependencies = [
'HintsCache' => '%$' . CacheInterface :: class . '.CMSMain_SiteTreeHints' ,
];
/**
* @ var CacheInterface
*/
protected $hintsCache ;
2017-01-25 21:59:25 +01:00
protected function init ()
{
// set reading lang
if ( SiteTree :: has_extension ( 'Translatable' ) && ! $this -> getRequest () -> isAjax ()) {
2022-04-13 07:07:59 +02:00
Translatable :: choose_site_locale ( array_keys ( Translatable :: get_existing_content_languages ( SiteTree :: class ) ? ? []));
2017-01-25 21:59:25 +01:00
}
parent :: init ();
2017-06-28 00:17:09 +02:00
Requirements :: javascript ( 'silverstripe/cms: client/dist/js/bundle.js' );
Requirements :: javascript ( 'silverstripe/cms: client/dist/js/SilverStripeNavigator.js' );
Requirements :: css ( 'silverstripe/cms: client/dist/styles/bundle.css' );
2017-09-07 02:36:26 +02:00
Requirements :: customCSS ( $this -> generatePageIconsCss (), self :: PAGE_ICONS_ID );
2017-06-28 00:17:09 +02:00
2022-12-07 22:44:36 +01:00
Requirements :: add_i18n_javascript ( 'silverstripe/cms: client/lang' , false );
2017-01-25 21:59:25 +01:00
CMSBatchActionHandler :: register ( 'restore' , CMSBatchAction_Restore :: class );
CMSBatchActionHandler :: register ( 'archive' , CMSBatchAction_Archive :: class );
CMSBatchActionHandler :: register ( 'unpublish' , CMSBatchAction_Unpublish :: class );
CMSBatchActionHandler :: register ( 'publish' , CMSBatchAction_Publish :: class );
}
2022-10-18 07:21:09 +02:00
public function index ( HTTPRequest $request ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
// In case we're not showing a specific record, explicitly remove any session state,
// to avoid it being highlighted in the tree, and causing an edit form to show.
if ( ! $request -> param ( 'Action' )) {
$this -> setCurrentPageID ( null );
}
return parent :: index ( $request );
}
2022-10-18 07:21:09 +02:00
public function getResponseNegotiator () : PjaxResponseNegotiator
2017-01-25 21:59:25 +01:00
{
$negotiator = parent :: getResponseNegotiator ();
// ListViewForm
$negotiator -> setCallback ( 'ListViewForm' , function () {
return $this -> ListViewForm () -> forTemplate ();
});
return $negotiator ;
}
/**
* Get pages listing area
*
* @ return DBHTMLText
*/
public function PageList ()
{
return $this -> renderWith ( $this -> getTemplatesWithSuffix ( '_PageList' ));
}
/**
* Page list view for edit - form
*
* @ return DBHTMLText
*/
public function PageListSidebar ()
{
return $this -> renderWith ( $this -> getTemplatesWithSuffix ( '_PageList_Sidebar' ));
}
/**
* If this is set to true , the " switchView " context in the
* template is shown , with links to the staging and publish site .
*
* @ return boolean
*/
public function ShowSwitchView ()
{
return true ;
}
/**
* Overloads the LeftAndMain :: ShowView . Allows to pass a page as a parameter , so we are able
* to switch view also for archived versions .
*
* @ param SiteTree $page
* @ return array
*/
public function SwitchView ( $page = null )
{
if ( ! $page ) {
$page = $this -> currentPage ();
}
if ( $page ) {
$nav = SilverStripeNavigator :: get_for_record ( $page );
return $nav [ 'items' ];
}
}
//------------------------------------------------------------------------------------------//
// Main controllers
//------------------------------------------------------------------------------------------//
// Main UI components
/**
* Override { @ link LeftAndMain } Link to allow blank URL segment for CMSMain .
*
* @ param string | null $action Action to link to .
* @ return string
*/
public function Link ( $action = null )
{
$link = Controller :: join_links (
AdminRootController :: admin_url (),
2017-08-22 23:46:46 +02:00
$this -> config () -> get ( 'url_segment' ), // in case we want to change the segment
2017-01-25 21:59:25 +01:00
'/' , // trailing slash needed if $action is null!
" $action "
);
$this -> extend ( 'updateLink' , $link );
return $link ;
}
public function LinkPages ()
{
return CMSPagesController :: singleton () -> Link ();
}
public function LinkPagesWithSearch ()
{
return $this -> LinkWithSearch ( $this -> LinkPages ());
}
/**
* Get link to tree view
*
* @ return string
*/
public function LinkTreeView ()
{
// Tree view is just default link to main pages section (no /treeview suffix)
2017-08-24 06:17:10 +02:00
return CMSMain :: singleton () -> Link ();
2017-01-25 21:59:25 +01:00
}
/**
* Get link to list view
*
* @ return string
*/
public function LinkListView ()
{
2017-08-21 02:45:56 +02:00
// Note : Force redirect to top level page controller (no parentid)
2017-01-25 21:59:25 +01:00
return $this -> LinkWithSearch ( CMSMain :: singleton () -> Link ( 'listview' ));
}
2017-08-21 02:45:56 +02:00
/**
* Link to list view for children of a parent page
*
* @ param int | string $parentID Literal parentID , or placeholder ( e . g . '%d' ) for
* client side substitution
* @ return string
*/
public function LinkListViewChildren ( $parentID )
{
2017-12-12 03:04:21 +01:00
return sprintf (
'%s?ParentID=%s' ,
CMSMain :: singleton () -> Link (),
2017-08-21 02:45:56 +02:00
$parentID
2017-12-12 03:04:21 +01:00
);
2017-08-21 02:45:56 +02:00
}
2017-12-07 04:24:01 +01:00
/**
* @ return string
*/
public function LinkListViewRoot ()
{
return $this -> LinkListViewChildren ( 0 );
}
2017-08-21 02:45:56 +02:00
/**
* Link to lazy - load deferred tree view
*
* @ return string
*/
public function LinkTreeViewDeferred ()
{
2017-09-04 05:37:47 +02:00
return $this -> Link ( 'treeview' );
2017-08-21 02:45:56 +02:00
}
2017-12-06 05:06:15 +01:00
/**
* Link to lazy - load deferred list view
*
* @ return string
*/
public function LinkListViewDeferred ()
{
return $this -> Link ( 'listview' );
}
2022-09-30 04:10:49 +02:00
/**
* Get the link for editing a page .
*
* @ see CMSEditLinkExtension :: getCMSEditLinkForManagedDataObject ()
*/
public function getCMSEditLinkForManagedDataObject ( SiteTree $obj ) : string
{
return Controller :: join_links ( CMSPageEditController :: singleton () -> Link ( 'show' ), $obj -> ID );
}
2017-01-25 21:59:25 +01:00
public function LinkPageEdit ( $id = null )
{
if ( ! $id ) {
$id = $this -> currentPageID ();
}
return $this -> LinkWithSearch (
Controller :: join_links ( CMSPageEditController :: singleton () -> Link ( 'show' ), $id )
);
}
public function LinkPageSettings ()
{
if ( $id = $this -> currentPageID ()) {
return $this -> LinkWithSearch (
Controller :: join_links ( CMSPageSettingsController :: singleton () -> Link ( 'show' ), $id )
);
} else {
return null ;
}
}
public function LinkPageHistory ()
{
2019-07-12 05:31:17 +02:00
$controller = Injector :: inst () -> get ( CMSPageHistoryViewerController :: class );
if (( $id = $this -> currentPageID ()) && $controller ) {
if ( $controller ) {
return $this -> LinkWithSearch (
Controller :: join_links ( $controller -> Link ( 'show' ), $id )
);
}
2017-01-25 21:59:25 +01:00
} else {
return null ;
}
}
2017-07-16 12:46:11 +02:00
/**
2017-07-18 03:19:04 +02:00
* Return the active tab identifier for the CMS . Used by templates to decide which tab to give the active state .
* The default value is " edit " , as the primary content tab . Child controllers will override this .
2017-07-16 12:46:11 +02:00
*
* @ return string
*/
2017-07-18 03:19:04 +02:00
public function getTabIdentifier ()
2017-07-16 12:46:11 +02:00
{
return 'edit' ;
}
2017-11-30 03:56:16 +01:00
/**
* @ param CacheInterface $cache
* @ return $this
*/
public function setHintsCache ( CacheInterface $cache )
{
$this -> hintsCache = $cache ;
return $this ;
}
/**
* @ return CacheInterface $cache
*/
public function getHintsCache ()
{
return $this -> hintsCache ;
}
/**
* Clears all dependent cache backends
*/
public function clearCache ()
{
$this -> getHintsCache () -> clear ();
}
2017-01-25 21:59:25 +01:00
public function LinkWithSearch ( $link )
{
// Whitelist to avoid side effects
2020-04-19 06:18:01 +02:00
$params = [
2017-01-25 21:59:25 +01:00
'q' => ( array ) $this -> getRequest () -> getVar ( 'q' ),
'ParentID' => $this -> getRequest () -> getVar ( 'ParentID' )
2020-04-19 06:18:01 +02:00
];
2017-01-25 21:59:25 +01:00
$link = Controller :: join_links (
$link ,
2022-04-13 07:07:59 +02:00
array_filter ( array_values ( $params ? ? [])) ? '?' . http_build_query ( $params ) : null
2017-01-25 21:59:25 +01:00
);
$this -> extend ( 'updateLinkWithSearch' , $link );
return $link ;
}
public function LinkPageAdd ( $extra = null , $placeholders = null )
{
$link = CMSPageAddController :: singleton () -> Link ();
$this -> extend ( 'updateLinkPageAdd' , $link );
if ( $extra ) {
$link = Controller :: join_links ( $link , $extra );
}
if ( $placeholders ) {
2022-04-13 07:07:59 +02:00
$link .= ( strpos ( $link ? ? '' , '?' ) === false ? " ? $placeholders " : " & $placeholders " );
2017-01-25 21:59:25 +01:00
}
return $link ;
}
/**
* @ return string
*/
public function LinkPreview ()
{
$record = $this -> getRecord ( $this -> currentPageID ());
$baseLink = Director :: absoluteBaseURL ();
if ( $record && $record instanceof SiteTree ) {
// if we are an external redirector don't show a link
if ( $record instanceof RedirectorPage && $record -> RedirectionType == 'External' ) {
$baseLink = false ;
} else {
$baseLink = $record -> Link ( '?stage=Stage' );
}
}
return $baseLink ;
}
/**
* Return the entire site tree as a nested set of ULs
*/
public function SiteTreeAsUL ()
{
2018-09-25 05:14:16 +02:00
$treeClass = $this -> config () -> get ( 'tree_class' );
$filter = $this -> getSearchFilter ();
DataObject :: singleton ( $treeClass ) -> prepopulateTreeDataCache ( null , [
'childrenMethod' => $filter ? $filter -> getChildrenMethod () : 'AllChildrenIncludingDeleted' ,
'numChildrenMethod' => $filter ? $filter -> getNumChildrenMethod () : 'numChildren' ,
]);
$html = $this -> getSiteTreeFor ( $treeClass );
2017-01-25 21:59:25 +01:00
$this -> extend ( 'updateSiteTreeAsUL' , $html );
return $html ;
}
2017-03-29 06:19:57 +02:00
/**
* Get a site tree HTML listing which displays the nodes under the given criteria .
*
* @ param string $className The class of the root object
* @ param string $rootID The ID of the root object . If this is null then a complete tree will be
* shown
* @ param string $childrenMethod The method to call to get the children of the tree . For example ,
* Children , AllChildrenIncludingDeleted , or AllHistoricalChildren
* @ param string $numChildrenMethod
* @ param callable $filterFunction
* @ param int $nodeCountThreshold
* @ return string Nested unordered list with links to each page
*/
public function getSiteTreeFor (
$className ,
$rootID = null ,
$childrenMethod = null ,
$numChildrenMethod = null ,
$filterFunction = null ,
2017-11-27 22:09:27 +01:00
$nodeCountThreshold = null
2017-03-29 06:19:57 +02:00
) {
2017-11-27 22:09:27 +01:00
$nodeCountThreshold = is_null ( $nodeCountThreshold ) ? Config :: inst () -> get ( $className , 'node_threshold_total' ) : $nodeCountThreshold ;
2017-04-06 07:38:15 +02:00
// Provide better defaults from filter
2017-03-29 06:19:57 +02:00
$filter = $this -> getSearchFilter ();
2017-04-06 07:38:15 +02:00
if ( $filter ) {
2017-04-22 01:44:49 +02:00
if ( ! $childrenMethod ) {
2017-04-06 07:38:15 +02:00
$childrenMethod = $filter -> getChildrenMethod ();
2017-04-22 01:44:49 +02:00
}
if ( ! $numChildrenMethod ) {
2017-03-29 06:19:57 +02:00
$numChildrenMethod = $filter -> getNumChildrenMethod ();
}
2017-04-06 07:38:15 +02:00
if ( ! $filterFunction ) {
2017-04-22 01:44:49 +02:00
$filterFunction = function ( $node ) use ( $filter ) {
return $filter -> isPageIncluded ( $node );
};
}
2017-04-06 07:38:15 +02:00
}
2017-03-29 06:19:57 +02:00
2017-04-06 07:38:15 +02:00
// Build set from node and begin marking
2017-03-29 06:19:57 +02:00
$record = ( $rootID ) ? $this -> getRecord ( $rootID ) : null ;
2017-04-06 07:38:15 +02:00
$rootNode = $record ? $record : DataObject :: singleton ( $className );
$markingSet = MarkedSet :: create ( $rootNode , $childrenMethod , $numChildrenMethod , $nodeCountThreshold );
2017-03-29 06:19:57 +02:00
2017-04-06 07:38:15 +02:00
// Set filter function
2017-03-29 06:19:57 +02:00
if ( $filterFunction ) {
2017-04-06 07:38:15 +02:00
$markingSet -> setMarkingFilterFunction ( $filterFunction );
2017-03-29 06:19:57 +02:00
}
2017-04-06 07:38:15 +02:00
// Mark tree from this node
$markingSet -> markPartialTree ();
2017-03-29 06:19:57 +02:00
// Ensure current page is exposed
2017-04-06 07:38:15 +02:00
$currentPage = $this -> currentPage ();
2017-03-29 06:19:57 +02:00
if ( $currentPage ) {
2017-04-06 07:38:15 +02:00
$markingSet -> markToExpose ( $currentPage );
2017-03-29 06:19:57 +02:00
}
2017-04-06 07:38:15 +02:00
// Pre-cache permissions
2017-05-12 02:47:46 +02:00
$checker = SiteTree :: getPermissionChecker ();
if ( $checker instanceof InheritedPermissions ) {
$checker -> prePopulatePermissionCache (
InheritedPermissions :: EDIT ,
$markingSet -> markedNodeIDs ()
);
}
2017-03-29 06:19:57 +02:00
2017-04-06 07:38:15 +02:00
// Render using full-subtree template
return $markingSet -> renderChildren (
[ self :: class . '_SubTree' , 'type' => 'Includes' ],
$this -> getTreeNodeCustomisations ()
);
}
/**
* Get callback to determine template customisations for nodes
*
* @ return callable
*/
protected function getTreeNodeCustomisations ()
{
$rootTitle = $this -> getCMSTreeTitle ();
2017-08-21 02:45:56 +02:00
return function ( SiteTree $node ) use ( $rootTitle ) {
2017-04-06 07:38:15 +02:00
return [
2017-08-21 02:45:56 +02:00
'listViewLink' => $this -> LinkListViewChildren ( $node -> ID ),
2017-04-06 07:38:15 +02:00
'rootTitle' => $rootTitle ,
'extraClass' => $this -> getTreeNodeClasses ( $node ),
2018-04-10 00:30:49 +02:00
'Title' => _t (
self :: class . '.PAGETYPE_TITLE' ,
'(Page type: {type}) {title}' ,
[
'type' => $node -> i18n_singular_name (),
'title' => $node -> Title ,
]
2018-04-09 18:11:29 +02:00
)
2017-04-06 07:38:15 +02:00
];
2017-04-22 01:44:49 +02:00
};
}
2017-03-29 06:19:57 +02:00
2017-04-06 07:38:15 +02:00
/**
* Get extra CSS classes for a page ' s tree node
*
* @ param SiteTree $node
* @ return string
*/
public function getTreeNodeClasses ( SiteTree $node )
{
// Get classes from object
$classes = $node -> CMSTreeClasses ();
// Get status flag classes
$flags = $node -> getStatusFlags ();
if ( $flags ) {
2022-04-13 07:07:59 +02:00
$statuses = array_keys ( $flags ? ? []);
2017-04-06 07:38:15 +02:00
foreach ( $statuses as $s ) {
$classes .= ' status-' . $s ;
2017-03-29 06:19:57 +02:00
}
2017-04-06 07:38:15 +02:00
}
2017-03-29 06:19:57 +02:00
2017-04-06 07:38:15 +02:00
// Get additional filter classes
$filter = $this -> getSearchFilter ();
if ( $filter && ( $filterClasses = $filter -> getPageClasses ( $node ))) {
if ( is_array ( $filterClasses )) {
$filterClasses = implode ( ' ' , $filterClasses );
}
$classes .= ' ' . $filterClasses ;
2017-03-29 06:19:57 +02:00
}
2022-04-13 07:07:59 +02:00
return trim ( $classes ? ? '' );
2017-03-29 06:19:57 +02:00
}
/**
* Get a subtree underneath the request param 'ID' .
* If ID = 0 , then get the whole tree .
*/
2022-10-18 07:21:09 +02:00
public function getsubtree ( HTTPRequest $request ) : HTTPResponse
2017-03-29 06:19:57 +02:00
{
$html = $this -> getSiteTreeFor (
2017-08-22 23:46:46 +02:00
$this -> config () -> get ( 'tree_class' ),
2017-03-29 06:19:57 +02:00
$request -> getVar ( 'ID' ),
null ,
null ,
null ,
$request -> getVar ( 'minNodeCount' )
);
// Trim off the outer tag
2022-04-13 07:07:59 +02:00
$html = preg_replace ( '/^[\s\t\r\n]*<ul[^>]*>/' , '' , $html ? ? '' );
$html = preg_replace ( '/<\/ul[^>]*>[\s\t\r\n]*$/' , '' , $html ? ? '' );
2017-03-29 06:19:57 +02:00
2022-10-18 07:21:09 +02:00
return $this -> getResponse () -> setBody ( $html );
2017-03-29 06:19:57 +02:00
}
/**
* Allows requesting a view update on specific tree nodes .
* Similar to { @ link getsubtree ()}, but doesn ' t enforce loading
* all children with the node . Useful to refresh views after
* state modifications , e . g . saving a form .
*/
2022-10-18 07:21:09 +02:00
public function updatetreenodes ( HTTPRequest $request ) : HTTPResponse
2017-03-29 06:19:57 +02:00
{
2020-04-19 06:18:01 +02:00
$data = [];
2022-04-13 07:07:59 +02:00
$ids = explode ( ',' , $request -> getVar ( 'ids' ) ? ? '' );
2017-03-29 06:19:57 +02:00
foreach ( $ids as $id ) {
if ( $id === " " ) {
continue ; // $id may be a blank string, which is invalid and should be skipped over
}
$record = $this -> getRecord ( $id );
if ( ! $record ) {
continue ; // In case a page is no longer available
}
2017-04-06 07:38:15 +02:00
// Create marking set with sole marked root
$markingSet = MarkedSet :: create ( $record );
$markingSet -> setMarkingFilterFunction ( function () {
return false ;
});
$markingSet -> markUnexpanded ( $record );
2017-03-29 06:19:57 +02:00
// Find the next & previous nodes, for proper positioning (Sort isn't good enough - it's not a raw offset)
// TODO: These methods should really be in hierarchy - for a start it assumes Sort exists
$prev = null ;
2017-08-22 23:46:46 +02:00
$className = $this -> config () -> get ( 'tree_class' );
2017-03-29 06:19:57 +02:00
$next = DataObject :: get ( $className )
-> filter ( 'ParentID' , $record -> ParentID )
-> filter ( 'Sort:GreaterThan' , $record -> Sort )
-> first ();
if ( ! $next ) {
$prev = DataObject :: get ( $className )
-> filter ( 'ParentID' , $record -> ParentID )
-> filter ( 'Sort:LessThan' , $record -> Sort )
-> reverse ()
-> first ();
}
2017-04-06 07:38:15 +02:00
// Render using single node template
$html = $markingSet -> renderChildren (
[ self :: class . '_TreeNode' , 'type' => 'Includes' ],
$this -> getTreeNodeCustomisations ()
);
2017-03-29 06:19:57 +02:00
2020-04-19 06:18:01 +02:00
$data [ $id ] = [
2017-03-29 06:19:57 +02:00
'html' => $html ,
'ParentID' => $record -> ParentID ,
'NextID' => $next ? $next -> ID : null ,
'PrevID' => $prev ? $prev -> ID : null
2020-04-19 06:18:01 +02:00
];
2017-03-29 06:19:57 +02:00
}
return $this
-> getResponse ()
-> addHeader ( 'Content-Type' , 'application/json' )
2018-10-28 22:21:19 +01:00
-> setBody ( json_encode ( $data ));
2017-03-29 06:19:57 +02:00
}
/**
* Update the position and parent of a tree node .
* Only saves the node if changes were made .
*
* Required data :
* - 'ID' : The moved node
* - 'ParentID' : New parent relation of the moved node ( 0 for root )
* - 'SiblingIDs' : Array of all sibling nodes to the moved node ( incl . the node itself ) .
* In case of a 'ParentID' change , relates to the new siblings under the new parent .
*
* @ throws HTTPResponse_Exception
*/
2022-10-18 07:21:09 +02:00
public function savetreenode ( HTTPRequest $request ) : HTTPResponse
2017-03-29 06:19:57 +02:00
{
if ( ! SecurityToken :: inst () -> checkRequest ( $request )) {
2022-10-18 07:21:09 +02:00
$this -> httpError ( 400 );
2017-03-29 06:19:57 +02:00
}
2019-01-11 11:56:40 +01:00
if ( ! $this -> CanOrganiseSitetree ()) {
2022-10-18 07:21:09 +02:00
$this -> httpError (
2017-03-29 06:19:57 +02:00
403 ,
_t (
2017-05-08 07:57:24 +02:00
__CLASS__ . '.CANT_REORGANISE' ,
2017-03-29 06:19:57 +02:00
" You do not have permission to rearange the site tree. Your change was not saved. "
)
);
}
2017-08-22 23:46:46 +02:00
$className = $this -> config () -> get ( 'tree_class' );
2017-03-29 06:19:57 +02:00
$id = $request -> requestVar ( 'ID' );
$parentID = $request -> requestVar ( 'ParentID' );
if ( ! is_numeric ( $id ) || ! is_numeric ( $parentID )) {
2022-10-18 07:21:09 +02:00
$this -> httpError ( 400 );
2017-03-29 06:19:57 +02:00
}
// Check record exists in the DB
/** @var SiteTree $node */
$node = DataObject :: get_by_id ( $className , $id );
if ( ! $node ) {
2022-10-18 07:21:09 +02:00
$this -> httpError (
2017-03-29 06:19:57 +02:00
500 ,
_t (
2017-05-08 07:57:24 +02:00
__CLASS__ . '.PLEASESAVE' ,
2017-03-29 06:19:57 +02:00
" Please Save Page: This page could not be updated because it hasn't been saved yet. "
)
);
}
// Check top level permissions
$root = $node -> getParentType ();
if (( $parentID == '0' || $root == 'root' ) && ! SiteConfig :: current_site_config () -> canCreateTopLevel ()) {
2022-10-18 07:21:09 +02:00
$this -> httpError (
2017-03-29 06:19:57 +02:00
403 ,
_t (
2017-05-08 07:57:24 +02:00
__CLASS__ . '.CANT_REORGANISE' ,
2017-03-29 06:19:57 +02:00
" You do not have permission to alter Top level pages. Your change was not saved. "
)
);
}
$siblingIDs = $request -> requestVar ( 'SiblingIDs' );
2020-04-19 06:18:01 +02:00
$statusUpdates = [ 'modified' => []];
2017-03-29 06:19:57 +02:00
if ( ! $node -> canEdit ()) {
return Security :: permissionFailure ( $this );
}
// Update hierarchy (only if ParentID changed)
if ( $node -> ParentID != $parentID ) {
$node -> ParentID = ( int ) $parentID ;
$node -> write ();
2020-04-19 06:18:01 +02:00
$statusUpdates [ 'modified' ][ $node -> ID ] = [
2017-03-29 06:19:57 +02:00
'TreeTitle' => $node -> TreeTitle
2020-04-19 06:18:01 +02:00
];
2017-03-29 06:19:57 +02:00
// Update all dependent pages
$virtualPages = VirtualPage :: get () -> filter ( " CopyContentFromID " , $node -> ID );
foreach ( $virtualPages as $virtualPage ) {
2020-04-19 06:18:01 +02:00
$statusUpdates [ 'modified' ][ $virtualPage -> ID ] = [
2020-06-24 18:23:10 +02:00
'TreeTitle' => $virtualPage -> TreeTitle
2020-04-19 06:18:01 +02:00
];
2017-03-29 06:19:57 +02:00
}
$this -> getResponse () -> addHeader (
'X-Status' ,
2022-04-13 07:07:59 +02:00
rawurlencode ( _t ( __CLASS__ . '.REORGANISATIONSUCCESSFUL' , 'Reorganised the site tree successfully.' ) ? ? '' )
2017-03-29 06:19:57 +02:00
);
}
// Update sorting
if ( is_array ( $siblingIDs )) {
$counter = 0 ;
foreach ( $siblingIDs as $id ) {
if ( $id == $node -> ID ) {
$node -> Sort = ++ $counter ;
$node -> write ();
2020-04-19 06:18:01 +02:00
$statusUpdates [ 'modified' ][ $node -> ID ] = [
2017-03-29 06:19:57 +02:00
'TreeTitle' => $node -> TreeTitle
2020-04-19 06:18:01 +02:00
];
2017-03-29 06:19:57 +02:00
} elseif ( is_numeric ( $id )) {
// Nodes that weren't "actually moved" shouldn't be registered as
// having been edited; do a direct SQL update instead
++ $counter ;
$table = DataObject :: getSchema () -> baseDataTable ( $className );
DB :: prepared_query (
" UPDATE \" $table\ " SET \ " Sort \" = ? WHERE \" ID \" = ? " ,
2020-04-19 06:18:01 +02:00
[ $counter , $id ]
2017-03-29 06:19:57 +02:00
);
}
}
$this -> getResponse () -> addHeader (
'X-Status' ,
2022-04-13 07:07:59 +02:00
rawurlencode ( _t ( __CLASS__ . '.REORGANISATIONSUCCESSFUL' , 'Reorganised the site tree successfully.' ) ? ? '' )
2017-03-29 06:19:57 +02:00
);
}
return $this
-> getResponse ()
-> addHeader ( 'Content-Type' , 'application/json' )
2018-10-28 22:21:19 +01:00
-> setBody ( json_encode ( $statusUpdates ));
2017-03-29 06:19:57 +02:00
}
2019-01-11 11:56:40 +01:00
/**
2019-01-11 12:12:35 +01:00
* Whether the current member has the permission to reorganise SiteTree objects .
2019-01-11 11:56:40 +01:00
* @ return bool
*/
2017-03-29 06:19:57 +02:00
public function CanOrganiseSitetree ()
{
2019-01-11 11:56:40 +01:00
return Permission :: check ( 'SITETREE_REORGANISE' );
2017-03-29 06:19:57 +02:00
}
2019-01-11 11:56:40 +01:00
2017-01-25 21:59:25 +01:00
/**
* @ return boolean
*/
public function TreeIsFiltered ()
{
$query = $this -> getRequest () -> getVar ( 'q' );
2017-12-13 06:16:34 +01:00
return ! empty ( $query );
2017-01-25 21:59:25 +01:00
}
public function ExtraTreeTools ()
{
$html = '' ;
$this -> extend ( 'updateExtraTreeTools' , $html );
return $html ;
}
2018-09-04 03:13:33 +02:00
/**
* This provides information required to generate the search form
* and can be modified on extensions through updateSearchContext
*
* @ return \SilverStripe\ORM\Search\SearchContext
*/
public function getSearchContext ()
{
$context = SiteTree :: singleton () -> getDefaultSearchContext ();
$this -> extend ( 'updateSearchContext' , $context );
return $context ;
}
/**
* Returns the search form schema for the current model
*
* @ return string
*/
public function getSearchFieldSchema ()
{
$schemaUrl = $this -> Link ( 'schema/SearchForm' );
$context = $this -> getSearchContext ();
$params = $this -> getRequest () -> requestVar ( 'q' ) ? : [];
$context -> setSearchParams ( $params );
2018-10-01 06:07:51 +02:00
$placeholder = _t ( 'SilverStripe\\CMS\\Search\\SearchForm.FILTERLABELTEXT' , 'Search' ) . ' "' .
SiteTree :: singleton () -> i18n_plural_name () . '"' ;
$searchParams = $context -> getSearchParams ();
2018-10-02 10:55:34 +02:00
$searchParams = array_combine ( array_map ( function ( $key ) {
2018-10-01 06:07:51 +02:00
return 'Search__' . $key ;
2022-04-13 07:07:59 +02:00
}, array_keys ( $searchParams ? ? [])), $searchParams ? ? []);
2018-09-04 03:13:33 +02:00
$schema = [
'formSchemaUrl' => $schemaUrl ,
'name' => 'Term' ,
'placeholder' => $placeholder ,
2018-10-01 06:07:51 +02:00
'filters' => $searchParams ? : new \stdClass // stdClass maps to empty json object '{}'
2018-09-04 03:13:33 +02:00
];
2018-10-28 22:21:19 +01:00
return json_encode ( $schema );
2018-09-04 03:13:33 +02:00
}
2017-01-25 21:59:25 +01:00
/**
* Returns a Form for page searching for use in templates .
*
* Can be modified from a decorator by a 'updateSearchForm' method
*
* @ return Form
*/
2018-09-04 03:13:33 +02:00
public function getSearchForm ()
2017-01-25 21:59:25 +01:00
{
// Create the fields
2018-09-04 03:13:33 +02:00
$dateFrom = DateField :: create (
2018-10-01 06:07:51 +02:00
'Search__LastEditedFrom' ,
2017-04-20 03:45:23 +02:00
_t ( 'SilverStripe\\CMS\\Search\\SearchForm.FILTERDATEFROM' , 'From' )
2018-09-05 23:41:52 +02:00
) -> setLocale ( Security :: getCurrentUser () -> Locale );
2018-09-04 03:13:33 +02:00
$dateTo = DateField :: create (
2018-10-01 06:07:51 +02:00
'Search__LastEditedTo' ,
2017-04-20 03:45:23 +02:00
_t ( 'SilverStripe\\CMS\\Search\\SearchForm.FILTERDATETO' , 'To' )
2018-09-05 23:41:52 +02:00
) -> setLocale ( Security :: getCurrentUser () -> Locale );
2018-09-04 03:13:33 +02:00
$filters = CMSSiteTreeFilter :: get_all_filters ();
// Remove 'All pages' as we set that to empty/default value
unset ( $filters [ CMSSiteTreeFilter_Search :: class ]);
$pageFilter = DropdownField :: create (
2018-10-01 06:07:51 +02:00
'Search__FilterClass' ,
2017-04-20 03:15:29 +02:00
_t ( 'SilverStripe\\CMS\\Controllers\\CMSMain.PAGES' , 'Page status' ),
2018-09-04 03:13:33 +02:00
$filters
2017-01-25 21:59:25 +01:00
);
2018-09-04 03:13:33 +02:00
$pageFilter -> setEmptyString ( _t ( 'SilverStripe\\CMS\\Controllers\\CMSMain.PAGESALLOPT' , 'All pages' ));
$pageClasses = DropdownField :: create (
2018-10-01 06:07:51 +02:00
'Search__ClassName' ,
2017-04-20 03:15:29 +02:00
_t ( 'SilverStripe\\CMS\\Controllers\\CMSMain.PAGETYPEOPT' , 'Page type' , 'Dropdown for limiting search to a page type' ),
2017-01-25 21:59:25 +01:00
$this -> getPageTypes ()
);
2017-04-20 03:15:29 +02:00
$pageClasses -> setEmptyString ( _t ( 'SilverStripe\\CMS\\Controllers\\CMSMain.PAGETYPEANYOPT' , 'Any' ));
2017-01-25 21:59:25 +01:00
// Group the Datefields
2018-09-04 03:13:33 +02:00
$dateGroup = FieldGroup :: create (
_t ( 'SilverStripe\\CMS\\Search\\SearchForm.PAGEFILTERDATEHEADING' , 'Last edited' ),
[ $dateFrom , $dateTo ]
2018-10-01 06:07:51 +02:00
) -> setName ( 'Search__LastEdited' )
2018-09-04 03:13:33 +02:00
-> addExtraClass ( 'fieldgroup--fill-width' );
2017-01-25 21:59:25 +01:00
// Create the Field list
$fields = new FieldList (
$pageFilter ,
$pageClasses ,
2017-12-13 02:35:21 +01:00
$dateGroup
2017-01-25 21:59:25 +01:00
);
// Create the form
/** @skipUpgrade */
2018-09-04 03:13:33 +02:00
$form = Form :: create (
$this ,
'SearchForm' ,
$fields ,
new FieldList ()
);
$form -> addExtraClass ( 'cms-search-form' );
$form -> setFormMethod ( 'GET' );
$form -> setFormAction ( CMSMain :: singleton () -> Link ());
$form -> disableSecurityToken ();
$form -> unsetValidator ();
2017-01-25 21:59:25 +01:00
// Load the form with previously sent search data
$form -> loadDataFrom ( $this -> getRequest () -> getVars ());
// Allow decorators to modify the form
$this -> extend ( 'updateSearchForm' , $form );
return $form ;
}
/**
* Returns a sorted array suitable for a dropdown with pagetypes and their translated name
*
* @ return array
*/
protected function getPageTypes ()
{
2020-04-19 06:18:01 +02:00
$pageTypes = [];
2017-01-25 21:59:25 +01:00
foreach ( SiteTree :: page_type_classes () as $pageTypeClass ) {
$pageTypes [ $pageTypeClass ] = SiteTree :: singleton ( $pageTypeClass ) -> i18n_singular_name ();
}
asort ( $pageTypes );
return $pageTypes ;
}
2022-10-18 07:21:09 +02:00
public function doSearch ( array $data , Form $form ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
return $this -> getsubtree ( $this -> getRequest ());
}
2017-08-24 07:09:34 +02:00
/**
* Get " back " url for breadcrumbs
*
* @ return string
*/
public function getBreadcrumbsBackLink ()
{
$breadcrumbs = $this -> Breadcrumbs ();
if ( $breadcrumbs -> count () < 2 ) {
return $this -> LinkPages ();
}
// Get second from end breadcrumb
return $breadcrumbs
-> offsetGet ( $breadcrumbs -> count () - 2 )
-> Link ;
}
2017-01-25 21:59:25 +01:00
/**
* @ param bool $unlinked
* @ return ArrayList
*/
public function Breadcrumbs ( $unlinked = false )
{
2017-08-24 07:09:34 +02:00
$items = new ArrayList ();
2018-09-04 03:13:33 +02:00
if ( $this -> TreeIsFiltered ()) {
$items -> push ( new ArrayData ([
'Title' => CMSPagesController :: menu_title (),
'Link' => ( $unlinked ) ? false : $this -> LinkPages ()
]));
$items -> push ( new ArrayData ([
'Title' => _t ( 'SilverStripe\\CMS\\Controllers\\CMSMain.SEARCHRESULTS' , 'Search results' ),
'Link' => ( $unlinked ) ? false : $this -> LinkPages ()
]));
$this -> extend ( 'updateBreadcrumbs' , $items );
return $items ;
}
2017-08-24 07:09:34 +02:00
// Check if we are editing a page
/** @var SiteTree $record */
$record = $this -> currentPage ();
if ( ! $record ) {
2020-04-19 06:18:01 +02:00
$items -> push ( new ArrayData ([
2017-08-24 07:09:34 +02:00
'Title' => CMSPagesController :: menu_title (),
'Link' => ( $unlinked ) ? false : $this -> LinkPages ()
2020-04-19 06:18:01 +02:00
]));
2018-02-19 05:01:20 +01:00
$this -> extend ( 'updateBreadcrumbs' , $items );
2017-08-24 07:09:34 +02:00
return $items ;
}
2017-01-25 21:59:25 +01:00
2017-08-24 07:09:34 +02:00
// Add all ancestors
$ancestors = $record -> getAncestors ();
2022-04-13 07:07:59 +02:00
$ancestors = new ArrayList ( array_reverse ( $ancestors -> toArray () ? ? []));
2017-08-24 07:09:34 +02:00
$ancestors -> push ( $record );
/** @var SiteTree $ancestor */
foreach ( $ancestors as $ancestor ) {
2020-04-19 06:18:01 +02:00
$items -> push ( new ArrayData ([
2017-08-24 07:09:34 +02:00
'Title' => $ancestor -> getMenuTitle (),
'Link' => ( $unlinked )
? false
: $ancestor -> CMSEditLink ()
2020-04-19 06:18:01 +02:00
]));
2017-01-25 21:59:25 +01:00
}
2018-02-19 05:01:20 +01:00
$this -> extend ( 'updateBreadcrumbs' , $items );
2017-01-25 21:59:25 +01:00
return $items ;
}
/**
* Create serialized JSON string with site tree hints data to be injected into
* 'data-hints' attribute of root node of jsTree .
*
* @ return string Serialized JSON
*/
public function SiteTreeHints ()
{
$classes = SiteTree :: page_type_classes ();
2017-11-30 03:56:16 +01:00
$memberID = Security :: getCurrentUser () ? Security :: getCurrentUser () -> ID : 0 ;
$cache = $this -> getHintsCache ();
$cacheKey = $this -> generateHintsCacheKey ( $memberID );
$json = $cache -> get ( $cacheKey );
2017-01-25 21:59:25 +01:00
2017-12-13 03:36:35 +01:00
if ( $json ) {
return $json ;
}
2017-01-25 21:59:25 +01:00
2017-11-30 03:56:16 +01:00
$canCreate = [];
foreach ( $classes as $class ) {
$canCreate [ $class ] = singleton ( $class ) -> canCreate ();
2017-01-25 21:59:25 +01:00
}
2017-11-30 03:56:16 +01:00
$def [ 'Root' ] = [];
$def [ 'Root' ][ 'disallowedChildren' ] = [];
2017-01-25 21:59:25 +01:00
2017-11-30 03:56:16 +01:00
// Contains all possible classes to support UI controls listing them all,
// such as the "add page here" context menu.
$def [ 'All' ] = [];
2017-01-25 21:59:25 +01:00
2017-11-30 03:56:16 +01:00
// Identify disallows and set globals
foreach ( $classes as $class ) {
$obj = singleton ( $class );
if ( $obj instanceof HiddenClass ) {
continue ;
}
2017-01-25 21:59:25 +01:00
2017-11-30 03:56:16 +01:00
// Name item
$def [ 'All' ][ $class ] = [
'title' => $obj -> i18n_singular_name ()
];
2017-01-25 21:59:25 +01:00
2017-11-30 03:56:16 +01:00
// Check if can be created at the root
$needsPerm = $obj -> config () -> get ( 'need_permission' );
if ( ! $obj -> config () -> get ( 'can_be_root' )
2022-04-13 07:07:59 +02:00
|| ( ! array_key_exists ( $class , $canCreate ? ? []) || ! $canCreate [ $class ])
2017-11-30 03:56:16 +01:00
|| ( $needsPerm && ! $this -> can ( $needsPerm ))
) {
$def [ 'Root' ][ 'disallowedChildren' ][] = $class ;
2017-01-25 21:59:25 +01:00
}
2017-11-30 03:56:16 +01:00
// Hint data specific to the class
$def [ $class ] = [];
$defaultChild = $obj -> defaultChild ();
if ( $defaultChild !== 'Page' && $defaultChild !== null ) {
$def [ $class ][ 'defaultChild' ] = $defaultChild ;
}
2017-01-25 21:59:25 +01:00
2017-11-30 03:56:16 +01:00
$defaultParent = $obj -> defaultParent ();
if ( $defaultParent !== 1 && $defaultParent !== null ) {
$def [ $class ][ 'defaultParent' ] = $defaultParent ;
}
2017-01-25 21:59:25 +01:00
}
2017-11-30 03:56:16 +01:00
$this -> extend ( 'updateSiteTreeHints' , $def );
2018-10-28 22:21:19 +01:00
$json = json_encode ( $def );
2017-11-30 03:56:16 +01:00
$cache -> set ( $cacheKey , $json );
2017-01-25 21:59:25 +01:00
return $json ;
}
/**
* Populates an array of classes in the CMS
* which allows the user to change the page type .
*
* @ return SS_List
*/
public function PageTypes ()
{
$classes = SiteTree :: page_type_classes ();
$result = new ArrayList ();
foreach ( $classes as $class ) {
2017-01-23 03:11:49 +01:00
$instance = SiteTree :: singleton ( $class );
2017-01-25 21:59:25 +01:00
if ( $instance instanceof HiddenClass ) {
continue ;
}
// skip this type if it is restricted
2017-10-18 01:32:08 +02:00
$needPermissions = $instance -> config () -> get ( 'need_permission' );
if ( $needPermissions && ! $this -> can ( $needPermissions )) {
2017-01-25 21:59:25 +01:00
continue ;
}
2020-04-19 06:18:01 +02:00
$result -> push ( new ArrayData ([
2017-01-25 21:59:25 +01:00
'ClassName' => $class ,
2017-10-18 01:32:08 +02:00
'AddAction' => $instance -> i18n_singular_name (),
'Description' => $instance -> i18n_classDescription (),
'IconURL' => $instance -> getPageIconURL (),
'Title' => $instance -> i18n_singular_name (),
2020-04-19 06:18:01 +02:00
]));
2017-01-25 21:59:25 +01:00
}
$result = $result -> sort ( 'AddAction' );
return $result ;
}
/**
* Get a database record to be managed by the CMS .
*
* @ param int $id Record ID
* @ param int $versionID optional Version id of the given record
* @ return SiteTree
*/
public function getRecord ( $id , $versionID = null )
{
2017-03-29 06:19:57 +02:00
if ( ! $id ) {
return null ;
}
2017-08-22 23:46:46 +02:00
$treeClass = $this -> config () -> get ( 'tree_class' );
2017-01-25 21:59:25 +01:00
if ( $id instanceof $treeClass ) {
return $id ;
2017-03-29 06:19:57 +02:00
}
2022-04-13 07:07:59 +02:00
if ( substr ( $id ? ? '' , 0 , 3 ) == 'new' ) {
2017-03-29 06:19:57 +02:00
return $this -> getNewItem ( $id );
}
if ( ! is_numeric ( $id )) {
return null ;
}
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
$currentStage = Versioned :: get_reading_mode ();
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
if ( $this -> getRequest () -> getVar ( 'Version' )) {
$versionID = ( int ) $this -> getRequest () -> getVar ( 'Version' );
}
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
/** @var SiteTree $record */
if ( $versionID ) {
$record = Versioned :: get_version ( $treeClass , $id , $versionID );
} else {
$record = DataObject :: get_by_id ( $treeClass , $id );
}
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
// Then, try getting a record from the live site
if ( ! $record ) {
// $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id");
Versioned :: set_stage ( Versioned :: LIVE );
singleton ( $treeClass ) -> flushCache ();
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
$record = DataObject :: get_by_id ( $treeClass , $id );
}
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
// Then, try getting a deleted record
if ( ! $record ) {
$record = Versioned :: get_latest_version ( $treeClass , $id );
}
// Set the reading mode back to what it was.
Versioned :: set_reading_mode ( $currentStage );
return $record ;
}
/**
* { @ inheritdoc }
*
* @ param HTTPRequest $request
* @ return Form
*/
public function EditForm ( $request = null )
{
// set page ID from request
if ( $request ) {
// Validate id is present
$id = $request -> param ( 'ID' );
if ( ! isset ( $id )) {
$this -> httpError ( 400 );
return null ;
}
$this -> setCurrentPageID ( $id );
2017-01-25 21:59:25 +01:00
}
2017-03-29 06:19:57 +02:00
return $this -> getEditForm ();
2017-01-25 21:59:25 +01:00
}
/**
* @ param int $id
* @ param FieldList $fields
* @ return Form
*/
public function getEditForm ( $id = null , $fields = null )
{
2017-03-29 06:19:57 +02:00
// Get record
2017-01-25 21:59:25 +01:00
if ( ! $id ) {
$id = $this -> currentPageID ();
}
2017-03-29 06:19:57 +02:00
/** @var SiteTree $record */
2017-01-25 21:59:25 +01:00
$record = $this -> getRecord ( $id );
2017-03-29 06:19:57 +02:00
// Check parent form can be generated
$form = parent :: getEditForm ( $record , $fields );
if ( ! $form || ! $record ) {
return $form ;
2017-01-25 21:59:25 +01:00
}
if ( ! $fields ) {
$fields = $form -> Fields ();
}
2017-03-31 05:34:42 +02:00
2017-03-29 06:19:57 +02:00
// Add extra fields
$deletedFromStage = ! $record -> isOnDraft ();
$fields -> push ( $idField = new HiddenField ( " ID " , false , $id ));
// Necessary for different subsites
$fields -> push ( $liveLinkField = new HiddenField ( " AbsoluteLink " , false , $record -> AbsoluteLink ()));
$fields -> push ( $liveLinkField = new HiddenField ( " LiveLink " ));
$fields -> push ( $stageLinkField = new HiddenField ( " StageLink " ));
$fields -> push ( $archiveWarningMsgField = new HiddenField ( " ArchiveWarningMessage " ));
$fields -> push ( new HiddenField ( " TreeTitle " , false , $record -> getTreeTitle ()));
$archiveWarningMsgField -> setValue ( $this -> getArchiveWarningMessage ( $record ));
// Build preview / live links
$liveLink = $record -> getAbsoluteLiveLink ();
if ( $liveLink ) {
$liveLinkField -> setValue ( $liveLink );
}
if ( ! $deletedFromStage ) {
$stageLink = Controller :: join_links ( $record -> AbsoluteLink (), '?stage=Stage' );
if ( $stageLink ) {
$stageLinkField -> setValue ( $stageLink );
2017-01-25 21:59:25 +01:00
}
2017-03-29 06:19:57 +02:00
}
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
/** @skipUpgrade */
2022-09-14 03:22:50 +02:00
if (( $record instanceof CMSPreviewable || $record -> has_extension ( CMSPreviewable :: class ))
&& ! $fields -> fieldByName ( 'SilverStripeNavigator' )
) {
2017-03-29 06:19:57 +02:00
$navField = new LiteralField ( 'SilverStripeNavigator' , $this -> getSilverStripeNavigator ());
$navField -> setAllowHTML ( true );
$fields -> push ( $navField );
}
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
// getAllCMSActions can be used to completely redefine the action list
if ( $record -> hasMethod ( 'getAllCMSActions' )) {
$actions = $record -> getAllCMSActions ();
} else {
$actions = $record -> getCMSActions ();
// Find and remove action menus that have no actions.
if ( $actions && $actions -> count ()) {
/** @var TabSet $tabset */
$tabset = $actions -> fieldByName ( 'ActionMenus' );
if ( $tabset ) {
/** @var Tab $tab */
foreach ( $tabset -> getChildren () as $tab ) {
if ( ! $tab -> getChildren () -> count ()) {
$tabset -> removeByName ( $tab -> getName ());
2017-01-25 21:59:25 +01:00
}
}
}
}
2017-03-29 06:19:57 +02:00
}
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
// Use <button> to allow full jQuery UI styling
$actionsFlattened = $actions -> dataFields ();
if ( $actionsFlattened ) {
/** @var FormAction $action */
foreach ( $actionsFlattened as $action ) {
$action -> setUseButtonTag ( true );
2017-01-25 21:59:25 +01:00
}
2017-03-29 06:19:57 +02:00
}
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
// TODO Can't merge $FormAttributes in template at the moment
$form -> addExtraClass ( 'center ' . $this -> BaseCSSClasses ());
// Set validation exemptions for specific actions
2020-04-19 06:18:01 +02:00
$form -> setValidationExemptActions ([
2017-07-15 17:21:51 +02:00
'restore' ,
'revert' ,
'deletefromlive' ,
'delete' ,
'unpublish' ,
'rollback' ,
'doRollback' ,
'archive' ,
2020-04-19 06:18:01 +02:00
]);
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
// Announce the capability so the frontend can decide whether to allow preview or not.
2022-09-14 03:22:50 +02:00
if ( $record instanceof CMSPreviewable || $record -> has_extension ( CMSPreviewable :: class )) {
2017-03-29 06:19:57 +02:00
$form -> addExtraClass ( 'cms-previewable' );
}
$form -> addExtraClass ( 'fill-height flexbox-area-grow' );
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
if ( ! $record -> canEdit () || $deletedFromStage ) {
$readonlyFields = $form -> Fields () -> makeReadonly ();
$form -> setFields ( $readonlyFields );
}
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
$form -> Fields () -> setForm ( $form );
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
$this -> extend ( 'updateEditForm' , $form );
2017-01-25 21:59:25 +01:00
2017-03-29 06:19:57 +02:00
// Use custom reqest handler for LeftAndMain requests;
// CMS Forms cannot be identified solely by name, but also need ID (and sometimes OtherID)
$form -> setRequestHandler (
LeftAndMainFormRequestHandler :: create ( $form , [ $id ])
);
return $form ;
2017-01-25 21:59:25 +01:00
}
2017-03-29 06:19:57 +02:00
public function EmptyForm ()
{
$fields = new FieldList (
2017-04-20 03:15:29 +02:00
new LabelField ( 'PageDoesntExistLabel' , _t ( 'SilverStripe\\CMS\\Controllers\\CMSMain.PAGENOTEXISTS' , " This page doesn't exist " ))
2017-03-29 06:19:57 +02:00
);
$form = parent :: EmptyForm ();
$form -> setFields ( $fields );
$fields -> setForm ( $form );
return $form ;
}
2018-09-13 05:52:54 +02:00
/**
* Build an archive warning message based on the page ' s children
*
* @ param SiteTree $record
* @ return string
*/
2017-03-29 06:19:57 +02:00
/**
* Build an archive warning message based on the page ' s children
*
* @ param SiteTree $record
* @ return string
*/
2017-04-04 01:44:41 +02:00
protected function getArchiveWarningMessage ( $record )
{
2018-09-28 03:43:46 +02:00
$defaultMessage = _t ( 'SilverStripe\\CMS\\Controllers\\CMSMain.ArchiveWarningWithChildren' , 'Warning: This page and all of its child pages will be unpublished before being sent to the archive.\n\nAre you sure you want to proceed?' );
// Option to disable this feature as it is slow on large sites
if ( ! $this -> config () -> enable_dynamic_archive_warning_message ) {
return $defaultMessage ;
}
2017-04-04 01:44:41 +02:00
// Get all page's descendants
2018-09-13 05:52:54 +02:00
$descendants = [];
$this -> collateDescendants ([ $record -> ID ], $descendants );
2017-04-04 01:44:41 +02:00
if ( ! $descendants ) {
$descendants = [];
}
2018-09-13 05:52:54 +02:00
// Get the IDs of all changeset including at least one of the pages.
$descendants [] = $record -> ID ;
$inChangeSetIDs = ChangeSetItem :: get () -> filter ([
'ObjectID' => $descendants ,
'ObjectClass' => SiteTree :: class
]) -> column ( 'ChangeSetID' );
2017-04-04 01:44:41 +02:00
2018-09-13 05:52:54 +02:00
// Count number of affected change set
$affectedChangeSetCount = 0 ;
2022-04-13 07:07:59 +02:00
if ( count ( $inChangeSetIDs ? ? []) > 0 ) {
2018-09-13 05:52:54 +02:00
$affectedChangeSetCount = ChangeSet :: get ()
-> filter ([ 'ID' => $inChangeSetIDs , 'State' => ChangeSet :: STATE_OPEN ])
-> count ();
2017-04-04 01:44:41 +02:00
}
2018-09-13 05:52:54 +02:00
$numCampaigns = ChangeSet :: singleton () -> i18n_pluralise ( $affectedChangeSetCount );
2022-04-13 07:07:59 +02:00
$numCampaigns = mb_strtolower ( $numCampaigns ? ? '' );
2017-04-04 01:44:41 +02:00
2022-04-13 07:07:59 +02:00
if ( count ( $descendants ? ? []) > 0 && $affectedChangeSetCount > 0 ) {
2017-04-20 03:15:29 +02:00
$archiveWarningMsg = _t ( 'SilverStripe\\CMS\\Controllers\\CMSMain.ArchiveWarningWithChildrenAndCampaigns' , 'Warning: This page and all of its child pages will be unpublished and automatically removed from their associated {NumCampaigns} before being sent to the archive.\n\nAre you sure you want to proceed?' , [ 'NumCampaigns' => $numCampaigns ]);
2022-04-13 07:07:59 +02:00
} elseif ( count ( $descendants ? ? []) > 0 ) {
2018-09-28 03:43:46 +02:00
$archiveWarningMsg = $defaultMessage ;
2018-09-13 05:52:54 +02:00
} elseif ( $affectedChangeSetCount > 0 ) {
2017-04-20 03:15:29 +02:00
$archiveWarningMsg = _t ( 'SilverStripe\\CMS\\Controllers\\CMSMain.ArchiveWarningWithCampaigns' , 'Warning: This page will be unpublished and automatically removed from their associated {NumCampaigns} before being sent to the archive.\n\nAre you sure you want to proceed?' , [ 'NumCampaigns' => $numCampaigns ]);
2017-04-04 01:44:41 +02:00
} else {
2017-04-20 03:15:29 +02:00
$archiveWarningMsg = _t ( 'SilverStripe\\CMS\\Controllers\\CMSMain.ArchiveWarning' , 'Warning: This page will be unpublished before being sent to the archive.\n\nAre you sure you want to proceed?' );
2017-04-04 01:44:41 +02:00
}
return $archiveWarningMsg ;
}
2018-09-13 05:52:54 +02:00
/**
* Find IDs of all descendant pages for the provided ID lists .
* @ param int [] $recordIDs
* @ param array $collator
* @ return bool
*/
protected function collateDescendants ( $recordIDs , & $collator )
{
$children = SiteTree :: get () -> filter ([ 'ParentID' => $recordIDs ]) -> column ();
if ( $children ) {
foreach ( $children as $item ) {
$collator [] = $item ;
}
$this -> collateDescendants ( $children , $collator );
return true ;
}
return false ;
}
2017-01-25 21:59:25 +01:00
/**
2017-08-21 02:45:56 +02:00
* This method exclusively handles deferred ajax requests to render the
* pages tree deferred handler ( no pjax - fragment )
*
2017-12-13 02:35:21 +01:00
* @ return DBHTMLText HTML response with the rendered treeview
2017-01-25 21:59:25 +01:00
*/
2017-09-04 05:37:47 +02:00
public function treeview ()
2017-01-25 21:59:25 +01:00
{
2017-08-21 02:45:56 +02:00
return $this -> renderWith ( $this -> getTemplatesWithSuffix ( '_TreeView' ));
2017-01-25 21:59:25 +01:00
}
2017-12-13 02:35:21 +01:00
/**
* Returns deferred listview for the current level
*
* @ return DBHTMLText HTML response with the rendered listview
*/
public function listview ()
2017-12-06 05:06:15 +01:00
{
return $this -> renderWith ( $this -> getTemplatesWithSuffix ( '_ListView' ));
}
2017-01-25 21:59:25 +01:00
/**
2017-12-13 02:35:21 +01:00
* Get view state based on the current action
*
2017-07-25 00:40:58 +02:00
* @ param string $default
2017-01-25 21:59:25 +01:00
* @ return string
*/
2017-07-25 00:40:58 +02:00
public function ViewState ( $default = 'treeview' )
2017-01-25 21:59:25 +01:00
{
2017-12-13 02:35:21 +01:00
$mode = $this -> getRequest () -> param ( 'Action' );
2017-01-25 21:59:25 +01:00
switch ( $mode ) {
case 'listview' :
case 'treeview' :
return $mode ;
default :
2017-07-25 00:40:58 +02:00
return $default ;
2017-01-25 21:59:25 +01:00
}
}
/**
* Callback to request the list of page types allowed under a given page instance .
* Provides a slower but more precise response over SiteTreeHints
*/
2022-10-18 07:21:09 +02:00
public function childfilter ( HTTPRequest $request ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
// Check valid parent specified
$parentID = $request -> requestVar ( 'ParentID' );
$parent = SiteTree :: get () -> byID ( $parentID );
if ( ! $parent || ! $parent -> exists ()) {
2022-10-18 07:21:09 +02:00
$this -> httpError ( 404 );
2017-01-25 21:59:25 +01:00
}
// Build hints specific to this class
// Identify disallows and set globals
$classes = SiteTree :: page_type_classes ();
2020-04-19 06:18:01 +02:00
$disallowedChildren = [];
2017-01-25 21:59:25 +01:00
foreach ( $classes as $class ) {
$obj = singleton ( $class );
if ( $obj instanceof HiddenClass ) {
continue ;
}
2020-04-19 06:18:01 +02:00
if ( ! $obj -> canCreate ( null , [ 'Parent' => $parent ])) {
2017-01-25 21:59:25 +01:00
$disallowedChildren [] = $class ;
}
}
$this -> extend ( 'updateChildFilter' , $disallowedChildren , $parentID );
return $this
-> getResponse ()
-> addHeader ( 'Content-Type' , 'application/json; charset=utf-8' )
2018-10-28 22:21:19 +01:00
-> setBody ( json_encode ( $disallowedChildren ));
2017-01-25 21:59:25 +01:00
}
/**
* Safely reconstruct a selected filter from a given set of query parameters
*
2018-09-04 03:13:33 +02:00
* @ param array $params Query parameters to use , or null if none present
* @ return CMSSiteTreeFilter The filter class
2017-01-25 21:59:25 +01:00
* @ throws InvalidArgumentException if invalid filter class is passed .
*/
protected function getQueryFilter ( $params )
{
if ( empty ( $params [ 'FilterClass' ])) {
return null ;
}
$filterClass = $params [ 'FilterClass' ];
2017-03-29 06:19:57 +02:00
if ( ! is_subclass_of ( $filterClass , CMSSiteTreeFilter :: class )) {
2017-01-25 21:59:25 +01:00
throw new InvalidArgumentException ( " Invalid filter class passed: { $filterClass } " );
}
return $filterClass :: create ( $params );
}
/**
* Returns the pages meet a certain criteria as { @ see CMSSiteTreeFilter } or the subpages of a parent page
* defaulting to no filter and show all pages in first level .
* Doubles as search results , if any search parameters are set through { @ link SearchForm ()} .
*
* @ param array $params Search filter criteria
* @ param int $parentID Optional parent node to filter on ( can ' t be combined with other search criteria )
* @ return SS_List
* @ throws InvalidArgumentException if invalid filter class is passed .
*/
2020-04-19 06:18:01 +02:00
public function getList ( $params = [], $parentID = 0 )
2017-01-25 21:59:25 +01:00
{
if ( $filter = $this -> getQueryFilter ( $params )) {
return $filter -> getFilteredPages ();
} else {
2017-08-22 23:46:46 +02:00
$list = DataList :: create ( $this -> config () -> get ( 'tree_class' ));
2017-01-25 21:59:25 +01:00
$parentID = is_numeric ( $parentID ) ? $parentID : 0 ;
return $list -> filter ( " ParentID " , $parentID );
}
}
/**
* @ return Form
*/
public function ListViewForm ()
{
$params = $this -> getRequest () -> requestVar ( 'q' );
2017-12-12 03:04:21 +01:00
$parentID = $this -> getRequest () -> requestVar ( 'ParentID' );
2018-09-04 03:13:33 +02:00
// Set default filter if other params are set
if ( $params && empty ( $params [ 'FilterClass' ])) {
$params [ 'FilterClass' ] = CMSSiteTreeFilter_Search :: class ;
}
2017-08-21 02:45:56 +02:00
$list = $this -> getList ( $params , $parentID );
2017-01-25 21:59:25 +01:00
$gridFieldConfig = GridFieldConfig :: create () -> addComponents (
2022-02-12 06:48:17 +01:00
Injector :: inst () -> create ( GridFieldSortableHeader :: class ),
Injector :: inst () -> create ( GridFieldDataColumns :: class ),
Injector :: inst () -> createWithArgs ( GridFieldPaginator :: class , [ $this -> config () -> get ( 'page_length' )])
2017-01-25 21:59:25 +01:00
);
if ( $parentID ) {
2017-08-21 02:45:56 +02:00
$linkSpec = $this -> LinkListViewChildren ( '%d' );
2017-01-25 21:59:25 +01:00
$gridFieldConfig -> addComponent (
GridFieldLevelup :: create ( $parentID )
-> setLinkSpec ( $linkSpec )
2020-04-19 06:18:01 +02:00
-> setAttributes ([ 'data-pjax-target' => 'ListViewForm,Breadcrumbs' ])
2017-01-25 21:59:25 +01:00
);
2017-12-12 03:04:21 +01:00
$this -> setCurrentPageID ( $parentID );
2017-01-25 21:59:25 +01:00
}
2022-02-12 06:48:17 +01:00
$gridField = GridField :: create ( 'Page' , 'Pages' , $list , $gridFieldConfig );
2017-08-24 06:17:10 +02:00
$gridField -> setAttribute ( 'cms-loading-ignore-url-params' , true );
2017-01-25 21:59:25 +01:00
/** @var GridFieldDataColumns $columns */
2017-12-13 03:36:35 +01:00
$columns = $gridField -> getConfig () -> getComponentByType ( GridFieldDataColumns :: class );
2017-01-25 21:59:25 +01:00
// Don't allow navigating into children nodes on filtered lists
2020-04-19 06:18:01 +02:00
$fields = [
2017-04-20 03:15:29 +02:00
'getTreeTitle' => _t ( 'SilverStripe\\CMS\\Model\\SiteTree.PAGETITLE' , 'Page Title' ),
2022-04-19 06:04:20 +02:00
'i18n_singular_name' => _t ( 'SilverStripe\\CMS\\Model\\SiteTree.PAGETYPE' , 'Page Type' ),
2017-04-20 03:15:29 +02:00
'LastEdited' => _t ( 'SilverStripe\\CMS\\Model\\SiteTree.LASTUPDATED' , 'Last Updated' ),
2020-04-19 06:18:01 +02:00
];
2017-01-25 21:59:25 +01:00
/** @var GridFieldSortableHeader $sortableHeader */
2017-12-13 03:36:35 +01:00
$sortableHeader = $gridField -> getConfig () -> getComponentByType ( GridFieldSortableHeader :: class );
2020-04-19 06:18:01 +02:00
$sortableHeader -> setFieldSorting ([ 'getTreeTitle' => 'Title' ]);
2017-01-25 21:59:25 +01:00
$gridField -> getState () -> ParentID = $parentID ;
if ( ! $params ) {
2020-04-19 06:18:01 +02:00
$fields = array_merge ([ 'listChildrenLink' => '' ], $fields );
2017-01-25 21:59:25 +01:00
}
$columns -> setDisplayFields ( $fields );
2020-04-19 06:18:01 +02:00
$columns -> setFieldCasting ([
2017-01-25 21:59:25 +01:00
'Created' => 'DBDatetime->Ago' ,
'LastEdited' => 'DBDatetime->FormatFromSettings' ,
'getTreeTitle' => 'HTMLFragment'
2020-04-19 06:18:01 +02:00
]);
2017-01-25 21:59:25 +01:00
2020-04-19 06:18:01 +02:00
$columns -> setFieldFormatting ([
2020-04-04 10:44:00 +02:00
'listChildrenLink' => function ( $value , & $item ) {
2017-01-25 21:59:25 +01:00
/** @var SiteTree $item */
$num = $item ? $item -> numChildren () : null ;
if ( $num ) {
return sprintf (
'<a class="btn btn-secondary btn--no-text btn--icon-large font-icon-right-dir cms-panel-link list-children-link" data-pjax-target="ListViewForm,Breadcrumbs" href="%s"><span class="sr-only">%s child pages</span></a>' ,
2017-08-21 02:45:56 +02:00
$this -> LinkListViewChildren (( int ) $item -> ID ),
2017-01-25 21:59:25 +01:00
$num
);
}
},
2020-04-04 10:44:00 +02:00
'getTreeTitle' => function ( $value , & $item ) {
2020-04-10 05:26:00 +02:00
/** @var SiteTree $item */
2017-07-19 01:52:19 +02:00
$title = sprintf (
2017-01-25 21:59:25 +01:00
'<a class="action-detail" href="%s">%s</a>' ,
2020-04-10 05:27:39 +02:00
$item -> CMSEditLink (),
2017-01-25 21:59:25 +01:00
$item -> TreeTitle // returns HTML, does its own escaping
);
2017-07-19 01:52:19 +02:00
$breadcrumbs = $item -> Breadcrumbs ( 20 , true , false , true , '/' );
// Remove item's tile
2022-04-13 07:07:59 +02:00
$breadcrumbs = preg_replace ( '/[^\/]+$/' , '' , trim ( $breadcrumbs ? ? '' ));
2017-07-19 01:52:19 +02:00
// Trim spaces around delimiters
2022-04-13 07:07:59 +02:00
$breadcrumbs = preg_replace ( '/\s?\/\s?/' , '/' , trim ( $breadcrumbs ? ? '' ));
2017-07-19 01:52:19 +02:00
return $title . sprintf ( '<p class="small cms-list__item-breadcrumbs">%s</p>' , $breadcrumbs );
2017-01-25 21:59:25 +01:00
}
2020-04-19 06:18:01 +02:00
]);
2017-01-25 21:59:25 +01:00
$negotiator = $this -> getResponseNegotiator ();
$listview = Form :: create (
$this ,
'ListViewForm' ,
new FieldList ( $gridField ),
new FieldList ()
) -> setHTMLID ( 'Form_ListViewForm' );
$listview -> setAttribute ( 'data-pjax-fragment' , 'ListViewForm' );
$listview -> setValidationResponseCallback ( function ( ValidationResult $errors ) use ( $negotiator , $listview ) {
$request = $this -> getRequest ();
if ( $request -> isAjax () && $negotiator ) {
$result = $listview -> forTemplate ();
2020-04-19 06:18:01 +02:00
return $negotiator -> respond ( $request , [
2017-01-25 21:59:25 +01:00
'CurrentForm' => function () use ( $result ) {
return $result ;
}
2020-04-19 06:18:01 +02:00
]);
2017-01-25 21:59:25 +01:00
}
});
$this -> extend ( 'updateListView' , $listview );
$listview -> disableSecurityToken ();
return $listview ;
}
public function currentPageID ()
{
$id = parent :: currentPageID ();
$this -> extend ( 'updateCurrentPageID' , $id );
return $id ;
}
//------------------------------------------------------------------------------------------//
// Data saving handlers
/**
* Save and Publish page handler
*
* @ throws HTTPResponse_Exception
*/
2022-10-18 07:21:09 +02:00
public function save ( array $data , Form $form ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
2017-08-22 23:46:46 +02:00
$className = $this -> config () -> get ( 'tree_class' );
2017-01-25 21:59:25 +01:00
// Existing or new record?
$id = $data [ 'ID' ];
2022-04-13 07:07:59 +02:00
if ( substr ( $id ? ? '' , 0 , 3 ) != 'new' ) {
2017-01-25 21:59:25 +01:00
/** @var SiteTree $record */
$record = DataObject :: get_by_id ( $className , $id );
// Check edit permissions
if ( $record && ! $record -> canEdit ()) {
return Security :: permissionFailure ( $this );
}
if ( ! $record || ! $record -> ID ) {
throw new HTTPResponse_Exception ( " Bad record ID # $id " , 404 );
}
} else {
if ( ! $className :: singleton () -> canCreate ()) {
return Security :: permissionFailure ( $this );
}
$record = $this -> getNewItem ( $id , false );
}
// Check publishing permissions
$doPublish = ! empty ( $data [ 'publish' ]);
if ( $record && $doPublish && ! $record -> canPublish ()) {
return Security :: permissionFailure ( $this );
}
// TODO Coupling to SiteTree
$record -> HasBrokenLink = 0 ;
$record -> HasBrokenFile = 0 ;
if ( ! $record -> ObsoleteClassName ) {
$record -> writeWithoutVersion ();
}
// Update the class instance if necessary
if ( isset ( $data [ 'ClassName' ]) && $data [ 'ClassName' ] != $record -> ClassName ) {
// Replace $record with a new instance of the new class
$newClassName = $data [ 'ClassName' ];
$record = $record -> newClassInstance ( $newClassName );
}
// save form data into record
$form -> saveInto ( $record );
$record -> write ();
2018-02-09 00:18:57 +01:00
// If the 'Publish' button was clicked, also publish the page
2017-01-25 21:59:25 +01:00
if ( $doPublish ) {
$record -> publishRecursive ();
$message = _t (
2017-12-13 03:36:35 +01:00
__CLASS__ . '.PUBLISHED' ,
2017-01-25 21:59:25 +01:00
" Published ' { title}' successfully. " ,
[ 'title' => $record -> Title ]
);
} else {
$message = _t (
2017-12-13 03:36:35 +01:00
__CLASS__ . '.SAVED' ,
2017-01-25 21:59:25 +01:00
" Saved ' { title}' successfully. " ,
[ 'title' => $record -> Title ]
);
}
2022-04-13 07:07:59 +02:00
$this -> getResponse () -> addHeader ( 'X-Status' , rawurlencode ( $message ? ? '' ));
2017-01-25 21:59:25 +01:00
return $this -> getResponseNegotiator () -> respond ( $this -> getRequest ());
}
/**
* @ uses LeftAndMainExtension -> augmentNewSiteTreeItem ()
*
* @ param int | string $id
* @ param bool $setID
* @ return mixed | DataObject
* @ throws HTTPResponse_Exception
*/
public function getNewItem ( $id , $setID = true )
{
2017-08-22 23:46:46 +02:00
$parentClass = $this -> config () -> get ( 'tree_class' );
2022-04-13 07:07:59 +02:00
list (, $className , $parentID ) = array_pad ( explode ( '-' , $id ? ? '' ), 3 , null );
2017-01-25 21:59:25 +01:00
2022-04-13 07:07:59 +02:00
if ( ! is_a ( $className , $parentClass ? ? '' , true )) {
2017-01-25 21:59:25 +01:00
$response = Security :: permissionFailure ( $this );
if ( ! $response ) {
$response = $this -> getResponse ();
}
throw new HTTPResponse_Exception ( $response );
}
/** @var SiteTree $newItem */
$newItem = Injector :: inst () -> create ( $className );
$newItem -> Title = _t (
2017-12-13 03:36:35 +01:00
__CLASS__ . '.NEWPAGE' ,
2017-01-25 21:59:25 +01:00
" New { pagetype} " ,
'followed by a page type title' ,
2020-04-19 06:18:01 +02:00
[ 'pagetype' => singleton ( $className ) -> i18n_singular_name ()]
2017-01-25 21:59:25 +01:00
);
$newItem -> ClassName = $className ;
$newItem -> ParentID = $parentID ;
// DataObject::fieldExists only checks the current class, not the hierarchy
// This allows the CMS to set the correct sort value
if ( $newItem -> castingHelper ( 'Sort' )) {
2019-05-17 03:40:15 +02:00
$table = DataObject :: singleton ( SiteTree :: class ) -> baseTable ();
2017-06-08 08:02:18 +02:00
$maxSort = DB :: prepared_query (
2019-05-17 03:40:15 +02:00
" SELECT MAX( \" Sort \" ) FROM \" $table\ " WHERE \ " ParentID \" = ? " ,
2020-04-19 06:18:01 +02:00
[ $parentID ]
2017-06-08 08:02:18 +02:00
) -> value ();
$newItem -> Sort = ( int ) $maxSort + 1 ;
2017-01-25 21:59:25 +01:00
}
2017-06-08 08:02:18 +02:00
if ( $setID && $id ) {
2017-01-25 21:59:25 +01:00
$newItem -> ID = $id ;
}
# Some modules like subsites add extra fields that need to be set when the new item is created
$this -> extend ( 'augmentNewSiteTreeItem' , $newItem );
return $newItem ;
}
/**
* Actually perform the publication step
*
* @ param Versioned | DataObject $record
* @ return mixed
*/
public function performPublish ( $record )
{
if ( $record && ! $record -> canPublish ()) {
return Security :: permissionFailure ( $this );
}
$record -> publishRecursive ();
}
/**
* Reverts a page by publishing it to live .
* Use { @ link restorepage ()} if you want to restore a page
* which was deleted from draft without publishing .
*
* @ uses SiteTree -> doRevertToLive ()
*
* @ throws HTTPResponse_Exception
*/
2022-10-18 07:21:09 +02:00
public function revert ( array $data , Form $form ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
if ( ! isset ( $data [ 'ID' ])) {
throw new HTTPResponse_Exception ( " Please pass an ID in the form content " , 400 );
}
$id = ( int ) $data [ 'ID' ];
$restoredPage = Versioned :: get_latest_version ( SiteTree :: class , $id );
if ( ! $restoredPage ) {
throw new HTTPResponse_Exception ( " SiteTree # $id not found " , 400 );
}
/** @var SiteTree $record */
2019-05-17 03:40:15 +02:00
$table = DataObject :: singleton ( SiteTree :: class ) -> baseTable ();
$liveTable = DataObject :: singleton ( SiteTree :: class ) -> stageTable ( $table , Versioned :: LIVE );
2020-04-19 06:18:01 +02:00
$record = Versioned :: get_one_by_stage ( SiteTree :: class , Versioned :: LIVE , [
2019-05-17 03:40:15 +02:00
" \" $liveTable\ " . \ " ID \" " => $id
2020-04-19 06:18:01 +02:00
]);
2017-01-25 21:59:25 +01:00
// a user can restore a page without publication rights, as it just adds a new draft state
// (this action should just be available when page has been "deleted from draft")
if ( $record && ! $record -> canEdit ()) {
return Security :: permissionFailure ( $this );
}
if ( ! $record || ! $record -> ID ) {
throw new HTTPResponse_Exception ( " Bad record ID # $id " , 404 );
}
$record -> doRevertToLive ();
$this -> getResponse () -> addHeader (
'X-Status' ,
rawurlencode ( _t (
2017-12-13 03:36:35 +01:00
__CLASS__ . '.RESTORED' ,
2017-01-25 21:59:25 +01:00
" Restored ' { title}' successfully " ,
2017-05-19 02:11:46 +02:00
'Param {title} is a title' ,
2020-04-19 06:18:01 +02:00
[ 'title' => $record -> Title ]
2022-04-13 07:07:59 +02:00
) ? ? '' )
2017-01-25 21:59:25 +01:00
);
return $this -> getResponseNegotiator () -> respond ( $this -> getRequest ());
}
/**
* Delete the current page from draft stage .
*
* @ see deletefromlive ()
*
* @ throws HTTPResponse_Exception
*/
2022-10-18 07:21:09 +02:00
public function delete ( array $data , Form $form ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
$id = $data [ 'ID' ];
$record = SiteTree :: get () -> byID ( $id );
if ( $record && ! $record -> canDelete ()) {
return Security :: permissionFailure ();
}
if ( ! $record || ! $record -> ID ) {
throw new HTTPResponse_Exception ( " Bad record ID # $id " , 404 );
}
// Delete record
$record -> delete ();
$this -> getResponse () -> addHeader (
'X-Status' ,
2017-07-31 00:23:57 +02:00
rawurlencode ( _t (
__CLASS__ . '.REMOVEDPAGEFROMDRAFT' ,
" Removed ' { title}' from the draft site " ,
[ 'title' => $record -> Title ]
2022-04-13 07:07:59 +02:00
) ? ? '' )
2017-01-25 21:59:25 +01:00
);
// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
return $this -> getResponseNegotiator () -> respond ( $this -> getRequest ());
}
/**
* Delete this page from both live and stage
*
* @ throws HTTPResponse_Exception
*/
2022-10-18 07:21:09 +02:00
public function archive ( array $data , Form $form ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
$id = $data [ 'ID' ];
/** @var SiteTree $record */
$record = SiteTree :: get () -> byID ( $id );
if ( ! $record || ! $record -> exists ()) {
throw new HTTPResponse_Exception ( " Bad record ID # $id " , 404 );
}
if ( ! $record -> canArchive ()) {
return Security :: permissionFailure ();
}
// Archive record
$record -> doArchive ();
$this -> getResponse () -> addHeader (
'X-Status' ,
2017-07-31 00:23:57 +02:00
rawurlencode ( _t (
__CLASS__ . '.ARCHIVEDPAGE' ,
" Archived page ' { title}' " ,
[ 'title' => $record -> Title ]
2022-04-13 07:07:59 +02:00
) ? ? '' )
2017-01-25 21:59:25 +01:00
);
// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
return $this -> getResponseNegotiator () -> respond ( $this -> getRequest ());
}
2022-10-18 07:21:09 +02:00
public function publish ( array $data , Form $form ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
$data [ 'publish' ] = '1' ;
return $this -> save ( $data , $form );
}
2022-10-18 07:21:09 +02:00
public function unpublish ( array $data , Form $form ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
2017-08-22 23:46:46 +02:00
$className = $this -> config () -> get ( 'tree_class' );
2017-01-25 21:59:25 +01:00
/** @var SiteTree $record */
$record = DataObject :: get_by_id ( $className , $data [ 'ID' ]);
if ( $record && ! $record -> canUnpublish ()) {
return Security :: permissionFailure ( $this );
}
if ( ! $record || ! $record -> ID ) {
throw new HTTPResponse_Exception ( " Bad record ID # " . ( int ) $data [ 'ID' ], 404 );
}
$record -> doUnpublish ();
$this -> getResponse () -> addHeader (
'X-Status' ,
2017-12-13 03:36:35 +01:00
rawurlencode ( _t (
__CLASS__ . '.REMOVEDPAGE' ,
" Removed ' { title}' from the published site " ,
[ 'title' => $record -> Title ]
2022-04-13 07:07:59 +02:00
) ? ? '' )
2017-01-25 21:59:25 +01:00
);
return $this -> getResponseNegotiator () -> respond ( $this -> getRequest ());
}
/**
* @ return HTTPResponse
*/
public function rollback ()
{
2020-04-19 06:18:01 +02:00
return $this -> doRollback ([
2017-01-25 21:59:25 +01:00
'ID' => $this -> currentPageID (),
'Version' => $this -> getRequest () -> param ( 'VersionID' )
2020-04-19 06:18:01 +02:00
], null );
2017-01-25 21:59:25 +01:00
}
/**
* Rolls a site back to a given version ID
*
* @ param array $data
* @ param Form $form
* @ return HTTPResponse
*/
public function doRollback ( $data , $form )
{
2017-10-19 22:26:35 +02:00
$this -> extend ( 'onBeforeRollback' , $data [ 'ID' ], $data [ 'Version' ]);
2017-01-25 21:59:25 +01:00
$id = ( isset ( $data [ 'ID' ])) ? ( int ) $data [ 'ID' ] : null ;
$version = ( isset ( $data [ 'Version' ])) ? ( int ) $data [ 'Version' ] : null ;
2020-04-10 05:26:00 +02:00
/** @var SiteTree|Versioned $record */
2017-10-19 22:26:35 +02:00
$record = Versioned :: get_latest_version ( $this -> config () -> get ( 'tree_class' ), $id );
2017-01-25 21:59:25 +01:00
if ( $record && ! $record -> canEdit ()) {
return Security :: permissionFailure ( $this );
}
if ( $version ) {
2022-09-02 00:56:28 +02:00
$record -> rollbackRecursive ( $version );
2017-01-25 21:59:25 +01:00
$message = _t (
2017-05-19 02:11:46 +02:00
__CLASS__ . '.ROLLEDBACKVERSIONv2' ,
" Rolled back to version # { version}. " ,
2020-04-19 06:18:01 +02:00
[ 'version' => $data [ 'Version' ]]
2017-01-25 21:59:25 +01:00
);
} else {
$record -> doRevertToLive ();
2018-11-16 02:12:01 +01:00
$record -> publishRecursive ();
2017-01-25 21:59:25 +01:00
$message = _t (
2017-05-19 02:11:46 +02:00
__CLASS__ . '.ROLLEDBACKPUBv2' ,
2017-01-25 21:59:25 +01:00
" Rolled back to published version. "
);
}
2022-04-13 07:07:59 +02:00
$this -> getResponse () -> addHeader ( 'X-Status' , rawurlencode ( $message ? ? '' ));
2017-01-25 21:59:25 +01:00
// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
// Or in history view, in which case a revert causes the CMS to re-load the edit view.
// The X-Pjax header forces a "full" content refresh on redirect.
2020-04-10 05:27:39 +02:00
$url = $record -> CMSEditLink ();
2017-01-25 21:59:25 +01:00
$this -> getResponse () -> addHeader ( 'X-ControllerURL' , $url );
$this -> getRequest () -> addHeader ( 'X-Pjax' , 'Content' );
$this -> getResponse () -> addHeader ( 'X-Pjax' , 'Content' );
return $this -> getResponseNegotiator () -> respond ( $this -> getRequest ());
}
/**
* Batch Actions Handler
*/
public function batchactions ()
{
return new CMSBatchActionHandler ( $this , 'batchactions' );
}
2022-07-06 04:11:20 +02:00
/**
* Returns a LiteralField containing parameter field HTML
* for batch actions
2022-07-07 00:32:19 +02:00
*
2022-07-06 04:11:20 +02:00
* Used by { @ link LeftAndMain } to render batch actions in
* the BatchActionsForm
*
* @ return LiteralField
*/
2017-01-25 21:59:25 +01:00
public function BatchActionParameters ()
{
2022-07-05 06:14:53 +02:00
$batchActions = $this -> batchactions () -> registeredActions ();
2017-01-25 21:59:25 +01:00
2020-04-19 06:18:01 +02:00
$forms = [];
2017-01-25 21:59:25 +01:00
foreach ( $batchActions as $urlSegment => $batchAction ) {
2022-07-06 04:11:20 +02:00
$SNG_action = singleton ( $batchAction [ 'class' ]);
2022-07-05 06:14:53 +02:00
if ( $SNG_action -> canView () && $fieldList = $SNG_action -> getParameterFields ()) {
2017-01-25 21:59:25 +01:00
$formHtml = '' ;
/** @var FormField $field */
2022-07-05 06:14:53 +02:00
foreach ( $fieldList as $field ) {
$formHtml .= $field -> FieldHolder ();
2017-01-25 21:59:25 +01:00
}
$forms [ $urlSegment ] = $formHtml ;
}
}
$pageHtml = '' ;
foreach ( $forms as $urlSegment => $html ) {
2022-07-06 04:11:20 +02:00
$pageHtml .= '<div class="params" id="BatchActionParameters_' . $urlSegment . '" style="display:none">' . $html . '</div>' ;
2017-01-25 21:59:25 +01:00
}
2022-07-07 06:45:50 +02:00
return new LiteralField ( 'BatchActionParameters' , '<div id="BatchActionParameters" class="action-parameters" style="display:none">' . $pageHtml . '</div>' );
2017-01-25 21:59:25 +01:00
}
2022-07-06 04:11:20 +02:00
2017-01-25 21:59:25 +01:00
/**
* Returns a list of batch actions
*/
public function BatchActionList ()
{
return $this -> batchactions () -> batchActionList ();
}
/**
* Restore a completely deleted page from the SiteTree_versions table .
*/
2022-10-18 07:21:09 +02:00
public function restore ( array $data , Form $form ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
if ( ! isset ( $data [ 'ID' ]) || ! is_numeric ( $data [ 'ID' ])) {
return new HTTPResponse ( " Please pass an ID in the form content " , 400 );
}
$id = ( int ) $data [ 'ID' ];
/** @var SiteTree $restoredPage */
$restoredPage = Versioned :: get_latest_version ( SiteTree :: class , $id );
if ( ! $restoredPage ) {
return new HTTPResponse ( " SiteTree # $id not found " , 400 );
}
$restoredPage = $restoredPage -> doRestoreToStage ();
$this -> getResponse () -> addHeader (
'X-Status' ,
rawurlencode ( _t (
2017-12-13 03:36:35 +01:00
__CLASS__ . '.RESTORED' ,
2017-01-25 21:59:25 +01:00
" Restored ' { title}' successfully " ,
2020-04-19 06:18:01 +02:00
[ 'title' => $restoredPage -> Title ]
2022-04-13 07:07:59 +02:00
) ? ? '' )
2017-01-25 21:59:25 +01:00
);
return $this -> getResponseNegotiator () -> respond ( $this -> getRequest ());
}
2022-10-18 07:21:09 +02:00
public function duplicate ( HTTPRequest $request ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
// Protect against CSRF on destructive action
if ( ! SecurityToken :: inst () -> checkRequest ( $request )) {
return $this -> httpError ( 400 );
}
if (( $id = $this -> urlParams [ 'ID' ]) && is_numeric ( $id )) {
/** @var SiteTree $page */
$page = SiteTree :: get () -> byID ( $id );
2018-10-08 13:55:59 +02:00
if ( $page && ! $page -> canCreate ( null , [ 'Parent' => $page -> Parent ()])) {
2017-01-25 21:59:25 +01:00
return Security :: permissionFailure ( $this );
}
if ( ! $page || ! $page -> ID ) {
throw new HTTPResponse_Exception ( " Bad record ID # $id " , 404 );
}
2020-04-10 05:26:00 +02:00
/** @var SiteTree $newPage */
2017-01-25 21:59:25 +01:00
$newPage = $page -> duplicate ();
// ParentID can be hard-set in the URL. This is useful for pages with multiple parents
if ( isset ( $_GET [ 'parentID' ]) && is_numeric ( $_GET [ 'parentID' ])) {
$newPage -> ParentID = $_GET [ 'parentID' ];
$newPage -> write ();
}
$this -> getResponse () -> addHeader (
'X-Status' ,
rawurlencode ( _t (
2017-12-13 03:36:35 +01:00
__CLASS__ . '.DUPLICATED' ,
2017-01-25 21:59:25 +01:00
" Duplicated ' { title}' successfully " ,
2020-04-19 06:18:01 +02:00
[ 'title' => $newPage -> Title ]
2022-04-13 07:07:59 +02:00
) ? ? '' )
2017-01-25 21:59:25 +01:00
);
2020-04-10 05:27:39 +02:00
$url = $newPage -> CMSEditLink ();
2017-01-25 21:59:25 +01:00
$this -> getResponse () -> addHeader ( 'X-ControllerURL' , $url );
$this -> getRequest () -> addHeader ( 'X-Pjax' , 'Content' );
$this -> getResponse () -> addHeader ( 'X-Pjax' , 'Content' );
return $this -> getResponseNegotiator () -> respond ( $this -> getRequest ());
}
2018-10-08 13:55:59 +02:00
return new HTTPResponse ( " CMSMain::duplicate() Bad ID: ' $id ' " , 400 );
2017-01-25 21:59:25 +01:00
}
2022-10-18 07:21:09 +02:00
public function duplicatewithchildren ( HTTPRequest $request ) : HTTPResponse
2017-01-25 21:59:25 +01:00
{
// Protect against CSRF on destructive action
if ( ! SecurityToken :: inst () -> checkRequest ( $request )) {
2022-10-18 07:21:09 +02:00
$this -> httpError ( 400 );
2017-01-25 21:59:25 +01:00
}
2017-06-20 07:12:07 +02:00
Environment :: increaseTimeLimitTo ();
2017-01-25 21:59:25 +01:00
if (( $id = $this -> urlParams [ 'ID' ]) && is_numeric ( $id )) {
/** @var SiteTree $page */
$page = SiteTree :: get () -> byID ( $id );
2018-10-08 13:55:59 +02:00
if ( $page && ! $page -> canCreate ( null , [ 'Parent' => $page -> Parent ()])) {
2017-01-25 21:59:25 +01:00
return Security :: permissionFailure ( $this );
}
if ( ! $page || ! $page -> ID ) {
throw new HTTPResponse_Exception ( " Bad record ID # $id " , 404 );
}
2020-04-10 05:26:00 +02:00
/** @var SiteTree $newPage */
2017-01-25 21:59:25 +01:00
$newPage = $page -> duplicateWithChildren ();
$this -> getResponse () -> addHeader (
'X-Status' ,
rawurlencode ( _t (
2017-12-13 03:36:35 +01:00
__CLASS__ . '.DUPLICATEDWITHCHILDREN' ,
2017-01-25 21:59:25 +01:00
" Duplicated ' { title}' and children successfully " ,
2020-04-19 06:18:01 +02:00
[ 'title' => $newPage -> Title ]
2022-04-13 07:07:59 +02:00
) ? ? '' )
2017-01-25 21:59:25 +01:00
);
2020-04-10 05:27:39 +02:00
$url = $newPage -> CMSEditLink ();
2017-01-25 21:59:25 +01:00
$this -> getResponse () -> addHeader ( 'X-ControllerURL' , $url );
$this -> getRequest () -> addHeader ( 'X-Pjax' , 'Content' );
$this -> getResponse () -> addHeader ( 'X-Pjax' , 'Content' );
return $this -> getResponseNegotiator () -> respond ( $this -> getRequest ());
}
2018-10-08 13:55:59 +02:00
return new HTTPResponse ( " CMSMain::duplicatewithchildren() Bad ID: ' $id ' " , 400 );
2017-01-25 21:59:25 +01:00
}
public function providePermissions ()
{
$title = CMSPagesController :: menu_title ();
2020-04-19 06:18:01 +02:00
return [
" CMS_ACCESS_CMSMain " => [
'name' => _t ( __CLASS__ . '.ACCESS' , " Access to ' { title}' section " , [ 'title' => $title ]),
2017-04-20 03:15:29 +02:00
'category' => _t ( 'SilverStripe\\Security\\Permission.CMS_ACCESS_CATEGORY' , 'CMS Access' ),
2017-01-25 21:59:25 +01:00
'help' => _t (
2017-12-13 03:36:35 +01:00
__CLASS__ . '.ACCESS_HELP' ,
2017-01-25 21:59:25 +01:00
'Allow viewing of the section containing page tree and content. View and edit permissions can be handled through page specific dropdowns, as well as the separate "Content permissions".'
),
'sort' => - 99 // below "CMS_ACCESS_LeftAndMain", but above everything else
2020-04-19 06:18:01 +02:00
]
];
2017-01-25 21:59:25 +01:00
}
2017-04-06 07:38:15 +02:00
/**
* Get title for root CMS node
*
* @ return string
*/
protected function getCMSTreeTitle ()
{
$rootTitle = SiteConfig :: current_site_config () -> Title ;
$this -> extend ( 'updateCMSTreeTitle' , $rootTitle );
return $rootTitle ;
}
2017-11-30 03:56:16 +01:00
2017-12-13 03:36:35 +01:00
/**
* Cache key for SiteTreeHints () method
*
* @ param $memberID
* @ return string
*/
2017-11-30 03:56:16 +01:00
protected function generateHintsCacheKey ( $memberID )
{
2019-05-31 00:21:07 +02:00
$baseKey = $memberID . '_' . __CLASS__ ;
$this -> extend ( 'updateHintsCacheKey' , $baseKey );
2022-04-13 07:07:59 +02:00
return md5 ( $baseKey ? ? '' );
2017-11-30 03:56:16 +01:00
}
/**
* Clear the cache on ? flush
*/
public static function flush ()
{
2017-12-13 03:36:35 +01:00
CMSMain :: singleton () -> clearCache ();
2017-11-30 03:56:16 +01:00
}
/**
* Flush the hints cache for a specific member
*
2017-12-13 03:36:35 +01:00
* @ param array $memberIDs
2017-11-30 03:56:16 +01:00
*/
public function flushMemberCache ( $memberIDs = null )
{
$cache = $this -> getHintsCache ();
if ( ! $memberIDs ) {
$cache -> clear ();
2017-12-13 03:36:35 +01:00
return ;
2017-11-30 03:56:16 +01:00
}
2017-12-13 03:36:35 +01:00
foreach ( $memberIDs as $memberID ) {
$key = $this -> generateHintsCacheKey ( $memberID );
$cache -> delete ( $key );
}
2017-11-30 03:56:16 +01:00
}
2007-07-19 12:40:05 +02:00
}