Completed
Pull Request — master (#1653)
by Damian
02:34
created

CMSMain::publish()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
namespace SilverStripe\CMS\Controllers;
4
5
use SilverStripe\Admin\AdminRootController;
6
use SilverStripe\Admin\CMSBatchActionHandler;
7
use SilverStripe\Admin\CMSPreviewable;
8
use SilverStripe\Admin\LeftAndMain;
9
use SilverStripe\CMS\BatchActions\CMSBatchAction_Delete;
10
use SilverStripe\CMS\BatchActions\CMSBatchAction_Publish;
11
use SilverStripe\CMS\BatchActions\CMSBatchAction_Restore;
12
use SilverStripe\CMS\BatchActions\CMSBatchAction_Unpublish;
13
use SilverStripe\CMS\Model\CurrentPageIdentifier;
14
use SilverStripe\CMS\Model\RedirectorPage;
15
use SilverStripe\CMS\Model\SiteTree;
16
use SilverStripe\Control\Controller;
17
use SilverStripe\Control\Director;
18
use SilverStripe\Control\Session;
19
use SilverStripe\Control\HTTPRequest;
20
use SilverStripe\Control\HTTPResponse;
21
use SilverStripe\Control\HTTPResponse_Exception;
22
use SilverStripe\Core\Convert;
23
use SilverStripe\Core\Injector\Injector;
24
use SilverStripe\Core\Cache;
25
use SilverStripe\Forms\DateField;
26
use SilverStripe\Forms\DropdownField;
27
use SilverStripe\Forms\FieldGroup;
28
use SilverStripe\Forms\FieldList;
29
use SilverStripe\Forms\Form;
30
use SilverStripe\Forms\FormAction;
31
use SilverStripe\Forms\FormField;
32
use SilverStripe\Forms\GridField\GridField;
33
use SilverStripe\Forms\GridField\GridFieldConfig;
34
use SilverStripe\Forms\GridField\GridFieldDataColumns;
35
use SilverStripe\Forms\GridField\GridFieldLevelup;
36
use SilverStripe\Forms\GridField\GridFieldPaginator;
37
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
38
use SilverStripe\Forms\HiddenField;
39
use SilverStripe\Forms\LabelField;
40
use SilverStripe\Forms\LiteralField;
41
use SilverStripe\Forms\RequiredFields;
42
use SilverStripe\Forms\ResetFormAction;
43
use SilverStripe\Forms\TabSet;
44
use SilverStripe\Forms\TextField;
45
use SilverStripe\ORM\ArrayList;
46
use SilverStripe\ORM\DataList;
47
use SilverStripe\ORM\DataObject;
48
use SilverStripe\ORM\DB;
49
use SilverStripe\ORM\HiddenClass;
50
use SilverStripe\ORM\SS_List;
51
use SilverStripe\ORM\Versioning\Versioned;
52
use SilverStripe\Security\Member;
53
use SilverStripe\Security\Permission;
54
use SilverStripe\Security\PermissionProvider;
55
use SilverStripe\Security\Security;
56
use SilverStripe\Security\SecurityToken;
57
use SilverStripe\View\ArrayData;
58
use SilverStripe\View\Requirements;
59
use Translatable;
60
use Page;
61
use Zend_Cache;
62
use InvalidArgumentException;
63
64
65
/**
66
 * The main "content" area of the CMS.
67
 *
68
 * This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main
69
 * admin menu.
70
 *
71
 * @todo Create some base classes to contain the generic functionality that will be replicated.
72
 *
73
 * @mixin LeftAndMainPageIconsExtension
74
 */
75
class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionProvider {
76
77
	private static $url_segment = 'pages';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
78
79
	private static $url_rule = '/$Action/$ID/$OtherID';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
80
81
	// Maintain a lower priority than other administration sections
82
	// so that Director does not think they are actions of CMSMain
83
	private static $url_priority = 39;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
84
85
	private static $menu_title = 'Edit Page';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
86
87
	private static $menu_priority = 10;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
88
89
	private static $tree_class = "SilverStripe\\CMS\\Model\\SiteTree";
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
90
91
	private static $subitem_class = "SilverStripe\\Security\\Member";
92
93
	private static $session_namespace = 'SilverStripe\\CMS\\Controllers\\CMSMain';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
94
95
	private static $required_permission_codes = 'CMS_ACCESS_CMSMain';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
96
97
	/**
98
	 * Amount of results showing on a single page.
99
	 *
100
	 * @config
101
	 * @var int
102
	 */
103
	private static $page_length = 15;
104
105
	private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
106
		'deleteitems',
107
		'DeleteItemsForm',
108
		'dialog',
109
		'duplicate',
110
		'duplicatewithchildren',
111
		'publishall',
112
		'publishitems',
113
		'PublishItemsForm',
114
		'submit',
115
		'EditForm',
116
		'SearchForm',
117
		'SiteTreeAsUL',
118
		'getshowdeletedsubtree',
119
		'batchactions',
120
		'treeview',
121
		'listview',
122
		'ListViewForm',
123
		'childfilter',
124
	);
125
126
	private static $casting = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
127
		'TreeIsFiltered' => 'Boolean',
128
		'AddForm' => 'HTMLFragment',
129
		'LinkPages' => 'Text',
130
		'Link' => 'Text',
131
		'ListViewForm' => 'HTMLFragment',
132
		'ExtraTreeTools' => 'HTMLFragment',
133
		'SiteTreeHints' => 'HTMLFragment',
134
		'SecurityID' => 'Text',
135
		'SiteTreeAsUL' => 'HTMLFragment',
136
	);
137
138
	public function init() {
139
		// set reading lang
140
		if(SiteTree::has_extension('Translatable') && !$this->getRequest()->isAjax()) {
141
			Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SilverStripe\\CMS\\Model\\SiteTree')));
142
		}
143
144
		parent::init();
145
146
		Requirements::javascript(CMS_DIR . '/client/dist/js/bundle.js');
147
		Requirements::javascript(CMS_DIR . '/client/dist/js/SilverStripeNavigator.js');
148
		Requirements::css(CMS_DIR . '/client/dist/styles/bundle.css');
149
		Requirements::customCSS($this->generatePageIconsCss());
0 ignored issues
show
Documentation Bug introduced by
The method generatePageIconsCss does not exist on object<SilverStripe\CMS\Controllers\CMSMain>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
150
		Requirements::add_i18n_javascript(CMS_DIR . '/client/lang', false, true);
151
152
		CMSBatchActionHandler::register('restore', CMSBatchAction_Restore::class);
153
		CMSBatchActionHandler::register('delete', CMSBatchAction_Delete::class);
154
		CMSBatchActionHandler::register('unpublish', CMSBatchAction_Unpublish::class);
155
		CMSBatchActionHandler::register('publish', CMSBatchAction_Publish::class);
156
	}
