Completed
Pull Request — master (#1638)
by
unknown
19:28
created

RemoveOrphanedPagesTask   C

Complexity

Total Complexity 37

Size/Duplication

Total Lines 322
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 19
dl 0
loc 322
rs 5.9125
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A index() 0 7 1
A run() 0 3 1
C doSubmit() 0 33 7
B removeOrphans() 0 32 4
A rebaseHolderTitle() 0 3 1
A init() 0 7 2
A Link() 0 5 1
D Form() 0 101 9
B rebaseOrphans() 0 53 6
B getOrphanedPages() 0 31 5
1
<?php
2
3
namespace SilverStripe\CMS\Tasks;
4
5
use SilverStripe\CMS\Model\SiteTree;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Forms\CheckboxSetField;
8
use SilverStripe\Forms\FieldList;
9
use SilverStripe\Forms\Form;
10
use SilverStripe\Forms\FormAction;
11
use SilverStripe\Forms\HeaderField;
12
use SilverStripe\Forms\LiteralField;
13
use SilverStripe\Forms\OptionsetField;
14
use SilverStripe\ORM\ArrayList;
15
use SilverStripe\ORM\DataObject;
16
use SilverStripe\ORM\SS_List;
17
use SilverStripe\ORM\Versioning\Versioned;
18
use SilverStripe\Security\Permission;
19
use SilverStripe\Security\Security;
20
use SilverStripe\View\Requirements;
21
22
/**
23
 * Identify "orphaned" pages which point to a parent
24
 * that no longer exists in a specific stage.
25
 * Shows the pages to an administrator, who can then
26
 * decide which pages to remove by ticking a checkbox
27
 * and manually executing the removal.
28
 *
29
 * Caution: Pages also count as orphans if they don't
30
 * have parents in this stage, even if the parent has a representation
31
 * in the other stage:
32
 * - A live child is orphaned if its parent was deleted from live, but still exists on stage
33
 * - A stage child is orphaned if its parent was deleted from stage, but still exists on live
34
 *
35
 * See {@link RemoveOrphanedPagesTaskTest} for an example sitetree
36
 * before and after orphan removal.
37
 *
38
 * @author Ingo Schommer (<firstname>@silverstripe.com), SilverStripe Ltd.
39
 */
40
class RemoveOrphanedPagesTask extends Controller {
41
42
	private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
43
		'index' => 'ADMIN',
44
		'Form' => 'ADMIN',
45
		'run' => 'ADMIN',
46
		'handleAction' => 'ADMIN',
47
	);
48
49
	protected $title = 'Removed orphaned pages without existing parents from both stage and live';
50
51
	protected $description = "
52
<p>
53
Identify 'orphaned' pages which point to a parent
54
that no longer exists in a specific stage.
55
</p>
56
<p>
57
Caution: Pages also count as orphans if they don't
58
have parents in this stage, even if the parent has a representation
59
in the other stage:<br />
60
- A live child is orphaned if its parent was deleted from live, but still exists on stage<br />
61
- A stage child is orphaned if its parent was deleted from stage, but still exists on live
62
</p>
63
	";
64
65
	protected $orphanedSearchClass = 'SilverStripe\\CMS\\Model\\SiteTree';
66
67
	public function init() {
68
		parent::init();
69
70
		if(!Permission::check('ADMIN')) {
71
			Security::permissionFailure($this);
72
		}
73
	}
74
75
	public function Link($action = null)
76
	{
77
		/** @skipUpgrade */
78
		return Controller::join_links('RemoveOrphanedPagesTask', $action, '/');
79
	}
80
81
	public function index() {
82
		Requirements::javascript('http://code.jquery.com/jquery-1.7.2.min.js');
83
		Requirements::customCSS('#OrphanIDs .middleColumn {width: auto;}');
84
		Requirements::customCSS('#OrphanIDs label {display: inline;}');
85
86
		return $this->renderWith('BlankPage');
87
	}
88
89
	public function Form() {
90
		$fields = new FieldList();
91
		$source = array();
92
93
		$fields->push(new HeaderField(
94
			'Header',
95
			_t('RemoveOrphanedPagesTask.HEADER', 'Remove all orphaned pages task')
96
		));
97
		$fields->push(new LiteralField(
98
			'Description',
99
			$this->description
100
		));
101
102
		$orphans = $this->getOrphanedPages($this->orphanedSearchClass);
103
		if($orphans) foreach($orphans as $orphan) {
104
			/** @var SiteTree $latestVersion */
105
			$latestVersion = Versioned::get_latest_version($this->orphanedSearchClass, $orphan->ID);
106
			$latestAuthor = DataObject::get_by_id('SilverStripe\\Security\\Member', $latestVersion->AuthorID);
0 ignored issues
show
Documentation introduced by
The property AuthorID does not exist on object<SilverStripe\CMS\Model\SiteTree>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
107
			$orphanBaseTable = DataObject::getSchema()->baseDataTable($this->orphanedSearchClass);
108
			$liveRecord = Versioned::get_one_by_stage(
109
				$this->orphanedSearchClass,
110
				'Live',
111
				array("\"$orphanBaseTable\".\"ID\"" => $orphan->ID)
0 ignored issues
show
Documentation introduced by
array("\"{$orphanBaseTab...\"ID\"" => $orphan->ID) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
112
			);
113
			$label = sprintf(
114
				'<a href="admin/pages/edit/show/%d">%s</a> <small>(#%d, Last Modified Date: %s, Last Modifier: %s, %s)</small>',
115
				$orphan->ID,
116
				$orphan->Title,
117
				$orphan->ID,
118
				$orphan->dbObject('LastEdited')->Nice(),
119
				($latestAuthor) ? $latestAuthor->Title : 'unknown',
120
				($liveRecord) ? 'is published' : 'not published'
121
			);
122
			$source[$orphan->ID] = $label;
123
		}
124
125
		if($orphans && $orphans->count()) {
126
			$fields->push(new CheckboxSetField('OrphanIDs', false, $source));
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
127
			$fields->push(new LiteralField(
128
				'SelectAllLiteral',
129
				sprintf(
130
					'<p><a href="#" onclick="javascript:jQuery(\'#Form_Form_OrphanIDs :checkbox\').attr(\'checked\', \'checked\'); return false;">%s</a>&nbsp;',
131
					_t('RemoveOrphanedPagesTask.SELECTALL', 'select all')
132
				)
133
			));
134
			$fields->push(new LiteralField(
135
				'UnselectAllLiteral',
136
				sprintf(
137
					'<a href="#" onclick="javascript:jQuery(\'#Form_Form_OrphanIDs :checkbox\').attr(\'checked\', \'\'); return false;">%s</a></p>',
138
					_t('RemoveOrphanedPagesTask.UNSELECTALL', 'unselect all')
139
				)
140
			));
141
			$fields->push(new OptionsetField(
142
				'OrphanOperation',
143
				_t('RemoveOrphanedPagesTask.CHOOSEOPERATION', 'Choose operation:'),
144
				array(
145
					'rebase' => _t(
146
						'RemoveOrphanedPagesTask.OPERATION_REBASE',
147
						sprintf(
148
							'Rebase selected to a new holder page "%s" and unpublish. None of these pages will show up for website visitors.',
149
							$this->rebaseHolderTitle()
150
						)
151
					),
152
					'remove' => _t('RemoveOrphanedPagesTask.OPERATION_REMOVE', 'Remove selected from all stages (WARNING: Will destroy all selected pages from both stage and live)'),
153
				),
154
				'rebase'
155
			));
156
			$fields->push(new LiteralField(
157
				'Warning',
158
				sprintf('<p class="message">%s</p>',
159
					_t(
160
						'RemoveOrphanedPagesTask.DELETEWARNING',
161
						'Warning: These operations are not reversible. Please handle with care.'
162
					)
163
				)
164
			));
165
		} else {
166
			$fields->push(new LiteralField(
167
				'NotFoundLabel',
168
				sprintf(
169
					'<p class="message">%s</p>',
170
					_t('RemoveOrphanedPagesTask.NONEFOUND', 'No orphans found')
171
				)
172
			));
173
		}
174
175
		$form = new Form(
176
			$this,
177
			'SilverStripe\\Forms\\Form',
178
			$fields,
179
			new FieldList(
180
				new FormAction('doSubmit', _t('RemoveOrphanedPagesTask.BUTTONRUN', 'Run'))
181
			)
182
		);
183
184
		if(!$orphans || !$orphans->count()) {
185
			$form->makeReadonly();
186
		}
187
188
		return $form;
189
	}
190
191
	public function run($request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
192
		// @todo Merge with BuildTask functionality
193
	}
194
195
	public function doSubmit($data, $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
196
		set_time_limit(60*10); // 10 minutes
197
198
		if(!isset($data['OrphanIDs']) || !isset($data['OrphanOperation'])) return false;
199
200
		$successIDs = null;
201
		switch($data['OrphanOperation']) {
202
			case 'remove':
203
				$successIDs = $this->removeOrphans($data['OrphanIDs']);
204
				break;
205
			case 'rebase':
206
				$successIDs = $this->rebaseOrphans($data['OrphanIDs']);
207
				break;
208
			default:
209
				user_error(sprintf("Unknown operation: '%s'", $data['OrphanOperation']), E_USER_ERROR);
210
		}
211
212
		$content = '';
213
		if($successIDs) {
214
			$content .= "<ul>";
215
			foreach($successIDs as $id => $label) {
216
				$content .= sprintf('<li>%s</li>', $label);
217
			}
218
			$content .= "</ul>";
219
		} else {
220
			$content = _t('RemoveOrphanedPagesTask.NONEREMOVED', 'None removed');
221
		}
222
223
		return $this->customise(array(
224
			'Content' => $content,
225
			'Form' => ' '
226
		))->renderWith('BlankPage');
227
	}
228
229
	protected function removeOrphans($orphanIDs) {
230
		$removedOrphans = array();
231
		$orphanBaseTable = DataObject::getSchema()->baseDataTable($this->orphanedSearchClass);
232
		foreach($orphanIDs as $id) {
233
			/** @var SiteTree $stageRecord */
234
			$stageRecord = Versioned::get_one_by_stage(
235
				$this->orphanedSearchClass,
236
				Versioned::DRAFT,
237
				array("\"$orphanBaseTable\".\"ID\"" => $id)
0 ignored issues
show
Documentation introduced by
array("\"{$orphanBaseTable}\".\"ID\"" => $id) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
238
			);
239
			if($stageRecord) {
240
				$removedOrphans[$stageRecord->ID] = sprintf('Removed %s (#%d) from Stage', $stageRecord->Title, $stageRecord->ID);
241
				$stageRecord->delete();
242
				$stageRecord->destroy();
243
				unset($stageRecord);
244
			}
245
			/** @var SiteTree $liveRecord */
246
			$liveRecord = Versioned::get_one_by_stage(
247
				$this->orphanedSearchClass,
248
				Versioned::LIVE,
249
				array("\"$orphanBaseTable\".\"ID\"" => $id)
0 ignored issues
show
Documentation introduced by
array("\"{$orphanBaseTable}\".\"ID\"" => $id) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
250
			);
251
			if($liveRecord) {
252
				$removedOrphans[$liveRecord->ID] = sprintf('Removed %s (#%d) from Live', $liveRecord->Title, $liveRecord->ID);
253
				$liveRecord->doUnpublish();
0 ignored issues
show
Documentation Bug introduced by
The method doUnpublish does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
254
				$liveRecord->destroy();
255
				unset($liveRecord);
256
			}
257
		}
258
259
		return $removedOrphans;
260
	}
261
262
	protected function rebaseHolderTitle() {
263
		return sprintf('Rebased Orphans (%s)', date('d/m/Y g:ia', time()));
264
	}
265
266
	protected function rebaseOrphans($orphanIDs) {
267
		$holder = new SiteTree();
268
		$holder->ShowInMenus = 0;
269
		$holder->ShowInSearch = 0;
270
		$holder->ParentID = 0;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\CMS\Model\SiteTree>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
271
		$holder->Title = $this->rebaseHolderTitle();
272
		$holder->write();
273
274
		$removedOrphans = array();
275
		$orphanBaseTable = DataObject::getSchema()->baseDataTable($this->orphanedSearchClass);
276
		foreach($orphanIDs as $id) {
277
			/** @var SiteTree $stageRecord */
278
			$stageRecord = Versioned::get_one_by_stage(
279
				$this->orphanedSearchClass,
280
				'Stage',
281
				array("\"$orphanBaseTable\".\"ID\"" => $id)
0 ignored issues
show
Documentation introduced by
array("\"{$orphanBaseTable}\".\"ID\"" => $id) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
282
			);
283
			if($stageRecord) {
284
				$removedOrphans[$stageRecord->ID] = sprintf('Rebased %s (#%d)', $stageRecord->Title, $stageRecord->ID);
285
				$stageRecord->ParentID = $holder->ID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\CMS\Model\SiteTree>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
286
				$stageRecord->ShowInMenus = 0;
287
				$stageRecord->ShowInSearch = 0;
288
				$stageRecord->write();
289
				$stageRecord->doUnpublish();
0 ignored issues
show
Documentation Bug introduced by
The method doUnpublish does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
290
				$stageRecord->destroy();
291
				//unset($stageRecord);
292
			}
293
			/** @var SiteTree $liveRecord */
294
			$liveRecord = Versioned::get_one_by_stage(
295
				$this->orphanedSearchClass,
296
				'Live',
297
				array("\"$orphanBaseTable\".\"ID\"" => $id)
0 ignored issues
show
Documentation introduced by
array("\"{$orphanBaseTable}\".\"ID\"" => $id) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
298
			);
299
			if($liveRecord) {
300
				$removedOrphans[$liveRecord->ID] = sprintf('Rebased %s (#%d)', $liveRecord->Title, $liveRecord->ID);
301
				$liveRecord->ParentID = $holder->ID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\CMS\Model\SiteTree>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
302
				$liveRecord->ShowInMenus = 0;
303
				$liveRecord->ShowInSearch = 0;
304
				$liveRecord->write();
305
				if(!$stageRecord) {
306
					$liveRecord->doRestoreToStage();
307
				}
308
				$liveRecord->doUnpublish();
0 ignored issues
show
Documentation Bug introduced by
The method doUnpublish does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
309
				$liveRecord->destroy();
310
				unset($liveRecord);
311
			}
312
			if($stageRecord) {
313
				unset($stageRecord);
314
			}
315
		}
316
317
		return $removedOrphans;
318
	}
319
320
	/**
321
	 * Gets all orphans from "Stage" and "Live" stages.
322
	 *
323
	 * @param string $class
324
	 * @param array $filter
325
	 * @param string $sort
326
	 * @param string $join
327
	 * @param int|array $limit
328
	 * @return SS_List
329
	 */
330
	public function getOrphanedPages($class = 'SilverStripe\\CMS\\Model\\SiteTree', $filter = array(), $sort = null, $join = null, $limit = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $join is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
331
		// Alter condition
332
		$table = DataObject::getSchema()->tableName($class);
333
		if(empty($filter)) {
334
			$where = array();
335
		} elseif(is_array($filter)) {
336
			$where = $filter;
337
		} else {
338
			$where = array($filter);
339
		}
340
		$where[] = array("\"{$table}\".\"ParentID\" != ?" => 0);
341
		$where[] = '"Parents"."ID" IS NULL';
342
343
		$orphans = new ArrayList();
344
		foreach(array(Versioned::DRAFT, Versioned::LIVE) as $stage) {
345
			$table .= ($stage == Versioned::LIVE) ? '_Live' : '';
346
			$stageOrphans = Versioned::get_by_stage(
347
				$class,
348
				$stage,
349
				$where,
0 ignored issues
show
Documentation introduced by
$where is of type array<integer,array<stri...nteger,integer>|string>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
350
				$sort,
351
				null,
352
				$limit
0 ignored issues
show
Bug introduced by
It seems like $limit defined by parameter $limit on line 330 can also be of type array; however, SilverStripe\ORM\Version...rsioned::get_by_stage() does only seem to accept integer|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
353
			)->leftJoin($table, "\"$table\".\"ParentID\" = \"Parents\".\"ID\"", "Parents");
354
			$orphans->merge($stageOrphans);
355
		}
356
357
		$orphans->removeDuplicates();
358
359
		return $orphans;
360
	}
361
}
362