Completed
Pull Request — master (#1587)
by
unknown
02:39
created

CMSMain::AddToCampaignForm()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
1244
			throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
1245
		}
1246
1247
		$record->doUnpublish();
1248
1249
		$this->getResponse()->addHeader(
1250
			'X-Status',
1251
			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...
1252
		);
1253
1254
		return $this->getResponseNegotiator()->respond($this->getRequest());
1255
	}
1256
1257
	/**
1258
	 * @return SS_HTTPResponse
1259
	 */
1260
	public function rollback() {
1261
		return $this->doRollback(array(
1262
			'ID' => $this->currentPageID(),
1263
			'Version' => $this->getRequest()->param('VersionID')
1264
		), null);
1265
	}
1266
1267
	/**
1268
	 * Rolls a site back to a given version ID
1269
	 *
1270
	 * @param array $data
1271
	 * @param Form $form
1272
	 * @return SS_HTTPResponse
1273
	 */
1274
	public function doRollback($data, $form) {
1275
		$this->extend('onBeforeRollback', $data['ID']);
1276
1277
		$id = (isset($data['ID'])) ? (int) $data['ID'] : null;
1278
		$version = (isset($data['Version'])) ? (int) $data['Version'] : null;
1279
1280
		/** @var DataObject|Versioned $record */
1281
		$record = DataObject::get_by_id($this->stat('tree_class'), $id);
1282
		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...
1283
			return Security::permissionFailure($this);
1284
		}
1285
1286
		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...
1287
			$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...
1288
			$message = _t(
1289
				'CMSMain.ROLLEDBACKVERSIONv2',
1290
				"Rolled back to version #%d.",
1291
				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...
1292
			);
1293
		} else {
1294
			$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...
1295
			$message = _t(
1296
				'CMSMain.ROLLEDBACKPUBv2',"Rolled back to published version."
1297
			);
1298
		}
1299
1300
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1301
1302
		// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
1303
		// Or in history view, in which case a revert causes the CMS to re-load the edit view.
1304
		// The X-Pjax header forces a "full" content refresh on redirect.
1305
		$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $record->ID);
1306
		$this->getResponse()->addHeader('X-ControllerURL', $url);
1307
		$this->getRequest()->addHeader('X-Pjax', 'Content');
1308
		$this->getResponse()->addHeader('X-Pjax', 'Content');
1309
1310
		return $this->getResponseNegotiator()->respond($this->getRequest());
1311
	}
1312
1313
	/**
1314
	 * Action handler for adding pages to a campaign
1315
	 *
1316
	 * @param SS_HTTPRequest
1317
	 * @return DBHTMLText|SS_HTTPResponse
1318
	 */
1319
	public function AddToCampaignForm($request) {
1320
		$data = $request->postVars();
1321
1322
		if (!$data['Campaign']) {
1323
			$this->httpError(400, _t(
1324
				'AddToCampaign.ErrorCampaignNotSelected',
1325
				'There was no campaign selected to be added to'
1326
			));
1327
			return null;
1328
		}
1329
		$handler = AddToCampaignHandler::create($this, $data);
1330
		return $handler->handle();
1331
	}
1332
1333
	/**
1334
	 * Action handler for adding pages to a campaign
1335
	 *
1336
	 * @param array $data
1337
	 * @param Form $form
1338
	 * @return DBHTMLText|SS_HTTPResponse
1339
	 */
1340
	public function addtocampaign($data, $form) {
1341
		$handler = AddToCampaignHandler::create($this, $data);
1342
		return $handler->handle();
1343
	}
1344
1345
	/**
1346
	 * Batch Actions Handler
1347
	 */
1348
	public function batchactions() {
1349
		return new CMSBatchActionHandler($this, 'batchactions');
1350
	}
1351
1352
	public function BatchActionParameters() {
1353
		$batchActions = CMSBatchActionHandler::config()->batch_actions;
1354
1355
		$forms = array();
1356
		foreach($batchActions as $urlSegment => $batchAction) {
1357
			$SNG_action = singleton($batchAction);
1358
			if ($SNG_action->canView() && $fieldset = $SNG_action->getParameterFields()) {
1359
				$formHtml = '';
1360
				/** @var FormField $field */
1361
				foreach($fieldset as $field) {
1362
					$formHtml .= $field->Field();
1363
				}
1364
				$forms[$urlSegment] = $formHtml;
1365
			}
1366
		}
1367
		$pageHtml = '';
1368
		foreach($forms as $urlSegment => $html) {
1369
			$pageHtml .= "<div class=\"params\" id=\"BatchActionParameters_$urlSegment\">$html</div>\n\n";
1370
		}
1371
		return new LiteralField("BatchActionParameters", '<div id="BatchActionParameters" style="display:none">'.$pageHtml.'</div>');
1372
	}
1373
	/**
1374
	 * Returns a list of batch actions
1375
	 */
1376
	public function BatchActionList() {
1377
		return $this->batchactions()->batchActionList();
1378
	}
