Completed
Pull Request — master (#1399)
by Ingo
02:43
created

CMSMain::getNewItem()   C

Complexity

Conditions 8
Paths 14

Size

Total Lines 47
Code Lines 28

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 47
rs 5.7377
cc 8
eloc 28
nc 14
nop 2
1
<?php
2
/**
3
 * The main "content" area of the CMS.
4
 *
5
 * This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main
6
 * admin menu.
7
 *
8
 * @package cms
9
 * @subpackage controller
10
 * @todo Create some base classes to contain the generic functionality that will be replicated.
11
 */
12
class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionProvider {
13
14
	private static $url_segment = 'pages';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
15
16
	private static $url_rule = '/$Action/$ID/$OtherID';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
17
18
	// Maintain a lower priority than other administration sections
19
	// so that Director does not think they are actions of CMSMain
20
	private static $url_priority = 39;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
21
22
	private static $menu_title = 'Edit Page';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
23
24
	private static $menu_priority = 10;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
25
26
	private static $tree_class = "SiteTree";
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
27
28
	private static $subitem_class = "Member";
29
30
	/**
31
	 * Amount of results showing on a single page.
32
	 *
33
	 * @config
34
	 * @var int
35
	 */
36
	private static $page_length = 15;
37
38
	private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
39
		'archive',
40
		'buildbrokenlinks',
41
		'deleteitems',
42
		'DeleteItemsForm',
43
		'dialog',
44
		'duplicate',
45
		'duplicatewithchildren',
46
		'publishall',
47
		'publishitems',
48
		'PublishItemsForm',
49
		'submit',
50
		'EditForm',
51
		'SearchForm',
52
		'SiteTreeAsUL',
53
		'getshowdeletedsubtree',
54
		'batchactions',
55
		'treeview',
56
		'listview',
57
		'ListViewForm',
58
		'childfilter',
59
	);
60
61
	/**
62
	 * Enable legacy batch actions.
63
	 * @deprecated since version 4.0
64
	 * @var array
65
	 * @config
66
	 */
67
	private static $enabled_legacy_actions = array();
68
69
	public function init() {
70
		// set reading lang
71
		if(SiteTree::has_extension('Translatable') && !$this->getRequest()->isAjax()) {
72
			Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SiteTree')));
73
		}
74
75
		parent::init();
76
77
		Requirements::css(CMS_DIR . '/css/screen.css');
78
		Requirements::customCSS($this->generatePageIconsCss());
0 ignored issues
show
Documentation Bug introduced by
The method generatePageIconsCss does not exist on object<CMSMain>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
79
		Requirements::add_i18n_javascript(CMS_DIR . '/javascript/lang', false, true);
80
		Requirements::javascript(CMS_DIR . '/javascript/dist/bundle-lib.js', [
81
			'provides' => [
82
				CMS_DIR . '/javascript/dist/CMSMain.AddForm.js',
83
				CMS_DIR . '/javascript/dist/CMSMain.EditForm.js',
84
				CMS_DIR . '/javascript/dist/CMSMain.js',
85
				CMS_DIR . '/javascript/dist/CMSMain.Tree.js',
86
				CMS_DIR . '/javascript/dist/CMSPageHistoryController.js',
87
				CMS_DIR . '/javascript/dist/RedirectorPage.js',
88
				CMS_DIR . '/javascript/dist/SilverStripeNavigator.js',
89
				CMS_DIR . '/javascript/dist/SiteTreeURLSegmentField.js'
90
			]
91
		]);
92
93
		CMSBatchActionHandler::register('publish', 'CMSBatchAction_Publish');
94
		CMSBatchActionHandler::register('unpublish', 'CMSBatchAction_Unpublish');
95
96
97
		// Check legacy actions
98
		$legacy = $this->config()->enabled_legacy_actions;
99
100
		// Delete from live is unnecessary since we have unpublish which does the same thing
101
		if(in_array('CMSBatchAction_DeleteFromLive', $legacy)) {
102
			Deprecation::notice('4.0', 'Delete From Live is deprecated. Use Un-publish instead');
103
			CMSBatchActionHandler::register('deletefromlive', 'CMSBatchAction_DeleteFromLive');
104
		}
105
106
		// Delete action
107
		if(in_array('CMSBatchAction_Delete', $legacy)) {
108
			Deprecation::notice('4.0', 'Delete from Stage is deprecated. Use Archive instead.');
109
			CMSBatchActionHandler::register('delete', 'CMSBatchAction_Delete');
110
		} else {
111
			CMSBatchActionHandler::register('archive', 'CMSBatchAction_Archive');
112
			CMSBatchActionHandler::register('restore', 'CMSBatchAction_Restore');
113
		}
114
	}
115
116
	public function index($request) {
117
		// In case we're not showing a specific record, explicitly remove any session state,
118
		// to avoid it being highlighted in the tree, and causing an edit form to show.
119
		if(!$request->param('Action')) $this->setCurrentPageId(null);
120
121
		return parent::index($request);
122
	}
123
124
	public function getResponseNegotiator() {
125
		$negotiator = parent::getResponseNegotiator();
126
		$controller = $this;
127
		$negotiator->setCallback('ListViewForm', function() use(&$controller) {
128
			return $controller->ListViewForm()->forTemplate();
129
		});
130
		return $negotiator;
131
	}
132
133
	/**
134
	 * If this is set to true, the "switchView" context in the
135
	 * template is shown, with links to the staging and publish site.
136
	 *
137
	 * @return boolean
138
	 */
139
	public function ShowSwitchView() {
140
		return true;
141
	}
142
143
	/**
144
	 * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
145
	 * to switch view also for archived versions.
146
	 */
147
	public function SwitchView($page = null) {
148
		if(!$page) {
149
			$page = $this->currentPage();
150
		}
151
152
		if($page) {
153
			$nav = SilverStripeNavigator::get_for_record($page);
154
			return $nav['items'];
155
		}
156
	}
157
158
	//------------------------------------------------------------------------------------------//
159
	// Main controllers
160
161
	//------------------------------------------------------------------------------------------//
162
	// Main UI components
163
164
	/**
165
	 * Override {@link LeftAndMain} Link to allow blank URL segment for CMSMain.
166
	 *
167
	 * @param string|null $action Action to link to.
168
	 * @return string
169
	 */
170
	public function Link($action = null) {
171
		$link = Controller::join_links(
172
			$this->stat('url_base', true),
173
			$this->stat('url_segment', true), // in case we want to change the segment
174
			'/', // trailing slash needed if $action is null!
175
			"$action"
176
		);
177
		$this->extend('updateLink', $link);
178
		return $link;
179
	}
180
181
	public function LinkPages() {
182
		return singleton('CMSPagesController')->Link();
183
	}
184
185
	public function LinkPagesWithSearch() {
186
		return $this->LinkWithSearch($this->LinkPages());
187
	}
188
189
	public function LinkTreeView() {
190
		return $this->LinkWithSearch(singleton('CMSMain')->Link('treeview'));
191
	}
192
193
	public function LinkListView() {
194
		return $this->LinkWithSearch(singleton('CMSMain')->Link('listview'));
195
	}
196
197
	public function LinkGalleryView() {
198
		return $this->LinkWithSearch(singleton('CMSMain')->Link('galleryview'));
199
	}
200
201
	public function LinkPageEdit($id = null) {
202
		if(!$id) $id = $this->currentPageID();
203
		return $this->LinkWithSearch(
204
			Controller::join_links(singleton('CMSPageEditController')->Link('show'), $id)
205
		);
206
	}
207
208 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...
209
		if($id = $this->currentPageID()) {
210
			return $this->LinkWithSearch(
211
				Controller::join_links(singleton('CMSPageSettingsController')->Link('show'), $id)
212
			);
213
		}
214
	}