157
158
	public function index($request) {
159
		// In case we're not showing a specific record, explicitly remove any session state,
160
		// to avoid it being highlighted in the tree, and causing an edit form to show.
161
		if(!$request->param('Action')) {
162
			$this->setCurrentPageID(null);
163
		}
164
165
		return parent::index($request);
166
	}
167
168
	public function getResponseNegotiator() {
169
		$negotiator = parent::getResponseNegotiator();
170
		$controller = $this;
171
		$negotiator->setCallback('ListViewForm', function() use($controller) {
172
			return $controller->ListViewForm()->forTemplate();
173
		});
174
		return $negotiator;
175
	}
176
177
	/**
178
	 * If this is set to true, the "switchView" context in the
179
	 * template is shown, with links to the staging and publish site.
180
	 *
181
	 * @return boolean
182
	 */
183
	public function ShowSwitchView() {
184
		return true;
185
	}
186
187
	/**
188
	 * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
189
	 * to switch view also for archived versions.
190
	 *
191
	 * @param SiteTree $page
192
	 * @return array
193
	 */
194
	public function SwitchView($page = null) {
195
		if(!$page) {
196
			$page = $this->currentPage();
197
		}
198
199
		if($page) {
200
			$nav = SilverStripeNavigator::get_for_record($page);
201
			return $nav['items'];
202
		}
203
	}
204
205
	//------------------------------------------------------------------------------------------//
206
	// Main controllers
207
208
	//------------------------------------------------------------------------------------------//
209
	// Main UI components
210
211
	/**
212
	 * Override {@link LeftAndMain} Link to allow blank URL segment for CMSMain.
213
	 *
214
	 * @param string|null $action Action to link to.
215
	 * @return string
216
	 */
217
	public function Link($action = null) {
218
		$link = Controller::join_links(
219
			AdminRootController::admin_url(),
220
			$this->stat('url_segment'), // in case we want to change the segment
221
			'/', // trailing slash needed if $action is null!
222
			"$action"
223
		);
224
		$this->extend('updateLink', $link);
225
		return $link;
226
	}
227
228
	public function LinkPages() {
229
		return CMSPagesController::singleton()->Link();
230
	}
231
232
	public function LinkPagesWithSearch() {
233
		return $this->LinkWithSearch($this->LinkPages());
234
	}
235
236
	public function LinkTreeView() {
237
		return $this->LinkWithSearch($this->Link('treeview'));
238
	}
239
240
	public function LinkListView() {
241
		return $this->LinkWithSearch($this->Link('listview'));
242
	}
243
244
	public function LinkGalleryView() {
245
		return $this->LinkWithSearch($this->Link('galleryview'));
246
	}
247
248 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...
249
		if(!$id) {
250
			$id = $this->currentPageID();
251
		}
252
		return $this->LinkWithSearch(
253
			Controller::join_links(CMSPageEditController::singleton()->Link('show'), $id)
254
		);
255
	}
256
257 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...
258
		if($id = $this->currentPageID()) {
259
			return $this->LinkWithSearch(
260
				Controller::join_links(CMSPageSettingsController::singleton()->Link('show'), $id)
261
			);
262
		} else {
263
			return null;
264
		}
265
	}
266
267 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...
268
		if($id = $this->currentPageID()) {
269
			return $this->LinkWithSearch(
270
				Controller::join_links(CMSPageHistoryController::singleton()->Link('show'), $id)
271
			);
272
		} else {
273
			return null;
274
		}
275
	}
276
277
	public function LinkWithSearch($link) {
278
		// Whitelist to avoid side effects
279
		$params = array(
280
			'q' => (array)$this->getRequest()->getVar('q'),
281
			'ParentID' => $this->getRequest()->getVar('ParentID')
282
		);
283
		$link = Controller::join_links(
284
			$link,
285
			array_filter(array_values($params)) ? '?' . http_build_query($params) : null
286
		);
287
		$this->extend('updateLinkWithSearch', $link);
288
		return $link;
289
	}
290
291
	public function LinkPageAdd($extra = null, $placeholders = null) {
292
		$link = CMSPageAddController::singleton()->Link();
293
		$this->extend('updateLinkPageAdd', $link);
294
295
		if($extra) {
296
			$link = Controller::join_links ($link, $extra);
297
		}
298
299
		if($placeholders) {
300
			$link .= (strpos($link, '?') === false ? "?$placeholders" : "&amp;$placeholders");
301
		}
302
303
		return $link;
304
	}
305
306
	/**
307
	 * @return string
308
	 */
309
	public function LinkPreview() {
310
		$record = $this->getRecord($this->currentPageID());
311
		$baseLink = Director::absoluteBaseURL();
312
		if ($record && $record instanceof Page) {
313
			// if we are an external redirector don't show a link
314
			if ($record instanceof RedirectorPage && $record->RedirectionType == 'External') {
315
				$baseLink = false;
316
			}
317
			else {
318
				$baseLink = $record->Link('?stage=Stage');
319
			}
320
		}
321
		return $baseLink;
322
	}
323
324
	/**
325
	 * Return the entire site tree as a nested set of ULs
326
	 */
327
	public function SiteTreeAsUL() {
328
		// Pre-cache sitetree version numbers for querying efficiency
329
		Versioned::prepopulate_versionnumber_cache("SilverStripe\\CMS\\Model\\SiteTree", "Stage");
330
		Versioned::prepopulate_versionnumber_cache("SilverStripe\\CMS\\Model\\SiteTree", "Live");
331
		$html = $this->getSiteTreeFor($this->stat('tree_class'));
332
333
		$this->extend('updateSiteTreeAsUL', $html);
334
335
		return $html;
336
	}
337
338
	/**
339
	 * @return boolean
340
	 */
341
	public function TreeIsFiltered() {
342
		$query = $this->getRequest()->getVar('q');
343
344
		if (!$query || (count($query) === 1 && isset($query['FilterClass']) && $query['FilterClass'] === 'SilverStripe\\CMS\\Controllers\\CMSSiteTreeFilter_Search')) {
345
			return false;
346
		}
347
348
		return true;
349
	}
350
351
	public function ExtraTreeTools() {
352
		$html = '';
353
		$this->extend('updateExtraTreeTools', $html);
354
		return $html;
355
	}
356
357
	/**
358
	 * Returns a Form for page searching for use in templates.
359
	 *
360
	 * Can be modified from a decorator by a 'updateSearchForm' method
361
	 *
362
	 * @return Form
363
	 */
