Completed
Push — master ( fdcc3f...9493d9 )
by Daniel
13s
created

CMSMain::Breadcrumbs()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 11
rs 9.4285
c 1
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
3
namespace SilverStripe\CMS\Controllers;
4
5
use FormField;
6
use Injector;
7
use ResetFormAction;
8
use SilverStripe\ORM\FieldType\DBHTMLText;
9
use SilverStripe\ORM\SS_List;
10
use SilverStripe\ORM\Versioning\Versioned;
11
use SilverStripe\ORM\HiddenClass;
12
use SilverStripe\ORM\ArrayList;
13
use SilverStripe\ORM\DataObject;
14
use SilverStripe\ORM\DataList;
15
use SilverStripe\ORM\DB;
16
use SilverStripe\Security\Member;
17
use SilverStripe\Security\Security;
18
use SilverStripe\Security\SecurityToken;
19
use SilverStripe\Security\Permission;
20
use SilverStripe\Security\PermissionProvider;
21
use SilverStripe\Admin\AdminRootController;
22
use SilverStripe\Admin\LeftAndMain;
23
use SilverStripe\Admin\CMSBatchActionHandler;
24
use SilverStripe\Admin\CMSPreviewable;
25
use SilverStripe\CMS\Model\SiteTree;
26
use SilverStripe\CMS\Model\RedirectorPage;
27
use SilverStripe\CMS\Model\CurrentPageIdentifier;
28
use SS_HTTPRequest;
29
use TabSet;
30
use Translatable;
31
use Requirements;
32
use Controller;
33
use Director;
34
use Page;
35
use TextField;
36
use DateField;
37
use DropdownField;
38
use FieldGroup;
39
use FieldList;
40
use FormAction;
41
use Form;
42
use SS_Cache;
43
use Zend_Cache;
44
use Convert;
45
use ArrayData;
46
use HiddenField;
47
use LiteralField;
48
use RequiredFields;
49
use LabelField;
50
use InvalidArgumentException;
51
use GridFieldConfig;
52
use GridFieldSortableHeader;
53
use GridFieldDataColumns;
54
use GridFieldPaginator;
55
use GridFieldLevelup;
56
use GridField;
57
use SS_HTTPResponse_Exception;
58
use Session;
59
use SS_HTTPResponse;
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
 * @package cms
68
 * @subpackage controller
69
 * @todo Create some base classes to contain the generic functionality that will be replicated.
70
 *
71
 * @mixin LeftAndMainPageIconsExtension
72
 */
73
class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionProvider {
0 ignored issues
show
Bug introduced by
There is one abstract method isCurrentPage in this class; you could implement it, or declare this class as abstract.
Loading history...
74
75
	private static $url_segment = 'pages';
76
77
	private static $url_rule = '/$Action/$ID/$OtherID';
78
79
	// Maintain a lower priority than other administration sections
80
	// so that Director does not think they are actions of CMSMain
81
	private static $url_priority = 39;
82
83
	private static $menu_title = 'Edit Page';
84
85
	private static $menu_priority = 10;
86
87
	private static $tree_class = "SilverStripe\\CMS\\Model\\SiteTree";
88
89
	private static $subitem_class = "SilverStripe\\Security\\Member";
90
91
	private static $session_namespace = 'SilverStripe\\CMS\\Controllers\\CMSMain';
92
93
	private static $required_permission_codes = 'CMS_ACCESS_CMSMain';
94
95
	/**
96
	 * Amount of results showing on a single page.
97
	 *
98
	 * @config
99
	 * @var int
100
	 */
101
	private static $page_length = 15;
102
103
	private static $allowed_actions = array(
104
		'archive',
105
		'deleteitems',
106
		'DeleteItemsForm',
107
		'dialog',
108
		'duplicate',
109
		'duplicatewithchildren',
110
		'publishall',
111
		'publishitems',
112
		'PublishItemsForm',
113
		'submit',
114
		'EditForm',
115
		'SearchForm',
116
		'SiteTreeAsUL',
117
		'getshowdeletedsubtree',
118
		'batchactions',
119
		'treeview',
120
		'listview',
121
		'ListViewForm',
122
		'childfilter',
123
	);
124
125
	private static $casting = array(
126
		'TreeIsFiltered' => 'Boolean',
127
		'AddForm' => 'HTMLFragment',
128
		'LinkPages' => 'Text',
129
		'Link' => 'Text',
130
		'ListViewForm' => 'HTMLFragment',
131
		'ExtraTreeTools' => 'HTMLFragment',
132
		'SiteTreeHints' => 'HTMLFragment',
133
		'SecurityID' => 'Text',
134
		'SiteTreeAsUL' => 'HTMLFragment',
135
	);
136
137
	public function init() {
138
		// set reading lang
139
		if(SiteTree::has_extension('Translatable') && !$this->getRequest()->isAjax()) {
140
			Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SilverStripe\\CMS\\Model\\SiteTree')));
141
		}
142
143
		parent::init();
144
145
		Requirements::css(CMS_DIR . '/client/dist/styles/bundle.css');
146
		Requirements::customCSS($this->generatePageIconsCss());
147
		Requirements::add_i18n_javascript(CMS_DIR . '/client/lang', false, true);
148
		Requirements::javascript(CMS_DIR . '/client/dist/js/bundle-legacy.js', [
149
			'provides' => [
150
				CMS_DIR . '/client/dist/js/CMSMain.AddForm.js',
151
				CMS_DIR . '/client/dist/js/CMSMain.EditForm.js',
152
				CMS_DIR . '/client/dist/js/CMSMain.js',
153
				CMS_DIR . '/client/dist/js/CMSMain.Tree.js',
154
				CMS_DIR . '/client/dist/js/CMSPageHistoryController.js',
155
				CMS_DIR . '/client/dist/js/RedirectorPage.js',
156
				CMS_DIR . '/client/dist/js/SilverStripeNavigator.js',
157
				CMS_DIR . '/client/dist/js/SiteTreeURLSegmentField.js'
158
			]
159
		]);
160
161
		CMSBatchActionHandler::register('publish', 'SilverStripe\\CMS\\BatchActions\\CMSBatchAction_Publish');
162
		CMSBatchActionHandler::register('unpublish', 'SilverStripe\\CMS\\BatchActions\\CMSBatchAction_Unpublish');
163
		CMSBatchActionHandler::register('delete', 'SilverStripe\\CMS\\BatchActions\\CMSBatchAction_Delete');
164
		CMSBatchActionHandler::register('archive', 'SilverStripe\\CMS\\BatchActions\\CMSBatchAction_Archive');
165
		CMSBatchActionHandler::register('restore', 'SilverStripe\\CMS\\BatchActions\\CMSBatchAction_Restore');
166
	}
167
168
	public function index($request) {
169
		// In case we're not showing a specific record, explicitly remove any session state,
170
		// to avoid it being highlighted in the tree, and causing an edit form to show.
171
		if(!$request->param('Action')) {
172
			$this->setCurrentPageID(null);
173
		}
174
175
		return parent::index($request);
176
	}
177
178
	public function getResponseNegotiator() {
179
		$negotiator = parent::getResponseNegotiator();
180
		$controller = $this;
181
		$negotiator->setCallback('ListViewForm', function() use(&$controller) {
182
			return $controller->ListViewForm()->forTemplate();
183
		});
184
		return $negotiator;
185
	}
186
187
	/**
188
	 * If this is set to true, the "switchView" context in the
189
	 * template is shown, with links to the staging and publish site.
190
	 *
191
	 * @return boolean
192
	 */
193
	public function ShowSwitchView() {
194
		return true;
195
	}
196
197
	/**
198
	 * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
199
	 * to switch view also for archived versions.
200
	 *
201
	 * @param SiteTree $page
202
	 * @return array
203
	 */
204
	public function SwitchView($page = null) {
205
		if(!$page) {
206
			$page = $this->currentPage();
207
		}
208
209
		if($page) {
210
			$nav = SilverStripeNavigator::get_for_record($page);
211
			return $nav['items'];
212
		}
213
	}
214
215
	//------------------------------------------------------------------------------------------//
216
	// Main controllers
217
218
	//------------------------------------------------------------------------------------------//
219
	// Main UI components
220
221
	/**
222
	 * Override {@link LeftAndMain} Link to allow blank URL segment for CMSMain.
223
	 *
224
	 * @param string|null $action Action to link to.
225
	 * @return string
226
	 */
227
	public function Link($action = null) {
228
		$link = Controller::join_links(
229
			AdminRootController::admin_url(),
230
			$this->stat('url_segment'), // in case we want to change the segment
231
			'/', // trailing slash needed if $action is null!
232
			"$action"
233
		);
234
		$this->extend('updateLink', $link);
235
		return $link;
236
	}
237
238
	public function LinkPages() {
239
		return CMSPagesController::singleton()->Link();
240
	}
241
242
	public function LinkPagesWithSearch() {
243
		return $this->LinkWithSearch($this->LinkPages());
244
	}
245
246
	public function LinkTreeView() {
247
		return $this->LinkWithSearch($this->Link('treeview'));
248
	}
249
250
	public function LinkListView() {
251
		return $this->LinkWithSearch($this->Link('listview'));
252
	}
253
254
	public function LinkGalleryView() {
255
		return $this->LinkWithSearch($this->Link('galleryview'));
256
	}
257
258 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...
259
		if(!$id) {
260
			$id = $this->currentPageID();
261
		}
262
		return $this->LinkWithSearch(
263
			Controller::join_links(CMSPageEditController::singleton()->Link('show'), $id)
264
		);
265
	}