215
216 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...
217
		if($id = $this->currentPageID()) {
218
			return $this->LinkWithSearch(
219
				Controller::join_links(singleton('CMSPageHistoryController')->Link('show'), $id)
220
			);
221
		}
222
	}
223
224
	public function LinkWithSearch($link) {
225
		// Whitelist to avoid side effects
226
		$params = array(
227
			'q' => (array)$this->getRequest()->getVar('q'),
228
			'ParentID' => $this->getRequest()->getVar('ParentID')
229
		);
230
		$link = Controller::join_links(
231
			$link,
232
			array_filter(array_values($params)) ? '?' . http_build_query($params) : null
233
		);
234
		$this->extend('updateLinkWithSearch', $link);
235
		return $link;
236
	}
237
238
	public function LinkPageAdd($extra = null, $placeholders = null) {
239
		$link = singleton("CMSPageAddController")->Link();
240
		$this->extend('updateLinkPageAdd', $link);
241
242
		if($extra) {
243
			$link = Controller::join_links ($link, $extra);
244
		}
245
246
		if($placeholders) {
247
			$link .= (strpos($link, '?') === false ? "?$placeholders" : "&amp;$placeholders");
248
		}
249
250
		return $link;
251
	}
252
253
	/**
254
	 * @return string
255
	 */
256
	public function LinkPreview() {
257
		$record = $this->getRecord($this->currentPageID());
258
		$baseLink = Director::absoluteBaseURL();
259
		if ($record && $record instanceof Page) {
0 ignored issues
show
Bug introduced by
The class Page does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

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

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

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

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

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

Loading history...
424
		$classes = SiteTree::page_type_classes();
425
426
	 	$cacheCanCreate = array();
427
	 	foreach($classes as $class) $cacheCanCreate[$class] = singleton($class)->canCreate();
428
429
	 	// Generate basic cache key. Too complex to encompass all variations
430
	 	$cache = SS_Cache::factory('CMSMain_SiteTreeHints');
431
	 	$cacheKey = md5(implode('_', array(Member::currentUserID(), implode(',', $cacheCanCreate), implode(',', $classes))));
432
	 	if($this->getRequest()->getVar('flush')) $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
433
	 	$json = $cache->load($cacheKey);
434
	 	if(!$json) {
435
			$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...
436
			$def['Root']['disallowedChildren'] = array();
437
438
			// Contains all possible classes to support UI controls listing them all,
439
			// such as the "add page here" context menu.
440
			$def['All'] = array();
441
442
			// Identify disallows and set globals
443
			foreach($classes as $class) {
444
				$obj = singleton($class);
445
				if($obj instanceof HiddenClass) continue;
446
447
				// Name item
448
				$def['All'][$class] = array(
449
					'title' => $obj->i18n_singular_name()
450
				);
451
452
				// Check if can be created at the root
453
				$needsPerm = $obj->stat('need_permission');
454
				if(
455
					!$obj->stat('can_be_root')
456
					|| (!array_key_exists($class, $cacheCanCreate) || !$cacheCanCreate[$class])
457
					|| ($needsPerm && !$this->can($needsPerm))
458
				) {
459
					$def['Root']['disallowedChildren'][] = $class;
460
				}
461
462
				// Hint data specific to the class
463
				$def[$class] = array();
464
465
				$defaultChild = $obj->defaultChild();
466
				if($defaultChild !== 'Page' && $defaultChild !== null) {
467
					$def[$class]['defaultChild'] = $defaultChild;
468
				}
469
470
				$defaultParent = $obj->defaultParent();
471
				if ($defaultParent !== 1 && $defaultParent !== null) {
472
					$def[$class]['defaultParent'] = $defaultParent;
473
				}
474
			}
475
476
			$this->extend('updateSiteTreeHints', $def);
477
478
			$json = Convert::raw2json($def);
479
			$cache->save($json, $cacheKey);
480
		}
481
		return $json;
482
	}
483
484
	/**
485
	 * Populates an array of classes in the CMS
486
	 * which allows the user to change the page type.
487
	 *
488
	 * @return SS_List
489
	 */
490
	public function PageTypes() {
491
		$classes = SiteTree::page_type_classes();
492
493
		$result = new ArrayList();
494
495
		foreach($classes as $class) {
496
			$instance = singleton($class);
497
498
			if($instance instanceof HiddenClass) continue;
499
500
			// skip this type if it is restricted
501
			if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue;
502
503
			$addAction = $instance->i18n_singular_name();
504
505
			// Get description (convert 'Page' to 'SiteTree' for correct localization lookups)
506
			$description = _t((($class == 'Page') ? 'SiteTree' : $class) . '.DESCRIPTION');
507
508
			if(!$description) {
509
				$description = $instance->uninherited('description');
510
			}
511
512
			if($class == 'Page' && !$description) {
513
				$description = singleton('SiteTree')->uninherited('description');
514
			}
515
516
			$result->push(new ArrayData(array(
517
				'ClassName' => $class,
518
				'AddAction' => $addAction,
519
				'Description' => $description,
520
				// TODO Sprite support
521
				'IconURL' => $instance->stat('icon'),
522
				'Title' => singleton($class)->i18n_singular_name(),
523
			)));
524
		}
525
526
		$result = $result->sort('AddAction');
527
528
		return $result;
529
	}
530
531
	/**
532
	 * Get a database record to be managed by the CMS.
533
	 *
534
	 * @param int $id Record ID
535
	 * @param int $versionID optional Version id of the given record
536
	 * @return SiteTree
537
	 */
538
 	public function getRecord($id, $versionID = null) {
539
		$treeClass = $this->stat('tree_class');
540
541
		if($id instanceof $treeClass) {
542
			return $id;
543
		}
544
		else if($id && is_numeric($id)) {
545
			if($this->getRequest()->getVar('Version')) {
546
				$versionID = (int) $this->getRequest()->getVar('Version');
547
			}
548
549
			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...
550
				$record = Versioned::get_version($treeClass, $id, $versionID);
551
			} else {
552
				$record = DataObject::get_by_id($treeClass, $id);
553
			}
554
555
			// Then, try getting a record from the live site
556
			if(!$record) {
557
				// $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id");
558
				Versioned::reading_stage('Live');
559
				singleton($treeClass)->flushCache();
560
561
				$record = DataObject::get_by_id($treeClass, $id);
562
				if($record) Versioned::set_reading_mode('');
563
			}
564
565
			// Then, try getting a deleted record
566
			if(!$record) {
567
				$record = Versioned::get_latest_version($treeClass, $id);
568
			}
569
570
			// Don't open a page from a different locale
571
			/** The record's Locale is saved in database in 2.4, and not related with Session,
572
			 *  we should not check their locale matches the Translatable::get_current_locale,
573
			 * 	here as long as we all the HTTPRequest is init with right locale.
574
			 *	This bit breaks the all FileIFrameField functions if the field is used in CMS
575
			 *  and its relevent ajax calles, like loading the tree dropdown for TreeSelectorField.
576
			 */
577
			/* if($record && SiteTree::has_extension('Translatable') && $record->Locale && $record->Locale != Translatable::get_current_locale()) {
578
				$record = null;
579
			}*/
580
581
			return $record;
582
583
		} else if(substr($id,0,3) == 'new') {
584
			return $this->getNewItem($id);
585
		}
586
	}
