Completed
Pull Request — master (#1617)
by Ingo
02:41
created

CMSMain::getEditForm()   F

Complexity

Conditions 26
Paths 3594

Size

Total Lines 99
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 99
rs 2
c 0
b 0
f 0
cc 26
eloc 57
nc 3594
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\CMS\Controllers;
4
5
use SilverStripe\Admin\AdminRootController;
6
use SilverStripe\Admin\CMSBatchActionHandler;
7
use SilverStripe\Admin\CMSPreviewable;
8
use SilverStripe\Admin\LeftAndMain;
9
use SilverStripe\CMS\Model\CurrentPageIdentifier;
10
use SilverStripe\CMS\Model\RedirectorPage;
11
use SilverStripe\CMS\Model\SiteTree;
12
use SilverStripe\Control\Controller;
13
use SilverStripe\Control\Director;
14
use SilverStripe\Control\Session;
15
use SilverStripe\Control\HTTPRequest;
16
use SilverStripe\Control\HTTPResponse;
17
use SilverStripe\Control\HTTPResponse_Exception;
18
use SilverStripe\Core\Convert;
19
use SilverStripe\Core\Injector\Injector;
20
use SilverStripe\Core\Cache;
21
use SilverStripe\Forms\DateField;
22
use SilverStripe\Forms\DropdownField;
23
use SilverStripe\Forms\FieldGroup;
24
use SilverStripe\Forms\FieldList;
25
use SilverStripe\Forms\Form;
26
use SilverStripe\Forms\FormAction;
27
use SilverStripe\Forms\FormField;
28
use SilverStripe\Forms\GridField\GridField;
29
use SilverStripe\Forms\GridField\GridFieldConfig;
30
use SilverStripe\Forms\GridField\GridFieldDataColumns;
31
use SilverStripe\Forms\GridField\GridFieldLevelup;
32
use SilverStripe\Forms\GridField\GridFieldPaginator;
33
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
34
use SilverStripe\Forms\HiddenField;
35
use SilverStripe\Forms\LabelField;
36
use SilverStripe\Forms\LiteralField;
37
use SilverStripe\Forms\RequiredFields;
38
use SilverStripe\Forms\ResetFormAction;
39
use SilverStripe\Forms\TabSet;
40
use SilverStripe\Forms\TextField;
41
use SilverStripe\ORM\ArrayList;
42
use SilverStripe\ORM\DataList;
43
use SilverStripe\ORM\DataObject;
44
use SilverStripe\ORM\DB;
45
use SilverStripe\ORM\HiddenClass;
46
use SilverStripe\ORM\SS_List;
47
use SilverStripe\ORM\Versioning\Versioned;
48
use SilverStripe\Security\Member;
49
use SilverStripe\Security\Permission;
50
use SilverStripe\Security\PermissionProvider;
51
use SilverStripe\Security\Security;
52
use SilverStripe\Security\SecurityToken;
53
use SilverStripe\View\ArrayData;
54
use SilverStripe\View\Requirements;
55
use Translatable;
56
use Page;
57
use Zend_Cache;
58
use InvalidArgumentException;
59
60
61
/**
62
 * The main "content" area of the CMS.
63
 *
64
 * This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main
65
 * admin menu.
66
 *
67
 * @todo Create some base classes to contain the generic functionality that will be replicated.
68
 *
69
 * @mixin LeftAndMainPageIconsExtension
70
 */
71
class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionProvider {
72
73
	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...
74
75
	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...
76
77
	// Maintain a lower priority than other administration sections
78
	// so that Director does not think they are actions of CMSMain
79
	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...
80
81
	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...
82
83
	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...
84
85
	private static $tree_class = "SilverStripe\\CMS\\Model\\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...
86
87
	private static $subitem_class = "SilverStripe\\Security\\Member";
88
89
	private static $session_namespace = 'SilverStripe\\CMS\\Controllers\\CMSMain';
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...
90
91
	private static $required_permission_codes = 'CMS_ACCESS_CMSMain';
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...
92
93
	/**
94
	 * Amount of results showing on a single page.
95
	 *
96
	 * @config
97
	 * @var int
98
	 */
99
	private static $page_length = 15;
100
101
	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...
102
		'archive',
103
		'deleteitems',
104
		'DeleteItemsForm',
105
		'dialog',
106
		'duplicate',
107
		'duplicatewithchildren',
108
		'publishall',
109
		'publishitems',
110
		'PublishItemsForm',
111
		'submit',
112
		'EditForm',
113
		'SearchForm',
114
		'SiteTreeAsUL',
115
		'getshowdeletedsubtree',
116
		'batchactions',
117
		'treeview',
118
		'listview',
119
		'ListViewForm',
120
		'childfilter',
121
	);
122
123
	private static $casting = 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...
124
		'TreeIsFiltered' => 'Boolean',
125
		'AddForm' => 'HTMLFragment',
126
		'LinkPages' => 'Text',
127
		'Link' => 'Text',
128
		'ListViewForm' => 'HTMLFragment',
129
		'ExtraTreeTools' => 'HTMLFragment',
130
		'SiteTreeHints' => 'HTMLFragment',
131
		'SecurityID' => 'Text',
132
		'SiteTreeAsUL' => 'HTMLFragment',
133
	);
134
135
	public function init() {
136
		// set reading lang
137
		if(SiteTree::has_extension('Translatable') && !$this->getRequest()->isAjax()) {
138
			Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SilverStripe\\CMS\\Model\\SiteTree')));
139
		}
140
141
		parent::init();
142
143
		Requirements::css(CMS_DIR . '/client/dist/styles/bundle.css');
144
		Requirements::customCSS($this->generatePageIconsCss());
0 ignored issues
show
Documentation Bug introduced by
The method generatePageIconsCss does not exist on object<SilverStripe\CMS\Controllers\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...
145
		Requirements::add_i18n_javascript(CMS_DIR . '/client/lang', false, true);
146
147
		CMSBatchActionHandler::register('publish', 'SilverStripe\\CMS\\BatchActions\\CMSBatchAction_Publish');
148
		CMSBatchActionHandler::register('unpublish', 'SilverStripe\\CMS\\BatchActions\\CMSBatchAction_Unpublish');
149
		CMSBatchActionHandler::register('delete', 'SilverStripe\\CMS\\BatchActions\\CMSBatchAction_Delete');
150
		CMSBatchActionHandler::register('archive', 'SilverStripe\\CMS\\BatchActions\\CMSBatchAction_Archive');
151
		CMSBatchActionHandler::register('restore', 'SilverStripe\\CMS\\BatchActions\\CMSBatchAction_Restore');
152
	}
153
154
	public function index($request) {
155
		// In case we're not showing a specific record, explicitly remove any session state,
156
		// to avoid it being highlighted in the tree, and causing an edit form to show.
157
		if(!$request->param('Action')) {
158
			$this->setCurrentPageID(null);
159
		}
160
161
		return parent::index($request);
162
	}
