Completed
Pull Request — master (#1415)
by Damian
02:39
created

CMSMain::ShowSwitchView()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * The main "content" area of the CMS.
4
 *
5
 * This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main
6
 * admin menu.
7
 *
8
 * @package cms
9
 * @subpackage controller
10
 * @todo Create some base classes to contain the generic functionality that will be replicated.
11
 */
12
class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionProvider {
13
14
	private static $url_segment = 'pages';
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...
15
16
	private static $url_rule = '/$Action/$ID/$OtherID';
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...
17
18
	// Maintain a lower priority than other administration sections
19
	// so that Director does not think they are actions of CMSMain
20
	private static $url_priority = 39;
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...
21
22
	private static $menu_title = 'Edit Page';
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...
23
24
	private static $menu_priority = 10;
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...
25
26
	private static $tree_class = "SiteTree";
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...
27
28
	private static $subitem_class = "Member";
29
30
	/**
31
	 * Amount of results showing on a single page.
32
	 *
33
	 * @config
34
	 * @var int
35
	 */
36
	private static $page_length = 15;
37
38
	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...
39
		'archive',
40
		'buildbrokenlinks',
41
		'deleteitems',
42
		'DeleteItemsForm',
43
		'dialog',
44
		'duplicate',
45
		'duplicatewithchildren',
46
		'publishall',
47
		'publishitems',
48
		'PublishItemsForm',
49
		'submit',
50
		'EditForm',
51
		'SearchForm',
52
		'SiteTreeAsUL',
53
		'getshowdeletedsubtree',
54
		'batchactions',
55
		'treeview',
56
		'listview',
57
		'ListViewForm',
58
		'childfilter',
59
	);
60
61
	public function init() {
62
		// set reading lang
63
		if(SiteTree::has_extension('Translatable') && !$this->getRequest()->isAjax()) {
64
			Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SiteTree')));
65
		}
66
67
		parent::init();
68
69
		Requirements::css(CMS_DIR . '/css/screen.css');
70
		Requirements::customCSS($this->generatePageIconsCss());
0 ignored issues
show
Documentation Bug introduced by
The method generatePageIconsCss does not exist on object<CMSMain>? 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...
71
		Requirements::add_i18n_javascript(CMS_DIR . '/javascript/lang', false, true);
72
		Requirements::javascript(CMS_DIR . '/javascript/dist/bundle-lib.js', [
73
			'provides' => [
74
				CMS_DIR . '/javascript/dist/CMSMain.AddForm.js',
75
				CMS_DIR . '/javascript/dist/CMSMain.EditForm.js',
76
				CMS_DIR . '/javascript/dist/CMSMain.js',
77
				CMS_DIR . '/javascript/dist/CMSMain.Tree.js',
78
				CMS_DIR . '/javascript/dist/CMSPageHistoryController.js',
79
				CMS_DIR . '/javascript/dist/RedirectorPage.js',
80
				CMS_DIR . '/javascript/dist/SilverStripeNavigator.js',
81
				CMS_DIR . '/javascript/dist/SiteTreeURLSegmentField.js'
82
			]
83
		]);
84
85
		CMSBatchActionHandler::register('publish', 'CMSBatchAction_Publish');
86
		CMSBatchActionHandler::register('unpublish', 'CMSBatchAction_Unpublish');
87
		CMSBatchActionHandler::register('delete', 'CMSBatchAction_Delete');
88
		CMSBatchActionHandler::register('archive', 'CMSBatchAction_Archive');
89
		CMSBatchActionHandler::register('restore', 'CMSBatchAction_Restore');
90
	}
91
92
	public function index($request) {
93
		// In case we're not showing a specific record, explicitly remove any session state,
94
		// to avoid it being highlighted in the tree, and causing an edit form to show.
95
		if(!$request->param('Action')) $this->setCurrentPageId(null);
96
97
		return parent::index($request);
98
	}
99
100
	public function getResponseNegotiator() {
101
		$negotiator = parent::getResponseNegotiator();
102
		$controller = $this;
103
		$negotiator->setCallback('ListViewForm', function() use(&$controller) {
104
			return $controller->ListViewForm()->forTemplate();
105
		});
106
		return $negotiator;
107
	}
108
109
	/**
110
	 * If this is set to true, the "switchView" context in the
111
	 * template is shown, with links to the staging and publish site.
112
	 *
113
	 * @return boolean
114
	 */
115
	public function ShowSwitchView() {
116
		return true;
117
	}
118
119
	/**
120
	 * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
121
	 * to switch view also for archived versions.
122
	 */
123
	public function SwitchView($page = null) {
124
		if(!$page) {
125
			$page = $this->currentPage();
126
		}
127
128
		if($page) {
129
			$nav = SilverStripeNavigator::get_for_record($page);
130
			return $nav['items'];
131
		}
132
	}
133
134
	//------------------------------------------------------------------------------------------//
135
	// Main controllers
136
137
	//------------------------------------------------------------------------------------------//
138
	// Main UI components
139
140
	/**
141
	 * Override {@link LeftAndMain} Link to allow blank URL segment for CMSMain.
142
	 *
143
	 * @param string|null $action Action to link to.
144
	 * @return string
145
	 */
146
	public function Link($action = null) {
147
		$link = Controller::join_links(
148
			$this->stat('url_base', true),
0 ignored issues
show
Unused Code introduced by
The call to CMSMain::stat() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
149
			$this->stat('url_segment', true), // in case we want to change the segment
0 ignored issues
show
Unused Code introduced by
The call to CMSMain::stat() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
150
			'/', // trailing slash needed if $action is null!
151
			"$action"
152
		);
153
		$this->extend('updateLink', $link);
154
		return $link;
155
	}
156
157
	public function LinkPages() {
158
		return singleton('CMSPagesController')->Link();
159
	}
160
161
	public function LinkPagesWithSearch() {
162
		return $this->LinkWithSearch($this->LinkPages());
163
	}
164
165
	public function LinkTreeView() {
166
		return $this->LinkWithSearch(singleton('CMSMain')->Link('treeview'));
167
	}
168
169
	public function LinkListView() {
170
		return $this->LinkWithSearch(singleton('CMSMain')->Link('listview'));
171
	}
172
173
	public function LinkGalleryView() {
174
		return $this->LinkWithSearch(singleton('CMSMain')->Link('galleryview'));
175
	}
176
177
	public function LinkPageEdit($id = null) {
178
		if(!$id) $id = $this->currentPageID();
179
		return $this->LinkWithSearch(
180
			Controller::join_links(singleton('CMSPageEditController')->Link('show'), $id)
181
		);
182
	}
183
184 View Code Duplication
	public function LinkPageSettings() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
185
		if($id = $this->currentPageID()) {
186
			return $this->LinkWithSearch(
187
				Controller::join_links(singleton('CMSPageSettingsController')->Link('show'), $id)
188
			);
189
		}
190
	}
191
192 View Code Duplication
	public function LinkPageHistory() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
		if($id = $this->currentPageID()) {
194
			return $this->LinkWithSearch(
195
				Controller::join_links(singleton('CMSPageHistoryController')->Link('show'), $id)
196
			);
197
		}
198
	}
199
200
	public function LinkWithSearch($link) {
201
		// Whitelist to avoid side effects
202
		$params = array(
203
			'q' => (array)$this->getRequest()->getVar('q'),
204
			'ParentID' => $this->getRequest()->getVar('ParentID')
205
		);
206
		$link = Controller::join_links(
207
			$link,
208
			array_filter(array_values($params)) ? '?' . http_build_query($params) : null
209
		);
210
		$this->extend('updateLinkWithSearch', $link);
211
		return $link;
212
	}