364
	public function SearchForm() {
365
		// Create the fields
366
		$content = new TextField('q[Term]', _t('CMSSearch.FILTERLABELTEXT', 'Search'));
367
		$dateFrom = new DateField(
368
			'q[LastEditedFrom]',
369
			_t('CMSSearch.FILTERDATEFROM', 'From')
370
		);
371
		$dateFrom->setConfig('showcalendar', true);
372
		$dateTo = new DateField(
373
			'q[LastEditedTo]',
374
			_t('CMSSearch.FILTERDATETO', 'To')
375
		);
376
		$dateTo->setConfig('showcalendar', true);
377
		$pageFilter = new DropdownField(
378
			'q[FilterClass]',
379
			_t('CMSMain.PAGES', 'Page status'),
380
			CMSSiteTreeFilter::get_all_filters()
381
		);
382
		$pageClasses = new DropdownField(
383
			'q[ClassName]',
384
			_t('CMSMain.PAGETYPEOPT', 'Page type', 'Dropdown for limiting search to a page type'),
385
			$this->getPageTypes()
386
		);
387
		$pageClasses->setEmptyString(_t('CMSMain.PAGETYPEANYOPT','Any'));
388
389
		// Group the Datefields
390
		$dateGroup = new FieldGroup(
391
			$dateFrom,
392
			$dateTo
393
		);
394
		$dateGroup->setTitle(_t('CMSSearch.PAGEFILTERDATEHEADING', 'Last edited'));
395
396
		// Create the Field list
397
		$fields = new FieldList(
398
			$content,
399
			$pageFilter,
400
			$pageClasses,
401
			$dateGroup
402
		);
403
404
		// Create the Search and Reset action
405
		$actions = new FieldList(
406
			FormAction::create('doSearch',  _t('CMSMain_left_ss.APPLY_FILTER', 'Search'))
407
				->addExtraClass('ss-ui-action-constructive'),
408
			ResetFormAction::create('clear', _t('CMSMain_left_ss.CLEAR_FILTER', 'Clear'))
409
		);
410
411
		// Use <button> to allow full jQuery UI styling on the all of the Actions
412
		/** @var FormAction $action */
413
		foreach($actions->dataFields() as $action) {
414
			/** @var FormAction $action */
415
			$action->setUseButtonTag(true);
416
		}
417
418
		// Create the form
419
		/** @skipUpgrade */
420
		$form = Form::create($this, 'SearchForm', $fields, $actions)
421
			->addExtraClass('cms-search-form')
422
			->setFormMethod('GET')
423
			->setFormAction($this->Link())
424
			->disableSecurityToken()
425
			->unsetValidator();
426
427
		// Load the form with previously sent search data
428
		$form->loadDataFrom($this->getRequest()->getVars());
429
430
		// Allow decorators to modify the form
431
		$this->extend('updateSearchForm', $form);
432
433
		return $form;
434
	}
435
436
	/**
437
	 * Returns a sorted array suitable for a dropdown with pagetypes and their translated name
438
	 *
439
	 * @return array
440
	 */
441
	protected function getPageTypes() {
442
		$pageTypes = array();
443
		foreach(SiteTree::page_type_classes() as $pageTypeClass) {
444
			$pageTypes[$pageTypeClass] = SiteTree::singleton($pageTypeClass)->i18n_singular_name();
445
		}
446
		asort($pageTypes);
447
		return $pageTypes;
448
	}
449
450
	public function doSearch($data, $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $form is not used and could be removed.

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

Loading history...
451
		return $this->getsubtree($this->getRequest());
452
	}
453
454
	/**
455
	 * @param bool $unlinked
456
	 * @return ArrayList
457
	 */
458
	public function Breadcrumbs($unlinked = false) {
459
		$items = parent::Breadcrumbs($unlinked);
460
461
		if($items->count() > 1) {
462
			// Specific to the SiteTree admin section, we never show the cms section and current
463
			// page in the same breadcrumbs block.
464
			$items->shift();
465
		}
466
467
		return $items;
468
	}
469
470
	/**
471
	 * Create serialized JSON string with site tree hints data to be injected into
472
	 * 'data-hints' attribute of root node of jsTree.
473
	 *
474
	 * @return string Serialized JSON
475
	 */
476
	public function SiteTreeHints() {
477
		$classes = SiteTree::page_type_classes();
478
479
	 	$cacheCanCreate = array();
480
	 	foreach($classes as $class) $cacheCanCreate[$class] = singleton($class)->canCreate();
481
482
	 	// Generate basic cache key. Too complex to encompass all variations
483
	 	$cache = Cache::factory('CMSMain_SiteTreeHints');
484
	 	$cacheKey = md5(implode('_', array(Member::currentUserID(), implode(',', $cacheCanCreate), implode(',', $classes))));
485
	 	if($this->getRequest()->getVar('flush')) {
486
	 		$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
487
		}
488
	 	$json = $cache->load($cacheKey);
489
	 	if(!$json) {
490
			$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...
491
			$def['Root']['disallowedChildren'] = array();
492
493
			// Contains all possible classes to support UI controls listing them all,
494
			// such as the "add page here" context menu.
495
			$def['All'] = array();
496
497
			// Identify disallows and set globals
498
			foreach($classes as $class) {
499
				$obj = singleton($class);
500
				if($obj instanceof HiddenClass) continue;
501
502
				// Name item
503
				$def['All'][$class] = array(
504
					'title' => $obj->i18n_singular_name()
505
				);
506
507
				// Check if can be created at the root
508
				$needsPerm = $obj->stat('need_permission');
509
				if(
510
					!$obj->stat('can_be_root')
511
					|| (!array_key_exists($class, $cacheCanCreate) || !$cacheCanCreate[$class])
512
					|| ($needsPerm && !$this->can($needsPerm))
513
				) {
514
					$def['Root']['disallowedChildren'][] = $class;
515
				}
516
517
				// Hint data specific to the class
518
				$def[$class] = array();
519
520
				$defaultChild = $obj->defaultChild();
521
				if($defaultChild !== 'Page' && $defaultChild !== null) {
522
					$def[$class]['defaultChild'] = $defaultChild;
523
				}
524
525
				$defaultParent = $obj->defaultParent();
526
				if ($defaultParent !== 1 && $defaultParent !== null) {
527
					$def[$class]['defaultParent'] = $defaultParent;
528
				}
529
			}
530
531
			$this->extend('updateSiteTreeHints', $def);
532
533
			$json = Convert::raw2json($def);
534
			$cache->save($json, $cacheKey);
535
		}
536
		return $json;
537
	}
538
539
	/**
540
	 * Populates an array of classes in the CMS
541
	 * which allows the user to change the page type.
542
	 *
543
	 * @return SS_List
544
	 */