163
164
	public function getResponseNegotiator() {
165
		$negotiator = parent::getResponseNegotiator();
166
		$controller = $this;
167
		$negotiator->setCallback('ListViewForm', function() use($controller) {
168
			return $controller->ListViewForm()->forTemplate();
169
		});
170
		return $negotiator;
171
	}
172
173
	/**
174
	 * If this is set to true, the "switchView" context in the
175
	 * template is shown, with links to the staging and publish site.
176
	 *
177
	 * @return boolean
178
	 */
179
	public function ShowSwitchView() {
180
		return true;
181
	}
182
183
	/**
184
	 * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
185
	 * to switch view also for archived versions.
186
	 *
187
	 * @param SiteTree $page
188
	 * @return array
189
	 */
190
	public function SwitchView($page = null) {
191
		if(!$page) {
192
			$page = $this->currentPage();
193
		}
194
195
		if($page) {
196
			$nav = SilverStripeNavigator::get_for_record($page);
197
			return $nav['items'];
198
		}
199
	}
200
201
	//------------------------------------------------------------------------------------------//
202
	// Main controllers
203
204
	//------------------------------------------------------------------------------------------//
205
	// Main UI components
206
207
	/**
208
	 * Override {@link LeftAndMain} Link to allow blank URL segment for CMSMain.
209
	 *
210
	 * @param string|null $action Action to link to.
211
	 * @return string
212
	 */
213
	public function Link($action = null) {
214
		$link = Controller::join_links(
215
			AdminRootController::admin_url(),
216
			$this->stat('url_segment'), // in case we want to change the segment
217
			'/', // trailing slash needed if $action is null!
218
			"$action"
219
		);
220
		$this->extend('updateLink', $link);
221
		return $link;
222
	}
223
224
	public function LinkPages() {
225
		return CMSPagesController::singleton()->Link();
226
	}
227
228
	public function LinkPagesWithSearch() {
229
		return $this->LinkWithSearch($this->LinkPages());
230
	}
231
232
	public function LinkTreeView() {
233
		return $this->LinkWithSearch($this->Link('treeview'));
234
	}
235
236
	public function LinkListView() {
237
		return $this->LinkWithSearch($this->Link('listview'));
238
	}
239
240
	public function LinkGalleryView() {
241
		return $this->LinkWithSearch($this->Link('galleryview'));
242
	}
243
244 View Code Duplication
	public function LinkPageEdit($id = null) {
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...
245
		if(!$id) {
246
			$id = $this->currentPageID();
247
		}
248
		return $this->LinkWithSearch(
249
			Controller::join_links(CMSPageEditController::singleton()->Link('show'), $id)
250
		);
251
	}
252
253 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...
254
		if($id = $this->currentPageID()) {
255
			return $this->LinkWithSearch(
256
				Controller::join_links(CMSPageSettingsController::singleton()->Link('show'), $id)
257
			);
258
		} else {
259
			return null;
260
		}
261
	}
262
263 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...
264
		if($id = $this->currentPageID()) {
265
			return $this->LinkWithSearch(
266
				Controller::join_links(CMSPageHistoryController::singleton()->Link('show'), $id)
267
			);
268
		} else {
269
			return null;
270
		}
271
	}
272
273
	public function LinkWithSearch($link) {
274
		// Whitelist to avoid side effects
275
		$params = array(
276
			'q' => (array)$this->getRequest()->getVar('q'),
277
			'ParentID' => $this->getRequest()->getVar('ParentID')
278
		);
279
		$link = Controller::join_links(
280
			$link,
281
			array_filter(array_values($params)) ? '?' . http_build_query($params) : null
282
		);
283
		$this->extend('updateLinkWithSearch', $link);
284
		return $link;
285
	}
286
287
	public function LinkPageAdd($extra = null, $placeholders = null) {
288
		$link = CMSPageAddController::singleton()->Link();
289
		$this->extend('updateLinkPageAdd', $link);
290
291
		if($extra) {
292
			$link = Controller::join_links ($link, $extra);
293
		}
294
295
		if($placeholders) {
296
			$link .= (strpos($link, '?') === false ? "?$placeholders" : "&amp;$placeholders");
297
		}
298
299
		return $link;
300
	}
301
302
	/**
303
	 * @return string
304
	 */
305
	public function LinkPreview() {
306
		$record = $this->getRecord($this->currentPageID());
307
		$baseLink = Director::absoluteBaseURL();
308
		if ($record && $record instanceof Page) {
0 ignored issues
show
Bug introduced by
The class Page does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
309
			// if we are an external redirector don't show a link
310
			if ($record instanceof RedirectorPage && $record->RedirectionType == 'External') {
311
				$baseLink = false;
312
			}
313
			else {
314
				$baseLink = $record->Link('?stage=Stage');
315
			}
316
		}
317
		return $baseLink;
318
	}
319
320
	/**
321
	 * Return the entire site tree as a nested set of ULs
322
	 */
323
	public function SiteTreeAsUL() {
324
		// Pre-cache sitetree version numbers for querying efficiency
325
		Versioned::prepopulate_versionnumber_cache("SilverStripe\\CMS\\Model\\SiteTree", "Stage");
326
		Versioned::prepopulate_versionnumber_cache("SilverStripe\\CMS\\Model\\SiteTree", "Live");
327
		$html = $this->getSiteTreeFor($this->stat('tree_class'));
328
329
		$this->extend('updateSiteTreeAsUL', $html);
330
331
		return $html;
332
	}
333
334
	/**
335
	 * @return boolean
336
	 */
337
	public function TreeIsFiltered() {
338
		$query = $this->getRequest()->getVar('q');
339
340
		if (!$query || (count($query) === 1 && isset($query['FilterClass']) && $query['FilterClass'] === 'SilverStripe\\CMS\\Controllers\\CMSSiteTreeFilter_Search')) {
341
			return false;
342
		}
343
344
		return true;
345
	}
346
347
	public function ExtraTreeTools() {
348
		$html = '';
349
		$this->extend('updateExtraTreeTools', $html);
350
		return $html;
351
	}
352
353
	/**
354
	 * Returns a Form for page searching for use in templates.
355
	 *
356
	 * Can be modified from a decorator by a 'updateSearchForm' method
357
	 *
358
	 * @return Form
359
	 */