213
214
	public function LinkPageAdd($extra = null, $placeholders = null) {
215
		$link = singleton("CMSPageAddController")->Link();
216
		$this->extend('updateLinkPageAdd', $link);
217
218
		if($extra) {
219
			$link = Controller::join_links ($link, $extra);
220
		}
221
222
		if($placeholders) {
223
			$link .= (strpos($link, '?') === false ? "?$placeholders" : "&amp;$placeholders");
224
		}
225
226
		return $link;
227
	}
228
229
	/**
230
	 * @return string
231
	 */
232
	public function LinkPreview() {
233
		$record = $this->getRecord($this->currentPageID());
234
		$baseLink = Director::absoluteBaseURL();
235
		if ($record && $record instanceof Page) {
0 ignored issues
show
Bug introduced by
The class Page does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
236
			// if we are an external redirector don't show a link
237
			if ($record instanceof RedirectorPage && $record->RedirectionType == 'External') {
238
				$baseLink = false;
239
			}
240
			else {
241
				$baseLink = $record->Link('?stage=Stage');
242
			}
243
		}
244
		return $baseLink;
245
	}
246
247
	/**
248
	 * Return the entire site tree as a nested set of ULs
249
	 */
250
	public function SiteTreeAsUL() {
251
		// Pre-cache sitetree version numbers for querying efficiency
252
		Versioned::prepopulate_versionnumber_cache("SiteTree", "Stage");
253
		Versioned::prepopulate_versionnumber_cache("SiteTree", "Live");
254
		$html = $this->getSiteTreeFor($this->stat('tree_class'));
255
256
		$this->extend('updateSiteTreeAsUL', $html);
257
258
		return $html;
259
	}
260
261
	/**
262
	 * @return boolean
263
	 */
264
	public function TreeIsFiltered() {
265
		$query = $this->getRequest()->getVar('q');
266
267
		if (!$query || (count($query) === 1 && isset($query['FilterClass']) && $query['FilterClass'] === 'CMSSiteTreeFilter_Search')) {
268
			return false;
269
		}
270
271
		return true;
272
	}
273
274
	public function ExtraTreeTools() {
275
		$html = '';
276
		$this->extend('updateExtraTreeTools', $html);
277
		return $html;
278
	}
279
280
	/**
281
	 * Returns a Form for page searching for use in templates.
282
	 *
283
	 * Can be modified from a decorator by a 'updateSearchForm' method
284
	 *
285
	 * @return Form
286
	 */
287
	public function SearchForm() {
288
		// Create the fields
289
		$content = new TextField('q[Term]', _t('CMSSearch.FILTERLABELTEXT', 'Search'));
290
		$dateHeader = new HeaderField('q[Date]', _t('CMSSearch.PAGEFILTERDATEHEADING', 'Last edited'), 4);
291
		$dateFrom = new DateField(
292
			'q[LastEditedFrom]',
293
			_t('CMSSearch.FILTERDATEFROM', 'From')
294
		);
295
		$dateFrom->setConfig('showcalendar', true);
296
		$dateTo = new DateField(
297
			'q[LastEditedTo]',
298
			_t('CMSSearch.FILTERDATETO', 'To')
299
		);
300
		$dateTo->setConfig('showcalendar', true);
301
		$pageFilter = new DropdownField(
302
			'q[FilterClass]',
303
			_t('CMSMain.PAGES', 'Page status'),
304
			CMSSiteTreeFilter::get_all_filters()
305
		);
306
		$pageClasses = new DropdownField(
307
			'q[ClassName]',
308
			_t('CMSMain.PAGETYPEOPT', 'Page type', 'Dropdown for limiting search to a page type'),
309
			$this->getPageTypes()
310
		);
311
		$pageClasses->setEmptyString(_t('CMSMain.PAGETYPEANYOPT','Any'));
312
313
		// Group the Datefields
314
		$dateGroup = new FieldGroup(
315
			$dateHeader,
316
			$dateFrom,
317
			$dateTo
318
		);
319
		$dateGroup->setFieldHolderTemplate('FieldGroup_DefaultFieldHolder')->addExtraClass('stacked');
320
321
		// Create the Field list
322
		$fields = new FieldList(
323
			$content,
324
			$dateGroup,
325
			$pageFilter,
326
			$pageClasses
327
		);
328
329
		// Create the Search and Reset action
330
		$actions = new FieldList(
331
			FormAction::create('doSearch',  _t('CMSMain_left_ss.APPLY_FILTER', 'Search'))
332
				->addExtraClass('ss-ui-action-constructive'),
333
			Object::create('ResetFormAction', 'clear', _t('CMSMain_left_ss.CLEAR_FILTER', 'Clear'))
334
		);
335
336
		// Use <button> to allow full jQuery UI styling on the all of the Actions
337
		foreach($actions->dataFields() as $action) {
338
			$action->setUseButtonTag(true);
339
		}
340
341
		// Create the form
342
		$form = Form::create($this, 'SearchForm', $fields, $actions)
343
			->addExtraClass('cms-search-form')
344
			->setFormMethod('GET')
345
			->setFormAction($this->Link())
346
			->disableSecurityToken()
347
			->unsetValidator();
348
349
		// Load the form with previously sent search data
350
		$form->loadDataFrom($this->getRequest()->getVars());
351
352
		// Allow decorators to modify the form
353
		$this->extend('updateSearchForm', $form);
354
355
		return $form;
356
	}
357
358
	/**
359
	 * Returns a sorted array suitable for a dropdown with pagetypes and their translated name
360
	 *
361
	 * @return array
362
	 */
363
	protected function getPageTypes() {
364
		$pageTypes = array();
365
		foreach(SiteTree::page_type_classes() as $pageTypeClass) {
366
			$pageTypes[$pageTypeClass] = _t($pageTypeClass.'.SINGULARNAME', $pageTypeClass);
367
		}
368
		asort($pageTypes);
369
		return $pageTypes;
370
	}
371
372
	public function doSearch($data, $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $data 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...
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...
373
		return $this->getsubtree($this->getRequest());
374
	}
375
376
	/**
377
	 * @param bool $unlinked
378
	 * @return ArrayList
379
	 */
380
	public function Breadcrumbs($unlinked = false) {
381
		$items = parent::Breadcrumbs($unlinked);
382
383
		if($items->count() > 1) {
384
			// Specific to the SiteTree admin section, we never show the cms section and current
385
			// page in the same breadcrumbs block.
386
			$items->shift();
387
		}
388
389
		return $items;
390
	}
391
392
	/**
393
	 * Create serialized JSON string with site tree hints data to be injected into
394
	 * 'data-hints' attribute of root node of jsTree.
395
	 *
396
	 * @return string Serialized JSON
397
	 */