266
267 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...
268
		if($id = $this->currentPageID()) {
269
			return $this->LinkWithSearch(
270
				Controller::join_links(CMSPageSettingsController::singleton()->Link('show'), $id)
271
			);
272
		} else {
273
			return null;
274
		}
275
	}
276
277 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...
278
		if($id = $this->currentPageID()) {
279
			return $this->LinkWithSearch(
280
				Controller::join_links(CMSPageHistoryController::singleton()->Link('show'), $id)
281
			);
282
		} else {
283
			return null;
284
		}
285
	}
286
287
	public function LinkWithSearch($link) {
288
		// Whitelist to avoid side effects
289
		$params = array(
290
			'q' => (array)$this->getRequest()->getVar('q'),
291
			'ParentID' => $this->getRequest()->getVar('ParentID')
292
		);
293
		$link = Controller::join_links(
294
			$link,
295
			array_filter(array_values($params)) ? '?' . http_build_query($params) : null
296
		);
297
		$this->extend('updateLinkWithSearch', $link);
298
		return $link;
299
	}
300
301
	public function LinkPageAdd($extra = null, $placeholders = null) {
302
		$link = CMSPageAddController::singleton()->Link();
303
		$this->extend('updateLinkPageAdd', $link);
304
305
		if($extra) {
306
			$link = Controller::join_links ($link, $extra);
307
		}
308
309
		if($placeholders) {
310
			$link .= (strpos($link, '?') === false ? "?$placeholders" : "&amp;$placeholders");
311
		}
312
313
		return $link;
314
	}
315
316
	/**
317
	 * @return string
318
	 */
319
	public function LinkPreview() {
320
		$record = $this->getRecord($this->currentPageID());
321
		$baseLink = Director::absoluteBaseURL();
322
		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...
323
			// if we are an external redirector don't show a link
324
			if ($record instanceof RedirectorPage && $record->RedirectionType == 'External') {
325
				$baseLink = false;
326
			}
327
			else {
328
				$baseLink = $record->Link('?stage=Stage');
329
			}
330
		}
331
		return $baseLink;
332
	}
333
334
	/**
335
	 * Return the entire site tree as a nested set of ULs
336
	 */
337
	public function SiteTreeAsUL() {
338
		// Pre-cache sitetree version numbers for querying efficiency
339
		Versioned::prepopulate_versionnumber_cache("SilverStripe\\CMS\\Model\\SiteTree", "Stage");
340
		Versioned::prepopulate_versionnumber_cache("SilverStripe\\CMS\\Model\\SiteTree", "Live");
341
		$html = $this->getSiteTreeFor($this->stat('tree_class'));
342
343
		$this->extend('updateSiteTreeAsUL', $html);
344
345
		return $html;
346
	}
347
348
	/**
349
	 * @return boolean
350
	 */
351
	public function TreeIsFiltered() {
352
		$query = $this->getRequest()->getVar('q');
353
354
		if (!$query || (count($query) === 1 && isset($query['FilterClass']) && $query['FilterClass'] === 'SilverStripe\\CMS\\Controllers\\CMSSiteTreeFilter_Search')) {
355
			return false;
356
		}
357
358
		return true;
359
	}
360
361
	public function ExtraTreeTools() {
362
		$html = '';
363
		$this->extend('updateExtraTreeTools', $html);
364
		return $html;
365
	}
366
367
	/**
368
	 * Returns a Form for page searching for use in templates.
369
	 *
370
	 * Can be modified from a decorator by a 'updateSearchForm' method
371
	 *
372
	 * @return Form
373
	 */