360
	public function SearchForm() {
361
		// Create the fields
362
		$content = new TextField('q[Term]', _t('CMSSearch.FILTERLABELTEXT', 'Search'));
363
		$dateFrom = new DateField(
364
			'q[LastEditedFrom]',
365
			_t('CMSSearch.FILTERDATEFROM', 'From')
366
		);
367
		$dateFrom->setConfig('showcalendar', true);
368
		$dateTo = new DateField(
369
			'q[LastEditedTo]',
370
			_t('CMSSearch.FILTERDATETO', 'To')
371
		);
372
		$dateTo->setConfig('showcalendar', true);
373
		$pageFilter = new DropdownField(
374
			'q[FilterClass]',
375
			_t('CMSMain.PAGES', 'Page status'),
376
			CMSSiteTreeFilter::get_all_filters()
377
		);
378
		$pageClasses = new DropdownField(
379
			'q[ClassName]',
380
			_t('CMSMain.PAGETYPEOPT', 'Page type', 'Dropdown for limiting search to a page type'),
381
			$this->getPageTypes()
382
		);
383
		$pageClasses->setEmptyString(_t('CMSMain.PAGETYPEANYOPT','Any'));
384
385
		// Group the Datefields
386
		$dateGroup = new FieldGroup(
387
			$dateFrom,
388
			$dateTo
389
		);
390
		$dateGroup->setTitle(_t('CMSSearch.PAGEFILTERDATEHEADING', 'Last edited'));
391
392
		// Create the Field list
393
		$fields = new FieldList(
394
			$content,
395
			$pageFilter,
396
			$pageClasses,
397
			$dateGroup
398
		);
399
400
		// Create the Search and Reset action
401
		$actions = new FieldList(
402
			FormAction::create('doSearch',  _t('CMSMain_left_ss.APPLY_FILTER', 'Search'))
403
				->addExtraClass('ss-ui-action-constructive'),
404
			ResetFormAction::create('clear', _t('CMSMain_left_ss.CLEAR_FILTER', 'Clear'))
405
		);
406
407
		// Use <button> to allow full jQuery UI styling on the all of the Actions
408
		/** @var FormAction $action */
409
		foreach($actions->dataFields() as $action) {
410
			/** @var FormAction $action */
411
			$action->setUseButtonTag(true);
412
		}
413
414
		// Create the form
415
		/** @skipUpgrade */
416
		$form = Form::create($this, 'SearchForm', $fields, $actions)
417
			->addExtraClass('cms-search-form')
418
			->setFormMethod('GET')
419
			->setFormAction($this->Link())
420
			->disableSecurityToken()
421
			->unsetValidator();
422
423
		// Load the form with previously sent search data
424
		$form->loadDataFrom($this->getRequest()->getVars());
425
426
		// Allow decorators to modify the form
427
		$this->extend('updateSearchForm', $form);
428
429
		return $form;
430
	}
431
432
	/**
433
	 * Returns a sorted array suitable for a dropdown with pagetypes and their translated name
434
	 *
435
	 * @return array
436
	 */
437
	protected function getPageTypes() {
438
		$pageTypes = array();
439
		foreach(SiteTree::page_type_classes() as $pageTypeClass) {
440
			$pageTypes[$pageTypeClass] = SiteTree::singleton($pageTypeClass)->i18n_singular_name();
441
		}
442
		asort($pageTypes);
443
		return $pageTypes;
444
	}
445
446
	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...
447
		return $this->getsubtree($this->getRequest());
448
	}
449
450
	/**
451
	 * @param bool $unlinked
452
	 * @return ArrayList
453
	 */
454
	public function Breadcrumbs($unlinked = false) {
455
		$items = parent::Breadcrumbs($unlinked);
456
457
		if($items->count() > 1) {
458
			// Specific to the SiteTree admin section, we never show the cms section and current
459
			// page in the same breadcrumbs block.
460
			$items->shift();
461
		}
462
463
		return $items;
464
	}
465
466
	/**
467
	 * Create serialized JSON string with site tree hints data to be injected into
468
	 * 'data-hints' attribute of root node of jsTree.
469
	 *
470
	 * @return string Serialized JSON
471
	 */
472
	public function SiteTreeHints() {
473
		$classes = SiteTree::page_type_classes();
474
475
	 	$cacheCanCreate = array();
476
	 	foreach($classes as $class) $cacheCanCreate[$class] = singleton($class)->canCreate();
477
478
	 	// Generate basic cache key. Too complex to encompass all variations
479
	 	$cache = Cache::factory('CMSMain_SiteTreeHints');
480
	 	$cacheKey = md5(implode('_', array(Member::currentUserID(), implode(',', $cacheCanCreate), implode(',', $classes))));
481
	 	if($this->getRequest()->getVar('flush')) {
482
	 		$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
483
		}
484
	 	$json = $cache->load($cacheKey);
485
	 	if(!$json) {
486
			$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...
487
			$def['Root']['disallowedChildren'] = array();
488
489
			// Contains all possible classes to support UI controls listing them all,
490
			// such as the "add page here" context menu.
491
			$def['All'] = array();
492
493
			// Identify disallows and set globals
494
			foreach($classes as $class) {
495
				$obj = singleton($class);
496
				if($obj instanceof HiddenClass) continue;
497
498
				// Name item
499
				$def['All'][$class] = array(
500
					'title' => $obj->i18n_singular_name()
501
				);
502
503
				// Check if can be created at the root
504
				$needsPerm = $obj->stat('need_permission');
505
				if(
506
					!$obj->stat('can_be_root')
507
					|| (!array_key_exists($class, $cacheCanCreate) || !$cacheCanCreate[$class])
508
					|| ($needsPerm && !$this->can($needsPerm))
509
				) {
510
					$def['Root']['disallowedChildren'][] = $class;
511
				}
512
513
				// Hint data specific to the class
514
				$def[$class] = array();
515
516
				$defaultChild = $obj->defaultChild();
517
				if($defaultChild !== 'Page' && $defaultChild !== null) {
518
					$def[$class]['defaultChild'] = $defaultChild;
519
				}
520
521
				$defaultParent = $obj->defaultParent();
522
				if ($defaultParent !== 1 && $defaultParent !== null) {
523
					$def[$class]['defaultParent'] = $defaultParent;
524
				}
525
			}
526
527
			$this->extend('updateSiteTreeHints', $def);
528
529
			$json = Convert::raw2json($def);
530
			$cache->save($json, $cacheKey);
531
		}
532
		return $json;
533
	}
534
535
	/**
536
	 * Populates an array of classes in the CMS
537
	 * which allows the user to change the page type.
538
	 *
539
	 * @return SS_List
540
	 */
541
	public function PageTypes() {
542
		$classes = SiteTree::page_type_classes();
543
544
		$result = new ArrayList();
545
546
		foreach($classes as $class) {
547
			$instance = singleton($class);
548
549
			if($instance instanceof HiddenClass) {
550
				continue;
551
			}
552
553
			// skip this type if it is restricted
554
			if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) {
555
				continue;
556
			}
557
558
			$addAction = $instance->i18n_singular_name();
559
560
			// Get description (convert 'Page' to 'SiteTree' for correct localization lookups)
561
			$i18nClass = ($class == 'Page') ? 'SilverStripe\\CMS\\Model\\SiteTree' : $class;
562
			$description = _t($i18nClass . '.DESCRIPTION');
563
564
			if(!$description) {
565
				$description = $instance->uninherited('description');
566
			}
567
568
			if($class == 'Page' && !$description) {
569
				$description = SiteTree::singleton()->uninherited('description');
570
			}
571
572
			$result->push(new ArrayData(array(
573
				'ClassName' => $class,
574
				'AddAction' => $addAction,
575
				'Description' => $description,
576
				// TODO Sprite support
577
				'IconURL' => $instance->stat('icon'),
578
				'Title' => singleton($class)->i18n_singular_name(),
579
			)));
580
		}
581
582
		$result = $result->sort('AddAction');
583
584
		return $result;
585
	}