398
	public function SiteTreeHints() {
399
		$json = '';
0 ignored issues
show
Unused Code introduced by
$json is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
400
		$classes = SiteTree::page_type_classes();
401
402
	 	$cacheCanCreate = array();
403
	 	foreach($classes as $class) $cacheCanCreate[$class] = singleton($class)->canCreate();
404
405
	 	// Generate basic cache key. Too complex to encompass all variations
406
	 	$cache = SS_Cache::factory('CMSMain_SiteTreeHints');
407
	 	$cacheKey = md5(implode('_', array(Member::currentUserID(), implode(',', $cacheCanCreate), implode(',', $classes))));
408
	 	if($this->getRequest()->getVar('flush')) $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
409
	 	$json = $cache->load($cacheKey);
410
	 	if(!$json) {
411
			$def['Root'] = array();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$def was never initialized. Although not strictly required by PHP, it is generally a good practice to add $def = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
412
			$def['Root']['disallowedChildren'] = array();
413
414
			// Contains all possible classes to support UI controls listing them all,
415
			// such as the "add page here" context menu.
416
			$def['All'] = array();
417
418
			// Identify disallows and set globals
419
			foreach($classes as $class) {
420
				$obj = singleton($class);
421
				if($obj instanceof HiddenClass) continue;
422
423
				// Name item
424
				$def['All'][$class] = array(
425
					'title' => $obj->i18n_singular_name()
426
				);
427
428
				// Check if can be created at the root
429
				$needsPerm = $obj->stat('need_permission');
430
				if(
431
					!$obj->stat('can_be_root')
432
					|| (!array_key_exists($class, $cacheCanCreate) || !$cacheCanCreate[$class])
433
					|| ($needsPerm && !$this->can($needsPerm))
434
				) {
435
					$def['Root']['disallowedChildren'][] = $class;
436
				}
437
438
				// Hint data specific to the class
439
				$def[$class] = array();
440
441
				$defaultChild = $obj->defaultChild();
442
				if($defaultChild !== 'Page' && $defaultChild !== null) {
443
					$def[$class]['defaultChild'] = $defaultChild;
444
				}
445
446
				$defaultParent = $obj->defaultParent();
447
				if ($defaultParent !== 1 && $defaultParent !== null) {
448
					$def[$class]['defaultParent'] = $defaultParent;
449
				}
450
			}
451
452
			$this->extend('updateSiteTreeHints', $def);
453
454
			$json = Convert::raw2json($def);
455
			$cache->save($json, $cacheKey);
456
		}
457
		return $json;
458
	}
459
460
	/**
461
	 * Populates an array of classes in the CMS
462
	 * which allows the user to change the page type.
463
	 *
464
	 * @return SS_List
465
	 */
466
	public function PageTypes() {
467
		$classes = SiteTree::page_type_classes();
468
469
		$result = new ArrayList();
470
471
		foreach($classes as $class) {
472
			$instance = singleton($class);
473
474
			if($instance instanceof HiddenClass) continue;
475
476
			// skip this type if it is restricted
477
			if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue;
478
479
			$addAction = $instance->i18n_singular_name();
480
481
			// Get description (convert 'Page' to 'SiteTree' for correct localization lookups)
482
			$description = _t((($class == 'Page') ? 'SiteTree' : $class) . '.DESCRIPTION');
483
484
			if(!$description) {
485
				$description = $instance->uninherited('description');
486
			}
487
488
			if($class == 'Page' && !$description) {
489
				$description = singleton('SiteTree')->uninherited('description');
490
			}
491
492
			$result->push(new ArrayData(array(
493
				'ClassName' => $class,
494
				'AddAction' => $addAction,
495
				'Description' => $description,
496
				// TODO Sprite support
497
				'IconURL' => $instance->stat('icon'),
498
				'Title' => singleton($class)->i18n_singular_name(),
499
			)));
500
		}
501
502
		$result = $result->sort('AddAction');
503
504
		return $result;
505
	}
506
507
	/**
508
	 * Get a database record to be managed by the CMS.
509
	 *
510
	 * @param int $id Record ID
511
	 * @param int $versionID optional Version id of the given record
512
	 * @return SiteTree
513
	 */
514
 	public function getRecord($id, $versionID = null) {
515
		$treeClass = $this->stat('tree_class');
516
517
		if($id instanceof $treeClass) {
518
			return $id;
519
		}
520
		else if($id && is_numeric($id)) {
521
			if($this->getRequest()->getVar('Version')) {
522
				$versionID = (int) $this->getRequest()->getVar('Version');
523
			}
524
525
			if($versionID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $versionID of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
526
				$record = Versioned::get_version($treeClass, $id, $versionID);
527
			} else {
528
				$record = DataObject::get_by_id($treeClass, $id);
529
			}
530
531
			// Then, try getting a record from the live site
532
			if(!$record) {
533
				// $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id");
534
				Versioned::set_stage(Versioned::LIVE);
0 ignored issues
show
Bug introduced by
The method set_stage() does not seem to exist on object<Versioned>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
535
				singleton($treeClass)->flushCache();
536
537
				$record = DataObject::get_by_id($treeClass, $id);
538
				if($record) Versioned::set_reading_mode('');
539
			}
540
541
			// Then, try getting a deleted record
542
			if(!$record) {
543
				$record = Versioned::get_latest_version($treeClass, $id);
544
			}
545
546
			// Don't open a page from a different locale
547
			/** The record's Locale is saved in database in 2.4, and not related with Session,
548
			 *  we should not check their locale matches the Translatable::get_current_locale,
549
			 * 	here as long as we all the HTTPRequest is init with right locale.
550
			 *	This bit breaks the all FileIFrameField functions if the field is used in CMS
551
			 *  and its relevent ajax calles, like loading the tree dropdown for TreeSelectorField.
552
			 */
553
			/* if($record && SiteTree::has_extension('Translatable') && $record->Locale && $record->Locale != Translatable::get_current_locale()) {
554
				$record = null;
555
			}*/
556
557
			return $record;
558
559
		} else if(substr($id,0,3) == 'new') {
560
			return $this->getNewItem($id);
561
		}
562
	}
563
564
	/**
565
	 * @param int $id
566
	 * @param FieldList $fields
567
	 * @return Form
568
	 */