374
	public function SearchForm() {
375
		// Create the fields
376
		$content = new TextField('q[Term]', _t('CMSSearch.FILTERLABELTEXT', 'Search'));
377
		$dateFrom = new DateField(
378
			'q[LastEditedFrom]',
379
			_t('CMSSearch.FILTERDATEFROM', 'From')
380
		);
381
		$dateFrom->setConfig('showcalendar', true);
382
		$dateTo = new DateField(
383
			'q[LastEditedTo]',
384
			_t('CMSSearch.FILTERDATETO', 'To')
385
		);
386
		$dateTo->setConfig('showcalendar', true);
387
		$pageFilter = new DropdownField(
388
			'q[FilterClass]',
389
			_t('CMSMain.PAGES', 'Page status'),
390
			CMSSiteTreeFilter::get_all_filters()
391
		);
392
		$pageClasses = new DropdownField(
393
			'q[ClassName]',
394
			_t('CMSMain.PAGETYPEOPT', 'Page type', 'Dropdown for limiting search to a page type'),
395
			$this->getPageTypes()
396
		);
397
		$pageClasses->setEmptyString(_t('CMSMain.PAGETYPEANYOPT','Any'));
398
399
		// Group the Datefields
400
		$dateGroup = new FieldGroup(
401
			$dateFrom,
402
			$dateTo
403
		);
404
		$dateGroup->setTitle(_t('CMSSearch.PAGEFILTERDATEHEADING', 'Last edited'));
405
406
		// Create the Field list
407
		$fields = new FieldList(
408
			$content,
409
			$pageFilter,
410
			$pageClasses,
411
			$dateGroup
412
		);
413
414
		// Create the Search and Reset action
415
		$actions = new FieldList(
416
			FormAction::create('doSearch',  _t('CMSMain_left_ss.APPLY_FILTER', 'Search'))
417
				->addExtraClass('ss-ui-action-constructive'),
418
			ResetFormAction::create('clear', _t('CMSMain_left_ss.CLEAR_FILTER', 'Clear'))
419
		);
420
421
		// Use <button> to allow full jQuery UI styling on the all of the Actions
422
		/** @var FormAction $action */
423
		foreach($actions->dataFields() as $action) {
424
			/** @var FormAction $action */
425
			$action->setUseButtonTag(true);
426
		}
427
428
		// Create the form
429
		/** @skipUpgrade */
430
		$form = Form::create($this, 'SearchForm', $fields, $actions)
431
			->addExtraClass('cms-search-form')
432
			->setFormMethod('GET')
433
			->setFormAction($this->Link())
434
			->disableSecurityToken()
435
			->unsetValidator();
436
437
		// Load the form with previously sent search data
438
		$form->loadDataFrom($this->getRequest()->getVars());
439
440
		// Allow decorators to modify the form
441
		$this->extend('updateSearchForm', $form);
442
443
		return $form;
444
	}
445
446
	/**
447
	 * Returns a sorted array suitable for a dropdown with pagetypes and their translated name
448
	 *
449
	 * @return array
450
	 */
451
	protected function getPageTypes() {
452
		$pageTypes = array();
453
		foreach(SiteTree::page_type_classes() as $pageTypeClass) {
454
			$pageTypes[$pageTypeClass] = SiteTree::singleton($pageTypeClass)->i18n_singular_name();
455
		}
456
		asort($pageTypes);
457
		return $pageTypes;
458
	}
459
460
	public function doSearch($data, $form) {
461
		return $this->getsubtree($this->getRequest());
462
	}
463
464
	/**
465
	 * @param bool $unlinked
466
	 * @return ArrayList
467
	 */
468
	public function Breadcrumbs($unlinked = false) {
469
		$items = parent::Breadcrumbs($unlinked);
470
471
		if($items->count() > 1) {
472
			// Specific to the SiteTree admin section, we never show the cms section and current
473
			// page in the same breadcrumbs block.
474
			$items->shift();
475
		}
476
477
		return $items;
478
	}
479
480
	/**
481
	 * Create serialized JSON string with site tree hints data to be injected into
482
	 * 'data-hints' attribute of root node of jsTree.
483
	 *
484
	 * @return string Serialized JSON
485
	 */
486
	public function SiteTreeHints() {
487
		$classes = SiteTree::page_type_classes();
488
489
	 	$cacheCanCreate = array();
490
	 	foreach($classes as $class) $cacheCanCreate[$class] = singleton($class)->canCreate();
491
492
	 	// Generate basic cache key. Too complex to encompass all variations
493
	 	$cache = SS_Cache::factory('CMSMain_SiteTreeHints');
494
	 	$cacheKey = md5(implode('_', array(Member::currentUserID(), implode(',', $cacheCanCreate), implode(',', $classes))));
495
	 	if($this->getRequest()->getVar('flush')) {
496
	 		$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
497
		}
498
	 	$json = $cache->load($cacheKey);
499
	 	if(!$json) {
500
			$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...
501
			$def['Root']['disallowedChildren'] = array();
502
503
			// Contains all possible classes to support UI controls listing them all,
504
			// such as the "add page here" context menu.
505
			$def['All'] = array();
506
507
			// Identify disallows and set globals
508
			foreach($classes as $class) {
509
				$obj = singleton($class);
510
				if($obj instanceof HiddenClass) continue;
511
512
				// Name item
513
				$def['All'][$class] = array(
514
					'title' => $obj->i18n_singular_name()
515
				);
516
517
				// Check if can be created at the root
518
				$needsPerm = $obj->stat('need_permission');
519
				if(
520
					!$obj->stat('can_be_root')
521
					|| (!array_key_exists($class, $cacheCanCreate) || !$cacheCanCreate[$class])
522
					|| ($needsPerm && !$this->can($needsPerm))
523
				) {
524
					$def['Root']['disallowedChildren'][] = $class;
525
				}
526
527
				// Hint data specific to the class
528
				$def[$class] = array();
529
530
				$defaultChild = $obj->defaultChild();
531
				if($defaultChild !== 'Page' && $defaultChild !== null) {
532
					$def[$class]['defaultChild'] = $defaultChild;
533
				}
534
535
				$defaultParent = $obj->defaultParent();
536
				if ($defaultParent !== 1 && $defaultParent !== null) {
537
					$def[$class]['defaultParent'] = $defaultParent;
538
				}
539
			}
540
541
			$this->extend('updateSiteTreeHints', $def);
542
543
			$json = Convert::raw2json($def);
544
			$cache->save($json, $cacheKey);
545
		}
546
		return $json;
547
	}
548
549
	/**
550
	 * Populates an array of classes in the CMS
551
	 * which allows the user to change the page type.
552
	 *
553
	 * @return SS_List
554
	 */
