2011-03-18 04:37:22 +01:00
< ? php
2016-06-16 06:57:19 +02:00
2016-08-10 06:08:39 +02:00
namespace SilverStripe\CMS\Tasks ;
2016-08-23 04:36:06 +02:00
use SilverStripe\CMS\Model\SiteTree ;
use SilverStripe\Control\Controller ;
use SilverStripe\Forms\CheckboxSetField ;
use SilverStripe\Forms\FieldList ;
use SilverStripe\Forms\Form ;
use SilverStripe\Forms\FormAction ;
use SilverStripe\Forms\HeaderField ;
use SilverStripe\Forms\LiteralField ;
use SilverStripe\Forms\OptionsetField ;
use SilverStripe\ORM\ArrayList ;
use SilverStripe\ORM\DataObject ;
2016-08-10 06:08:39 +02:00
use SilverStripe\ORM\SS_List ;
2017-03-21 05:26:46 +01:00
use SilverStripe\Versioned\Versioned ;
2016-06-23 01:51:20 +02:00
use SilverStripe\Security\Permission ;
use SilverStripe\Security\Security ;
2016-08-23 04:36:06 +02:00
use SilverStripe\View\Requirements ;
2016-06-02 05:56:45 +02:00
2011-03-18 04:37:22 +01:00
/**
* Identify " orphaned " pages which point to a parent
* that no longer exists in a specific stage .
* Shows the pages to an administrator , who can then
* decide which pages to remove by ticking a checkbox
* and manually executing the removal .
2016-01-06 00:42:07 +01:00
*
2011-03-18 04:37:22 +01:00
* Caution : Pages also count as orphans if they don ' t
* have parents in this stage , even if the parent has a representation
* in the other stage :
* - A live child is orphaned if its parent was deleted from live , but still exists on stage
* - A stage child is orphaned if its parent was deleted from stage , but still exists on live
*
* See { @ link RemoveOrphanedPagesTaskTest } for an example sitetree
* before and after orphan removal .
*
* @ author Ingo Schommer ( < firstname >@ silverstripe . com ), SilverStripe Ltd .
*/
2017-01-25 21:59:25 +01:00
class RemoveOrphanedPagesTask extends Controller
{
2016-03-08 21:50:55 +01:00
2020-04-19 06:18:01 +02:00
private static $allowed_actions = [
2017-01-25 21:59:25 +01:00
'index' => 'ADMIN' ,
'Form' => 'ADMIN' ,
'run' => 'ADMIN' ,
'handleAction' => 'ADMIN' ,
2020-04-19 06:18:01 +02:00
];
2016-03-08 21:50:55 +01:00
2017-01-25 21:59:25 +01:00
protected $title = 'Removed orphaned pages without existing parents from both stage and live' ;
2016-03-08 21:50:55 +01:00
2017-01-25 21:59:25 +01:00
protected $description = "
2011-03-18 04:37:22 +01:00
< p >
Identify 'orphaned' pages which point to a parent
that no longer exists in a specific stage .
</ p >
< p >
Caution : Pages also count as orphans if they don ' t
have parents in this stage , even if the parent has a representation
in the other stage :< br />
- A live child is orphaned if its parent was deleted from live , but still exists on stage < br />
- A stage child is orphaned if its parent was deleted from stage , but still exists on live
</ p >
" ;
2016-03-08 21:50:55 +01:00
2017-09-20 03:51:07 +02:00
protected $orphanedSearchClass = SiteTree :: class ;
2017-01-25 21:59:25 +01:00
protected function init ()
{
parent :: init ();
if ( ! Permission :: check ( 'ADMIN' )) {
Security :: permissionFailure ( $this );
}
}
public function Link ( $action = null )
{
/** @skipUpgrade */
return Controller :: join_links ( 'RemoveOrphanedPagesTask' , $action , '/' );
}
public function index ()
{
Requirements :: javascript ( 'http://code.jquery.com/jquery-1.7.2.min.js' );
Requirements :: customCSS ( '#OrphanIDs .middleColumn {width: auto;}' );
Requirements :: customCSS ( '#OrphanIDs label {display: inline;}' );
return $this -> renderWith ( 'BlankPage' );
}
public function Form ()
{
$fields = new FieldList ();
2020-04-19 06:18:01 +02:00
$source = [];
2017-01-25 21:59:25 +01:00
$fields -> push ( new HeaderField (
'Header' ,
2017-04-20 03:15:29 +02:00
_t ( 'SilverStripe\\CMS\\Tasks\\RemoveOrphanedPagesTask.HEADER' , 'Remove all orphaned pages task' )
2017-01-25 21:59:25 +01:00
));
$fields -> push ( new LiteralField (
'Description' ,
$this -> description
));
$orphans = $this -> getOrphanedPages ( $this -> orphanedSearchClass );
if ( $orphans ) {
foreach ( $orphans as $orphan ) {
/** @var SiteTree $latestVersion */
$latestVersion = Versioned :: get_latest_version ( $this -> orphanedSearchClass , $orphan -> ID );
$latestAuthor = DataObject :: get_by_id ( 'SilverStripe\\Security\\Member' , $latestVersion -> AuthorID );
$orphanBaseTable = DataObject :: getSchema () -> baseDataTable ( $this -> orphanedSearchClass );
$liveRecord = Versioned :: get_one_by_stage (
$this -> orphanedSearchClass ,
'Live' ,
2020-04-19 06:18:01 +02:00
[ " \" $orphanBaseTable\ " . \ " ID \" " => $orphan -> ID ]
2017-01-25 21:59:25 +01:00
);
$label = sprintf (
'<a href="admin/pages/edit/show/%d">%s</a> <small>(#%d, Last Modified Date: %s, Last Modifier: %s, %s)</small>' ,
$orphan -> ID ,
$orphan -> Title ,
$orphan -> ID ,
$orphan -> dbObject ( 'LastEdited' ) -> Nice (),
( $latestAuthor ) ? $latestAuthor -> Title : 'unknown' ,
( $liveRecord ) ? 'is published' : 'not published'
);
$source [ $orphan -> ID ] = $label ;
}
}
if ( $orphans && $orphans -> count ()) {
$fields -> push ( new CheckboxSetField ( 'OrphanIDs' , false , $source ));
$fields -> push ( new LiteralField (
'SelectAllLiteral' ,
sprintf (
'<p><a href="#" onclick="javascript:jQuery(\'#Form_Form_OrphanIDs :checkbox\').attr(\'checked\', \'checked\'); return false;">%s</a> ' ,
2017-04-20 03:15:29 +02:00
_t ( 'SilverStripe\\CMS\\Tasks\\RemoveOrphanedPagesTask.SELECTALL' , 'select all' )
2017-01-25 21:59:25 +01:00
)
));
$fields -> push ( new LiteralField (
'UnselectAllLiteral' ,
sprintf (
'<a href="#" onclick="javascript:jQuery(\'#Form_Form_OrphanIDs :checkbox\').attr(\'checked\', \'\'); return false;">%s</a></p>' ,
2017-04-20 03:15:29 +02:00
_t ( 'SilverStripe\\CMS\\Tasks\\RemoveOrphanedPagesTask.UNSELECTALL' , 'unselect all' )
2017-01-25 21:59:25 +01:00
)
));
$fields -> push ( new OptionsetField (
'OrphanOperation' ,
2017-04-20 03:15:29 +02:00
_t ( 'SilverStripe\\CMS\\Tasks\\RemoveOrphanedPagesTask.CHOOSEOPERATION' , 'Choose operation:' ),
2020-04-19 06:18:01 +02:00
[
2017-01-25 21:59:25 +01:00
'rebase' => _t (
2017-04-20 03:15:29 +02:00
'SilverStripe\\CMS\\Tasks\\RemoveOrphanedPagesTask.OPERATION_REBASE' ,
2017-01-25 21:59:25 +01:00
sprintf (
'Rebase selected to a new holder page "%s" and unpublish. None of these pages will show up for website visitors.' ,
$this -> rebaseHolderTitle ()
)
),
2017-04-20 03:15:29 +02:00
'remove' => _t ( 'SilverStripe\\CMS\\Tasks\\RemoveOrphanedPagesTask.OPERATION_REMOVE' , 'Remove selected from all stages (WARNING: Will destroy all selected pages from both stage and live)' ),
2020-04-19 06:18:01 +02:00
],
2017-01-25 21:59:25 +01:00
'rebase'
));
$fields -> push ( new LiteralField (
'Warning' ,
sprintf (
'<p class="message">%s</p>' ,
_t (
2017-04-20 03:15:29 +02:00
'SilverStripe\\CMS\\Tasks\\RemoveOrphanedPagesTask.DELETEWARNING' ,
2017-01-25 21:59:25 +01:00
'Warning: These operations are not reversible. Please handle with care.'
)
)
));
} else {
$fields -> push ( new LiteralField (
'NotFoundLabel' ,
sprintf (
'<p class="message">%s</p>' ,
2017-04-20 03:15:29 +02:00
_t ( 'SilverStripe\\CMS\\Tasks\\RemoveOrphanedPagesTask.NONEFOUND' , 'No orphans found' )
2017-01-25 21:59:25 +01:00
)
));
}
$form = new Form (
$this ,
'SilverStripe\\Forms\\Form' ,
$fields ,
new FieldList (
2017-04-20 03:15:29 +02:00
new FormAction ( 'doSubmit' , _t ( 'SilverStripe\\CMS\\Tasks\\RemoveOrphanedPagesTask.BUTTONRUN' , 'Run' ))
2017-01-25 21:59:25 +01:00
)
);
if ( ! $orphans || ! $orphans -> count ()) {
$form -> makeReadonly ();
}
return $form ;
}
public function run ( $request )
{
// @todo Merge with BuildTask functionality
}
public function doSubmit ( $data , $form )
{
set_time_limit ( 60 * 10 ); // 10 minutes
if ( ! isset ( $data [ 'OrphanIDs' ]) || ! isset ( $data [ 'OrphanOperation' ])) {
return false ;
}
$successIDs = null ;
switch ( $data [ 'OrphanOperation' ]) {
case 'remove' :
$successIDs = $this -> removeOrphans ( $data [ 'OrphanIDs' ]);
break ;
case 'rebase' :
$successIDs = $this -> rebaseOrphans ( $data [ 'OrphanIDs' ]);
break ;
default :
user_error ( sprintf ( " Unknown operation: '%s' " , $data [ 'OrphanOperation' ]), E_USER_ERROR );
}
$content = '' ;
if ( $successIDs ) {
$content .= " <ul> " ;
foreach ( $successIDs as $id => $label ) {
$content .= sprintf ( '<li>%s</li>' , $label );
}
$content .= " </ul> " ;
} else {
2017-04-20 03:15:29 +02:00
$content = _t ( 'SilverStripe\\CMS\\Tasks\\RemoveOrphanedPagesTask.NONEREMOVED' , 'None removed' );
2017-01-25 21:59:25 +01:00
}
2020-04-19 06:18:01 +02:00
return $this -> customise ([
2017-01-25 21:59:25 +01:00
'Content' => $content ,
'Form' => ' '
2020-04-19 06:18:01 +02:00
]) -> renderWith ( 'BlankPage' );
2017-01-25 21:59:25 +01:00
}
protected function removeOrphans ( $orphanIDs )
{
2020-04-19 06:18:01 +02:00
$removedOrphans = [];
2017-01-25 21:59:25 +01:00
$orphanBaseTable = DataObject :: getSchema () -> baseDataTable ( $this -> orphanedSearchClass );
foreach ( $orphanIDs as $id ) {
/** @var SiteTree $stageRecord */
$stageRecord = Versioned :: get_one_by_stage (
$this -> orphanedSearchClass ,
Versioned :: DRAFT ,
2020-04-19 06:18:01 +02:00
[ " \" $orphanBaseTable\ " . \ " ID \" " => $id ]
2017-01-25 21:59:25 +01:00
);
if ( $stageRecord ) {
$removedOrphans [ $stageRecord -> ID ] = sprintf ( 'Removed %s (#%d) from Stage' , $stageRecord -> Title , $stageRecord -> ID );
$stageRecord -> delete ();
$stageRecord -> destroy ();
unset ( $stageRecord );
}
/** @var SiteTree $liveRecord */
$liveRecord = Versioned :: get_one_by_stage (
$this -> orphanedSearchClass ,
Versioned :: LIVE ,
2020-04-19 06:18:01 +02:00
[ " \" $orphanBaseTable\ " . \ " ID \" " => $id ]
2017-01-25 21:59:25 +01:00
);
if ( $liveRecord ) {
$removedOrphans [ $liveRecord -> ID ] = sprintf ( 'Removed %s (#%d) from Live' , $liveRecord -> Title , $liveRecord -> ID );
$liveRecord -> doUnpublish ();
$liveRecord -> destroy ();
unset ( $liveRecord );
}
}
return $removedOrphans ;
}
protected function rebaseHolderTitle ()
{
return sprintf ( 'Rebased Orphans (%s)' , date ( 'd/m/Y g:ia' , time ()));
}
protected function rebaseOrphans ( $orphanIDs )
{
$holder = new SiteTree ();
$holder -> ShowInMenus = 0 ;
$holder -> ShowInSearch = 0 ;
$holder -> ParentID = 0 ;
$holder -> Title = $this -> rebaseHolderTitle ();
$holder -> write ();
2020-04-19 06:18:01 +02:00
$removedOrphans = [];
2017-01-25 21:59:25 +01:00
$orphanBaseTable = DataObject :: getSchema () -> baseDataTable ( $this -> orphanedSearchClass );
foreach ( $orphanIDs as $id ) {
/** @var SiteTree $stageRecord */
$stageRecord = Versioned :: get_one_by_stage (
$this -> orphanedSearchClass ,
'Stage' ,
2020-04-19 06:18:01 +02:00
[ " \" $orphanBaseTable\ " . \ " ID \" " => $id ]
2017-01-25 21:59:25 +01:00
);
if ( $stageRecord ) {
$removedOrphans [ $stageRecord -> ID ] = sprintf ( 'Rebased %s (#%d)' , $stageRecord -> Title , $stageRecord -> ID );
$stageRecord -> ParentID = $holder -> ID ;
$stageRecord -> ShowInMenus = 0 ;
$stageRecord -> ShowInSearch = 0 ;
$stageRecord -> write ();
$stageRecord -> doUnpublish ();
$stageRecord -> destroy ();
//unset($stageRecord);
}
/** @var SiteTree $liveRecord */
$liveRecord = Versioned :: get_one_by_stage (
$this -> orphanedSearchClass ,
'Live' ,
2020-04-19 06:18:01 +02:00
[ " \" $orphanBaseTable\ " . \ " ID \" " => $id ]
2017-01-25 21:59:25 +01:00
);
if ( $liveRecord ) {
$removedOrphans [ $liveRecord -> ID ] = sprintf ( 'Rebased %s (#%d)' , $liveRecord -> Title , $liveRecord -> ID );
$liveRecord -> ParentID = $holder -> ID ;
$liveRecord -> ShowInMenus = 0 ;
$liveRecord -> ShowInSearch = 0 ;
$liveRecord -> write ();
if ( ! $stageRecord ) {
$liveRecord -> doRestoreToStage ();
}
$liveRecord -> doUnpublish ();
$liveRecord -> destroy ();
unset ( $liveRecord );
}
if ( $stageRecord ) {
unset ( $stageRecord );
}
}
return $removedOrphans ;
}
/**
* Gets all orphans from " Stage " and " Live " stages .
*
* @ param string $class
* @ param array $filter
* @ param string $sort
* @ param string $join
* @ param int | array $limit
* @ return SS_List
*/
2020-04-19 06:18:01 +02:00
public function getOrphanedPages ( $class = SiteTree :: class , $filter = [], $sort = null , $join = null , $limit = null )
2017-01-25 21:59:25 +01:00
{
// Alter condition
$table = DataObject :: getSchema () -> tableName ( $class );
if ( empty ( $filter )) {
2020-04-19 06:18:01 +02:00
$where = [];
2017-01-25 21:59:25 +01:00
} elseif ( is_array ( $filter )) {
$where = $filter ;
} else {
2020-04-19 06:18:01 +02:00
$where = [ $filter ];
2017-01-25 21:59:25 +01:00
}
2020-04-19 06:18:01 +02:00
$where [] = [ " \" { $table } \" . \" ParentID \" != ? " => 0 ];
2017-01-25 21:59:25 +01:00
$where [] = '"Parents"."ID" IS NULL' ;
$orphans = new ArrayList ();
2020-04-19 06:18:01 +02:00
foreach ([ Versioned :: DRAFT , Versioned :: LIVE ] as $stage ) {
2017-01-25 21:59:25 +01:00
$table .= ( $stage == Versioned :: LIVE ) ? '_Live' : '' ;
$stageOrphans = Versioned :: get_by_stage (
$class ,
$stage ,
$where ,
$sort ,
null ,
$limit
) -> leftJoin ( $table , " \" $table\ " . \ " ParentID \" = \" Parents \" . \" ID \" " , " Parents " );
$orphans -> merge ( $stageOrphans );
}
$orphans -> removeDuplicates ();
return $orphans ;
}
2011-03-18 04:37:22 +01:00
}