569
	public function getEditForm($id = null, $fields = null) {
570
		if(!$id) $id = $this->currentPageID();
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
571
		$form = parent::getEditForm($id);
572
573
		// TODO Duplicate record fetching (see parent implementation)
574
		$record = $this->getRecord($id);
575
		if($record && !$record->canView()) return Security::permissionFailure($this);
0 ignored issues
show
Bug Compatibility introduced by
The expression \Security::permissionFailure($this); of type SS_HTTPResponse|null adds the type SS_HTTPResponse to the return on line 575 which is incompatible with the return type documented by CMSMain::getEditForm of type Form.
Loading history...
576
577
		if(!$fields) $fields = $form->Fields();
578
		$actions = $form->Actions();
0 ignored issues
show
Unused Code introduced by
$actions is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
579
580
		if($record) {
581
			$deletedFromStage = $record->getIsDeletedFromStage();
582
583
			$fields->push($idField = new HiddenField("ID", false, $id));
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a null|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...
584
			// Necessary for different subsites
585
			$fields->push($liveLinkField = new HiddenField("AbsoluteLink", false, $record->AbsoluteLink()));
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a null|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...
586
			$fields->push($liveLinkField = new HiddenField("LiveLink"));
587
			$fields->push($stageLinkField = new HiddenField("StageLink"));
588
			$fields->push(new HiddenField("TreeTitle", false, $record->TreeTitle));
0 ignored issues
show
Documentation introduced by
The property TreeTitle does not exist on object<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...
Documentation introduced by
false is of type boolean, but the function expects a null|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...
589
590
			if($record->ID && is_numeric( $record->ID ) ) {
591
				$liveLink = $record->getAbsoluteLiveLink();
592
				if($liveLink) $liveLinkField->setValue($liveLink);
593
				if(!$deletedFromStage) {
594
					$stageLink = Controller::join_links($record->AbsoluteLink(), '?stage=Stage');
595
					if($stageLink) $stageLinkField->setValue($stageLink);
596
				}
597
			}
598
599
			// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
600
			if(in_array('CMSPreviewable', class_implements($record)) && !$fields->fieldByName('SilverStripeNavigator')) {
601
				$navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator());
602
				$navField->setAllowHTML(true);
603
				$fields->push($navField);
604
			}
605
606
			// getAllCMSActions can be used to completely redefine the action list
607
			if($record->hasMethod('getAllCMSActions')) {
608
				$actions = $record->getAllCMSActions();
0 ignored issues
show
Documentation Bug introduced by
The method getAllCMSActions does not exist on object<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...
609
			} else {
610
				$actions = $record->getCMSActions();
611
612
				// Find and remove action menus that have no actions.
613
				if ($actions && $actions->Count()) {
614
					$tabset = $actions->fieldByName('ActionMenus');
615
					if ($tabset) {
616
						foreach ($tabset->getChildren() as $tab) {
617
							if (!$tab->getChildren()->count()) {
618
								$tabset->removeByName($tab->getName());
619
							}
620
						}
621
					}
622
				}
623
			}
624
625
			// Use <button> to allow full jQuery UI styling
626
			$actionsFlattened = $actions->dataFields();
627
			if($actionsFlattened) foreach($actionsFlattened as $action) $action->setUseButtonTag(true);
628
629
			if($record->hasMethod('getCMSValidator')) {
630
				$validator = $record->getCMSValidator();
0 ignored issues
show
Documentation Bug introduced by
The method getCMSValidator does not exist on object<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...
Unused Code introduced by
$validator is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
631
			} else {
632
				$validator = new RequiredFields();
0 ignored issues
show
Unused Code introduced by
$validator is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
633
			}
634
635
			// TODO Can't merge $FormAttributes in template at the moment
636
			$form->addExtraClass('center ' . $this->BaseCSSClasses());
637
			// Set validation exemptions for specific actions
638
			$form->setValidationExemptActions(array('restore', 'revert', 'deletefromlive', 'delete', 'unpublish', 'rollback', 'doRollback'));
639
640
			// Announce the capability so the frontend can decide whether to allow preview or not.
641
			if(in_array('CMSPreviewable', class_implements($record))) {
642
				$form->addExtraClass('cms-previewable');
643
			}
644
645
			if(!$record->canEdit() || $deletedFromStage) {
646
				$readonlyFields = $form->Fields()->makeReadonly();
647
				$form->setFields($readonlyFields);
648
			}
649
650
			$this->extend('updateEditForm', $form);
651
			return $form;
652
		} else if($id) {
653
			$form = Form::create( $this, "EditForm", new FieldList(
654
				new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList()
655
			)->setHTMLID('Form_EditForm');
656
			return $form;
657
		}
658
	}
659
660
	/**
661
	 * @param SS_HTTPRequest $request
662
	 * @return string HTML
663
	 */
664
	public function treeview($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...
665
		return $this->renderWith($this->getTemplatesWithSuffix('_TreeView'));
666
	}
667
668
	/**
669
	 * @param SS_HTTPRequest $request
670
	 * @return string HTML
671
	 */
672
	public function listview($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...
673
		return $this->renderWith($this->getTemplatesWithSuffix('_ListView'));
674
	}
675
676
	/**
677
	 * Callback to request the list of page types allowed under a given page instance.
678
	 * Provides a slower but more precise response over SiteTreeHints
679
	 *
680
	 * @param SS_HTTPRequest $request
681
	 * @return SS_HTTPResponse
682
	 */
683
	public function childfilter($request) {
684
		// Check valid parent specified
685
		$parentID = $request->requestVar('ParentID');
686
		$parent = SiteTree::get()->byID($parentID);
687
		if(!$parent || !$parent->exists()) return $this->httpError(404);
688
689
		// Build hints specific to this class
690
		// Identify disallows and set globals
691
		$classes = SiteTree::page_type_classes();
692
		$disallowedChildren = array();
693
		foreach($classes as $class) {
694
			$obj = singleton($class);
695
			if($obj instanceof HiddenClass) continue;
696
697
			if(!$obj->canCreate(null, array('Parent' => $parent))) {
698
				$disallowedChildren[] = $class;
699
			}
700
		}
701
702
		$this->extend('updateChildFilter', $disallowedChildren, $parentID);
703
		return $this
704
			->getResponse()
705
			->addHeader('Content-Type', 'application/json; charset=utf-8')
706
			->setBody(Convert::raw2json($disallowedChildren));
707
	}
708
709
	/**
710
	 * Safely reconstruct a selected filter from a given set of query parameters
711
	 *
712
	 * @param array $params Query parameters to use
713
	 * @return CMSSiteTreeFilter The filter class, or null if none present
714
	 * @throws InvalidArgumentException if invalid filter class is passed.
715
	 */
716
	protected function getQueryFilter($params) {
717
		if(empty($params['FilterClass'])) return null;
718
		$filterClass = $params['FilterClass'];
719
		if(!is_subclass_of($filterClass, 'CMSSiteTreeFilter')) {
720
			throw new InvalidArgumentException("Invalid filter class passed: {$filterClass}");
721
		}
722
		return $filterClass::create($params);
723
	}
724
725
	/**
726
	 * Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page
727
	 * defaulting to no filter and show all pages in first level.
728
	 * Doubles as search results, if any search parameters are set through {@link SearchForm()}.
729
	 *
730
	 * @param array $params Search filter criteria
731
	 * @param int $parentID Optional parent node to filter on (can't be combined with other search criteria)
732
	 * @return SS_List
733
	 * @throws InvalidArgumentException if invalid filter class is passed.
734
	 */
735
	public function getList($params = array(), $parentID = 0) {
736
		if($filter = $this->getQueryFilter($params)) {
737
			return $filter->getFilteredPages();
738
		} else {
739
			$list = DataList::create($this->stat('tree_class'));
740
			$parentID = is_numeric($parentID) ? $parentID : 0;
741
			return $list->filter("ParentID", $parentID);
742
		}
743
	}