586
587
	/**
588
	 * Get a database record to be managed by the CMS.
589
	 *
590
	 * @param int $id Record ID
591
	 * @param int $versionID optional Version id of the given record
592
	 * @return SiteTree
593
	 */
594
 	public function getRecord($id, $versionID = null) {
595
		$treeClass = $this->stat('tree_class');
596
597
		if($id instanceof $treeClass) {
598
			return $id;
599
		}
600
		else if($id && is_numeric($id)) {
601
			$currentStage = Versioned::get_reading_mode();
602
603
			if($this->getRequest()->getVar('Version')) {
604
				$versionID = (int) $this->getRequest()->getVar('Version');
605
			}
606
607
			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...
608
				$record = Versioned::get_version($treeClass, $id, $versionID);
609
			} else {
610
				$record = DataObject::get_by_id($treeClass, $id);
611
			}
612
613
			// Then, try getting a record from the live site
614
			if(!$record) {
615
				// $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id");
616
				Versioned::set_stage(Versioned::LIVE);
617
				singleton($treeClass)->flushCache();
618
619
				$record = DataObject::get_by_id($treeClass, $id);
620
			}
621
622
			// Then, try getting a deleted record
623
			if(!$record) {
624
				$record = Versioned::get_latest_version($treeClass, $id);
625
			}
626
627
			// Don't open a page from a different locale
628
			/** The record's Locale is saved in database in 2.4, and not related with Session,
629
			 *  we should not check their locale matches the Translatable::get_current_locale,
630
			 * 	here as long as we all the HTTPRequest is init with right locale.
631
			 *	This bit breaks the all FileIFrameField functions if the field is used in CMS
632
			 *  and its relevent ajax calles, like loading the tree dropdown for TreeSelectorField.
633
			 */
634
			/* if($record && SiteTree::has_extension('Translatable') && $record->Locale && $record->Locale != Translatable::get_current_locale()) {
635
				$record = null;
636
			}*/
637
638
			// Set the reading mode back to what it was.
639
			Versioned::set_reading_mode($currentStage);
640
641
			return $record;
642
643
		} else if(substr($id,0,3) == 'new') {
644
			return $this->getNewItem($id);
645
		}
646
	}
647
648
	/**
649
	 * @param int $id
650
	 * @param FieldList $fields
651
	 * @return Form
652
	 */
653
	public function getEditForm($id = null, $fields = null) {
654
		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...
655
		$form = parent::getEditForm($id, $fields);
656
657
		// TODO Duplicate record fetching (see parent implementation)
658
		$record = $this->getRecord($id);
659
		if($record && !$record->canView()) return Security::permissionFailure($this);
0 ignored issues
show
Bug Compatibility introduced by
The expression \SilverStripe\Security\S...rmissionFailure($this); of type SS_HTTPResponse|null adds the type SS_HTTPResponse to the return on line 659 which is incompatible with the return type documented by SilverStripe\CMS\Controllers\CMSMain::getEditForm of type SilverStripe\Forms\Form.
Loading history...
660
661
		if(!$fields) $fields = $form->Fields();
662
		$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...
663
664
		if($record) {
665
			$deletedFromStage = $record->getIsDeletedFromStage();
666
667
			$fields->push($idField = new HiddenField("ID", false, $id));
668
			// Necessary for different subsites
669
			$fields->push($liveLinkField = new HiddenField("AbsoluteLink", false, $record->AbsoluteLink()));
670
			$fields->push($liveLinkField = new HiddenField("LiveLink"));
671
			$fields->push($stageLinkField = new HiddenField("StageLink"));
672
			$fields->push(new HiddenField("TreeTitle", false, $record->TreeTitle));
673
674
			if($record->ID && is_numeric( $record->ID ) ) {
675
				$liveLink = $record->getAbsoluteLiveLink();
676
				if($liveLink) $liveLinkField->setValue($liveLink);
677
				if(!$deletedFromStage) {
678
					$stageLink = Controller::join_links($record->AbsoluteLink(), '?stage=Stage');
679
					if($stageLink) $stageLinkField->setValue($stageLink);
680
				}
681
			}
682
683
			// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
684
			/** @skipUpgrade */
685
			if($record instanceof CMSPreviewable && !$fields->fieldByName('SilverStripeNavigator')) {
686
				$navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator());
687
				$navField->setAllowHTML(true);
688
				$fields->push($navField);
689
			}
690
691
			// getAllCMSActions can be used to completely redefine the action list
692
			if($record->hasMethod('getAllCMSActions')) {
693
				$actions = $record->getAllCMSActions();
694
			} else {
695
				$actions = $record->getCMSActions();
696
697
				// Find and remove action menus that have no actions.
698
				if ($actions && $actions->count()) {
699
					/** @var TabSet $tabset */
700
					$tabset = $actions->fieldByName('ActionMenus');
701
					if ($tabset) {
702
						foreach ($tabset->getChildren() as $tab) {
703
							if (!$tab->getChildren()->count()) {
704
								$tabset->removeByName($tab->getName());
705
							}
706
						}
707
					}
708
				}
709
			}
710
711
			// Use <button> to allow full jQuery UI styling
712
			$actionsFlattened = $actions->dataFields();
713
			if($actionsFlattened) {
714
				/** @var FormAction $action */
715
				foreach($actionsFlattened as $action) {
716
					$action->setUseButtonTag(true);
717
				}
718
			}
719
720
			if($record->hasMethod('getCMSValidator')) {
721
				$validator = $record->getCMSValidator();
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...
722
			} else {
723
				$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...
724
			}
725
726
			// TODO Can't merge $FormAttributes in template at the moment
727
			$form->addExtraClass('center ' . $this->BaseCSSClasses());
728
			// Set validation exemptions for specific actions
729
			$form->setValidationExemptActions(array('restore', 'revert', 'deletefromlive', 'delete', 'unpublish', 'rollback', 'doRollback'));
730
731
			// Announce the capability so the frontend can decide whether to allow preview or not.
732
			if ($record instanceof CMSPreviewable) {
733
				$form->addExtraClass('cms-previewable');
734
			}
735
736
			if(!$record->canEdit() || $deletedFromStage) {
737
				$readonlyFields = $form->Fields()->makeReadonly();
738
				$form->setFields($readonlyFields);
739
			}
740
741
			$form->Fields()->setForm($form);
742
743
			$this->extend('updateEditForm', $form);
744
			return $form;
745
		} else if($id) {
746
			$form = Form::create( $this, "EditForm", new FieldList(
747
				new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList()
748
			)->setHTMLID('Form_EditForm');
749
			return $form;
750
		}
751
	}
752
753
	/**
754
	 * @param HTTPRequest $request
755
	 * @return string HTML
756
	 */
757
	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...
758
		return $this->renderWith($this->getTemplatesWithSuffix('_TreeView'));
759
	}
760
761
	/**
762
	 * @param HTTPRequest $request
763
	 * @return string HTML
764
	 */
765
	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...
766
		return $this->renderWith($this->getTemplatesWithSuffix('_ListView'));
767
	}