545
	public function PageTypes() {
546
		$classes = SiteTree::page_type_classes();
547
548
		$result = new ArrayList();
549
550
		foreach($classes as $class) {
551
			$instance = singleton($class);
552
553
			if($instance instanceof HiddenClass) {
554
				continue;
555
			}
556
557
			// skip this type if it is restricted
558
			if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) {
559
				continue;
560
			}
561
562
			$addAction = $instance->i18n_singular_name();
563
564
			// Get description (convert 'Page' to 'SiteTree' for correct localization lookups)
565
			$i18nClass = ($class == 'Page') ? 'SilverStripe\\CMS\\Model\\SiteTree' : $class;
566
			$description = _t($i18nClass . '.DESCRIPTION');
567
568
			if(!$description) {
569
				$description = $instance->uninherited('description');
570
			}
571
572
			if($class == 'Page' && !$description) {
573
				$description = SiteTree::singleton()->uninherited('description');
574
			}
575
576
			$result->push(new ArrayData(array(
577
				'ClassName' => $class,
578
				'AddAction' => $addAction,
579
				'Description' => $description,
580
				// TODO Sprite support
581
				'IconURL' => $instance->stat('icon'),
582
				'Title' => singleton($class)->i18n_singular_name(),
583
			)));
584
		}
585
586
		$result = $result->sort('AddAction');
587
588
		return $result;
589
	}
590
591
	/**
592
	 * Get a database record to be managed by the CMS.
593
	 *
594
	 * @param int $id Record ID
595
	 * @param int $versionID optional Version id of the given record
596
	 * @return SiteTree
597
	 */
598
 	public function getRecord($id, $versionID = null) {
599
		$treeClass = $this->stat('tree_class');
600
601
		if($id instanceof $treeClass) {
602
			return $id;
603
		}
604
		else if($id && is_numeric($id)) {
605
			$currentStage = Versioned::get_reading_mode();
606
607
			if($this->getRequest()->getVar('Version')) {
608
				$versionID = (int) $this->getRequest()->getVar('Version');
609
			}
610
611
			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...
612
				$record = Versioned::get_version($treeClass, $id, $versionID);
613
			} else {
614
				$record = DataObject::get_by_id($treeClass, $id);
615
			}
616
617
			// Then, try getting a record from the live site
618
			if(!$record) {
619
				// $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id");
620
				Versioned::set_stage(Versioned::LIVE);
621
				singleton($treeClass)->flushCache();
622
623
				$record = DataObject::get_by_id($treeClass, $id);
624
			}
625
626
			// Then, try getting a deleted record
627
			if(!$record) {
628
				$record = Versioned::get_latest_version($treeClass, $id);
629
			}
630
631
			// Don't open a page from a different locale
632
			/** The record's Locale is saved in database in 2.4, and not related with Session,
633
			 *  we should not check their locale matches the Translatable::get_current_locale,
634
			 * 	here as long as we all the HTTPRequest is init with right locale.
635
			 *	This bit breaks the all FileIFrameField functions if the field is used in CMS
636
			 *  and its relevent ajax calles, like loading the tree dropdown for TreeSelectorField.
637
			 */
638
			/* if($record && SiteTree::has_extension('Translatable') && $record->Locale && $record->Locale != Translatable::get_current_locale()) {
639
				$record = null;
640
			}*/
641
642
			// Set the reading mode back to what it was.
643
			Versioned::set_reading_mode($currentStage);
644
645
			return $record;
646
647
		} else if(substr($id,0,3) == 'new') {
648
			return $this->getNewItem($id);
649
		}
650
	}
651
652
	/**
653
	 * @param int $id
654
	 * @param FieldList $fields
655
	 * @return Form
656
	 */
657
	public function getEditForm($id = null, $fields = null) {
658
		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...
659
		$form = parent::getEditForm($id, $fields);
660
661
		// TODO Duplicate record fetching (see parent implementation)
662
		$record = $this->getRecord($id);
663
		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 SilverStripe\Control\HTTPResponse|null adds the type SilverStripe\Control\HTTPResponse to the return on line 663 which is incompatible with the return type documented by SilverStripe\CMS\Controllers\CMSMain::getEditForm of type SilverStripe\Forms\Form.
Loading history...
664
665
		if(!$fields) $fields = $form->Fields();
666
		$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...
667
668
		if($record) {
669
			$deletedFromStage = !$record->isOnDraft();
0 ignored issues
show
Documentation Bug introduced by
The method isOnDraft does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
670
671
			$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...
672
			// Necessary for different subsites
673
			$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...
674
			$fields->push($liveLinkField = new HiddenField("LiveLink"));
675
			$fields->push($stageLinkField = new HiddenField("StageLink"));
676
			$fields->push(new HiddenField("TreeTitle", false, $record->TreeTitle));
0 ignored issues
show
Documentation introduced by
The property TreeTitle does not exist on object<SilverStripe\CMS\Model\SiteTree>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
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...
677
678
			if($record->ID && is_numeric( $record->ID ) ) {
679
				$liveLink = $record->getAbsoluteLiveLink();
680
				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...
681
				if(!$deletedFromStage) {
682
					$stageLink = Controller::join_links($record->AbsoluteLink(), '?stage=Stage');
683
					if($stageLink) $stageLinkField->setValue($stageLink);
684
				}
685
			}
686
687
			// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
688
			/** @skipUpgrade */
689
			if($record instanceof CMSPreviewable && !$fields->fieldByName('SilverStripeNavigator')) {
690
				$navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator());
691
				$navField->setAllowHTML(true);
692
				$fields->push($navField);
693
			}
694
695
			// getAllCMSActions can be used to completely redefine the action list
696
			if($record->hasMethod('getAllCMSActions')) {
697
				$actions = $record->getAllCMSActions();
0 ignored issues
show
Documentation Bug introduced by
The method getAllCMSActions does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
698
			} else {
699
				$actions = $record->getCMSActions();
700
701
				// Find and remove action menus that have no actions.
702
				if ($actions && $actions->count()) {
703
					/** @var TabSet $tabset */
704
					$tabset = $actions->fieldByName('ActionMenus');
705
					if ($tabset) {
706
						foreach ($tabset->getChildren() as $tab) {
707
							if (!$tab->getChildren()->count()) {
708
								$tabset->removeByName($tab->getName());
709
							}
710
						}
711
					}
712
				}
713
			}
714
715
			// Use <button> to allow full jQuery UI styling
716
			$actionsFlattened = $actions->dataFields();
717
			if($actionsFlattened) {
718
				/** @var FormAction $action */
719
				foreach($actionsFlattened as $action) {
720
					$action->setUseButtonTag(true);
721
				}
722
			}
723
724
			if($record->hasMethod('getCMSValidator')) {
725
				$validator = $record->getCMSValidator();
0 ignored issues
show
Documentation Bug introduced by
The method getCMSValidator does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
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...
726
			} else {
727
				$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...
728
			}
729
730
			// TODO Can't merge $FormAttributes in template at the moment
731
			$form->addExtraClass('center ' . $this->BaseCSSClasses());
732
			// Set validation exemptions for specific actions
733
			$form->setValidationExemptActions(array('restore', 'revert', 'deletefromlive', 'delete', 'unpublish', 'rollback', 'doRollback'));
734
735
			// Announce the capability so the frontend can decide whether to allow preview or not.
736
			if ($record instanceof CMSPreviewable) {
737
				$form->addExtraClass('cms-previewable');
738
			}
739
			$form->addExtraClass('fill-height flexbox-area-grow');
740
741
			if(!$record->canEdit() || $deletedFromStage) {
742
				$readonlyFields = $form->Fields()->makeReadonly();
743
				$form->setFields($readonlyFields);
744
			}
745
746
			$form->Fields()->setForm($form);
747
748
			$this->extend('updateEditForm', $form);
749
			return $form;
750
		} else if($id) {
751
			$form = Form::create( $this, "EditForm", new FieldList(
752
				new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList()
753
			)->setHTMLID('Form_EditForm');
754
			return $form;
755
		}
756
	}