744
745
	public function ListViewForm() {
746
		$params = $this->getRequest()->requestVar('q');
747
		$list = $this->getList($params, $parentID = $this->getRequest()->requestVar('ParentID'));
748
		$gridFieldConfig = GridFieldConfig::create()->addComponents(
749
			new GridFieldSortableHeader(),
750
			new GridFieldDataColumns(),
751
			new GridFieldPaginator(self::config()->page_length)
752
		);
753
		if($parentID){
754
			$gridFieldConfig->addComponent(
755
				GridFieldLevelup::create($parentID)
756
					->setLinkSpec('?ParentID=%d&view=list')
757
					->setAttributes(array('data-pjax' => 'ListViewForm,Breadcrumbs'))
758
			);
759
		}
760
		$gridField = new GridField('Page','Pages', $list, $gridFieldConfig);
761
		$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
762
763
		// Don't allow navigating into children nodes on filtered lists
764
		$fields = array(
765
			'getTreeTitle' => _t('SiteTree.PAGETITLE', 'Page Title'),
766
			'singular_name' => _t('SiteTree.PAGETYPE'),
767
			'LastEdited' => _t('SiteTree.LASTUPDATED', 'Last Updated'),
768
		);
769
		$gridField->getConfig()->getComponentByType('GridFieldSortableHeader')->setFieldSorting(array('getTreeTitle' => 'Title'));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface GridFieldComponent as the method setFieldSorting() does only exist in the following implementations of said interface: GridFieldSortableHeader.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
770
		$gridField->getState()->ParentID = $parentID;
771
772
		if(!$params) {
773
			$fields = array_merge(array('listChildrenLink' => ''), $fields);
774
		}
775
776
		$columns->setDisplayFields($fields);
777
		$columns->setFieldCasting(array(
778
			'Created' => 'SS_Datetime->Ago',
779
			'LastEdited' => 'SS_Datetime->FormatFromSettings',
780
			'getTreeTitle' => 'HTMLText'
781
		));
782
783
		$controller = $this;
784
		$columns->setFieldFormatting(array(
785
			'listChildrenLink' => function($value, &$item) use($controller) {
786
				$num = $item ? $item->numChildren() : null;
787
				if($num) {
788
					return sprintf(
789
						'<a class="cms-panel-link list-children-link" data-pjax-target="ListViewForm,Breadcrumbs" href="%s">%s</a>',
790
						Controller::join_links(
791
							$controller->Link(),
792
							sprintf("?ParentID=%d&view=list", (int)$item->ID)
793
						),
794
						$num
795
					);
796
				}
797
			},
798
			'getTreeTitle' => function($value, &$item) use($controller) {
799
				return sprintf(
800
					'<a class="action-detail" href="%s">%s</a>',
801
					Controller::join_links(
802
						singleton('CMSPageEditController')->Link('show'),
803
						(int)$item->ID
804
					),
805
					$item->TreeTitle // returns HTML, does its own escaping
806
				);
807
			}
808
		));
809
810
		$negotiator = $this->getResponseNegotiator();
811
		$listview = Form::create(
812
			$this,
813
			'ListViewForm',
814
			new FieldList($gridField),
815
			new FieldList()
816
		)->setHTMLID('Form_ListViewForm');
817
		$listview->setAttribute('data-pjax-fragment', 'ListViewForm');
818 View Code Duplication
		$listview->setValidationResponseCallback(function() use ($negotiator, $listview) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
819
			$request = $this->getRequest();
820
			if($request->isAjax() && $negotiator) {
821
				$listview->setupFormErrors();
822
				$result = $listview->forTemplate();
823
824
				return $negotiator->respond($request, array(
825
					'CurrentForm' => function() use($result) {
826
						return $result;
827
					}
828
				));
829
			}
830
		});
831
832
		$this->extend('updateListView', $listview);
833
834
		$listview->disableSecurityToken();
835
		return $listview;
836
	}
837
838
	public function currentPageID() {
839
		$id = parent::currentPageID();
840
841
		$this->extend('updateCurrentPageID', $id);
842
843
		return $id;
844
	}
845
846
	//------------------------------------------------------------------------------------------//
847
	// Data saving handlers
848
849
	/**
850
	 * Save and Publish page handler
851
	 *
852
	 * @param array $data
853
	 * @param Form $form
854
	 * @return SS_HTTPResponse
855
	 * @throws SS_HTTPResponse_Exception
856
	 */
857
	public function save($data, $form) {
858
		$className = $this->stat('tree_class');
859
860
		// Existing or new record?
861
		$id = $data['ID'];
862
		if(substr($id,0,3) != 'new') {
863
			/** @var SiteTree $record */
864
			$record = DataObject::get_by_id($className, $id);
865
			// Check edit permissions
866
			if($record && !$record->canEdit()) {
867
				return Security::permissionFailure($this);
868
			}
869
			if(!$record || !$record->ID) {
870
				throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
871
			}
872
		} else {
873
			if(!$className::singleton()->canCreate()) {
874
				return Security::permissionFailure($this);
875
			}
876
			$record = $this->getNewItem($id, false);
877
		}
878
879
		// Check publishing permissions
880
		$doPublish = !empty($data['publish']);
881
		if($record && $doPublish && !$record->canPublish()) {
882
			return Security::permissionFailure($this);
883
		}
884
885
		// TODO Coupling to SiteTree
886
		$record->HasBrokenLink = 0;
887
		$record->HasBrokenFile = 0;
888
889
		if (!$record->ObsoleteClassName) {
890
			$record->writeWithoutVersion();
891
		}
892
893
		// Update the class instance if necessary
894
		if(isset($data['ClassName']) && $data['ClassName'] != $record->ClassName) {
895
			$newClassName = $record->ClassName;
896
			// The records originally saved attribute was overwritten by $form->saveInto($record) before.
897
			// This is necessary for newClassInstance() to work as expected, and trigger change detection
898
			// on the ClassName attribute
899
			$record->setClassName($data['ClassName']);
900
			// Replace $record with a new instance
901
			$record = $record->newClassInstance($newClassName);
902
		}
903
904
		// save form data into record
905
		$form->saveInto($record);
906
		$record->write();
907
908
		// If the 'Save & Publish' button was clicked, also publish the page
909
		if ($doPublish) {
910
			$record->doPublish();
911
			$owned = $record->findOwned()->count();
912 View Code Duplication
			if($owned) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
913
				$message = _t(
914
					'CMSMain.PUBLISHED_OWNED',
915
					"Published '{title}' and {owned} other object(s) successfully.",
916
					['title' => $record->Title, 'owned' => $owned]
0 ignored issues
show
Documentation introduced by
array('title' => $record...tle, 'owned' => $owned) is of type array<string,?,{"title":"?","owned":"?"}>, 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...
917
				);
918
			} else {
919
				$message = _t(
920
					'CMSMain.PUBLISHED',
921
					"Published '{title}' successfully.",
922
					['title' => $record->Title]
0 ignored issues
show
Documentation introduced by
array('title' => $record->Title) is of type array<string,?,{"title":"?"}>, 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...
923
				);
924
			}
925
		} else {
926
			$message = _t(
927
				'CMSMain.SAVED',
928
				"Saved '{title}' successfully",
929
				['title' => $record->Title]
0 ignored issues
show
Documentation introduced by
array('title' => $record->Title) is of type array<string,?,{"title":"?"}>, 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...
930
			);
931
		}
932
933
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
934
		return $this->getResponseNegotiator()->respond($this->getRequest());
935
	}
936
937
	/**
938
	 * @uses LeftAndMainExtension->augmentNewSiteTreeItem()
939
	 */
940
	public function getNewItem($id, $setID = true) {
941
		$parentClass = $this->stat('tree_class');
942
		list($dummy, $className, $parentID, $suffix) = array_pad(explode('-',$id),4,null);
0 ignored issues
show
Unused Code introduced by
The assignment to $dummy is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
943
944
		if(!is_subclass_of($className, $parentClass) && strcasecmp($className, $parentClass) != 0) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $parentClass can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
945
			$response = Security::permissionFailure($this);
946
			if (!$response) {
947
				$response = $this->getResponse();
948
			}
949
			throw new SS_HTTPResponse_Exception($response);
950
		}
951
952
		$newItem = new $className();
953
954
		if( !$suffix ) {
955
			$sessionTag = "NewItems." . $parentID . "." . $className;
956
			if(Session::get($sessionTag)) {
957
				$suffix = '-' . Session::get($sessionTag);
958
				Session::set($sessionTag, Session::get($sessionTag) + 1);
959
			}
960
			else
961
				Session::set($sessionTag, 1);
962
963
				$id = $id . $suffix;
964
		}
965
966
		$newItem->Title = _t(
967
			'CMSMain.NEWPAGE',
968
			"New {pagetype}",'followed by a page type title',
969
			array('pagetype' => singleton($className)->i18n_singular_name())
0 ignored issues
show
Documentation introduced by
array('pagetype' => sing...->i18n_singular_name()) is of type array<string,?,{"pagetype":"?"}>, 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...
970
		);