555
	public function PageTypes() {
556
		$classes = SiteTree::page_type_classes();
557
558
		$result = new ArrayList();
559
560
		foreach($classes as $class) {
561
			$instance = singleton($class);
562
563
			if($instance instanceof HiddenClass) {
564
				continue;
565
			}
566
567
			// skip this type if it is restricted
568
			if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) {
569
				continue;
570
			}
571
572
			$addAction = $instance->i18n_singular_name();
573
574
			// Get description (convert 'Page' to 'SiteTree' for correct localization lookups)
575
			$i18nClass = ($class == 'Page') ? 'SilverStripe\\CMS\\Model\\SiteTree' : $class;
576
			$description = _t($i18nClass . '.DESCRIPTION');
577
578
			if(!$description) {
579
				$description = $instance->uninherited('description');
580
			}
581
582
			if($class == 'Page' && !$description) {
583
				$description = SiteTree::singleton()->uninherited('description');
584
			}
585
586
			$result->push(new ArrayData(array(
587
				'ClassName' => $class,
588
				'AddAction' => $addAction,
589
				'Description' => $description,
590
				// TODO Sprite support
591
				'IconURL' => $instance->stat('icon'),
592
				'Title' => singleton($class)->i18n_singular_name(),
593
			)));
594
		}
595
596
		$result = $result->sort('AddAction');
597
598
		return $result;
599
	}
600
601
	/**
602
	 * Get a database record to be managed by the CMS.
603
	 *
604
	 * @param int $id Record ID
605
	 * @param int $versionID optional Version id of the given record
606
	 * @return SiteTree
607
	 */
608
 	public function getRecord($id, $versionID = null) {
609
		$treeClass = $this->stat('tree_class');
610
611
		if($id instanceof $treeClass) {
612
			return $id;
613
		}
614
		else if($id && is_numeric($id)) {
615
			$currentStage = Versioned::get_reading_mode();
616
617
			if($this->getRequest()->getVar('Version')) {
618
				$versionID = (int) $this->getRequest()->getVar('Version');
619
			}
620
621
			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...
622
				$record = Versioned::get_version($treeClass, $id, $versionID);
623
			} else {
624
				$record = DataObject::get_by_id($treeClass, $id);
625
			}
626
627
			// Then, try getting a record from the live site
628
			if(!$record) {
629
				// $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id");
630
				Versioned::set_stage(Versioned::LIVE);
631
				singleton($treeClass)->flushCache();
632
633
				$record = DataObject::get_by_id($treeClass, $id);
634
			}
635
636
			// Then, try getting a deleted record
637
			if(!$record) {
638
				$record = Versioned::get_latest_version($treeClass, $id);
639
			}
640
641
			// Don't open a page from a different locale
642
			/** The record's Locale is saved in database in 2.4, and not related with Session,
643
			 *  we should not check their locale matches the Translatable::get_current_locale,
644
			 * 	here as long as we all the HTTPRequest is init with right locale.
645
			 *	This bit breaks the all FileIFrameField functions if the field is used in CMS
646
			 *  and its relevent ajax calles, like loading the tree dropdown for TreeSelectorField.
647
			 */
648
			/* if($record && SiteTree::has_extension('Translatable') && $record->Locale && $record->Locale != Translatable::get_current_locale()) {
649
				$record = null;
650
			}*/
651
652
			// Set the reading mode back to what it was.
653
			Versioned::set_reading_mode($currentStage);
654
655
			return $record;
656
657
		} else if(substr($id,0,3) == 'new') {
658
			return $this->getNewItem($id);
659
		}
660
	}
661
662
	/**
663
	 * @param int $id
664
	 * @param FieldList $fields
665
	 * @return Form
666
	 */
667
	public function getEditForm($id = null, $fields = null) {
668
		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...
669
		$form = parent::getEditForm($id, $fields);
670
671
		// TODO Duplicate record fetching (see parent implementation)
672
		$record = $this->getRecord($id);
673
		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 673 which is incompatible with the return type documented by SilverStripe\CMS\Controllers\CMSMain::getEditForm of type Form.
Loading history...
674
675
		if(!$fields) $fields = $form->Fields();
676
		$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...
677
678
		if($record) {
679
			$deletedFromStage = $record->getIsDeletedFromStage();
680
681
			$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...
682
			// Necessary for different subsites
683
			$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...
684
			$fields->push($liveLinkField = new HiddenField("LiveLink"));
685
			$fields->push($stageLinkField = new HiddenField("StageLink"));
686
			$fields->push(new HiddenField("TreeTitle", false, $record->TreeTitle));
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...
687
688
			if($record->ID && is_numeric( $record->ID ) ) {
689
				$liveLink = $record->getAbsoluteLiveLink();
690
				if($liveLink) $liveLinkField->setValue($liveLink);
0 ignored issues
show
Bug Best Practice introduced by
The expression $liveLink of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
691
				if(!$deletedFromStage) {
692
					$stageLink = Controller::join_links($record->AbsoluteLink(), '?stage=Stage');
693
					if($stageLink) $stageLinkField->setValue($stageLink);
694
				}
695
			}
696
697
			// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
698
			/** @skipUpgrade */
699
			if($record instanceof CMSPreviewable && !$fields->fieldByName('SilverStripeNavigator')) {
0 ignored issues
show
Bug introduced by
The class SilverStripe\Admin\CMSPreviewable 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...
700
				$navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator());
701
				$navField->setAllowHTML(true);
702
				$fields->push($navField);
703
			}
704
705
			// getAllCMSActions can be used to completely redefine the action list
706
			if($record->hasMethod('getAllCMSActions')) {
707
				$actions = $record->getAllCMSActions();
708
			} else {
709
				$actions = $record->getCMSActions();
710
711
				// Find and remove action menus that have no actions.
712
				if ($actions && $actions->count()) {
713
					/** @var TabSet $tabset */
714
					$tabset = $actions->fieldByName('ActionMenus');
715
					if ($tabset) {
716
						foreach ($tabset->getChildren() as $tab) {
717
							if (!$tab->getChildren()->count()) {
718
								$tabset->removeByName($tab->getName());
719
							}
720
						}
721
					}
722
				}
723
			}
724
725
			// Use <button> to allow full jQuery UI styling
726
			$actionsFlattened = $actions->dataFields();
727
			if($actionsFlattened) {
728
				/** @var FormAction $action */
729
				foreach($actionsFlattened as $action) {
730
					$action->setUseButtonTag(true);
731
				}
732
			}
733
734
			if($record->hasMethod('getCMSValidator')) {
735
				$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...
736
			} else {
737
				$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...
738
			}
739
740
			// TODO Can't merge $FormAttributes in template at the moment
741
			$form->addExtraClass('center ' . $this->BaseCSSClasses());
742
			// Set validation exemptions for specific actions
743
			$form->setValidationExemptActions(array('restore', 'revert', 'deletefromlive', 'delete', 'unpublish', 'rollback', 'doRollback'));
744
745
			// Announce the capability so the frontend can decide whether to allow preview or not.
746
			if ($record instanceof CMSPreviewable) {
0 ignored issues
show
Bug introduced by
The class SilverStripe\Admin\CMSPreviewable 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...
747
				$form->addExtraClass('cms-previewable');
748
			}
749
750
			if(!$record->canEdit() || $deletedFromStage) {
751
				$readonlyFields = $form->Fields()->makeReadonly();
752
				$form->setFields($readonlyFields);
753
			}
754
755
			$form->Fields()->setForm($form);
756
757
			$this->extend('updateEditForm', $form);
758
			return $form;
759
		} else if($id) {
760
			$form = Form::create( $this, "EditForm", new FieldList(
761
				new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList()
762
			)->setHTMLID('Form_EditForm');
763
			return $form;
764
		}
765
	}