587
588
	/**
589
	 * @param int $id
590
	 * @param FieldList $fields
591
	 * @return CMSForm
592
	 */
593
	public function getEditForm($id = null, $fields = null) {
594
		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...
595
		$form = parent::getEditForm($id);
596
597
		// TODO Duplicate record fetching (see parent implementation)
598
		$record = $this->getRecord($id);
599
		if($record && !$record->canView()) return Security::permissionFailure($this);
0 ignored issues
show
Bug Compatibility introduced by
The expression \Security::permissionFailure($this); of type SS_HTTPResponse|null adds the type SS_HTTPResponse to the return on line 599 which is incompatible with the return type documented by CMSMain::getEditForm of type CMSForm.
Loading history...
600
601
		if(!$fields) $fields = $form->Fields();
0 ignored issues
show
Bug introduced by
The method Fields does only exist in CMSForm, but not in SS_HTTPResponse.

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...
602
		$actions = $form->Actions();
0 ignored issues
show
Bug introduced by
The method Actions does only exist in CMSForm, but not in SS_HTTPResponse.

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...
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...
603
604
		if($record) {
605
			$deletedFromStage = $record->getIsDeletedFromStage();
606
607
			$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...
608
			// Necessary for different subsites
609
			$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...
610
			$fields->push($liveLinkField = new HiddenField("LiveLink"));
611
			$fields->push($stageLinkField = new HiddenField("StageLink"));
612
			$fields->push(new HiddenField("TreeTitle", false, $record->TreeTitle));
0 ignored issues
show
Documentation introduced by
The property TreeTitle does not exist on object<SiteTree>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
false is of type boolean, but the function expects a null|string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
613
614
			if($record->ID && is_numeric( $record->ID ) ) {
615
				$liveLink = $record->getAbsoluteLiveLink();
616
				if($liveLink) $liveLinkField->setValue($liveLink);
617
				if(!$deletedFromStage) {
618
					$stageLink = Controller::join_links($record->AbsoluteLink(), '?stage=Stage');
619
					if($stageLink) $stageLinkField->setValue($stageLink);
620
				}
621
			}
622
623
			// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
624
			if(in_array('CMSPreviewable', class_implements($record)) && !$fields->fieldByName('SilverStripeNavigator')) {
625
				$navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator());
626
				$navField->setAllowHTML(true);
627
				$fields->push($navField);
628
			}
629
630
			// getAllCMSActions can be used to completely redefine the action list
631
			if($record->hasMethod('getAllCMSActions')) {
632
				$actions = $record->getAllCMSActions();
0 ignored issues
show
Documentation Bug introduced by
The method getAllCMSActions does not exist on object<SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
633
			} else {
634
				$actions = $record->getCMSActions();
635
636
				// Find and remove action menus that have no actions.
637
				if ($actions && $actions->Count()) {
638
					$tabset = $actions->fieldByName('ActionMenus');
639
					if ($tabset) {
640
						foreach ($tabset->getChildren() as $tab) {
641
							if (!$tab->getChildren()->count()) {
642
								$tabset->removeByName($tab->getName());
643
							}
644
						}
645
					}
646
				}
647
			}
648
649
			// Use <button> to allow full jQuery UI styling
650
			$actionsFlattened = $actions->dataFields();
651
			if($actionsFlattened) foreach($actionsFlattened as $action) $action->setUseButtonTag(true);
652
653
			if($record->hasMethod('getCMSValidator')) {
654
				$validator = $record->getCMSValidator();
0 ignored issues
show
Documentation Bug introduced by
The method getCMSValidator does not exist on object<SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
655
			} else {
656
				$validator = new RequiredFields();
657
			}
658
659
			$form = CMSForm::create(
660
				$this, "EditForm", $fields, $actions, $validator
661
			)->setHTMLID('Form_EditForm');
662
			$form->setResponseNegotiator($this->getResponseNegotiator());
0 ignored issues
show
Documentation introduced by
$this->getResponseNegotiator() is of type object<PjaxResponseNegotiator>, but the function expects a object<ResponseNegotiator>.

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...
663
			$form->loadDataFrom($record);
664
			$form->disableDefaultAction();
665
			$form->addExtraClass('cms-edit-form');
666
			$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
0 ignored issues
show
Documentation introduced by
$this->getTemplatesWithSuffix('_EditForm') is of type array, 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...
667
			// TODO Can't merge $FormAttributes in template at the moment
668
			$form->addExtraClass('center ' . $this->BaseCSSClasses());
669
			// if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
670
			$form->setAttribute('data-pjax-fragment', 'CurrentForm');
671
			// Set validation exemptions for specific actions
672
			$form->setValidationExemptActions(array('restore', 'revert', 'deletefromlive', 'delete', 'unpublish', 'rollback'));
673
674
			// Announce the capability so the frontend can decide whether to allow preview or not.
675
			if(in_array('CMSPreviewable', class_implements($record))) {
676
				$form->addExtraClass('cms-previewable');
677
			}
678
679
			if(!$record->canEdit() || $deletedFromStage) {
680
				$readonlyFields = $form->Fields()->makeReadonly();
681
				$form->setFields($readonlyFields);
682
			}
683
684
			$this->extend('updateEditForm', $form);
685
			return $form;
686
		} else if($id) {
687
			$form = CMSForm::create( $this, "EditForm", new FieldList(
688
				new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList()
689
			)->setHTMLID('Form_EditForm');
690
			$form->setResponseNegotiator($this->getResponseNegotiator());
0 ignored issues
show
Documentation introduced by
$this->getResponseNegotiator() is of type object<PjaxResponseNegotiator>, but the function expects a object<ResponseNegotiator>.

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...
691
			return $form;
692
		}
693
	}