757
758
	/**
759
	 * @param HTTPRequest $request
760
	 * @return string HTML
761
	 */
762
	public function treeview($request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

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

Loading history...
763
		return $this->renderWith($this->getTemplatesWithSuffix('_TreeView'));
764
	}
765
766
	/**
767
	 * @param HTTPRequest $request
768
	 * @return string HTML
769
	 */
770
	public function listview($request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

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

Loading history...
771
		return $this->renderWith($this->getTemplatesWithSuffix('_ListView'));
772
	}
773
774
	/**
775
	 * Callback to request the list of page types allowed under a given page instance.
776
	 * Provides a slower but more precise response over SiteTreeHints
777
	 *
778
	 * @param HTTPRequest $request
779
	 * @return HTTPResponse
780
	 */
781
	public function childfilter($request) {
782
		// Check valid parent specified
783
		$parentID = $request->requestVar('ParentID');
784
		$parent = SiteTree::get()->byID($parentID);
785
		if(!$parent || !$parent->exists()) return $this->httpError(404);
786
787
		// Build hints specific to this class
788
		// Identify disallows and set globals
789
		$classes = SiteTree::page_type_classes();
790
		$disallowedChildren = array();
791
		foreach($classes as $class) {
792
			$obj = singleton($class);
793
			if($obj instanceof HiddenClass) continue;
794
795
			if(!$obj->canCreate(null, array('Parent' => $parent))) {
796
				$disallowedChildren[] = $class;
797
			}
798
		}
799
800
		$this->extend('updateChildFilter', $disallowedChildren, $parentID);
801
		return $this
802
			->getResponse()
803
			->addHeader('Content-Type', 'application/json; charset=utf-8')
804
			->setBody(Convert::raw2json($disallowedChildren));
805
	}
806
807
	/**
808
	 * Safely reconstruct a selected filter from a given set of query parameters
809
	 *
810
	 * @param array $params Query parameters to use
811
	 * @return CMSSiteTreeFilter The filter class, or null if none present
812
	 * @throws InvalidArgumentException if invalid filter class is passed.
813
	 */
814
	protected function getQueryFilter($params) {
815
		if(empty($params['FilterClass'])) return null;
816
		$filterClass = $params['FilterClass'];
817
		if(!is_subclass_of($filterClass, 'SilverStripe\\CMS\\Controllers\\CMSSiteTreeFilter')) {
818
			throw new InvalidArgumentException("Invalid filter class passed: {$filterClass}");
819
		}
820
		return $filterClass::create($params);
821
	}
822
823
	/**
824
	 * Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page
825
	 * defaulting to no filter and show all pages in first level.
826
	 * Doubles as search results, if any search parameters are set through {@link SearchForm()}.
827
	 *
828
	 * @param array $params Search filter criteria
829
	 * @param int $parentID Optional parent node to filter on (can't be combined with other search criteria)
830
	 * @return SS_List
831
	 * @throws InvalidArgumentException if invalid filter class is passed.
832
	 */
833
	public function getList($params = array(), $parentID = 0) {
834
		if($filter = $this->getQueryFilter($params)) {
835
			return $filter->getFilteredPages();
836
		} else {
837
			$list = DataList::create($this->stat('tree_class'));
838
			$parentID = is_numeric($parentID) ? $parentID : 0;
839
			return $list->filter("ParentID", $parentID);
840
		}
841
	}
842
843
	/**
844
	 * @return Form
845
	 */
846
	public function ListViewForm() {
847
		$params = $this->getRequest()->requestVar('q');
848
		$list = $this->getList($params, $parentID = $this->getRequest()->requestVar('ParentID'));
849
		$gridFieldConfig = GridFieldConfig::create()->addComponents(
850
			new GridFieldSortableHeader(),
851
			new GridFieldDataColumns(),
852
			new GridFieldPaginator(self::config()->page_length)
853
		);
854
		if($parentID){
855
			$linkSpec = $this->Link();
856
			$linkSpec = $linkSpec . (strstr($linkSpec, '?') ? '&' : '?') . 'ParentID=%d&view=list';
857
			$gridFieldConfig->addComponent(
858
				GridFieldLevelup::create($parentID)
859
					->setLinkSpec($linkSpec)
860
					->setAttributes(array('data-pjax' => 'ListViewForm,Breadcrumbs'))
861
			);
862
		}
863
		$gridField = new GridField('Page','Pages', $list, $gridFieldConfig);
864
		/** @var GridFieldDataColumns $columns */
865
		$columns = $gridField->getConfig()->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns');
866
867
		// Don't allow navigating into children nodes on filtered lists
868
		$fields = array(
869
			'getTreeTitle' => _t('SiteTree.PAGETITLE', 'Page Title'),
870
			'singular_name' => _t('SiteTree.PAGETYPE'),
871
			'LastEdited' => _t('SiteTree.LASTUPDATED', 'Last Updated'),
872
		);
873
		/** @var GridFieldSortableHeader $sortableHeader */
874
		$sortableHeader = $gridField->getConfig()->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldSortableHeader');
875
		$sortableHeader->setFieldSorting(array('getTreeTitle' => 'Title'));
876
		$gridField->getState()->ParentID = $parentID;
877
878
		if(!$params) {
879
			$fields = array_merge(array('listChildrenLink' => ''), $fields);
880
		}
881
882
		$columns->setDisplayFields($fields);
883
		$columns->setFieldCasting(array(
884
			'Created' => 'DBDatetime->Ago',
885
			'LastEdited' => 'DBDatetime->FormatFromSettings',
886
			'getTreeTitle' => 'HTMLFragment'
887
		));
888
889
		$controller = $this;
890
		$columns->setFieldFormatting(array(
891
			'listChildrenLink' => function($value, &$item) use($controller) {
892
				/** @var SiteTree $item */
893
				$num = $item ? $item->numChildren() : null;
0 ignored issues
show
Documentation Bug introduced by
The method numChildren does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
894
				if($num) {
895
					return sprintf(
896
						'<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>',
897
						Controller::join_links(
898
							$controller->Link(),
899
							sprintf("?ParentID=%d&view=list", (int)$item->ID)
900
						),
901
						$num
902
					);
903
				}
904
			},
905
			'getTreeTitle' => function($value, &$item) use($controller) {
906
				return sprintf(
907
					'<a class="action-detail" href="%s">%s</a>',
908
					Controller::join_links(
909
						CMSPageEditController::singleton()->Link('show'),
910
						(int)$item->ID
911
					),
912
					$item->TreeTitle // returns HTML, does its own escaping
913
				);
914
			}
915
		));
916
917
		$negotiator = $this->getResponseNegotiator();
918
		$listview = Form::create(
919
			$this,
920
			'ListViewForm',
921
			new FieldList($gridField),
922
			new FieldList()
923
		)->setHTMLID('Form_ListViewForm');
924
		$listview->setAttribute('data-pjax-fragment', 'ListViewForm');
925 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...
926
			$request = $this->getRequest();
927
			if($request->isAjax() && $negotiator) {
928
				$listview->setupFormErrors();
929
				$result = $listview->forTemplate();
930
931
				return $negotiator->respond($request, array(
932
					'CurrentForm' => function() use($result) {
933
						return $result;
934
					}
935
				));
936
			}
937
		});