768
769
	/**
770
	 * Callback to request the list of page types allowed under a given page instance.
771
	 * Provides a slower but more precise response over SiteTreeHints
772
	 *
773
	 * @param HTTPRequest $request
774
	 * @return HTTPResponse
775
	 */
776
	public function childfilter($request) {
777
		// Check valid parent specified
778
		$parentID = $request->requestVar('ParentID');
779
		$parent = SiteTree::get()->byID($parentID);
780
		if(!$parent || !$parent->exists()) return $this->httpError(404);
781
782
		// Build hints specific to this class
783
		// Identify disallows and set globals
784
		$classes = SiteTree::page_type_classes();
785
		$disallowedChildren = array();
786
		foreach($classes as $class) {
787
			$obj = singleton($class);
788
			if($obj instanceof HiddenClass) continue;
789
790
			if(!$obj->canCreate(null, array('Parent' => $parent))) {
791
				$disallowedChildren[] = $class;
792
			}
793
		}
794
795
		$this->extend('updateChildFilter', $disallowedChildren, $parentID);
796
		return $this
797
			->getResponse()
798
			->addHeader('Content-Type', 'application/json; charset=utf-8')
799
			->setBody(Convert::raw2json($disallowedChildren));
800
	}
801
802
	/**
803
	 * Safely reconstruct a selected filter from a given set of query parameters
804
	 *
805
	 * @param array $params Query parameters to use
806
	 * @return CMSSiteTreeFilter The filter class, or null if none present
807
	 * @throws InvalidArgumentException if invalid filter class is passed.
808
	 */
809
	protected function getQueryFilter($params) {
810
		if(empty($params['FilterClass'])) return null;
811
		$filterClass = $params['FilterClass'];
812
		if(!is_subclass_of($filterClass, 'SilverStripe\\CMS\\Controllers\\CMSSiteTreeFilter')) {
813
			throw new InvalidArgumentException("Invalid filter class passed: {$filterClass}");
814
		}
815
		return $filterClass::create($params);
816
	}
817
818
	/**
819
	 * Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page
820
	 * defaulting to no filter and show all pages in first level.
821
	 * Doubles as search results, if any search parameters are set through {@link SearchForm()}.
822
	 *
823
	 * @param array $params Search filter criteria
824
	 * @param int $parentID Optional parent node to filter on (can't be combined with other search criteria)
825
	 * @return SS_List
826
	 * @throws InvalidArgumentException if invalid filter class is passed.
827
	 */
828
	public function getList($params = array(), $parentID = 0) {
829
		if($filter = $this->getQueryFilter($params)) {
830
			return $filter->getFilteredPages();
831
		} else {
832
			$list = DataList::create($this->stat('tree_class'));
833
			$parentID = is_numeric($parentID) ? $parentID : 0;
834
			return $list->filter("ParentID", $parentID);
835
		}
836
	}
837
838
	/**
839
	 * @return Form
840
	 */
841
	public function ListViewForm() {
842
		$params = $this->getRequest()->requestVar('q');
843
		$list = $this->getList($params, $parentID = $this->getRequest()->requestVar('ParentID'));
844
		$gridFieldConfig = GridFieldConfig::create()->addComponents(
845
			new GridFieldSortableHeader(),
846
			new GridFieldDataColumns(),
847
			new GridFieldPaginator(self::config()->page_length)
848
		);
849
		if($parentID){
850
			$linkSpec = $this->Link();
851
			$linkSpec = $linkSpec . (strstr($linkSpec, '?') ? '&' : '?') . 'ParentID=%d&view=list';
852
			$gridFieldConfig->addComponent(
853
				GridFieldLevelup::create($parentID)
854
					->setLinkSpec($linkSpec)
855
					->setAttributes(array('data-pjax' => 'ListViewForm,Breadcrumbs'))
856
			);
857
		}
858
		$gridField = new GridField('Page','Pages', $list, $gridFieldConfig);
859
		/** @var GridFieldDataColumns $columns */
860
		$columns = $gridField->getConfig()->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns');
861
862
		// Don't allow navigating into children nodes on filtered lists
863
		$fields = array(
864
			'getTreeTitle' => _t('SiteTree.PAGETITLE', 'Page Title'),
865
			'singular_name' => _t('SiteTree.PAGETYPE'),
866
			'LastEdited' => _t('SiteTree.LASTUPDATED', 'Last Updated'),
867
		);
868
		/** @var GridFieldSortableHeader $sortableHeader */
869
		$sortableHeader = $gridField->getConfig()->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldSortableHeader');
870
		$sortableHeader->setFieldSorting(array('getTreeTitle' => 'Title'));
871
		$gridField->getState()->ParentID = $parentID;
872
873
		if(!$params) {
874
			$fields = array_merge(array('listChildrenLink' => ''), $fields);
875
		}
876
877
		$columns->setDisplayFields($fields);
878
		$columns->setFieldCasting(array(
879
			'Created' => 'DBDatetime->Ago',
880
			'LastEdited' => 'DBDatetime->FormatFromSettings',
881
			'getTreeTitle' => 'HTMLFragment'
882
		));
883
884
		$controller = $this;
885
		$columns->setFieldFormatting(array(
886
			'listChildrenLink' => function($value, &$item) use($controller) {
887
				/** @var SiteTree $item */
888
				$num = $item ? $item->numChildren() : null;
889
				if($num) {
890
					return sprintf(
891
						'<a class="btn btn-secondary btn--no-text btn--icon-large font-icon-right-dir cms-panel-link list-children-link" data-pjax-target="ListViewForm,Breadcrumbs" href="%s"><span class="sr-only">%s child pages</span></a>',
892
						Controller::join_links(
893
							$controller->Link(),
894
							sprintf("?ParentID=%d&view=list", (int)$item->ID)
895
						),
896
						$num
897
					);
898
				}
899
			},
900
			'getTreeTitle' => function($value, &$item) use($controller) {
901
				return sprintf(
902
					'<a class="action-detail" href="%s">%s</a>',
903
					Controller::join_links(
904
						CMSPageEditController::singleton()->Link('show'),
905
						(int)$item->ID
906
					),
907
					$item->TreeTitle // returns HTML, does its own escaping
908
				);
909
			}
910
		));
911
912
		$negotiator = $this->getResponseNegotiator();
913
		$listview = Form::create(
914
			$this,
915
			'ListViewForm',
916
			new FieldList($gridField),
917
			new FieldList()
918
		)->setHTMLID('Form_ListViewForm');
919
		$listview->setAttribute('data-pjax-fragment', 'ListViewForm');
920 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...
921
			$request = $this->getRequest();
922
			if($request->isAjax() && $negotiator) {
923
				$listview->setupFormErrors();
924
				$result = $listview->forTemplate();
925
926
				return $negotiator->respond($request, array(
927
					'CurrentForm' => function() use($result) {
928
						return $result;
929
					}
930
				));
931
			}
932
		});
933
934
		$this->extend('updateListView', $listview);
935
936
		$listview->disableSecurityToken();
937
		return $listview;
938
	}
939
940
	public function currentPageID() {
941
		$id = parent::currentPageID();
942
943
		$this->extend('updateCurrentPageID', $id);
944
945
		return $id;
946
	}