694
695
	/**
696
	 * @param SS_HTTPRequest $request
697
	 * @return string HTML
698
	 */
699
	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...
700
		return $this->renderWith($this->getTemplatesWithSuffix('_TreeView'));
701
	}
702
703
	/**
704
	 * @param SS_HTTPRequest $request
705
	 * @return string HTML
706
	 */
707
	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...
708
		return $this->renderWith($this->getTemplatesWithSuffix('_ListView'));
709
	}
710
711
	/**
712
	 * Callback to request the list of page types allowed under a given page instance.
713
	 * Provides a slower but more precise response over SiteTreeHints
714
	 *
715
	 * @param SS_HTTPRequest $request
716
	 * @return SS_HTTPResponse
717
	 */
718
	public function childfilter($request) {
719
		// Check valid parent specified
720
		$parentID = $request->requestVar('ParentID');
721
		$parent = SiteTree::get()->byID($parentID);
722
		if(!$parent || !$parent->exists()) return $this->httpError(404);
723
724
		// Build hints specific to this class
725
		// Identify disallows and set globals
726
		$classes = SiteTree::page_type_classes();
727
		$disallowedChildren = array();
728
		foreach($classes as $class) {
729
			$obj = singleton($class);
730
			if($obj instanceof HiddenClass) continue;
731
732
			if(!$obj->canCreate(null, array('Parent' => $parent))) {
733
				$disallowedChildren[] = $class;
734
			}
735
		}
736
737
		$this->extend('updateChildFilter', $disallowedChildren, $parentID);
738
		return $this
739
			->getResponse()
740
			->addHeader('Content-Type', 'application/json; charset=utf-8')
741
			->setBody(Convert::raw2json($disallowedChildren));
742
	}
743
744
	/**
745
	 * Safely reconstruct a selected filter from a given set of query parameters
746
	 *
747
	 * @param array $params Query parameters to use
748
	 * @return CMSSiteTreeFilter The filter class, or null if none present
749
	 * @throws InvalidArgumentException if invalid filter class is passed.
750
	 */
751
	protected function getQueryFilter($params) {
752
		if(empty($params['FilterClass'])) return null;
753
		$filterClass = $params['FilterClass'];
754
		if(!is_subclass_of($filterClass, 'CMSSiteTreeFilter')) {
755
			throw new InvalidArgumentException("Invalid filter class passed: {$filterClass}");
756
		}
757
		return $filterClass::create($params);
758
	}
759
760
	/**
761
	 * Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page
762
	 * defaulting to no filter and show all pages in first level.
763
	 * Doubles as search results, if any search parameters are set through {@link SearchForm()}.
764
	 *
765
	 * @param array $params Search filter criteria
766
	 * @param int $parentID Optional parent node to filter on (can't be combined with other search criteria)
767
	 * @return SS_List
768
	 * @throws InvalidArgumentException if invalid filter class is passed.
769
	 */
770
	public function getList($params = array(), $parentID = 0) {
771
		if($filter = $this->getQueryFilter($params)) {
772
			return $filter->getFilteredPages();
773
		} else {
774
			$list = DataList::create($this->stat('tree_class'));
775
			$parentID = is_numeric($parentID) ? $parentID : 0;
776
			return $list->filter("ParentID", $parentID);
777
		}
778
	}
