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 ;
2016-06-16 06:57:19 +02:00
use SilverStripe\ORM\Versioning\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 .
*/
class RemoveOrphanedPagesTask extends Controller {
2016-03-08 21:50:55 +01:00
2013-03-18 11:47:15 +01:00
private static $allowed_actions = array (
2011-03-18 04:37:22 +01:00
'index' => 'ADMIN' ,
'Form' => 'ADMIN' ,
'run' => 'ADMIN' ,
'handleAction' => 'ADMIN' ,
);
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
protected $title = 'Removed orphaned pages without existing parents from both stage and live' ;
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
protected $description = "
< 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
2016-07-22 01:32:32 +02:00
protected $orphanedSearchClass = 'SilverStripe\\CMS\\Model\\SiteTree' ;
2016-03-08 21:50:55 +01:00
2012-09-19 12:07:46 +02:00
public function init () {
2011-03-18 04:37:22 +01:00
parent :: init ();
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
if ( ! Permission :: check ( 'ADMIN' )) {
2016-08-23 04:36:06 +02:00
Security :: permissionFailure ( $this );
2011-03-18 04:37:22 +01:00
}
}
2016-03-08 21:50:55 +01:00
2016-08-10 06:08:39 +02:00
public function Link ( $action = null )
{
2016-08-23 04:36:06 +02:00
/** @skipUpgrade */
2016-08-10 06:08:39 +02:00
return Controller :: join_links ( 'RemoveOrphanedPagesTask' , $action , '/' );
}
2012-09-19 12:07:46 +02:00
public function index () {
2012-04-12 10:36:25 +02:00
Requirements :: javascript ( FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js' );
2011-03-18 04:37:22 +01:00
Requirements :: customCSS ( '#OrphanIDs .middleColumn {width: auto;}' );
Requirements :: customCSS ( '#OrphanIDs label {display: inline;}' );
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
return $this -> renderWith ( 'BlankPage' );
}
2016-03-08 21:50:55 +01:00
2012-09-19 12:07:46 +02:00
public function Form () {
2011-10-26 07:35:51 +02:00
$fields = new FieldList ();
2011-03-18 04:37:22 +01:00
$source = array ();
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
$fields -> push ( new HeaderField (
'Header' ,
_t ( 'RemoveOrphanedPagesTask.HEADER' , 'Remove all orphaned pages task' )
));
$fields -> push ( new LiteralField (
'Description' ,
$this -> description
));
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
$orphans = $this -> getOrphanedPages ( $this -> orphanedSearchClass );
if ( $orphans ) foreach ( $orphans as $orphan ) {
2016-08-23 04:36:06 +02:00
/** @var SiteTree $latestVersion */
2011-03-18 04:37:22 +01:00
$latestVersion = Versioned :: get_latest_version ( $this -> orphanedSearchClass , $orphan -> ID );
2016-06-23 01:51:20 +02:00
$latestAuthor = DataObject :: get_by_id ( 'SilverStripe\\Security\\Member' , $latestVersion -> AuthorID );
2016-06-02 05:56:45 +02:00
$orphanBaseTable = DataObject :: getSchema () -> baseDataTable ( $this -> orphanedSearchClass );
2011-03-18 04:37:22 +01:00
$liveRecord = Versioned :: get_one_by_stage (
2016-01-06 00:42:07 +01:00
$this -> orphanedSearchClass ,
'Live' ,
2016-06-02 05:56:45 +02:00
array ( " \" $orphanBaseTable\ " . \ " ID \" " => $orphan -> ID )
2011-03-18 04:37:22 +01:00
);
$label = sprintf (
2012-09-25 05:31:42 +02:00
'<a href="admin/pages/edit/show/%d">%s</a> <small>(#%d, Last Modified Date: %s, Last Modifier: %s, %s)</small>' ,
2011-03-18 04:37:22 +01:00
$orphan -> ID ,
$orphan -> Title ,
$orphan -> ID ,
2016-06-02 05:56:45 +02:00
$orphan -> dbObject ( 'LastEdited' ) -> Nice (),
2011-03-18 04:37:22 +01:00
( $latestAuthor ) ? $latestAuthor -> Title : 'unknown' ,
( $liveRecord ) ? 'is published' : 'not published'
);
$source [ $orphan -> ID ] = $label ;
}
2016-03-08 21:50:55 +01:00
2016-08-10 06:08:39 +02:00
if ( $orphans && $orphans -> count ()) {
2011-03-18 04:37:22 +01:00
$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> ' ,
_t ( 'RemoveOrphanedPagesTask.SELECTALL' , 'select all' )
)
));
$fields -> push ( new LiteralField (
'UnselectAllLiteral' ,
sprintf (
'<a href="#" onclick="javascript:jQuery(\'#Form_Form_OrphanIDs :checkbox\').attr(\'checked\', \'\'); return false;">%s</a></p>' ,
_t ( 'RemoveOrphanedPagesTask.UNSELECTALL' , 'unselect all' )
)
));
2016-06-02 05:56:45 +02:00
$fields -> push ( new OptionsetField (
2016-01-06 00:42:07 +01:00
'OrphanOperation' ,
2011-03-18 04:37:22 +01:00
_t ( 'RemoveOrphanedPagesTask.CHOOSEOPERATION' , 'Choose operation:' ),
array (
'rebase' => _t (
2016-01-06 00:42:07 +01:00
'RemoveOrphanedPagesTask.OPERATION_REBASE' ,
2011-03-18 04:37:22 +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 ()
)
),
'remove' => _t ( 'RemoveOrphanedPagesTask.OPERATION_REMOVE' , 'Remove selected from all stages (WARNING: Will destroy all selected pages from both stage and live)' ),
),
'rebase'
));
$fields -> push ( new LiteralField (
'Warning' ,
sprintf ( '<p class="message">%s</p>' ,
_t (
2016-01-06 00:42:07 +01:00
'RemoveOrphanedPagesTask.DELETEWARNING' ,
2011-03-18 04:37:22 +01:00
'Warning: These operations are not reversible. Please handle with care.'
)
)
));
} else {
$fields -> push ( new LiteralField (
'NotFoundLabel' ,
sprintf (
'<p class="message">%s</p>' ,
_t ( 'RemoveOrphanedPagesTask.NONEFOUND' , 'No orphans found' )
)
));
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
$form = new Form (
$this ,
2016-08-23 04:36:06 +02:00
'SilverStripe\\Forms\\Form' ,
2011-03-18 04:37:22 +01:00
$fields ,
2011-10-26 07:35:51 +02:00
new FieldList (
2011-03-18 04:37:22 +01:00
new FormAction ( 'doSubmit' , _t ( 'RemoveOrphanedPagesTask.BUTTONRUN' , 'Run' ))
)
);
2016-03-08 21:50:55 +01:00
2016-08-10 06:08:39 +02:00
if ( ! $orphans || ! $orphans -> count ()) {
2011-03-18 04:37:22 +01:00
$form -> makeReadonly ();
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
return $form ;
}
2016-03-08 21:50:55 +01:00
2012-09-19 12:07:46 +02:00
public function run ( $request ) {
2011-03-18 04:37:22 +01:00
// @todo Merge with BuildTask functionality
}
2016-03-08 21:50:55 +01:00
2012-09-19 12:07:46 +02:00
public function doSubmit ( $data , $form ) {
2011-03-18 04:37:22 +01:00
set_time_limit ( 60 * 10 ); // 10 minutes
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
if ( ! isset ( $data [ 'OrphanIDs' ]) || ! isset ( $data [ 'OrphanOperation' ])) return false ;
2016-03-08 21:50:55 +01:00
2016-06-02 05:56:45 +02:00
$successIDs = null ;
2011-03-18 04:37:22 +01:00
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 );
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
$content = '' ;
if ( $successIDs ) {
$content .= " <ul> " ;
foreach ( $successIDs as $id => $label ) {
$content .= sprintf ( '<li>%s</li>' , $label );
}
$content .= " </ul> " ;
} else {
$content = _t ( 'RemoveOrphanedPagesTask.NONEREMOVED' , 'None removed' );
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
return $this -> customise ( array (
'Content' => $content ,
'Form' => ' '
)) -> renderWith ( 'BlankPage' );
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
protected function removeOrphans ( $orphanIDs ) {
$removedOrphans = array ();
2016-06-02 05:56:45 +02:00
$orphanBaseTable = DataObject :: getSchema () -> baseDataTable ( $this -> orphanedSearchClass );
2011-03-18 04:37:22 +01:00
foreach ( $orphanIDs as $id ) {
2016-01-26 06:38:42 +01:00
/** @var SiteTree $stageRecord */
2011-03-18 04:37:22 +01:00
$stageRecord = Versioned :: get_one_by_stage (
2016-01-06 00:42:07 +01:00
$this -> orphanedSearchClass ,
2016-06-02 05:56:45 +02:00
Versioned :: DRAFT ,
array ( " \" $orphanBaseTable\ " . \ " ID \" " => $id )
2011-03-18 04:37:22 +01:00
);
if ( $stageRecord ) {
$removedOrphans [ $stageRecord -> ID ] = sprintf ( 'Removed %s (#%d) from Stage' , $stageRecord -> Title , $stageRecord -> ID );
$stageRecord -> delete ();
$stageRecord -> destroy ();
unset ( $stageRecord );
}
2016-01-26 06:38:42 +01:00
/** @var SiteTree $liveRecord */
2011-03-18 04:37:22 +01:00
$liveRecord = Versioned :: get_one_by_stage (
2016-01-06 00:42:07 +01:00
$this -> orphanedSearchClass ,
2016-06-02 05:56:45 +02:00
Versioned :: LIVE ,
array ( " \" $orphanBaseTable\ " . \ " ID \" " => $id )
2011-03-18 04:37:22 +01:00
);
if ( $liveRecord ) {
$removedOrphans [ $liveRecord -> ID ] = sprintf ( 'Removed %s (#%d) from Live' , $liveRecord -> Title , $liveRecord -> ID );
2016-01-26 06:38:42 +01:00
$liveRecord -> doUnpublish ();
2011-03-18 04:37:22 +01:00
$liveRecord -> destroy ();
unset ( $liveRecord );
}
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
return $removedOrphans ;
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
protected function rebaseHolderTitle () {
return sprintf ( 'Rebased Orphans (%s)' , date ( 'd/m/Y g:ia' , time ()));
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
protected function rebaseOrphans ( $orphanIDs ) {
$holder = new SiteTree ();
$holder -> ShowInMenus = 0 ;
$holder -> ShowInSearch = 0 ;
$holder -> ParentID = 0 ;
$holder -> Title = $this -> rebaseHolderTitle ();
$holder -> write ();
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
$removedOrphans = array ();
2016-06-02 05:56:45 +02:00
$orphanBaseTable = DataObject :: getSchema () -> baseDataTable ( $this -> orphanedSearchClass );
2011-03-18 04:37:22 +01:00
foreach ( $orphanIDs as $id ) {
2016-08-23 04:36:06 +02:00
/** @var SiteTree $stageRecord */
2011-03-18 04:37:22 +01:00
$stageRecord = Versioned :: get_one_by_stage (
2016-01-06 00:42:07 +01:00
$this -> orphanedSearchClass ,
2013-06-21 00:45:33 +02:00
'Stage' ,
2016-06-02 05:56:45 +02:00
array ( " \" $orphanBaseTable\ " . \ " ID \" " => $id )
2011-03-18 04:37:22 +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);
}
2016-08-23 04:36:06 +02:00
/** @var SiteTree $liveRecord */
2011-03-18 04:37:22 +01:00
$liveRecord = Versioned :: get_one_by_stage (
2016-01-06 00:42:07 +01:00
$this -> orphanedSearchClass ,
2013-06-21 00:45:33 +02:00
'Live' ,
2016-06-02 05:56:45 +02:00
array ( " \" $orphanBaseTable\ " . \ " ID \" " => $id )
2011-03-18 04:37:22 +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 ();
2016-08-23 04:36:06 +02:00
if ( ! $stageRecord ) {
$liveRecord -> doRestoreToStage ();
}
2011-03-18 04:37:22 +01:00
$liveRecord -> doUnpublish ();
$liveRecord -> destroy ();
unset ( $liveRecord );
}
if ( $stageRecord ) {
unset ( $stageRecord );
}
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
return $removedOrphans ;
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
/**
* Gets all orphans from " Stage " and " Live " stages .
2016-01-06 00:42:07 +01:00
*
2011-03-18 04:37:22 +01:00
* @ param string $class
2013-06-21 00:45:33 +02:00
* @ param array $filter
2011-03-18 04:37:22 +01:00
* @ param string $sort
* @ param string $join
* @ param int | array $limit
2011-10-26 07:39:21 +02:00
* @ return SS_List
2011-03-18 04:37:22 +01:00
*/
2016-07-22 01:32:32 +02:00
public function getOrphanedPages ( $class = 'SilverStripe\\CMS\\Model\\SiteTree' , $filter = array (), $sort = null , $join = null , $limit = null ) {
2013-06-21 00:45:33 +02:00
// Alter condition
2016-08-10 06:08:39 +02:00
$table = DataObject :: getSchema () -> tableName ( $class );
if ( empty ( $filter )) {
$where = array ();
} elseif ( is_array ( $filter )) {
$where = $filter ;
} else {
$where = array ( $filter );
}
$where [] = array ( " \" { $table } \" . \" ParentID \" != ? " => 0 );
2013-06-21 00:45:33 +02:00
$where [] = '"Parents"."ID" IS NULL' ;
2016-03-08 21:50:55 +01:00
2011-05-05 12:40:26 +02:00
$orphans = new ArrayList ();
2016-04-01 05:17:37 +02:00
foreach ( array ( Versioned :: DRAFT , Versioned :: LIVE ) as $stage ) {
2016-06-02 05:56:45 +02:00
$table .= ( $stage == Versioned :: LIVE ) ? '_Live' : '' ;
2011-03-18 04:37:22 +01:00
$stageOrphans = Versioned :: get_by_stage (
$class ,
$stage ,
2013-06-21 00:45:33 +02:00
$where ,
2011-03-18 04:37:22 +01:00
$sort ,
2011-11-07 23:27:48 +01:00
null ,
2011-03-18 04:37:22 +01:00
$limit
2011-11-07 23:27:48 +01:00
) -> leftJoin ( $table , " \" $table\ " . \ " ParentID \" = \" Parents \" . \" ID \" " , " Parents " );
2011-03-18 04:37:22 +01:00
$orphans -> merge ( $stageOrphans );
}
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
$orphans -> removeDuplicates ();
2016-03-08 21:50:55 +01:00
2011-03-18 04:37:22 +01:00
return $orphans ;
}
}