947
948
	//------------------------------------------------------------------------------------------//
949
	// Data saving handlers
950
951
	/**
952
	 * Save and Publish page handler
953
	 *
954
	 * @param array $data
955
	 * @param Form $form
956
	 * @return HTTPResponse
957
	 * @throws HTTPResponse_Exception
958
	 */
959
	public function save($data, $form) {
960
		$className = $this->stat('tree_class');
961
962
		// Existing or new record?
963
		$id = $data['ID'];
964
		if(substr($id,0,3) != 'new') {
965
			/** @var SiteTree $record */
966
			$record = DataObject::get_by_id($className, $id);
967
			// Check edit permissions
968
			if($record && !$record->canEdit()) {
969
				return Security::permissionFailure($this);
970
			}
971
			if(!$record || !$record->ID) {
972
				throw new HTTPResponse_Exception("Bad record ID #$id", 404);
973
			}
974
		} else {
975
			if(!$className::singleton()->canCreate()) {
976
				return Security::permissionFailure($this);
977
			}
978
			$record = $this->getNewItem($id, false);
979
		}
980
981
		// Check publishing permissions
982
		$doPublish = !empty($data['publish']);
983
		if($record && $doPublish && !$record->canPublish()) {
984
			return Security::permissionFailure($this);
985
		}
986
987
		// TODO Coupling to SiteTree
988
		$record->HasBrokenLink = 0;
989
		$record->HasBrokenFile = 0;
990
991
		if (!$record->ObsoleteClassName) {
992
			$record->writeWithoutVersion();
993
		}
994
995
		// Update the class instance if necessary
996
		if(isset($data['ClassName']) && $data['ClassName'] != $record->ClassName) {
997
			// Replace $record with a new instance of the new class
998
			$newClassName = $data['ClassName'];
999
			$record = $record->newClassInstance($newClassName);
1000
		}
1001
1002
		// save form data into record
1003
		$form->saveInto($record);
1004
		$record->write();
1005
1006
		// If the 'Save & Publish' button was clicked, also publish the page
1007
		if ($doPublish) {
1008
			$record->publishRecursive();
1009
			$message = _t(
1010
				'CMSMain.PUBLISHED',
1011
				"Published '{title}' successfully.",
1012
				['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...
1013
			);
1014
		} else {
1015
			$message = _t(
1016
				'CMSMain.SAVED',
1017
				"Saved '{title}' successfully.",
1018
				['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...
1019
			);
1020
		}
1021
1022
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1023
		return $this->getResponseNegotiator()->respond($this->getRequest());
1024
	}
1025
1026
	/**
1027
	 * @uses LeftAndMainExtension->augmentNewSiteTreeItem()
1028
	 *
1029
	 * @param int|string $id
1030
	 * @param bool $setID
1031
	 * @return mixed|DataObject
1032
	 * @throws HTTPResponse_Exception
1033
	 */
1034
	public function getNewItem($id, $setID = true) {
1035
		$parentClass = $this->stat('tree_class');
1036
		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...
1037
1038
		if (!is_a($className, $parentClass, true)) {
1039
			$response = Security::permissionFailure($this);
1040
			if (!$response) {
1041
				$response = $this->getResponse();
1042
			}
1043
			throw new HTTPResponse_Exception($response);
1044
		}
1045
1046
		/** @var SiteTree $newItem */
1047
		$newItem = Injector::inst()->create($className);
1048
		if( !$suffix ) {
1049
			$sessionTag = "NewItems." . $parentID . "." . $className;
1050
			if(Session::get($sessionTag)) {
1051
				$suffix = '-' . Session::get($sessionTag);
1052
				Session::set($sessionTag, Session::get($sessionTag) + 1);
1053
			}
1054
			else
1055
				Session::set($sessionTag, 1);
1056
1057
				$id = $id . $suffix;
1058
		}
1059
1060
		$newItem->Title = _t(
1061
			'CMSMain.NEWPAGE',
1062
			"New {pagetype}",'followed by a page type title',
1063
			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...
1064
		);
1065
		$newItem->ClassName = $className;
1066
		$newItem->ParentID = $parentID;
1067
1068
		// DataObject::fieldExists only checks the current class, not the hierarchy
1069
		// This allows the CMS to set the correct sort value
1070
		if($newItem->castingHelper('Sort')) {
1071
			$newItem->Sort = DB::prepared_query('SELECT MAX("Sort") FROM "SiteTree" WHERE "ParentID" = ?', array($parentID))->value() + 1;
1072
		}
1073
1074
		if($setID) $newItem->ID = $id;
1075
1076
		# Some modules like subsites add extra fields that need to be set when the new item is created
1077
		$this->extend('augmentNewSiteTreeItem', $newItem);
1078
1079
		return $newItem;
1080
	}
1081
1082
	/**
1083
	 * Actually perform the publication step
1084
	 *
1085
	 * @param Versioned|DataObject $record
1086
	 * @return mixed
1087
	 */
1088
	public function performPublish($record) {
1089
		if($record && !$record->canPublish()) {
0 ignored issues
show
Bug introduced by
The method canPublish does only exist in SilverStripe\ORM\Versioning\Versioned, but not in SilverStripe\ORM\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...
1090
			return Security::permissionFailure($this);
1091
		}
1092
1093
		$record->publishRecursive();
0 ignored issues
show
Bug introduced by
The method publishRecursive does only exist in SilverStripe\ORM\Versioning\Versioned, but not in SilverStripe\ORM\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...
1094
	}
1095
1096
	/**
1097
	 * Reverts a page by publishing it to live.
1098
	 * Use {@link restorepage()} if you want to restore a page
1099
	 * which was deleted from draft without publishing.
1100
	 *
1101
	 * @uses SiteTree->doRevertToLive()
1102
	 *
1103
	 * @param array $data
1104
	 * @param Form $form
1105
	 * @return HTTPResponse
1106
	 * @throws HTTPResponse_Exception
1107
	 */
1108
	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...
1109
		if(!isset($data['ID'])) {
1110
			throw new HTTPResponse_Exception("Please pass an ID in the form content", 400);
1111
		}
1112
1113
		$id = (int) $data['ID'];
1114
		$restoredPage = Versioned::get_latest_version("SilverStripe\\CMS\\Model\\SiteTree", $id);
1115
		if(!$restoredPage) {
1116
			throw new HTTPResponse_Exception("SiteTree #$id not found", 400);
1117
		}
1118
1119
		/** @var SiteTree $record */
1120
		$record = Versioned::get_one_by_stage('SilverStripe\\CMS\\Model\\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...
1121
			'"SiteTree_Live"."ID"' => $id
1122
		));
1123
1124
		// a user can restore a page without publication rights, as it just adds a new draft state
1125
		// (this action should just be available when page has been "deleted from draft")
1126
		if($record && !$record->canEdit()) {
1127
			return Security::permissionFailure($this);
1128
		}
1129
		if(!$record || !$record->ID) {
1130
			throw new HTTPResponse_Exception("Bad record ID #$id", 404);
1131
		}
1132
1133
		$record->doRevertToLive();
1134
1135
		$this->getResponse()->addHeader(
1136
			'X-Status',
1137
			rawurlencode(_t(
1138
				'CMSMain.RESTORED',
1139
				"Restored '{title}' successfully",
1140
				'Param %s is a title',
1141
				array('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...
1142
			))
1143
		);
1144
1145
		return $this->getResponseNegotiator()->respond($this->getRequest());
1146
	}
1147
1148
	/**
1149
	 * Delete the current page from draft stage.
1150
	 *
1151
	 * @see deletefromlive()
1152
	 *
1153
	 * @param array $data
1154
	 * @param Form $form
1155
	 * @return HTTPResponse
1156
	 * @throws HTTPResponse_Exception
1157
	 */
1158 View Code Duplication
	public function delete($data, $form) {
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...
1159
		$id = $data['ID'];
1160
		$record = SiteTree::get()->byID($id);
1161
		if($record && !$record->canDelete()) {
1162
			return Security::permissionFailure();
1163
		}
1164
		if(!$record || !$record->ID) {
1165
			throw new HTTPResponse_Exception("Bad record ID #$id", 404);
1166
		}
1167
1168
		// Delete record
1169
		$record->delete();
1170
1171
		$this->getResponse()->addHeader(
1172
			'X-Status',
1173
			rawurlencode(sprintf(_t('CMSMain.REMOVEDPAGEFROMDRAFT',"Removed '%s' from the draft site"), $record->Title))
1174
		);
1175
1176
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1177
		return $this->getResponseNegotiator()->respond($this->getRequest());
1178
	}
1179
1180
	/**
1181
	 * Delete this page from both live and stage
1182
	 *
1183
	 * @param array $data
1184
	 * @param Form $form
1185
	 * @return HTTPResponse
1186
	 * @throws HTTPResponse_Exception
1187
	 */
1188 View Code Duplication
	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...
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...
1189
		$id = $data['ID'];
1190
		/** @var SiteTree $record */
1191
		$record = SiteTree::get()->byID($id);
1192
		if(!$record || !$record->exists()) {
1193
			throw new HTTPResponse_Exception("Bad record ID #$id", 404);
1194
		}
1195
		if(!$record->canArchive()) {
1196
			return Security::permissionFailure();
1197
		}
1198
1199
		// Archive record
1200
		$record->doArchive();
1201
1202
		$this->getResponse()->addHeader(
1203
			'X-Status',
1204
			rawurlencode(sprintf(_t('CMSMain.ARCHIVEDPAGE',"Archived page '%s'"), $record->Title))
1205
		);
1206
1207
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1208
		return $this->getResponseNegotiator()->respond($this->getRequest());
1209
	}