779
780
	public function ListViewForm() {
781
		$params = $this->getRequest()->requestVar('q');
782
		$list = $this->getList($params, $parentID = $this->getRequest()->requestVar('ParentID'));
783
		$gridFieldConfig = GridFieldConfig::create()->addComponents(
784
			new GridFieldSortableHeader(),
785
			new GridFieldDataColumns(),
786
			new GridFieldPaginator(self::config()->page_length)
787
		);
788
		if($parentID){
789
			$gridFieldConfig->addComponent(
790
				GridFieldLevelup::create($parentID)
791
					->setLinkSpec('?ParentID=%d&view=list')
792
					->setAttributes(array('data-pjax' => 'ListViewForm,Breadcrumbs'))
793
			);
794
		}
795
		$gridField = new GridField('Page','Pages', $list, $gridFieldConfig);
796
		$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
797
798
		// Don't allow navigating into children nodes on filtered lists
799
		$fields = array(
800
			'getTreeTitle' => _t('SiteTree.PAGETITLE', 'Page Title'),
801
			'singular_name' => _t('SiteTree.PAGETYPE'),
802
			'LastEdited' => _t('SiteTree.LASTUPDATED', 'Last Updated'),
803
		);
804
		$gridField->getConfig()->getComponentByType('GridFieldSortableHeader')->setFieldSorting(array('getTreeTitle' => 'Title'));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface GridFieldComponent as the method setFieldSorting() does only exist in the following implementations of said interface: GridFieldSortableHeader.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
805
		$gridField->getState()->ParentID = $parentID;
806
807
		if(!$params) {
808
			$fields = array_merge(array('listChildrenLink' => ''), $fields);
809
		}
810
811
		$columns->setDisplayFields($fields);
812
		$columns->setFieldCasting(array(
813
			'Created' => 'SS_Datetime->Ago',
814
			'LastEdited' => 'SS_Datetime->FormatFromSettings',
815
			'getTreeTitle' => 'HTMLText'
816
		));
817
818
		$controller = $this;
819
		$columns->setFieldFormatting(array(
820
			'listChildrenLink' => function($value, &$item) use($controller) {
821
				$num = $item ? $item->numChildren() : null;
822
				if($num) {
823
					return sprintf(
824
						'<a class="cms-panel-link list-children-link" data-pjax-target="ListViewForm,Breadcrumbs" href="%s">%s</a>',
825
						Controller::join_links(
826
							$controller->Link(),
827
							sprintf("?ParentID=%d&view=list", (int)$item->ID)
828
						),
829
						$num
830
					);
831
				}
832
			},
833
			'getTreeTitle' => function($value, &$item) use($controller) {
834
				return sprintf(
835
					'<a class="action-detail" href="%s">%s</a>',
836
					Controller::join_links(
837
						singleton('CMSPageEditController')->Link('show'),
838
						(int)$item->ID
839
					),
840
					$item->TreeTitle // returns HTML, does its own escaping
841
				);
842
			}
843
		));
844
845
		$listview = CMSForm::create(
846
			$this,
847
			'ListViewForm',
848
			new FieldList($gridField),
849
			new FieldList()
850
		)->setHTMLID('Form_ListViewForm');
851
		$listview->setAttribute('data-pjax-fragment', 'ListViewForm');
852
		$listview->setResponseNegotiator($this->getResponseNegotiator());
0 ignored issues
show
Documentation introduced by
$this->getResponseNegotiator() is of type object<PjaxResponseNegotiator>, but the function expects a object<ResponseNegotiator>.

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...
853
854
		$this->extend('updateListView', $listview);
855
856
		$listview->disableSecurityToken();
857
		return $listview;
858
	}
859
860
	public function currentPageID() {
861
		$id = parent::currentPageID();
862
863
		$this->extend('updateCurrentPageID', $id);
864
865
		return $id;
866
	}
867
868
	//------------------------------------------------------------------------------------------//
869
	// Data saving handlers
870
871
	/**
872
	 * Save and Publish page handler
873
	 */
874
	public function save($data, $form) {
875
		$className = $this->stat('tree_class');
876
877
		// Existing or new record?
878
		$id = $data['ID'];
879
		if(substr($id,0,3) != 'new') {
880
			$record = DataObject::get_by_id($className, $id);
881
			if($record && !$record->canEdit()) return Security::permissionFailure($this);
882
			if(!$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
883
		} else {
884
			if(!singleton($this->stat('tree_class'))->canCreate()) return Security::permissionFailure($this);
885
			$record = $this->getNewItem($id, false);
886
		}
887
888
		// TODO Coupling to SiteTree
889
		$record->HasBrokenLink = 0;
890
		$record->HasBrokenFile = 0;
891
892
		if (!$record->ObsoleteClassName) $record->writeWithoutVersion();
893
894
		// Update the class instance if necessary
895
		if(isset($data['ClassName']) && $data['ClassName'] != $record->ClassName) {
896
			$newClassName = $record->ClassName;
897
			// The records originally saved attribute was overwritten by $form->saveInto($record) before.
898
			// This is necessary for newClassInstance() to work as expected, and trigger change detection
899
			// on the ClassName attribute
900
			$record->setClassName($data['ClassName']);
901
			// Replace $record with a new instance
902
			$record = $record->newClassInstance($newClassName);
903
		}
904
905
		// save form data into record
906
		$form->saveInto($record);
907
		$record->write();
908
909
		// If the 'Save & Publish' button was clicked, also publish the page
910
		if (isset($data['publish']) && $data['publish'] == 1) {
911
			$record->doPublish();
912
		}
913
914
		return $this->getResponseNegotiator()->respond($this->getRequest());
915
	}
916
917
	/**
918
	 * @uses LeftAndMainExtension->augmentNewSiteTreeItem()
919
	 */
920
	public function getNewItem($id, $setID = true) {
921
		$parentClass = $this->stat('tree_class');
922
		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...
923
924
		if(!is_subclass_of($className, $parentClass) && strcasecmp($className, $parentClass) != 0) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $parentClass can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
925
			$response = Security::permissionFailure($this);
926
			if (!$response) {
927
				$response = $this->getResponse();
928
			}
929
			throw new SS_HTTPResponse_Exception($response);
930
		}
931
932
		$newItem = new $className();
933
934
		if( !$suffix ) {
935
			$sessionTag = "NewItems." . $parentID . "." . $className;
936
			if(Session::get($sessionTag)) {
937
				$suffix = '-' . Session::get($sessionTag);
938
				Session::set($sessionTag, Session::get($sessionTag) + 1);
939
			}
940
			else
941
				Session::set($sessionTag, 1);
942
943
				$id = $id . $suffix;
944
		}
945
946
		$newItem->Title = _t(
947
			'CMSMain.NEWPAGE',
948
			"New {pagetype}",'followed by a page type title',
949
			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...
950
		);
951
		$newItem->ClassName = $className;
952
		$newItem->ParentID = $parentID;
953
954
		// DataObject::fieldExists only checks the current class, not the hierarchy
955
		// This allows the CMS to set the correct sort value
956
		if($newItem->castingHelper('Sort')) {
957
			$newItem->Sort = DB::prepared_query('SELECT MAX("Sort") FROM "SiteTree" WHERE "ParentID" = ?', array($parentID))->value() + 1;
958
		}
959
960
		if($setID) $newItem->ID = $id;
961
962
		# Some modules like subsites add extra fields that need to be set when the new item is created
963
		$this->extend('augmentNewSiteTreeItem', $newItem);
964
965
		return $newItem;
966
	}