971
		$newItem->ClassName = $className;
972
		$newItem->ParentID = $parentID;
973
974
		// DataObject::fieldExists only checks the current class, not the hierarchy
975
		// This allows the CMS to set the correct sort value
976
		if($newItem->castingHelper('Sort')) {
977
			$newItem->Sort = DB::prepared_query('SELECT MAX("Sort") FROM "SiteTree" WHERE "ParentID" = ?', array($parentID))->value() + 1;
978
		}
979
980
		if($setID) $newItem->ID = $id;
981
982
		# Some modules like subsites add extra fields that need to be set when the new item is created
983
		$this->extend('augmentNewSiteTreeItem', $newItem);
984
985
		return $newItem;
986
	}
987
988
	/**
989
	 * Actually perform the publication step
990
	 *
991
	 * @param Versioned|DataObject $record
992
	 * @return mixed
993
	 */
994
	public function performPublish($record) {
995
		if($record && !$record->canPublish()) {
0 ignored issues
show
Bug introduced by
The method canPublish does only exist in Versioned, but not in DataObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
996
			return Security::permissionFailure($this);
997
		}
998
999
		$record->doPublish();
0 ignored issues
show
Bug introduced by
The method doPublish does only exist in Versioned, but not in DataObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1000
	}
1001
1002
	/**
1003
	 * Reverts a page by publishing it to live.
1004
	 * Use {@link restorepage()} if you want to restore a page
1005
	 * which was deleted from draft without publishing.
1006
	 *
1007
	 * @uses SiteTree->doRevertToLive()
1008
	 *
1009
	 * @param array $data
1010
	 * @param Form $form
1011
	 * @return SS_HTTPResponse
1012
	 * @throws SS_HTTPResponse_Exception
1013
	 */
1014
	public function revert($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...
1015
		if(!isset($data['ID'])) {
1016
			throw new SS_HTTPResponse_Exception("Please pass an ID in the form content", 400);
1017
		}
1018
1019
		$id = (int) $data['ID'];
1020
		$restoredPage = Versioned::get_latest_version("SiteTree", $id);
1021
		if(!$restoredPage) {
1022
			throw new SS_HTTPResponse_Exception("SiteTree #$id not found", 400);
1023
		}
1024
1025
		/** @var SiteTree $record */
1026
		$record = Versioned::get_one_by_stage('SiteTree', 'Live', array(
0 ignored issues
show
Documentation introduced by
array('"SiteTree_Live"."ID"' => $id) is of type array<string,integer,{"\...e\".\"ID\"":"integer"}>, 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...
1027
			'"SiteTree_Live"."ID"' => $id
1028
		));
1029
1030
		// a user can restore a page without publication rights, as it just adds a new draft state
1031
		// (this action should just be available when page has been "deleted from draft")
1032
		if($record && !$record->canEdit()) {
1033
			return Security::permissionFailure($this);
1034
		}
1035
		if(!$record || !$record->ID) {
1036
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1037
		}
1038
1039
		$record->doRevertToLive();
0 ignored issues
show
Documentation Bug introduced by
The method doRevertToLive does not exist on object<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...
1040
		$owned = $record->findOwned()->count();
0 ignored issues
show
Documentation Bug introduced by
The method findOwned does not exist on object<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...
1041 View Code Duplication
		if($owned) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1042
			$message = _t(
1043
				'CMSMain.RESTORED_OWNED',
1044
				"Restored '{title}' and {owned} other object(s) successfully",
1045
				['title' => $record->Title, 'owned' => $owned]
0 ignored issues
show
Documentation introduced by
array('title' => $record...tle, 'owned' => $owned) is of type array<string,?,{"title":"string","owned":"?"}>, 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...
1046
			);
1047
		} else {
1048
			$message = _t(
1049
				'CMSMain.RESTORED',
1050
				"Restored '{title}' successfully",
1051
				['title' => $record->Title]
0 ignored issues
show
Documentation introduced by
array('title' => $record->Title) is of type array<string,string,{"title":"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...
1052
			);
1053
		}
1054
1055
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1056
		return $this->getResponseNegotiator()->respond($this->getRequest());
1057
	}
1058
1059
	/**
1060
	 * Delete the current page from draft stage.
1061
	 *
1062
	 * @see deletefromlive()
1063
	 *
1064
	 * @param array $data
1065
	 * @param Form $form
1066
	 * @return SS_HTTPResponse
1067
	 * @throws SS_HTTPResponse_Exception
1068
	 */
1069
	public function delete($data, $form) {
1070
		$id = $data['ID'];
1071
		$record = DataObject::get_by_id("SiteTree", $id);
1072
		if($record && !$record->canDelete()) {
1073
			return Security::permissionFailure();
1074
		}
1075
		if(!$record || !$record->ID) {
1076
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1077
		}
1078
1079
		// Delete record
1080
		$record->delete();
1081
1082
		$this->getResponse()->addHeader(
1083
			'X-Status',
1084
			rawurlencode(sprintf(_t('CMSMain.REMOVEDPAGEFROMDRAFT',"Removed '%s' from the draft site"), $record->Title))
1085
		);
1086
1087
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1088
		return $this->getResponseNegotiator()->respond($this->getRequest());
1089
	}
1090
1091
	/**
1092
	 * Delete this page from both live and stage
1093
	 *
1094
	 * @param array $data
1095
	 * @param Form $form
1096
	 * @return SS_HTTPResponse
1097
	 * @throws SS_HTTPResponse_Exception
1098
	 */