1210
1211
	public function publish($data, $form) {
1212
		$data['publish'] = '1';
1213
1214
		return $this->save($data, $form);
1215
	}
1216
1217
	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...
1218
		$className = $this->stat('tree_class');
1219
		/** @var SiteTree $record */
1220
		$record = DataObject::get_by_id($className, $data['ID']);
1221
1222
		if($record && !$record->canUnpublish()) {
1223
			return Security::permissionFailure($this);
1224
		}
1225
		if(!$record || !$record->ID) {
1226
			throw new HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
1227
		}
1228
1229
		$record->doUnpublish();
1230
1231
		$this->getResponse()->addHeader(
1232
			'X-Status',
1233
			rawurlencode(_t('CMSMain.REMOVEDPAGE',"Removed '{title}' from the published site", array('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...
1234
		);
1235
1236
		return $this->getResponseNegotiator()->respond($this->getRequest());
1237
	}
1238
1239
	/**
1240
	 * @return HTTPResponse
1241
	 */
1242
	public function rollback() {
1243
		return $this->doRollback(array(
1244
			'ID' => $this->currentPageID(),
1245
			'Version' => $this->getRequest()->param('VersionID')
1246
		), null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<SilverStripe\Forms\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...
1247
	}
1248
1249
	/**
1250
	 * Rolls a site back to a given version ID
1251
	 *
1252
	 * @param array $data
1253
	 * @param Form $form
1254
	 * @return HTTPResponse
1255
	 */
1256
	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...
1257
		$this->extend('onBeforeRollback', $data['ID']);
1258
1259
		$id = (isset($data['ID'])) ? (int) $data['ID'] : null;
1260
		$version = (isset($data['Version'])) ? (int) $data['Version'] : null;
1261
1262
		/** @var DataObject|Versioned $record */
1263
		$record = DataObject::get_by_id($this->stat('tree_class'), $id);
1264
		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...
1265
			return Security::permissionFailure($this);
1266
		}
1267
1268
		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...
1269
			$record->doRollbackTo($version);
0 ignored issues
show
Bug introduced by
The method doRollbackTo does only exist in SilverStripe\ORM\Versioning\Versioned, but not in SilverStripe\ORM\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...
1270
			$message = _t(
1271
				'CMSMain.ROLLEDBACKVERSIONv2',
1272
				"Rolled back to version #%d.",
1273
				array('version' => $data['Version'])
0 ignored issues
show
Documentation introduced by
array('version' => $data['Version']) is of type array<string,?,{"version":"?"}>, 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...
1274
			);
1275
		} else {
1276
			$record->doRevertToLive();
0 ignored issues
show
Bug introduced by
The method doRevertToLive does only exist in SilverStripe\ORM\Versioning\Versioned, but not in SilverStripe\ORM\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...
1277
			$message = _t(
1278
				'CMSMain.ROLLEDBACKPUBv2',"Rolled back to published version."
1279
			);
1280
		}
1281
1282
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1283
1284
		// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
1285
		// Or in history view, in which case a revert causes the CMS to re-load the edit view.
1286
		// The X-Pjax header forces a "full" content refresh on redirect.
1287
		$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $record->ID);
1288
		$this->getResponse()->addHeader('X-ControllerURL', $url);
1289
		$this->getRequest()->addHeader('X-Pjax', 'Content');
1290
		$this->getResponse()->addHeader('X-Pjax', 'Content');
1291
1292
		return $this->getResponseNegotiator()->respond($this->getRequest());
1293
	}
1294
1295
	/**
1296
	 * Batch Actions Handler
1297
	 */
1298
	public function batchactions() {
1299
		return new CMSBatchActionHandler($this, 'batchactions');
1300
	}
1301
1302
	public function BatchActionParameters() {
1303
		$batchActions = CMSBatchActionHandler::config()->batch_actions;
1304
1305
		$forms = array();
1306
		foreach($batchActions as $urlSegment => $batchAction) {
1307
			$SNG_action = singleton($batchAction);
1308
			if ($SNG_action->canView() && $fieldset = $SNG_action->getParameterFields()) {
1309
				$formHtml = '';
1310
				/** @var FormField $field */
1311
				foreach($fieldset as $field) {
1312
					$formHtml .= $field->Field();
1313
				}
1314
				$forms[$urlSegment] = $formHtml;
1315
			}
1316
		}
1317
		$pageHtml = '';
1318
		foreach($forms as $urlSegment => $html) {
1319
			$pageHtml .= "<div class=\"params\" id=\"BatchActionParameters_$urlSegment\">$html</div>\n\n";
1320
		}
1321
		return new LiteralField("BatchActionParameters", '<div id="BatchActionParameters" style="display:none">'.$pageHtml.'</div>');
1322
	}