766
767
	/**
768
	 * @param SS_HTTPRequest $request
769
	 * @return string HTML
770
	 */
771
	public function treeview($request) {
772
		return $this->renderWith($this->getTemplatesWithSuffix('_TreeView'));
773
	}
774
775
	/**
776
	 * @param SS_HTTPRequest $request
777
	 * @return string HTML
778
	 */
779
	public function listview($request) {
780
		return $this->renderWith($this->getTemplatesWithSuffix('_ListView'));
781
	}
782
783
	/**
784
	 * Callback to request the list of page types allowed under a given page instance.
785
	 * Provides a slower but more precise response over SiteTreeHints
786
	 *
787
	 * @param SS_HTTPRequest $request
788
	 * @return SS_HTTPResponse
789
	 */
790
	public function childfilter($request) {
791
		// Check valid parent specified
792
		$parentID = $request->requestVar('ParentID');
793
		$parent = SiteTree::get()->byID($parentID);
794
		if(!$parent || !$parent->exists()) return $this->httpError(404);
795
796
		// Build hints specific to this class
797
		// Identify disallows and set globals
798
		$classes = SiteTree::page_type_classes();
799
		$disallowedChildren = array();
800
		foreach($classes as $class) {
801
			$obj = singleton($class);
802
			if($obj instanceof HiddenClass) continue;
803
804
			if(!$obj->canCreate(null, array('Parent' => $parent))) {
805
				$disallowedChildren[] = $class;
806
			}
807
		}
808
809
		$this->extend('updateChildFilter', $disallowedChildren, $parentID);
810
		return $this
811
			->getResponse()
812
			->addHeader('Content-Type', 'application/json; charset=utf-8')
813
			->setBody(Convert::raw2json($disallowedChildren));
814
	}
815
816
	/**
817
	 * Safely reconstruct a selected filter from a given set of query parameters
818
	 *
819
	 * @param array $params Query parameters to use
820
	 * @return CMSSiteTreeFilter The filter class, or null if none present
821
	 * @throws InvalidArgumentException if invalid filter class is passed.
822
	 */
823
	protected function getQueryFilter($params) {
824
		if(empty($params['FilterClass'])) return null;
825
		$filterClass = $params['FilterClass'];
826
		if(!is_subclass_of($filterClass, 'SilverStripe\\CMS\\Controllers\\CMSSiteTreeFilter')) {
827
			throw new InvalidArgumentException("Invalid filter class passed: {$filterClass}");
828
		}
829
		return $filterClass::create($params);
830
	}
831
832
	/**
833
	 * Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page
834
	 * defaulting to no filter and show all pages in first level.
835
	 * Doubles as search results, if any search parameters are set through {@link SearchForm()}.
836
	 *
837
	 * @param array $params Search filter criteria
838
	 * @param int $parentID Optional parent node to filter on (can't be combined with other search criteria)
839
	 * @return SS_List
840
	 * @throws InvalidArgumentException if invalid filter class is passed.
841
	 */
842
	public function getList($params = array(), $parentID = 0) {
843
		if($filter = $this->getQueryFilter($params)) {
844
			return $filter->getFilteredPages();
845
		} else {
846
			$list = DataList::create($this->stat('tree_class'));
847
			$parentID = is_numeric($parentID) ? $parentID : 0;
848
			return $list->filter("ParentID", $parentID);
849
		}
850
	}
851
852
	/**
853
	 * @return Form
854
	 */
855
	public function ListViewForm() {
856
		$params = $this->getRequest()->requestVar('q');
857
		$list = $this->getList($params, $parentID = $this->getRequest()->requestVar('ParentID'));
858
		$gridFieldConfig = GridFieldConfig::create()->addComponents(
859
			new GridFieldSortableHeader(),
860
			new GridFieldDataColumns(),
861
			new GridFieldPaginator(self::config()->page_length)
862
		);
863
		if($parentID){
864
			$gridFieldConfig->addComponent(
865
				GridFieldLevelup::create($parentID)
866
					->setLinkSpec('?ParentID=%d&view=list')
867
					->setAttributes(array('data-pjax' => 'ListViewForm,Breadcrumbs'))
868
			);
869
		}
870
		$gridField = new GridField('Page','Pages', $list, $gridFieldConfig);
871
		/** @var GridFieldDataColumns $columns */
872
		$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
873
874
		// Don't allow navigating into children nodes on filtered lists
875
		$fields = array(
876
			'getTreeTitle' => _t('SiteTree.PAGETITLE', 'Page Title'),
877
			'singular_name' => _t('SiteTree.PAGETYPE'),
878
			'LastEdited' => _t('SiteTree.LASTUPDATED', 'Last Updated'),
879
		);
880
		/** @var GridFieldSortableHeader $sortableHeader */
881
		$sortableHeader = $gridField->getConfig()->getComponentByType('GridFieldSortableHeader');
882
		$sortableHeader->setFieldSorting(array('getTreeTitle' => 'Title'));
883
		$gridField->getState()->ParentID = $parentID;
884
885
		if(!$params) {
886
			$fields = array_merge(array('listChildrenLink' => ''), $fields);
887
		}
888
889
		$columns->setDisplayFields($fields);
890
		$columns->setFieldCasting(array(
891
			'Created' => 'DBDatetime->Ago',
892
			'LastEdited' => 'DBDatetime->FormatFromSettings',
893
			'getTreeTitle' => 'HTMLFragment'
894
		));
895
896
		$controller = $this;
897
		$columns->setFieldFormatting(array(
898
			'listChildrenLink' => function($value, &$item) use($controller) {
899
				/** @var SiteTree $item */
900
				$num = $item ? $item->numChildren() : null;
901
				if($num) {
902
					return sprintf(
903
						'<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>',
904
						Controller::join_links(
905
							$controller->Link(),
906
							sprintf("?ParentID=%d&view=list", (int)$item->ID)
907
						),
908
						$num
909
					);
910
				}
911
			},
912
			'getTreeTitle' => function($value, &$item) use($controller) {
913
				return sprintf(
914
					'<a class="action-detail" href="%s">%s</a>',
915
					Controller::join_links(
916
						CMSPageEditController::singleton()->Link('show'),
917
						(int)$item->ID
918
					),
919
					$item->TreeTitle // returns HTML, does its own escaping
920
				);
921
			}
922
		));
923
924
		$negotiator = $this->getResponseNegotiator();
925
		$listview = Form::create(
926
			$this,
927
			'ListViewForm',
928
			new FieldList($gridField),
929
			new FieldList()
930
		)->setHTMLID('Form_ListViewForm');
931
		$listview->setAttribute('data-pjax-fragment', 'ListViewForm');
932 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...
933
			$request = $this->getRequest();
934
			if($request->isAjax() && $negotiator) {
935
				$listview->setupFormErrors();
936
				$result = $listview->forTemplate();
937
938
				return $negotiator->respond($request, array(
939
					'CurrentForm' => function() use($result) {
940
						return $result;
941
					}
942
				));
943
			}
944
		});