1099
	public function archive($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...
1100
		$id = $data['ID'];
1101
		/** @var SiteTree $record */
1102
		$record = DataObject::get_by_id("SiteTree", $id);
1103
		if(!$record || !$record->exists()) {
1104
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1105
		}
1106
		if(!$record->canArchive()) {
0 ignored issues
show
Documentation Bug introduced by
The method canArchive does not exist on object<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...
1107
			return Security::permissionFailure();
1108
		}
1109
1110
		// Count number of live owners affected by this change
1111
		$liveOwners = $record->findOwners()->filterByCallback(function($object) {
0 ignored issues
show
Documentation Bug introduced by
The method findOwners does not exist on object<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...
1112
			/** @var SiteTree $object */
1113
			return $object->isPublished();
0 ignored issues
show
Documentation Bug introduced by
The method isPublished does not exist on object<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...
1114
		})->count();
1115
1116
		// Archive record
1117
		$record->doArchive();
0 ignored issues
show
Documentation Bug introduced by
The method doArchive does not exist on object<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...
1118
		if($liveOwners) {
1119
			$message = _t(
1120
				'CMSMain.ARCHIVEDPAGE_OWNERS',
1121
				"Archived page '{title}', and removed {owners} objects from live stage.",
1122
				['title' => $record->Title, 'owners' => $liveOwners]
0 ignored issues
show
Documentation introduced by
array('title' => $record...owners' => $liveOwners) is of type array<string,?,{"title":"string","owners":"?"}>, 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...
1123
			);
1124
		} else {
1125
			$message = sprintf(_t('CMSMain.ARCHIVEDPAGE',"Archived page '%s'"), $record->Title);
1126
		}
1127
1128
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1129
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1130
		return $this->getResponseNegotiator()->respond($this->getRequest());
1131
	}
1132
1133
	public function publish($data, $form) {
1134
		$data['publish'] = '1';
1135
		return $this->save($data, $form);
1136
	}
1137
1138
	public function unpublish($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...
1139
		$className = $this->stat('tree_class');
1140
		/** @var SiteTree $record */
1141
		$record = DataObject::get_by_id($className, $data['ID']);
1142
1143
		if($record && !$record->canUnpublish()) {
0 ignored issues
show
Documentation Bug introduced by
The method canUnpublish does not exist on object<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...
1144
			return Security::permissionFailure($this);
1145
		}
1146 View Code Duplication
		if(!$record || !$record->ID) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1147
			throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
1148
		}
1149
1150
		// Count number of live owners affected by this change
1151
		$liveOwners = $record->findOwners()->filterByCallback(function($object) {
0 ignored issues
show
Documentation Bug introduced by
The method findOwners does not exist on object<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...
1152
			/** @var SiteTree $object */
1153
			return $object->isPublished();
0 ignored issues
show
Documentation Bug introduced by
The method isPublished does not exist on object<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...
1154
		})->count();
1155
1156
		// Unpublish this object
1157
		$record->doUnpublish();
0 ignored issues
show
Documentation Bug introduced by
The method doUnpublish does not exist on object<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...
1158
1159
		if($liveOwners) {
1160
			$message = _t(
1161
				'CMSMain.REMOVEDPAGE',
1162
				"Removed '{title}' and {owners} other object(s) from the published site.",
1163
				['title' => $record->Title]
0 ignored issues
show
Documentation introduced by
array('title' => $record->Title) is of type array<string,string,{"title":"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...
1164
			);
1165
		} else {
1166
			$message = _t(
1167
				'CMSMain.REMOVEDPAGE',
1168
				"Removed '{title}' from the published site.",
1169
				['title' => $record->Title]
0 ignored issues
show
Documentation introduced by
array('title' => $record->Title) is of type array<string,string,{"title":"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...
1170
			);
1171
		}
1172
1173
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1174
		return $this->getResponseNegotiator()->respond($this->getRequest());
1175
	}
1176
1177
	/**
1178
	 * @return array
1179
	 */
1180
	public function rollback() {
1181
		return $this->doRollback(array(
1182
			'ID' => $this->currentPageID(),
1183
			'Version' => $this->getRequest()->param('VersionID')
1184
		), null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<Form>.

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...
1185
	}
1186
1187
	/**
1188
	 * Rolls a site back to a given version ID
1189
	 *
1190
	 * @param array $data
1191
	 * @param Form $form
1192
	 * @return SS_HTTPResponse
1193
	 */
1194
	public function doRollback($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...
1195
		$this->extend('onBeforeRollback', $data['ID']);
1196
1197
		$id = (isset($data['ID'])) ? (int) $data['ID'] : null;
1198
		$version = (isset($data['Version'])) ? (int) $data['Version'] : null;
1199
1200
		/** @var DataObject|Versioned $record */
1201
		$record = DataObject::get_by_id($this->stat('tree_class'), $id);
1202
		if($record && !$record->canEdit()) {
0 ignored issues
show
Bug introduced by
The call to canEdit() misses a required argument $member.

This check looks for function calls that miss required arguments.

Loading history...
1203
			return Security::permissionFailure($this);
1204
		}
1205
1206
		if($version) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $version of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1207
			$record->doRollbackTo($version);
0 ignored issues
show
Bug introduced by
The method doRollbackTo does only exist in Versioned, but not in DataObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1208
			$record = DataObject::get_by_id($this->stat('tree_class'), $id);
1209
			$owned = $record->findOwned()->count();
1210
			if($owned) {
1211
				$message = _t(
1212
					'CMSMain.ROLLEDBACKVERSION_OWNED',
1213
					"Rolled back to version #{version} with {owned} other object(s).",
1214
					['version' => $version, 'owned' => $owned]
0 ignored issues
show
Documentation introduced by
array('version' => $version, 'owned' => $owned) is of type array<string,?,{"version":"integer","owned":"?"}>, 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...
1215
				);
1216
			} else {
1217
				$message = _t(
1218
					'CMSMain.ROLLEDBACKVERSIONv2',
1219
					"Rolled back to version #{version}.",
1220
					['version' => $version]
0 ignored issues
show
Documentation introduced by
array('version' => $version) is of type array<string,integer,{"version":"integer"}>, 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...
1221
				);
1222
			}
1223
		} else {
1224
			$record->doRevertToLive();
1225
			$record = DataObject::get_by_id($this->stat('tree_class'), $id);
1226
			$owned = $record->findOwned()->count();
1227
			if($owned) {
1228
				$message = _t(
1229
					'CMSMain.ROLLEDBACKPUB_OWNED',
1230
					"Rolled back to published version with {owned} other object(s).",
1231
					['owned' => $owned]
0 ignored issues
show
Documentation introduced by
array('owned' => $owned) is of type array<string,?,{"owned":"?"}>, 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...
1232
				);
1233
			} else {
1234
				$message = _t(
1235
					'CMSMain.ROLLEDBACKPUBv2', "Rolled back to published version."
1236
				);
1237
			}
1238
		}
1239
1240
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1241
1242
		// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
1243
		// Or in history view, in which case a revert causes the CMS to re-load the edit view.
1244
		// The X-Pjax header forces a "full" content refresh on redirect.
1245
		$url = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $record->ID);
1246
		$this->getResponse()->addHeader('X-ControllerURL', $url);
1247
		$this->getRequest()->addHeader('X-Pjax', 'Content');
1248
		$this->getResponse()->addHeader('X-Pjax', 'Content');
1249
1250
		return $this->getResponseNegotiator()->respond($this->getRequest());
1251
	}
1252
1253
	/**
1254
	 * Batch Actions Handler
1255
	 */
1256
	public function batchactions() {
1257
		return new CMSBatchActionHandler($this, 'batchactions');
1258
	}
1259
1260
	public function BatchActionParameters() {
1261
		$batchActions = CMSBatchActionHandler::config()->batch_actions;
1262
1263
		$forms = array();
1264
		foreach($batchActions as $urlSegment => $batchAction) {
1265
			$SNG_action = singleton($batchAction);
1266
			if ($SNG_action->canView() && $fieldset = $SNG_action->getParameterFields()) {
1267
				$formHtml = '';
1268
				foreach($fieldset as $field) {
1269
					$formHtml .= $field->Field();
1270
				}
1271
				$forms[$urlSegment] = $formHtml;
1272
			}
1273
		}
1274
		$pageHtml = '';
1275
		foreach($forms as $urlSegment => $html) {
1276
			$pageHtml .= "<div class=\"params\" id=\"BatchActionParameters_$urlSegment\">$html</div>\n\n";
1277
		}
1278
		return new LiteralField("BatchActionParameters", '<div id="BatchActionParameters" style="display:none">'.$pageHtml.'</div>');
1279
	}
1280
	/**
1281
	 * Returns a list of batch actions
1282
	 */
1283
	public function BatchActionList() {
1284
		return $this->batchactions()->batchActionList();
1285
	}
1286
1287
	public function buildbrokenlinks($request) {
1288
		// Protect against CSRF on destructive action
1289
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1290
1291
		increase_time_limit_to();
1292
		increase_memory_limit_to();
1293
1294
		if($this->urlParams['ID']) {
1295
			$newPageSet[] = DataObject::get_by_id("Page", $this->urlParams['ID']);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$newPageSet was never initialized. Although not strictly required by PHP, it is generally a good practice to add $newPageSet = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1296
		} else {
1297
			$pages = DataObject::get("Page");
1298
			foreach($pages as $page) $newPageSet[] = $page;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$newPageSet was never initialized. Although not strictly required by PHP, it is generally a good practice to add $newPageSet = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1299
			$pages = null;
0 ignored issues
show
Unused Code introduced by
$pages is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1300
		}
1301
1302
		$content = new HtmlEditorField('Content');
1303
		$download = new HtmlEditorField('Download');
1304
1305
		foreach($newPageSet as $i => $page) {
1306
			$page->HasBrokenLink = 0;
1307
			$page->HasBrokenFile = 0;
1308
1309
			$content->setValue($page->Content);
1310
			$content->saveInto($page);
1311
1312
			$download->setValue($page->Download);
1313
			$download->saveInto($page);
1314
1315
			echo "<li>$page->Title (link:$page->HasBrokenLink, file:$page->HasBrokenFile)";
1316
1317
			$page->writeWithoutVersion();
1318
			$page->destroy();
1319
			$newPageSet[$i] = null;
0 ignored issues
show
Bug introduced by
The variable $newPageSet does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1320
		}
1321
	}
1322
1323
	public function publishall($request) {
1324
		if(!Permission::check('ADMIN')) return Security::permissionFailure($this);
1325
1326
		increase_time_limit_to();
1327
		increase_memory_limit_to();
1328
1329
		$response = "";
1330
1331
		if(isset($this->requestParams['confirm'])) {
1332
			// Protect against CSRF on destructive action
1333
			if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1334
1335
			$start = 0;
1336
			$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
1337
			$count = 0;
1338
			while($pages) {
1339
				foreach($pages as $page) {
1340
					if($page && !$page->canPublish()) return Security::permissionFailure($this);
1341
1342
					$page->doPublish();
1343
					$page->destroy();
1344
					unset($page);
1345
					$count++;
1346
					$response .= "<li>$count</li>";
1347
				}
1348
				if($pages->Count() > 29) {
1349
					$start += 30;
1350
					$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
1351
				} else {
1352
					break;
1353
				}
1354
			}
1355
			$response .= _t('CMSMain.PUBPAGES',"Done: Published {count} pages", array('count' => $count));
0 ignored issues
show
Documentation introduced by
array('count' => $count) is of type array<string,integer,{"count":"integer"}>, 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...
1356
1357
		} else {
1358
			$token = SecurityToken::inst();
1359
			$fields = new FieldList();
1360
			$token->updateFieldSet($fields);
1361
			$tokenField = $fields->First();
1362
			$tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : '';
1363
			$response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
1364
				<p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish".  It\'s
1365
				intended to be used after there have been massive edits of the content, such as when the site was
1366
				first built.') . '</p>
1367
				<form method="post" action="publishall">
1368
					<input type="submit" name="confirm" value="'
1369
					. _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",'Confirmation button') .'" />'
1370
					. $tokenHtml .
1371
				'</form>';
1372
		}
1373
1374
		return $response;
1375
	}
1376
1377
	/**
1378
	 * Restore a completely deleted page from the SiteTree_versions table.
1379
	 */
1380
	public function restore($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...
1381
		if(!isset($data['ID']) || !is_numeric($data['ID'])) {
1382
			return new SS_HTTPResponse("Please pass an ID in the form content", 400);
1383
		}
1384
1385
		$id = (int)$data['ID'];
1386
		$restoredPage = Versioned::get_latest_version("SiteTree", $id);
1387
		if(!$restoredPage) 	return new SS_HTTPResponse("SiteTree #$id not found", 400);
1388
1389
		$restoredPage = $restoredPage->doRestoreToStage();
1390
1391
		$this->getResponse()->addHeader(
1392
			'X-Status',
1393
			rawurlencode(_t(
1394
				'CMSMain.RESTORED',
1395
				"Restored '{title}' successfully",
1396
				array('title' => $restoredPage->Title)
0 ignored issues
show
Documentation introduced by
array('title' => $restoredPage->Title) is of type array<string,?,{"title":"?"}>, 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...
1397
			))
1398
		);
1399
1400
		return $this->getResponseNegotiator()->respond($this->getRequest());
1401
	}
1402
1403
	public function duplicate($request) {
1404
		// Protect against CSRF on destructive action
1405
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1406
1407
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1408
			$page = DataObject::get_by_id("SiteTree", $id);
1409 View Code Duplication
			if($page && (!$page->canEdit() || !$page->canCreate(null, array('Parent' => $page->Parent())))) {
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on DataObject. Did you maybe mean parentClass()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1410
				return Security::permissionFailure($this);
1411
			}
1412
			if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1413
1414
			$newPage = $page->duplicate();
1415
1416
			// ParentID can be hard-set in the URL.  This is useful for pages with multiple parents
1417
			if(isset($_GET['parentID']) && is_numeric($_GET['parentID'])) {
1418
				$newPage->ParentID = $_GET['parentID'];
1419
				$newPage->write();
1420
			}
1421
1422
			$this->getResponse()->addHeader(
1423
				'X-Status',
1424
				rawurlencode(_t(
1425
					'CMSMain.DUPLICATED',
1426
					"Duplicated '{title}' successfully",
1427
					array('title' => $newPage->Title)
0 ignored issues
show
Documentation introduced by
array('title' => $newPage->Title) is of type array<string,?,{"title":"?"}>, 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...
1428
				))
1429
			);
1430
			$url = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $newPage->ID);
1431
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1432
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1433
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1434
1435
			return $this->getResponseNegotiator()->respond($this->getRequest());
1436
		} else {
1437
			return new SS_HTTPResponse("CMSMain::duplicate() Bad ID: '$id'", 400);
1438
		}