967
968
	/**
969
	 * Delete the page from live. This means a page in draft mode might still exist.
970
	 *
971
	 * @see delete()
972
	 */
973
	public function deletefromlive($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...
974
		Versioned::reading_stage('Live');
975
976
		/** @var SiteTree $record */
977
		$record = DataObject::get_by_id("SiteTree", $data['ID']);
978
		if($record && !($record->canDelete() && $record->canUnpublish())) {
0 ignored issues
show
Documentation Bug introduced by
The method canUnpublish does not exist on object<SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
979
			return Security::permissionFailure($this);
980
		}
981
982
		$descendantsRemoved = 0;
983
		$recordTitle = $record->Title;
984
985
		// before deleting the records, get the descendants of this tree
986
		if($record) {
987
			$descendantIDs = $record->getDescendantIDList();
0 ignored issues
show
Documentation Bug introduced by
The method getDescendantIDList does not exist on object<SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
988
989
			// then delete them from the live site too
990
			$descendantsRemoved = 0;
991
			foreach( $descendantIDs as $descID )
992
				/** @var SiteTree $descendant */
993
				if( $descendant = DataObject::get_by_id('SiteTree', $descID) ) {
994
					$descendant->doUnpublish();
995
					$descendantsRemoved++;
996
				}
997
998
			// delete the record
999
			$record->doUnpublish();
1000
		}
1001
1002
		Versioned::reading_stage('Stage');
1003
1004
		if(isset($descendantsRemoved)) {
1005
			$descRemoved = ' ' . _t(
1006
				'CMSMain.DESCREMOVED',
1007
				'and {count} descendants',
1008
				array('count' => $descendantsRemoved)
0 ignored issues
show
Documentation introduced by
array('count' => $descendantsRemoved) 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...
1009
			);
1010
		} else {
1011
			$descRemoved = '';
1012
		}
1013
1014
		$this->getResponse()->addHeader(
1015
			'X-Status',
1016
			rawurlencode(
1017
				_t(
1018
					'CMSMain.REMOVED',
1019
					'Deleted \'{title}\'{description} from live site',
1020
					array('title' => $recordTitle, 'description' => $descRemoved)
0 ignored issues
show
Documentation introduced by
array('title' => $record...ption' => $descRemoved) is of type array<string,string,{"ti...description":"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...
1021
				)
1022
			)
1023
		);
1024
1025
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1026
		return $this->getResponseNegotiator()->respond($this->getRequest());
1027
	}
1028
1029
	/**
1030
	 * Actually perform the publication step
1031
	 */
1032
	public function performPublish($record) {
1033
		if($record && !$record->canPublish()) return Security::permissionFailure($this);
1034
1035
		$record->doPublish();
1036
	}
1037
1038
	/**
1039
 	 * Reverts a page by publishing it to live.
1040
 	 * Use {@link restorepage()} if you want to restore a page
1041
 	 * which was deleted from draft without publishing.
1042
 	 *
1043
 	 * @uses SiteTree->doRevertToLive()
1044
	 */
1045
	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...
1046
		if(!isset($data['ID'])) return new SS_HTTPResponse("Please pass an ID in the form content", 400);
1047
1048
		$id = (int) $data['ID'];
1049
		$restoredPage = Versioned::get_latest_version("SiteTree", $id);
1050
		if(!$restoredPage) 	return new SS_HTTPResponse("SiteTree #$id not found", 400);
1051
1052
		$record = Versioned::get_one_by_stage('SiteTree', 'Live', array(
0 ignored issues
show
Documentation introduced by
array('"SiteTree_Live"."ID"' => $id) is of type array<string,integer,{"\...e\".\"ID\"":"integer"}>, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1053
			'"SiteTree_Live"."ID"' => $id
1054
		));
1055
1056
		// a user can restore a page without publication rights, as it just adds a new draft state
1057
		// (this action should just be available when page has been "deleted from draft")
1058
		if($record && !$record->canEdit()) return Security::permissionFailure($this);
1059
		if(!$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1060
1061
		$record->doRevertToLive();
1062
1063
		$this->getResponse()->addHeader(
1064
			'X-Status',
1065
			rawurlencode(_t(
1066
				'CMSMain.RESTORED',
1067
				"Restored '{title}' successfully",
1068
				'Param %s is a title',
1069
				array('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...
1070
			))
1071
		);
1072
1073
		return $this->getResponseNegotiator()->respond($this->getRequest());
1074
	}
1075
1076
	/**
1077
	 * Delete the current page from draft stage.
1078
	 * @see deletefromlive()
1079
	 */
1080
	public function delete($data, $form) {
1081
		Deprecation::notice('4.0', 'Delete from stage is deprecated. Use archive instead');
1082
		$id = $data['ID'];
1083
		$record = DataObject::get_by_id("SiteTree", $id);
1084
		if($record && !$record->canDelete()) return Security::permissionFailure();
1085
		if(!$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1086
1087
		// Delete record
1088
		$record->delete();
1089
1090
		$this->getResponse()->addHeader(
1091
			'X-Status',
1092
			rawurlencode(sprintf(_t('CMSMain.REMOVEDPAGEFROMDRAFT',"Removed '%s' from the draft site"), $record->Title))
1093
		);
1094
1095
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1096
		return $this->getResponseNegotiator()->respond($this->getRequest());
1097
	}
1098
1099
	/**
1100
	 * Delete this page from both live and stage
1101
	 *
1102
	 * @param array $data
1103
	 * @param Form $form
1104
	 */
1105
	public function archive($data, $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

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

Loading history...
1106
		$id = $data['ID'];
1107
		$record = DataObject::get_by_id("SiteTree", $id);
1108
		if(!$record || !$record->exists()) {
1109
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1110
		}
1111
		if(!$record->canArchive()) {
1112
			return Security::permissionFailure();
1113
		}
1114
1115
		// Archive record
1116
		$record->doArchive();
1117
1118
		$this->getResponse()->addHeader(
1119
			'X-Status',
1120
			rawurlencode(sprintf(_t('CMSMain.ARCHIVEDPAGE',"Archived page '%s'"), $record->Title))
1121
		);
1122
1123
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1124
		return $this->getResponseNegotiator()->respond($this->getRequest());
1125
	}
1126
1127
	public function publish($data, $form) {
1128
		$data['publish'] = '1';
1129
1130
		return $this->save($data, $form);
1131
	}
1132
1133
	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...
1134
		$className = $this->stat('tree_class');
1135
		/** @var SiteTree $record */
1136
		$record = DataObject::get_by_id($className, $data['ID']);
1137
1138
		if($record && !$record->canUnpublish()) {
0 ignored issues
show
Documentation Bug introduced by
The method canUnpublish does not exist on object<SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1139
			return Security::permissionFailure($this);
1140
		}
1141 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...
1142
			throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
1143
		}
1144
1145
		$record->doUnpublish();
1146
1147
		$this->getResponse()->addHeader(
1148
			'X-Status',
1149
			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...
1150
		);
1151
1152
		return $this->getResponseNegotiator()->respond($this->getRequest());
1153
	}
1154
1155
	/**
1156
	 * @return array
1157
	 */
1158
	public function rollback() {
1159
		return $this->doRollback(array(
1160
			'ID' => $this->currentPageID(),
1161
			'Version' => $this->getRequest()->param('VersionID')
1162
		), null);
1163
	}
1164
1165
	/**
1166
	 * Rolls a site back to a given version ID
1167
	 *
1168
	 * @param array
1169
	 * @param Form
1170
	 *
1171
	 * @return html
1172
	 */
1173
	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...
1174
		$this->extend('onBeforeRollback', $data['ID']);
1175
1176
		$id = (isset($data['ID'])) ? (int) $data['ID'] : null;
1177
		$version = (isset($data['Version'])) ? (int) $data['Version'] : null;
1178
1179
		$record = DataObject::get_by_id($this->stat('tree_class'), $id);
1180
		if($record && !$record->canEdit()) return Security::permissionFailure($this);
1181
1182
		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...
1183
			$record->doRollbackTo($version);
1184
			$message = _t(
1185
				'CMSMain.ROLLEDBACKVERSIONv2',
1186
				"Rolled back to version #%d.",
1187
				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...
1188
			);
1189
		} else {
1190
			$record->doRollbackTo('Live');
1191
			$message = _t(
1192
				'CMSMain.ROLLEDBACKPUBv2',"Rolled back to published version."
1193
			);
1194
		}