945
946
		$this->extend('updateListView', $listview);
947
948
		$listview->disableSecurityToken();
949
		return $listview;
950
	}
951
952
	public function currentPageID() {
953
		$id = parent::currentPageID();
954
955
		$this->extend('updateCurrentPageID', $id);
956
957
		return $id;
958
	}
959
960
	//------------------------------------------------------------------------------------------//
961
	// Data saving handlers
962
963
	/**
964
	 * Save and Publish page handler
965
	 *
966
	 * @param array $data
967
	 * @param Form $form
968
	 * @return SS_HTTPResponse
969
	 * @throws SS_HTTPResponse_Exception
970
	 */
971
	public function save($data, $form) {
972
		$className = $this->stat('tree_class');
973
974
		// Existing or new record?
975
		$id = $data['ID'];
976
		if(substr($id,0,3) != 'new') {
977
			/** @var SiteTree $record */
978
			$record = DataObject::get_by_id($className, $id);
979
			// Check edit permissions
980
			if($record && !$record->canEdit()) {
981
				return Security::permissionFailure($this);
982
			}
983
			if(!$record || !$record->ID) {
984
				throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
985
			}
986
		} else {
987
			if(!$className::singleton()->canCreate()) {
988
				return Security::permissionFailure($this);
989
			}
990
			$record = $this->getNewItem($id, false);
991
		}
992
993
		// Check publishing permissions
994
		$doPublish = !empty($data['publish']);
995
		if($record && $doPublish && !$record->canPublish()) {
996
			return Security::permissionFailure($this);
997
		}
998
999
		// TODO Coupling to SiteTree
1000
		$record->HasBrokenLink = 0;
1001
		$record->HasBrokenFile = 0;
1002
1003
		if (!$record->ObsoleteClassName) {
1004
			$record->writeWithoutVersion();
1005
		}
1006
1007
		// Update the class instance if necessary
1008
		if(isset($data['ClassName']) && $data['ClassName'] != $record->ClassName) {
1009
			$newClassName = $record->ClassName;
1010
			// The records originally saved attribute was overwritten by $form->saveInto($record) before.
1011
			// This is necessary for newClassInstance() to work as expected, and trigger change detection
1012
			// on the ClassName attribute
1013
			$record->setClassName($data['ClassName']);
1014
			// Replace $record with a new instance
1015
			$record = $record->newClassInstance($newClassName);
1016
		}
1017
1018
		// save form data into record
1019
		$form->saveInto($record);
1020
		$record->write();
1021
1022
		// If the 'Save & Publish' button was clicked, also publish the page
1023
		if ($doPublish) {
1024
			$record->publishRecursive();
1025
			$message = _t(
1026
				'CMSMain.PUBLISHED',
1027
				"Published '{title}' successfully.",
1028
				['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...
1029
			);
1030
		} else {
1031
			$message = _t(
1032
				'CMSMain.SAVED',
1033
				"Saved '{title}' successfully.",
1034
				['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...
1035
			);
1036
		}
1037
1038
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1039
		return $this->getResponseNegotiator()->respond($this->getRequest());
1040
	}
1041
1042
	/**
1043
	 * @uses LeftAndMainExtension->augmentNewSiteTreeItem()
1044
	 *
1045
	 * @param int|string $id
1046
	 * @param bool $setID
1047
	 * @return mixed|DataObject
1048
	 * @throws SS_HTTPResponse_Exception
1049
	 */
1050
	public function getNewItem($id, $setID = true) {
1051
		$parentClass = $this->stat('tree_class');
1052
		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...
1053
1054
		if (!is_a($className, $parentClass, true)) {
1055
			$response = Security::permissionFailure($this);
1056
			if (!$response) {
1057
				$response = $this->getResponse();
1058
			}
1059
			throw new SS_HTTPResponse_Exception($response);
1060
		}
1061
1062
		/** @var SiteTree $newItem */
1063
		$newItem = Injector::inst()->create($className);
1064
		if( !$suffix ) {
1065
			$sessionTag = "NewItems." . $parentID . "." . $className;
1066
			if(Session::get($sessionTag)) {
1067
				$suffix = '-' . Session::get($sessionTag);
1068
				Session::set($sessionTag, Session::get($sessionTag) + 1);
1069
			}
1070
			else
1071
				Session::set($sessionTag, 1);
1072
1073
				$id = $id . $suffix;
1074
		}
1075
1076
		$newItem->Title = _t(
1077
			'CMSMain.NEWPAGE',
1078
			"New {pagetype}",'followed by a page type title',
1079
			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...
1080
		);
1081
		$newItem->ClassName = $className;
1082
		$newItem->ParentID = $parentID;
1083
1084
		// DataObject::fieldExists only checks the current class, not the hierarchy
1085
		// This allows the CMS to set the correct sort value
1086
		if($newItem->castingHelper('Sort')) {
1087
			$newItem->Sort = DB::prepared_query('SELECT MAX("Sort") FROM "SiteTree" WHERE "ParentID" = ?', array($parentID))->value() + 1;
1088
		}
1089
1090
		if($setID) $newItem->ID = $id;
1091
1092
		# Some modules like subsites add extra fields that need to be set when the new item is created
1093
		$this->extend('augmentNewSiteTreeItem', $newItem);
1094
1095
		return $newItem;
1096
	}
1097
1098
	/**
1099
	 * Actually perform the publication step
1100
	 *
1101
	 * @param Versioned|DataObject $record
1102
	 * @return mixed
1103
	 */
1104
	public function performPublish($record) {
1105
		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...
1106
			return Security::permissionFailure($this);
1107
		}
1108
1109
		$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...
1110
	}
1111
1112
	/**
1113
	 * Reverts a page by publishing it to live.
1114
	 * Use {@link restorepage()} if you want to restore a page
1115
	 * which was deleted from draft without publishing.
1116
	 *
1117
	 * @uses SiteTree->doRevertToLive()
1118
	 *
1119
	 * @param array $data
1120
	 * @param Form $form
1121
	 * @return SS_HTTPResponse
1122
	 * @throws SS_HTTPResponse_Exception
1123
	 */
1124
	public function revert($data, $form) {
1125
		if(!isset($data['ID'])) {
1126
			throw new SS_HTTPResponse_Exception("Please pass an ID in the form content", 400);
1127
		}
1128
1129
		$id = (int) $data['ID'];
1130
		$restoredPage = Versioned::get_latest_version("SilverStripe\\CMS\\Model\\SiteTree", $id);
1131
		if(!$restoredPage) {
1132
			throw new SS_HTTPResponse_Exception("SiteTree #$id not found", 400);
1133
		}
1134
1135
		/** @var SiteTree $record */
1136
		$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...
1137
			'"SiteTree_Live"."ID"' => $id
1138
		));