938
939
		$this->extend('updateListView', $listview);
940
941
		$listview->disableSecurityToken();
942
		return $listview;
943
	}
944
945
	public function currentPageID() {
946
		$id = parent::currentPageID();
947
948
		$this->extend('updateCurrentPageID', $id);
949
950
		return $id;
951
	}
952
953
	//------------------------------------------------------------------------------------------//
954
	// Data saving handlers
955
956
	/**
957
	 * Save and Publish page handler
958
	 *
959
	 * @param array $data
960
	 * @param Form $form
961
	 * @return HTTPResponse
962
	 * @throws HTTPResponse_Exception
963
	 */
964
	public function save($data, $form) {
965
		$className = $this->stat('tree_class');
966
967
		// Existing or new record?
968
		$id = $data['ID'];
969
		if(substr($id,0,3) != 'new') {
970
			/** @var SiteTree $record */
971
			$record = DataObject::get_by_id($className, $id);
972
			// Check edit permissions
973
			if($record && !$record->canEdit()) {
974
				return Security::permissionFailure($this);
975
			}
976
			if(!$record || !$record->ID) {
977
				throw new HTTPResponse_Exception("Bad record ID #$id", 404);
978
			}
979
		} else {
980
			if(!$className::singleton()->canCreate()) {
981
				return Security::permissionFailure($this);
982
			}
983
			$record = $this->getNewItem($id, false);
984
		}
985
986
		// Check publishing permissions
987
		$doPublish = !empty($data['publish']);
988
		if($record && $doPublish && !$record->canPublish()) {
989
			return Security::permissionFailure($this);
990
		}
991
992
		// TODO Coupling to SiteTree
993
		$record->HasBrokenLink = 0;
994
		$record->HasBrokenFile = 0;
995
996
		if (!$record->ObsoleteClassName) {
997
			$record->writeWithoutVersion();
998
		}
999
1000
		// Update the class instance if necessary
1001
		if(isset($data['ClassName']) && $data['ClassName'] != $record->ClassName) {
1002
			// Replace $record with a new instance of the new class
1003
			$newClassName = $data['ClassName'];
1004
			$record = $record->newClassInstance($newClassName);
1005
		}
1006
1007
		// save form data into record
1008
		$form->saveInto($record);
1009
		$record->write();
1010
1011
		// If the 'Save & Publish' button was clicked, also publish the page
1012
		if ($doPublish) {
1013
			$record->publishRecursive();
1014
			$message = _t(
1015
				'CMSMain.PUBLISHED',
1016
				"Published '{title}' successfully.",
1017
				['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...
1018
			);
1019
		} else {
1020
			$message = _t(
1021
				'CMSMain.SAVED',
1022
				"Saved '{title}' successfully.",
1023
				['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...
1024
			);
1025
		}
1026
1027
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1028
		return $this->getResponseNegotiator()->respond($this->getRequest());
1029
	}
1030
1031
	/**
1032
	 * @uses LeftAndMainExtension->augmentNewSiteTreeItem()
1033
	 *
1034
	 * @param int|string $id
1035
	 * @param bool $setID
1036
	 * @return mixed|DataObject
1037
	 * @throws HTTPResponse_Exception
1038
	 */
1039
	public function getNewItem($id, $setID = true) {
1040
		$parentClass = $this->stat('tree_class');
1041
		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...
1042
1043
		if (!is_a($className, $parentClass, true)) {
1044
			$response = Security::permissionFailure($this);
1045
			if (!$response) {
1046
				$response = $this->getResponse();
1047
			}
1048
			throw new HTTPResponse_Exception($response);
1049
		}
1050
1051
		/** @var SiteTree $newItem */
1052
		$newItem = Injector::inst()->create($className);
1053
		if( !$suffix ) {
1054
			$sessionTag = "NewItems." . $parentID . "." . $className;
1055
			if(Session::get($sessionTag)) {
1056
				$suffix = '-' . Session::get($sessionTag);
1057
				Session::set($sessionTag, Session::get($sessionTag) + 1);
1058
			}
1059
			else
1060
				Session::set($sessionTag, 1);
1061
1062
				$id = $id . $suffix;
1063
		}
1064
1065
		$newItem->Title = _t(
1066
			'CMSMain.NEWPAGE',
1067
			"New {pagetype}",'followed by a page type title',
1068
			array('pagetype' => singleton($className)->i18n_singular_name())
1069
		);
1070
		$newItem->ClassName = $className;
1071
		$newItem->ParentID = $parentID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\CMS\Model\SiteTree>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
1072
1073
		// DataObject::fieldExists only checks the current class, not the hierarchy
1074
		// This allows the CMS to set the correct sort value
1075
		if($newItem->castingHelper('Sort')) {
1076
			$newItem->Sort = DB::prepared_query('SELECT MAX("Sort") FROM "SiteTree" WHERE "ParentID" = ?', array($parentID))->value() + 1;
1077
		}
1078
1079
		if($setID) $newItem->ID = $id;
1080
1081
		# Some modules like subsites add extra fields that need to be set when the new item is created
1082
		$this->extend('augmentNewSiteTreeItem', $newItem);
1083
1084
		return $newItem;
1085
	}
1086
1087
	/**
1088
	 * Actually perform the publication step
1089
	 *
1090
	 * @param Versioned|DataObject $record
1091
	 * @return mixed
1092
	 */
1093
	public function performPublish($record) {
1094
		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...
1095
			return Security::permissionFailure($this);
1096
		}
1097
1098
		$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...
1099
	}
1100
1101
	/**
1102
	 * Reverts a page by publishing it to live.
1103
	 * Use {@link restorepage()} if you want to restore a page
1104
	 * which was deleted from draft without publishing.
1105
	 *
1106
	 * @uses SiteTree->doRevertToLive()
1107
	 *
1108
	 * @param array $data
1109
	 * @param Form $form
1110
	 * @return HTTPResponse
1111
	 * @throws HTTPResponse_Exception
1112
	 */
1113
	public function revert($data, $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

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

Loading history...
1114
		if(!isset($data['ID'])) {
1115
			throw new HTTPResponse_Exception("Please pass an ID in the form content", 400);
1116
		}
1117
1118
		$id = (int) $data['ID'];
1119
		$restoredPage = Versioned::get_latest_version("SilverStripe\\CMS\\Model\\SiteTree", $id);
1120
		if(!$restoredPage) {
1121
			throw new HTTPResponse_Exception("SiteTree #$id not found", 400);
1122
		}
1123
1124
		/** @var SiteTree $record */
1125
		$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...
1126
			'"SiteTree_Live"."ID"' => $id
1127
		));