1379
1380
	public function publishall($request) {
1381
		if(!Permission::check('ADMIN')) return Security::permissionFailure($this);
1382
1383
		increase_time_limit_to();
1384
		increase_memory_limit_to();
1385
1386
		$response = "";
1387
1388
		if(isset($this->requestParams['confirm'])) {
1389
			// Protect against CSRF on destructive action
1390
			if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1391
1392
			$start = 0;
1393
			$pages = SiteTree::get()->limit("$start,30");
1394
			$count = 0;
1395
			while($pages) {
1396
				/** @var SiteTree $page */
1397
				foreach($pages as $page) {
1398
					if($page && !$page->canPublish()) {
1399
						return Security::permissionFailure($this);
1400
					}
1401
1402
					$page->publishRecursive();
1403
					$page->destroy();
1404
					unset($page);
1405
					$count++;
1406
					$response .= "<li>$count</li>";
1407
				}
1408
				if($pages->count() > 29) {
1409
					$start += 30;
1410
					$pages = SiteTree::get()->limit("$start,30");
1411
				} else {
1412
					break;
1413
				}
1414
			}
1415
			$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...
1416
1417
		} else {
1418
			$token = SecurityToken::inst();
1419
			$fields = new FieldList();
1420
			$token->updateFieldSet($fields);
1421
			$tokenField = $fields->first();
1422
			$tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : '';
1423
			$response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
1424
				<p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish".  It\'s
1425
				intended to be used after there have been massive edits of the content, such as when the site was
1426
				first built.') . '</p>
1427
				<form method="post" action="publishall">
1428
					<input type="submit" name="confirm" value="'
1429
					. _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",'Confirmation button') .'" />'
1430
					. $tokenHtml .
1431
				'</form>';
1432
		}
1433
1434
		return $response;
1435
	}
1436
1437
	/**
1438
	 * Restore a completely deleted page from the SiteTree_versions table.
1439
	 *
1440
	 * @param array $data
1441
	 * @param Form $form
1442
	 * @return SS_HTTPResponse
1443
	 */
1444
	public function restore($data, $form) {
1445
		if(!isset($data['ID']) || !is_numeric($data['ID'])) {
1446
			return new SS_HTTPResponse("Please pass an ID in the form content", 400);
1447
		}
1448
1449
		$id = (int)$data['ID'];
1450
		/** @var SiteTree $restoredPage */
1451
		$restoredPage = Versioned::get_latest_version("SilverStripe\\CMS\\Model\\SiteTree", $id);
1452
		if(!$restoredPage) {
1453
			return new SS_HTTPResponse("SiteTree #$id not found", 400);
1454
		}
1455
1456
		$restoredPage = $restoredPage->doRestoreToStage();
1457
1458
		$this->getResponse()->addHeader(
1459
			'X-Status',
1460
			rawurlencode(_t(
1461
				'CMSMain.RESTORED',
1462
				"Restored '{title}' successfully",
1463
				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...
1464
			))
1465
		);
1466
1467
		return $this->getResponseNegotiator()->respond($this->getRequest());
1468
	}
1469
1470
	public function duplicate($request) {
1471
		// Protect against CSRF on destructive action
1472
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1473
1474
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1475
			/** @var SiteTree $page */
1476
			$page = SiteTree::get()->byID($id);
1477 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...
1478
				return Security::permissionFailure($this);
1479
			}
1480
			if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1481
1482
			$newPage = $page->duplicate();
1483
1484
			// ParentID can be hard-set in the URL.  This is useful for pages with multiple parents
1485
			if(isset($_GET['parentID']) && is_numeric($_GET['parentID'])) {
1486
				$newPage->ParentID = $_GET['parentID'];
1487
				$newPage->write();
1488
			}
1489
1490
			$this->getResponse()->addHeader(
1491
				'X-Status',
1492
				rawurlencode(_t(
1493
					'CMSMain.DUPLICATED',
1494
					"Duplicated '{title}' successfully",
1495
					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...
1496
				))
1497
			);
1498
			$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $newPage->ID);
1499
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1500
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1501
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1502
1503
			return $this->getResponseNegotiator()->respond($this->getRequest());
1504
		} else {
1505
			return new SS_HTTPResponse("CMSMain::duplicate() Bad ID: '$id'", 400);
1506
		}
1507
	}
1508
1509
	public function duplicatewithchildren($request) {
1510
		// Protect against CSRF on destructive action
1511
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1512
		increase_time_limit_to();
1513
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1514
			/** @var SiteTree $page */
1515
			$page = SiteTree::get()->byID($id);
1516 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...
1517
				return Security::permissionFailure($this);
1518
			}
1519
			if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1520
1521
			$newPage = $page->duplicateWithChildren();
1522
1523
			$this->getResponse()->addHeader(
1524
				'X-Status',
1525
				rawurlencode(_t(
1526
					'CMSMain.DUPLICATEDWITHCHILDREN',
1527
					"Duplicated '{title}' and children successfully",
1528
					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...
1529
				))
1530
			);
1531
			$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $newPage->ID);
1532
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1533
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1534
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1535
1536
			return $this->getResponseNegotiator()->respond($this->getRequest());
1537
		} else {
1538
			return new SS_HTTPResponse("CMSMain::duplicatewithchildren() Bad ID: '$id'", 400);
1539
		}
1540
	}
1541
1542
	public function providePermissions() {
1543
		$title = CMSPagesController::menu_title();
1544
		return array(
1545
			"CMS_ACCESS_CMSMain" => array(
1546
				'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...
1547
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
1548
				'help' => _t(
1549
					'CMSMain.ACCESS_HELP',
1550
					'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".'
1551
				),
1552
				'sort' => -99 // below "CMS_ACCESS_LeftAndMain", but above everything else
1553
			)
1554
		);
1555
	}
1556
1557
}
1558