1439
	}
1440
1441
	public function duplicatewithchildren($request) {
1442
		// Protect against CSRF on destructive action
1443
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1444
		increase_time_limit_to();
1445
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1446
			$page = DataObject::get_by_id("SiteTree", $id);
1447 View Code Duplication
			if($page && (!$page->canEdit() || !$page->canCreate(null, array('Parent' => $page->Parent())))) {
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on DataObject. Did you maybe mean parentClass()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1448
				return Security::permissionFailure($this);
1449
			}
1450
			if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1451
1452
			$newPage = $page->duplicateWithChildren();
1453
1454
			$this->getResponse()->addHeader(
1455
				'X-Status',
1456
				rawurlencode(_t(
1457
					'CMSMain.DUPLICATEDWITHCHILDREN',
1458
					"Duplicated '{title}' and children successfully",
1459
					array('title' => $newPage->Title)
0 ignored issues
show
Documentation introduced by
array('title' => $newPage->Title) is of type array<string,?,{"title":"?"}>, 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...
1460
				))
1461
			);
1462
			$url = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $newPage->ID);
1463
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1464
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1465
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1466
1467
			return $this->getResponseNegotiator()->respond($this->getRequest());
1468
		} else {
1469
			return new SS_HTTPResponse("CMSMain::duplicatewithchildren() Bad ID: '$id'", 400);
1470
		}
1471
	}
1472
1473
	public function providePermissions() {
1474
		$title = _t("CMSPagesController.MENUTITLE", LeftAndMain::menu_title_for_class('CMSPagesController'));
1475
		return array(
1476
			"CMS_ACCESS_CMSMain" => array(
1477
				'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array('title' => $title)),
0 ignored issues
show
Documentation introduced by
array('title' => $title) is of type array<string,string,{"title":"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...
1478
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
1479
				'help' => _t(
1480
					'CMSMain.ACCESS_HELP',
1481
					'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".'
1482
				),
1483
				'sort' => -99 // below "CMS_ACCESS_LeftAndMain", but above everything else
1484
			)
1485
		);
1486
	}
1487
1488
}
1489