1128
1129
		// a user can restore a page without publication rights, as it just adds a new draft state
1130
		// (this action should just be available when page has been "deleted from draft")
1131
		if($record && !$record->canEdit()) {
1132
			return Security::permissionFailure($this);
1133
		}
1134
		if(!$record || !$record->ID) {
1135
			throw new HTTPResponse_Exception("Bad record ID #$id", 404);
1136
		}
1137
1138
		$record->doRevertToLive();
0 ignored issues
show
Documentation Bug introduced by
The method doRevertToLive does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1139
1140
		$this->getResponse()->addHeader(
1141
			'X-Status',
1142
			rawurlencode(_t(
1143
				'CMSMain.RESTORED',
1144
				"Restored '{title}' successfully",
1145
				'Param %s is a title',
1146
				array('title' => $record->Title)
1147
			))
1148
		);
1149
1150
		return $this->getResponseNegotiator()->respond($this->getRequest());
1151
	}
1152
1153
	/**
1154
	 * Deletes the current record from all stages and archives it
1155
	 *
1156
	 * @see deletefromlive()
1157
	 *
1158
	 * @param array $data
1159
	 * @param Form $form
1160
	 * @return HTTPResponse
1161
	 * @throws HTTPResponse_Exception
1162
	 */
1163
	public function delete($data, $form) {
1164
		$id = $data['ID'];
1165
		/** @var SiteTree $record */
1166
		$record = SiteTree::get()->byID($id);
1167
		if($record && !$record->canArchive()) {
0 ignored issues
show
Documentation Bug introduced by
The method canArchive does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1168
			return Security::permissionFailure();
1169
		}
1170
		if(!$record || !$record->ID) {
1171
			throw new HTTPResponse_Exception("Bad record ID #$id", 404);
1172
		}
1173
1174
		// Archive record
1175
		$record->doArchive();
0 ignored issues
show
Documentation Bug introduced by
The method doArchive does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1176
1177
		$this->getResponse()->addHeader(
1178
			'X-Status',
1179
			rawurlencode(_t(
1180
				'CMSMain.DELETEDPAGE',
1181
				'Deleted \'{title}\' from draft and live, and sent it to the archive',
1182
				[ '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...
1183
			))
1184
		);
1185
1186
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1187
		return $this->getResponseNegotiator()->respond($this->getRequest());
1188
	}
1189
1190
	public function publish($data, $form) {
1191
		$data['publish'] = '1';
1192
1193
		return $this->save($data, $form);
1194
	}
1195
1196
	public function unpublish($data, $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

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

Loading history...
1197
		$className = $this->stat('tree_class');
1198
		/** @var SiteTree $record */
1199
		$record = DataObject::get_by_id($className, $data['ID']);
1200
1201
		if($record && !$record->canUnpublish()) {
0 ignored issues
show
Documentation Bug introduced by
The method canUnpublish does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1202
			return Security::permissionFailure($this);
1203
		}
1204
		if(!$record || !$record->ID) {
1205
			throw new HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
1206
		}
1207
1208
		$record->doUnpublish();
0 ignored issues
show
Documentation Bug introduced by
The method doUnpublish does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1209
1210
		$this->getResponse()->addHeader(
1211
			'X-Status',
1212
			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...
1213
		);
1214
1215
		return $this->getResponseNegotiator()->respond($this->getRequest());
1216
	}
1217
1218
	/**
1219
	 * @return HTTPResponse
1220
	 */
1221
	public function rollback() {
1222
		return $this->doRollback(array(
1223
			'ID' => $this->currentPageID(),
1224
			'Version' => $this->getRequest()->param('VersionID')
1225
		), null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<SilverStripe\Forms\Form>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1226
	}
1227
1228
	/**
1229
	 * Rolls a site back to a given version ID
1230
	 *
1231
	 * @param array $data
1232
	 * @param Form $form
1233
	 * @return HTTPResponse
1234
	 */
1235
	public function doRollback($data, $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

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

Loading history...
1236
		$this->extend('onBeforeRollback', $data['ID']);
1237
1238
		$id = (isset($data['ID'])) ? (int) $data['ID'] : null;
1239
		$version = (isset($data['Version'])) ? (int) $data['Version'] : null;
1240
1241
		/** @var DataObject|Versioned $record */
1242
		$record = DataObject::get_by_id($this->stat('tree_class'), $id);
1243
		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...
1244
			return Security::permissionFailure($this);
1245
		}
1246
1247
		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...
1248
			$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...
1249
			$message = _t(
1250
				'CMSMain.ROLLEDBACKVERSIONv2',
1251
				"Rolled back to version #%d.",
1252
				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...
1253
			);
1254
		} else {
1255
			$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...
1256
			$message = _t(
1257
				'CMSMain.ROLLEDBACKPUBv2',"Rolled back to published version."
1258
			);
1259
		}
1260
1261
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1262
1263
		// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
1264
		// Or in history view, in which case a revert causes the CMS to re-load the edit view.
1265
		// The X-Pjax header forces a "full" content refresh on redirect.
1266
		$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $record->ID);
1267
		$this->getResponse()->addHeader('X-ControllerURL', $url);
1268
		$this->getRequest()->addHeader('X-Pjax', 'Content');
1269
		$this->getResponse()->addHeader('X-Pjax', 'Content');
1270
1271
		return $this->getResponseNegotiator()->respond($this->getRequest());
1272
	}
1273
1274
	/**
1275
	 * Batch Actions Handler
1276
	 */
1277
	public function batchactions() {
1278
		return new CMSBatchActionHandler($this, 'batchactions');
1279
	}
1280
1281
	public function BatchActionParameters() {
1282
		$batchActions = CMSBatchActionHandler::config()->batch_actions;
1283
1284
		$forms = array();
1285
		foreach($batchActions as $urlSegment => $batchAction) {
1286
			$SNG_action = singleton($batchAction);
1287
			if ($SNG_action->canView() && $fieldset = $SNG_action->getParameterFields()) {
1288
				$formHtml = '';
1289
				/** @var FormField $field */
1290
				foreach($fieldset as $field) {
1291
					$formHtml .= $field->Field();
1292
				}
1293
				$forms[$urlSegment] = $formHtml;
1294
			}
1295
		}
1296
		$pageHtml = '';
1297
		foreach($forms as $urlSegment => $html) {
1298
			$pageHtml .= "<div class=\"params\" id=\"BatchActionParameters_$urlSegment\">$html</div>\n\n";
1299
		}
1300
		return new LiteralField("BatchActionParameters", '<div id="BatchActionParameters" style="display:none">'.$pageHtml.'</div>');
1301
	}