1195
1196
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1197
1198
		// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
1199
		// Or in history view, in which case a revert causes the CMS to re-load the edit view.
1200
		// The X-Pjax header forces a "full" content refresh on redirect.
1201
		$url = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $record->ID);
1202
		$this->getResponse()->addHeader('X-ControllerURL', $url);
1203
		$this->getRequest()->addHeader('X-Pjax', 'Content');
1204
		$this->getResponse()->addHeader('X-Pjax', 'Content');
1205
1206
		return $this->getResponseNegotiator()->respond($this->getRequest());
1207
	}
1208
1209
	/**
1210
	 * Batch Actions Handler
1211
	 */
1212
	public function batchactions() {
1213
		return new CMSBatchActionHandler($this, 'batchactions');
1214
	}
1215
1216
	public function BatchActionParameters() {
1217
		$batchActions = CMSBatchActionHandler::config()->batch_actions;
1218
1219
		$forms = array();
1220
		foreach($batchActions as $urlSegment => $batchAction) {
1221
			$SNG_action = singleton($batchAction);
1222
			if ($SNG_action->canView() && $fieldset = $SNG_action->getParameterFields()) {
1223
				$formHtml = '';
1224
				foreach($fieldset as $field) {
1225
					$formHtml .= $field->Field();
1226
				}
1227
				$forms[$urlSegment] = $formHtml;
1228
			}
1229
		}
1230
		$pageHtml = '';
1231
		foreach($forms as $urlSegment => $html) {
1232
			$pageHtml .= "<div class=\"params\" id=\"BatchActionParameters_$urlSegment\">$html</div>\n\n";
1233
		}
1234
		return new LiteralField("BatchActionParameters", '<div id="BatchActionParameters" style="display:none">'.$pageHtml.'</div>');
1235
	}
1236
	/**
1237
	 * Returns a list of batch actions
1238
	 */
1239
	public function BatchActionList() {
1240
		return $this->batchactions()->batchActionList();
1241
	}
1242
1243
	public function buildbrokenlinks($request) {
1244
		// Protect against CSRF on destructive action
1245
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1246
1247
		increase_time_limit_to();
1248
		increase_memory_limit_to();
1249
1250
		if($this->urlParams['ID']) {
1251
			$newPageSet[] = DataObject::get_by_id("Page", $this->urlParams['ID']);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$newPageSet was never initialized. Although not strictly required by PHP, it is generally a good practice to add $newPageSet = array(); before regardless.

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

Let’s take a look at an example:

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

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

    // do something with $myArray
}

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

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

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

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

Let’s take a look at an example:

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

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

    // do something with $myArray
}

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

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

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

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

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

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

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

Loading history...
1256
		}
1257
1258
		$content = new HtmlEditorField('Content');
1259
		$download = new HtmlEditorField('Download');
1260
1261
		foreach($newPageSet as $i => $page) {
1262
			$page->HasBrokenLink = 0;
1263
			$page->HasBrokenFile = 0;
1264
1265
			$content->setValue($page->Content);
1266
			$content->saveInto($page);
1267
1268
			$download->setValue($page->Download);
1269
			$download->saveInto($page);
1270
1271
			echo "<li>$page->Title (link:$page->HasBrokenLink, file:$page->HasBrokenFile)";
1272
1273
			$page->writeWithoutVersion();
1274
			$page->destroy();
1275
			$newPageSet[$i] = null;
0 ignored issues
show
Bug introduced by
The variable $newPageSet does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1276
		}