1323
	/**
1324
	 * Returns a list of batch actions
1325
	 */
1326
	public function BatchActionList() {
1327
		return $this->batchactions()->batchActionList();
1328
	}
1329
1330
	public function publishall($request) {
1331
		if(!Permission::check('ADMIN')) return Security::permissionFailure($this);
1332
1333
		increase_time_limit_to();
1334
		increase_memory_limit_to();
1335
1336
		$response = "";
1337
1338
		if(isset($this->requestParams['confirm'])) {
1339
			// Protect against CSRF on destructive action
1340
			if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1341
1342
			$start = 0;
1343
			$pages = SiteTree::get()->limit("$start,30");
1344
			$count = 0;
1345
			while($pages) {
1346
				/** @var SiteTree $page */
1347
				foreach($pages as $page) {
1348
					if($page && !$page->canPublish()) {
1349
						return Security::permissionFailure($this);
1350
					}
1351
1352
					$page->publishRecursive();
1353
					$page->destroy();
1354
					unset($page);
1355
					$count++;
1356
					$response .= "<li>$count</li>";
1357
				}
1358
				if($pages->count() > 29) {
1359
					$start += 30;
1360
					$pages = SiteTree::get()->limit("$start,30");
1361
				} else {
1362
					break;
1363
				}
1364
			}
1365
			$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...
1366
1367
		} else {
1368
			$token = SecurityToken::inst();
1369
			$fields = new FieldList();
1370
			$token->updateFieldSet($fields);
1371
			$tokenField = $fields->first();
1372
			$tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : '';
1373
			$response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
1374
				<p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish".  It\'s
1375
				intended to be used after there have been massive edits of the content, such as when the site was
1376
				first built.') . '</p>
1377
				<form method="post" action="publishall">
1378
					<input type="submit" name="confirm" value="'
1379
					. _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",'Confirmation button') .'" />'
1380
					. $tokenHtml .
1381
				'</form>';
1382
		}
1383
1384
		return $response;
1385
	}
1386
1387
	/**
1388
	 * Restore a completely deleted page from the SiteTree_versions table.
1389
	 *
1390
	 * @param array $data
1391
	 * @param Form $form
1392
	 * @return HTTPResponse
1393
	 */
1394
	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...
1395
		if(!isset($data['ID']) || !is_numeric($data['ID'])) {
1396
			return new HTTPResponse("Please pass an ID in the form content", 400);
1397
		}
1398
1399
		$id = (int)$data['ID'];
1400
		/** @var SiteTree $restoredPage */
1401
		$restoredPage = Versioned::get_latest_version("SilverStripe\\CMS\\Model\\SiteTree", $id);
1402
		if(!$restoredPage) {
1403
			return new HTTPResponse("SiteTree #$id not found", 400);
1404
		}
1405
1406
		$restoredPage = $restoredPage->doRestoreToStage();
1407
1408
		$this->getResponse()->addHeader(
1409
			'X-Status',
1410
			rawurlencode(_t(
1411
				'CMSMain.RESTORED',
1412
				"Restored '{title}' successfully",
1413
				array('title' => $restoredPage->Title)
0 ignored issues
show
Documentation introduced by
array('title' => $restoredPage->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...
1414
			))
1415
		);
1416
1417
		return $this->getResponseNegotiator()->respond($this->getRequest());
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getRespons...d($this->getRequest()); (SS_HTTPResponse) is incompatible with the return type documented by SilverStripe\CMS\Controllers\CMSMain::restore of type SilverStripe\Control\HTTPResponse.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1418
	}
1419
1420
	public function duplicate($request) {
1421
		// Protect against CSRF on destructive action
1422
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1423
1424
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1425
			/** @var SiteTree $page */
1426
			$page = SiteTree::get()->byID($id);
1427 View Code Duplication
			if($page && (!$page->canEdit() || !$page->canCreate(null, array('Parent' => $page->Parent())))) {
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...
1428
				return Security::permissionFailure($this);
1429
			}
1430
			if(!$page || !$page->ID) throw new HTTPResponse_Exception("Bad record ID #$id", 404);
1431
1432
			$newPage = $page->duplicate();
1433
1434
			// ParentID can be hard-set in the URL.  This is useful for pages with multiple parents
1435
			if(isset($_GET['parentID']) && is_numeric($_GET['parentID'])) {
1436
				$newPage->ParentID = $_GET['parentID'];
1437
				$newPage->write();
1438
			}
1439
1440
			$this->getResponse()->addHeader(
1441
				'X-Status',
1442
				rawurlencode(_t(
1443
					'CMSMain.DUPLICATED',
1444
					"Duplicated '{title}' successfully",
1445
					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...
1446
				))
1447
			);
1448
			$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $newPage->ID);
1449
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1450
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1451
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1452
1453
			return $this->getResponseNegotiator()->respond($this->getRequest());
1454
		} else {
1455
			return new HTTPResponse("CMSMain::duplicate() Bad ID: '$id'", 400);
1456
		}
1457
	}
1458
1459
	public function duplicatewithchildren($request) {
1460
		// Protect against CSRF on destructive action
1461
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1462
		increase_time_limit_to();
1463
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1464
			/** @var SiteTree $page */
1465
			$page = SiteTree::get()->byID($id);
1466 View Code Duplication
			if($page && (!$page->canEdit() || !$page->canCreate(null, array('Parent' => $page->Parent())))) {
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...
1467
				return Security::permissionFailure($this);
1468
			}
1469
			if(!$page || !$page->ID) throw new HTTPResponse_Exception("Bad record ID #$id", 404);
1470
1471
			$newPage = $page->duplicateWithChildren();
1472
1473
			$this->getResponse()->addHeader(
1474
				'X-Status',
1475
				rawurlencode(_t(
1476
					'CMSMain.DUPLICATEDWITHCHILDREN',
1477
					"Duplicated '{title}' and children successfully",
1478
					array('title' => $newPage->Title)
0 ignored issues
show
Documentation introduced by
array('title' => $newPage->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...
1479
				))
1480
			);
1481
			$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $newPage->ID);
1482
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1483
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1484
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1485
1486
			return $this->getResponseNegotiator()->respond($this->getRequest());
1487
		} else {
1488
			return new HTTPResponse("CMSMain::duplicatewithchildren() Bad ID: '$id'", 400);
1489
		}
1490
	}
1491
1492
	public function providePermissions() {
1493
		$title = CMSPagesController::menu_title();
1494
		return array(
1495
			"CMS_ACCESS_CMSMain" => array(
1496
				'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...
1497
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
1498
				'help' => _t(
1499
					'CMSMain.ACCESS_HELP',
1500
					'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".'
1501
				),
1502
				'sort' => -99 // below "CMS_ACCESS_LeftAndMain", but above everything else
1503
			)
1504
		);
1505
	}
1506
1507
}
1508