1302
	/**
1303
	 * Returns a list of batch actions
1304
	 */
1305
	public function BatchActionList() {
1306
		return $this->batchactions()->batchActionList();
1307
	}
1308
1309
	public function publishall($request) {
1310
		if(!Permission::check('ADMIN')) return Security::permissionFailure($this);
1311
1312
		increase_time_limit_to();
1313
		increase_memory_limit_to();
1314
1315
		$response = "";
1316
1317
		if(isset($this->requestParams['confirm'])) {
1318
			// Protect against CSRF on destructive action
1319
			if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1320
1321
			$start = 0;
1322
			$pages = SiteTree::get()->limit("$start,30");
1323
			$count = 0;
1324
			while($pages) {
1325
				/** @var SiteTree $page */
1326
				foreach($pages as $page) {
1327
					if($page && !$page->canPublish()) {
1328
						return Security::permissionFailure($this);
1329
					}
1330
1331
					$page->publishRecursive();
1332
					$page->destroy();
1333
					unset($page);
1334
					$count++;
1335
					$response .= "<li>$count</li>";
1336
				}
1337
				if($pages->count() > 29) {
1338
					$start += 30;
1339
					$pages = SiteTree::get()->limit("$start,30");
1340
				} else {
1341
					break;
1342
				}
1343
			}
1344
			$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...
1345
1346
		} else {
1347
			$token = SecurityToken::inst();
1348
			$fields = new FieldList();
1349
			$token->updateFieldSet($fields);
1350
			$tokenField = $fields->first();
1351
			$tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : '';
1352
			$response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
1353
				<p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish".  It\'s
1354
				intended to be used after there have been massive edits of the content, such as when the site was
1355
				first built.') . '</p>
1356
				<form method="post" action="publishall">
1357
					<input type="submit" name="confirm" value="'
1358
					. _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",'Confirmation button') .'" />'
1359
					. $tokenHtml .
1360
				'</form>';
1361
		}
1362
1363
		return $response;
1364
	}
1365
1366
	/**
1367
	 * Restore a completely deleted page from the SiteTree_versions table.
1368
	 *
1369
	 * @param array $data
1370
	 * @param Form $form
1371
	 * @return HTTPResponse
1372
	 */
1373
	public function restore($data, $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

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

Loading history...
1374
		if(!isset($data['ID']) || !is_numeric($data['ID'])) {
1375
			return new HTTPResponse("Please pass an ID in the form content", 400);
1376
		}
1377
1378
		$id = (int)$data['ID'];
1379
		/** @var SiteTree $restoredPage */
1380
		$restoredPage = Versioned::get_latest_version("SilverStripe\\CMS\\Model\\SiteTree", $id);
1381
		if(!$restoredPage) {
1382
			return new HTTPResponse("SiteTree #$id not found", 400);
1383
		}
1384
1385
		$restoredPage = $restoredPage->doRestoreToStage();
1386
1387
		$this->getResponse()->addHeader(
1388
			'X-Status',
1389
			rawurlencode(_t(
1390
				'CMSMain.RESTORED',
1391
				"Restored '{title}' successfully",
1392
				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...
1393
			))
1394
		);
1395
1396
		return $this->getResponseNegotiator()->respond($this->getRequest());
1397
	}
1398
1399
	public function duplicate($request) {
1400
		// Protect against CSRF on destructive action
1401
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1402
1403
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1404
			/** @var SiteTree $page */
1405
			$page = SiteTree::get()->byID($id);
1406 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...
1407
				return Security::permissionFailure($this);
1408
			}
1409
			if(!$page || !$page->ID) throw new HTTPResponse_Exception("Bad record ID #$id", 404);
1410
1411
			$newPage = $page->duplicate();
1412
1413
			// ParentID can be hard-set in the URL.  This is useful for pages with multiple parents
1414
			if(isset($_GET['parentID']) && is_numeric($_GET['parentID'])) {
1415
				$newPage->ParentID = $_GET['parentID'];
1416
				$newPage->write();
1417
			}
1418
1419
			$this->getResponse()->addHeader(
1420
				'X-Status',
1421
				rawurlencode(_t(
1422
					'CMSMain.DUPLICATED',
1423
					"Duplicated '{title}' successfully",
1424
					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...
1425
				))
1426
			);
1427
			$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $newPage->ID);
1428
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1429
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1430
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1431
1432
			return $this->getResponseNegotiator()->respond($this->getRequest());
1433
		} else {
1434
			return new HTTPResponse("CMSMain::duplicate() Bad ID: '$id'", 400);
1435
		}
1436
	}
1437
1438
	public function duplicatewithchildren($request) {
1439
		// Protect against CSRF on destructive action
1440
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1441
		increase_time_limit_to();
1442
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1443
			/** @var SiteTree $page */
1444
			$page = SiteTree::get()->byID($id);
1445 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...
1446
				return Security::permissionFailure($this);
1447
			}
1448
			if(!$page || !$page->ID) throw new HTTPResponse_Exception("Bad record ID #$id", 404);
1449
1450
			$newPage = $page->duplicateWithChildren();
1451
1452
			$this->getResponse()->addHeader(
1453
				'X-Status',
1454
				rawurlencode(_t(
1455
					'CMSMain.DUPLICATEDWITHCHILDREN',
1456
					"Duplicated '{title}' and children successfully",
1457
					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...
1458
				))
1459
			);
1460
			$url = Controller::join_links(CMSPageEditController::singleton()->Link('show'), $newPage->ID);
1461
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1462
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1463
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1464
1465
			return $this->getResponseNegotiator()->respond($this->getRequest());
1466
		} else {
1467
			return new HTTPResponse("CMSMain::duplicatewithchildren() Bad ID: '$id'", 400);
1468
		}
1469
	}
1470
1471
	public function providePermissions() {
1472
		$title = CMSPagesController::menu_title();
1473
		return array(
1474
			"CMS_ACCESS_CMSMain" => array(
1475
				'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array('title' => $title)),
0 ignored issues
show
Documentation introduced by
array('title' => $title) is of type array<string,string,{"title":"string"}>, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1476
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
1477
				'help' => _t(
1478
					'CMSMain.ACCESS_HELP',
1479
					'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".'
1480
				),
1481
				'sort' => -99 // below "CMS_ACCESS_LeftAndMain", but above everything else
1482
			)
1483
		);
1484
	}
1485
1486
}
1487