1277
	}
1278
1279
	public function publishall($request) {
1280
		if(!Permission::check('ADMIN')) return Security::permissionFailure($this);
1281
1282
		increase_time_limit_to();
1283
		increase_memory_limit_to();
1284
1285
		$response = "";
1286
1287
		if(isset($this->requestParams['confirm'])) {
1288
			// Protect against CSRF on destructive action
1289
			if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1290
1291
			$start = 0;
1292
			$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
1293
			$count = 0;
1294
			while($pages) {
1295
				foreach($pages as $page) {
1296
					if($page && !$page->canPublish()) return Security::permissionFailure($this);
1297
1298
					$page->doPublish();
1299
					$page->destroy();
1300
					unset($page);
1301
					$count++;
1302
					$response .= "<li>$count</li>";
1303
				}
1304
				if($pages->Count() > 29) {
1305
					$start += 30;
1306
					$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
1307
				} else {
1308
					break;
1309
				}
1310
			}
1311
			$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...
1312
1313
		} else {
1314
			$token = SecurityToken::inst();
1315
			$fields = new FieldList();
1316
			$token->updateFieldSet($fields);
1317
			$tokenField = $fields->First();
1318
			$tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : '';
1319
			$response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
1320
				<p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish".  It\'s
1321
				intended to be used after there have been massive edits of the content, such as when the site was
1322
				first built.') . '</p>
1323
				<form method="post" action="publishall">
1324
					<input type="submit" name="confirm" value="'
1325
					. _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",'Confirmation button') .'" />'
1326
					. $tokenHtml .
1327
				'</form>';
1328
		}
1329
1330
		return $response;
1331
	}
1332
1333
	/**
1334
	 * Restore a completely deleted page from the SiteTree_versions table.
1335
	 */
1336
	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...
1337
		if(!isset($data['ID']) || !is_numeric($data['ID'])) {
1338
			return new SS_HTTPResponse("Please pass an ID in the form content", 400);
1339
		}
1340
1341
		$id = (int)$data['ID'];
1342
		$restoredPage = Versioned::get_latest_version("SiteTree", $id);
1343
		if(!$restoredPage) 	return new SS_HTTPResponse("SiteTree #$id not found", 400);
1344
1345
		$restoredPage = $restoredPage->doRestoreToStage();
1346
1347
		$this->getResponse()->addHeader(
1348
			'X-Status',
1349
			rawurlencode(_t(
1350
				'CMSMain.RESTORED',
1351
				"Restored '{title}' successfully",
1352
				array('title' => $restoredPage->Title)
0 ignored issues
show
Documentation introduced by
array('title' => $restoredPage->Title) is of type array<string,?,{"title":"?"}>, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1353
			))
1354
		);
1355
1356
		return $this->getResponseNegotiator()->respond($this->getRequest());
1357
	}
1358
1359
	public function duplicate($request) {
1360
		// Protect against CSRF on destructive action
1361
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1362
1363
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1364
			$page = DataObject::get_by_id("SiteTree", $id);
1365 View Code Duplication
			if($page && (!$page->canEdit() || !$page->canCreate(null, array('Parent' => $page->Parent())))) {
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on DataObject. Did you maybe mean parentClass()?

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

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

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

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

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

Loading history...
1366
				return Security::permissionFailure($this);
1367
			}
1368
			if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1369
1370
			$newPage = $page->duplicate();
1371
1372
			// ParentID can be hard-set in the URL.  This is useful for pages with multiple parents
1373
			if(isset($_GET['parentID']) && is_numeric($_GET['parentID'])) {
1374
				$newPage->ParentID = $_GET['parentID'];
1375
				$newPage->write();
1376
			}
1377
1378
			$this->getResponse()->addHeader(
1379
				'X-Status',
1380
				rawurlencode(_t(
1381
					'CMSMain.DUPLICATED',
1382
					"Duplicated '{title}' successfully",
1383
					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...
1384
				))
1385
			);
1386
			$url = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $newPage->ID);
1387
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1388
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1389
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1390
1391
			return $this->getResponseNegotiator()->respond($this->getRequest());
1392
		} else {
1393
			return new SS_HTTPResponse("CMSMain::duplicate() Bad ID: '$id'", 400);
1394
		}
1395
	}
1396
1397
	public function duplicatewithchildren($request) {
1398
		// Protect against CSRF on destructive action
1399
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1400
		increase_time_limit_to();
1401
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1402
			$page = DataObject::get_by_id("SiteTree", $id);
1403 View Code Duplication
			if($page && (!$page->canEdit() || !$page->canCreate(null, array('Parent' => $page->Parent())))) {
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on DataObject. Did you maybe mean parentClass()?

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

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

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

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

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

Loading history...
1404
				return Security::permissionFailure($this);
1405
			}
1406
			if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1407
1408
			$newPage = $page->duplicateWithChildren();
1409
1410
			$this->getResponse()->addHeader(
1411
				'X-Status',
1412
				rawurlencode(_t(
1413
					'CMSMain.DUPLICATEDWITHCHILDREN',
1414
					"Duplicated '{title}' and children successfully",
1415
					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...
1416
				))
1417
			);
1418
			$url = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $newPage->ID);
1419
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1420
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1421
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1422
1423
			return $this->getResponseNegotiator()->respond($this->getRequest());
1424
		} else {
1425
			return new SS_HTTPResponse("CMSMain::duplicatewithchildren() Bad ID: '$id'", 400);
1426
		}
1427
	}
1428
1429
	public function providePermissions() {
1430
		$title = _t("CMSPagesController.MENUTITLE", LeftAndMain::menu_title_for_class('CMSPagesController'));
1431
		return array(
1432
			"CMS_ACCESS_CMSMain" => array(
1433
				'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...
1434
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
1435
				'help' => _t(
1436
					'CMSMain.ACCESS_HELP',
1437
					'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".'
1438
				),
1439
				'sort' => -99 // below "CMS_ACCESS_LeftAndMain", but above everything else
1440
			)
1441
		);
1442
	}
1443
1444
}
1445