1139
1140
		// a user can restore a page without publication rights, as it just adds a new draft state
1141
		// (this action should just be available when page has been "deleted from draft")
1142
		if($record && !$record->canEdit()) {
1143
			return Security::permissionFailure($this);
1144
		}
1145
		if(!$record || !$record->ID) {
1146
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1147
		}
1148
1149
		$record->doRevertToLive();
1150
1151
		$this->getResponse()->addHeader(
1152
			'X-Status',
1153
			rawurlencode(_t(
1154
				'CMSMain.RESTORED',
1155
				"Restored '{title}' successfully",
1156
				'Param %s is a title',
1157
				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...
1158
			))
1159
		);
1160
1161
		return $this->getResponseNegotiator()->respond($this->getRequest());
1162
	}
1163
1164
	/**
1165
	 * Delete the current page from draft stage.
1166
	 *
1167
	 * @see deletefromlive()
1168
	 *
1169
	 * @param array $data
1170
	 * @param Form $form
1171
	 * @return SS_HTTPResponse
1172
	 * @throws SS_HTTPResponse_Exception
1173
	 */
1174 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...
1175
		$id = $data['ID'];
1176
		$record = SiteTree::get()->byID($id);
1177
		if($record && !$record->canDelete()) {
1178
			return Security::permissionFailure();
1179
		}
1180
		if(!$record || !$record->ID) {
1181
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1182
		}
1183
1184
		// Delete record
1185
		$record->delete();
1186
1187
		$this->getResponse()->addHeader(
1188
			'X-Status',
1189
			rawurlencode(sprintf(_t('CMSMain.REMOVEDPAGEFROMDRAFT',"Removed '%s' from the draft site"), $record->Title))
1190
		);
1191
1192
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1193
		return $this->getResponseNegotiator()->respond($this->getRequest());
1194
	}
1195
1196
	/**
1197
	 * Delete this page from both live and stage
1198
	 *
1199
	 * @param array $data
1200
	 * @param Form $form
1201
	 * @return SS_HTTPResponse
1202
	 * @throws SS_HTTPResponse_Exception
1203
	 */
1204 View Code Duplication
	public function archive($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...
1205
		$id = $data['ID'];
1206
		/** @var SiteTree $record */
1207
		$record = SiteTree::get()->byID($id);
1208
		if(!$record || !$record->exists()) {
1209
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1210
		}
1211
		if(!$record->canArchive()) {
1212
			return Security::permissionFailure();
1213
		}
1214
1215
		// Archive record
1216
		$record->doArchive();
1217
1218
		$this->getResponse()->addHeader(
1219
			'X-Status',
1220
			rawurlencode(sprintf(_t('CMSMain.ARCHIVEDPAGE',"Archived page '%s'"), $record->Title))
1221
		);
1222
1223
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1224
		return $this->getResponseNegotiator()->respond($this->getRequest());
1225
	}
1226
1227
	public function publish($data, $form) {
1228
		$data['publish'] = '1';
1229
1230
		return $this->save($data, $form);
1231
	}
1232
1233
	public function unpublish($data, $form) {
1234
		$className = $this->stat('tree_class');
1235
		/** @var SiteTree $record */
1236
		$record = DataObject::get_by_id($className, $data['ID']);
1237
1238
		if($record && !$record->canUnpublish()) {
1239
			return Security::permissionFailure($this);
1240
		}
1241
		if(!$record || !$record->ID) {
1242
			throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
1243
		}
1244
1245
		$record->doUnpublish();
1246
1247
		$this->getResponse()->addHeader(
1248
			'X-Status',
1249
			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...
1250
		);
1251
1252
		return $this->getResponseNegotiator()->respond($this->getRequest());
1253
	}
1254
1255
	/**
1256
	 * @return SS_HTTPResponse
1257
	 */
1258
	public function rollback() {
1259
		return $this->doRollback(array(
1260
			'ID' => $this->currentPageID(),
1261
			'Version' => $this->getRequest()->param('VersionID')
1262
		), null);
1263
	}
1264
1265
	/**
1266
	 * Rolls a site back to a given version ID
1267
	 *
1268
	 * @param array $data
1269
	 * @param Form $form
1270
	 * @return SS_HTTPResponse
1271
	 */
1272
	public function doRollback($data, $form) {
1273
		$this->extend('onBeforeRollback', $data['ID']);
1274
1275
		$id = (isset($data['ID'])) ? (int) $data['ID'] : null;
1276
		$version = (isset($data['Version'])) ? (int) $data['Version'] : null;
1277
1278
		/** @var DataObject|Versioned $record */
1279
		$record = DataObject::get_by_id($this->stat('tree_class'), $id);
1280
		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...
1281
			return Security::permissionFailure($this);
1282
		}
1283
1284
		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...
1285
			$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...
1286
			$message = _t(
1287
				'CMSMain.ROLLEDBACKVERSIONv2',
1288
				"Rolled back to version #%d.",
1289
				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...
1290
			);
1291
		} else {
1292
			$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...
1293
			$message = _t(
1294
				'CMSMain.ROLLEDBACKPUBv2',"Rolled back to published version."
1295
			);
1296
		}
1297
1298
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1299
1300
		// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
1301
		// Or in history view, in which case a revert causes the CMS to re-load the edit view.
1302
		// The X-Pjax header forces a "full" content refresh on redirect.
1303
		$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $record->ID);
1304
		$this->getResponse()->addHeader('X-ControllerURL', $url);
1305
		$this->getRequest()->addHeader('X-Pjax', 'Content');
1306
		$this->getResponse()->addHeader('X-Pjax', 'Content');
1307
1308
		return $this->getResponseNegotiator()->respond($this->getRequest());
1309
	}
1310
1311
	/**
1312
	 * Batch Actions Handler
1313
	 */
1314
	public function batchactions() {
1315
		return new CMSBatchActionHandler($this, 'batchactions');
1316
	}
1317
1318
	public function BatchActionParameters() {
1319
		$batchActions = CMSBatchActionHandler::config()->batch_actions;
1320
1321
		$forms = array();
1322
		foreach($batchActions as $urlSegment => $batchAction) {
1323
			$SNG_action = singleton($batchAction);
1324
			if ($SNG_action->canView() && $fieldset = $SNG_action->getParameterFields()) {
1325
				$formHtml = '';
1326
				/** @var FormField $field */
1327
				foreach($fieldset as $field) {
1328
					$formHtml .= $field->Field();
1329
				}
1330
				$forms[$urlSegment] = $formHtml;
1331
			}
1332
		}
1333
		$pageHtml = '';
1334
		foreach($forms as $urlSegment => $html) {
1335
			$pageHtml .= "<div class=\"params\" id=\"BatchActionParameters_$urlSegment\">$html</div>\n\n";
1336
		}
1337
		return new LiteralField("BatchActionParameters", '<div id="BatchActionParameters" style="display:none">'.$pageHtml.'</div>');
1338
	}
1339
	/**
1340
	 * Returns a list of batch actions
1341
	 */
1342
	public function BatchActionList() {
1343
		return $this->batchactions()->batchActionList();
1344
	}
1345
1346
	public function publishall($request) {
1347
		if(!Permission::check('ADMIN')) return Security::permissionFailure($this);
1348
1349
		increase_time_limit_to();
1350
		increase_memory_limit_to();
1351
1352
		$response = "";
1353
1354
		if(isset($this->requestParams['confirm'])) {
1355
			// Protect against CSRF on destructive action
1356
			if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1357
1358
			$start = 0;
1359
			$pages = SiteTree::get()->limit("$start,30");
1360
			$count = 0;
1361
			while($pages) {
1362
				/** @var SiteTree $page */
1363
				foreach($pages as $page) {
1364
					if($page && !$page->canPublish()) {
1365
						return Security::permissionFailure($this);
1366
					}
1367
1368
					$page->publishRecursive();
1369
					$page->destroy();
1370
					unset($page);
1371
					$count++;
1372
					$response .= "<li>$count</li>";
1373
				}
1374
				if($pages->count() > 29) {
1375
					$start += 30;
1376
					$pages = SiteTree::get()->limit("$start,30");
1377
				} else {
1378
					break;
1379
				}
1380
			}
1381
			$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...
1382
1383
		} else {
1384
			$token = SecurityToken::inst();
1385
			$fields = new FieldList();
1386
			$token->updateFieldSet($fields);
1387
			$tokenField = $fields->first();
1388
			$tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : '';
1389
			$response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
1390
				<p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish".  It\'s
1391
				intended to be used after there have been massive edits of the content, such as when the site was
1392
				first built.') . '</p>
1393
				<form method="post" action="publishall">
1394
					<input type="submit" name="confirm" value="'
1395
					. _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",'Confirmation button') .'" />'
1396
					. $tokenHtml .
1397
				'</form>';
1398
		}
1399
1400
		return $response;
1401
	}
1402
1403
	/**
1404
	 * Restore a completely deleted page from the SiteTree_versions table.
1405
	 *
1406
	 * @param array $data
1407
	 * @param Form $form
1408
	 * @return SS_HTTPResponse
1409
	 */
1410
	public function restore($data, $form) {
1411
		if(!isset($data['ID']) || !is_numeric($data['ID'])) {
1412
			return new SS_HTTPResponse("Please pass an ID in the form content", 400);
1413
		}
1414
1415
		$id = (int)$data['ID'];
1416
		/** @var SiteTree $restoredPage */
1417
		$restoredPage = Versioned::get_latest_version("SilverStripe\\CMS\\Model\\SiteTree", $id);
1418
		if(!$restoredPage) {
1419
			return new SS_HTTPResponse("SiteTree #$id not found", 400);
1420
		}
1421
1422
		$restoredPage = $restoredPage->doRestoreToStage();
1423
1424
		$this->getResponse()->addHeader(
1425
			'X-Status',
1426
			rawurlencode(_t(
1427
				'CMSMain.RESTORED',
1428
				"Restored '{title}' successfully",
1429
				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...
1430
			))
1431
		);
1432
1433
		return $this->getResponseNegotiator()->respond($this->getRequest());
1434
	}
1435
1436
	public function duplicate($request) {
1437
		// Protect against CSRF on destructive action
1438
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1439
1440
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1441
			/** @var SiteTree $page */
1442
			$page = SiteTree::get()->byID($id);
1443 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...
1444
				return Security::permissionFailure($this);
1445
			}
1446
			if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1447
1448
			$newPage = $page->duplicate();
1449
1450
			// ParentID can be hard-set in the URL.  This is useful for pages with multiple parents
1451
			if(isset($_GET['parentID']) && is_numeric($_GET['parentID'])) {
1452
				$newPage->ParentID = $_GET['parentID'];
1453
				$newPage->write();
1454
			}
1455
1456
			$this->getResponse()->addHeader(
1457
				'X-Status',
1458
				rawurlencode(_t(
1459
					'CMSMain.DUPLICATED',
1460
					"Duplicated '{title}' successfully",
1461
					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...
1462
				))
1463
			);
1464
			$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $newPage->ID);
1465
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1466
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1467
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1468
1469
			return $this->getResponseNegotiator()->respond($this->getRequest());
1470
		} else {
1471
			return new SS_HTTPResponse("CMSMain::duplicate() Bad ID: '$id'", 400);
1472
		}
1473
	}
1474
1475
	public function duplicatewithchildren($request) {
1476
		// Protect against CSRF on destructive action
1477
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1478
		increase_time_limit_to();
1479
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1480
			/** @var SiteTree $page */
1481
			$page = SiteTree::get()->byID($id);
1482 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...
1483
				return Security::permissionFailure($this);
1484
			}
1485
			if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1486
1487
			$newPage = $page->duplicateWithChildren();
1488
1489
			$this->getResponse()->addHeader(
1490
				'X-Status',
1491
				rawurlencode(_t(
1492
					'CMSMain.DUPLICATEDWITHCHILDREN',
1493
					"Duplicated '{title}' and children successfully",
1494
					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...
1495
				))
1496
			);
1497
			$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $newPage->ID);
1498
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1499
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1500
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1501
1502
			return $this->getResponseNegotiator()->respond($this->getRequest());
1503
		} else {
1504
			return new SS_HTTPResponse("CMSMain::duplicatewithchildren() Bad ID: '$id'", 400);
1505
		}
1506
	}
1507
1508
	public function providePermissions() {
1509
		$title = CMSPagesController::menu_title();
1510
		return array(
1511
			"CMS_ACCESS_CMSMain" => array(
1512
				'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,?,{"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...
1513
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
1514
				'help' => _t(
1515
					'CMSMain.ACCESS_HELP',
1516
					'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".'
1517
				),
1518
				'sort' => -99 // below "CMS_ACCESS_LeftAndMain", but above everything else
1519
			)
1520
		);
1521
	}
1522
1523
}
1524