Completed
Pull Request — master (#1381)
by Damian
02:47
created

CMSMain::deletefromlive()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 55
Code Lines 31

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 55
rs 7.4033
cc 8
eloc 31
nc 5
nop 2

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * 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
		Versioned::reading_stage("Stage");
78
79
		Requirements::css(CMS_DIR . '/css/screen.css');
80
		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...
81
		Requirements::add_i18n_javascript(CMS_DIR . '/javascript/lang', true, true);
82
		Requirements::javascript(CMS_DIR . '/javascript/dist/bundle-lib.js', [
83
			'provides' => [
84
				CMS_DIR . '/javascript/dist/CMSMain.AddForm.js',
85
				CMS_DIR . '/javascript/dist/CMSMain.EditForm.js',
86
				CMS_DIR . '/javascript/dist/CMSMain.js',
87
				CMS_DIR . '/javascript/dist/CMSMain.Tree.js',
88
				CMS_DIR . '/javascript/dist/CMSPageHistoryController.js',
89
				CMS_DIR . '/javascript/dist/RedirectorPage.js',
90
				CMS_DIR . '/javascript/dist/SilverStripeNavigator.js',
91
				CMS_DIR . '/javascript/dist/SiteTreeURLSegmentField.js'
92
			]
93
		]);
94
95
		CMSBatchActionHandler::register('publish', 'CMSBatchAction_Publish');
96
		CMSBatchActionHandler::register('unpublish', 'CMSBatchAction_Unpublish');
97
98
99
		// Check legacy actions
100
		$legacy = $this->config()->enabled_legacy_actions;
101
102
		// Delete from live is unnecessary since we have unpublish which does the same thing
103
		if(in_array('CMSBatchAction_DeleteFromLive', $legacy)) {
104
			Deprecation::notice('4.0', 'Delete From Live is deprecated. Use Un-publish instead');
105
			CMSBatchActionHandler::register('deletefromlive', 'CMSBatchAction_DeleteFromLive');
106
		}
107
108
		// Delete action
109
		if(in_array('CMSBatchAction_Delete', $legacy)) {
110
			Deprecation::notice('4.0', 'Delete from Stage is deprecated. Use Archive instead.');
111
			CMSBatchActionHandler::register('delete', 'CMSBatchAction_Delete');
112
		} else {
113
			CMSBatchActionHandler::register('archive', 'CMSBatchAction_Archive');
114
			CMSBatchActionHandler::register('restore', 'CMSBatchAction_Restore');
115
		}
116
	}
117
118
	public function index($request) {
119
		// In case we're not showing a specific record, explicitly remove any session state,
120
		// to avoid it being highlighted in the tree, and causing an edit form to show.
121
		if(!$request->param('Action')) $this->setCurrentPageId(null);
122
123
		return parent::index($request);
124
	}
125
126
	public function getResponseNegotiator() {
127
		$negotiator = parent::getResponseNegotiator();
128
		$controller = $this;
129
		$negotiator->setCallback('ListViewForm', function() use(&$controller) {
130
			return $controller->ListViewForm()->forTemplate();
131
		});
132
		return $negotiator;
133
	}
134
135
	/**
136
	 * If this is set to true, the "switchView" context in the
137
	 * template is shown, with links to the staging and publish site.
138
	 *
139
	 * @return boolean
140
	 */
141
	public function ShowSwitchView() {
142
		return true;
143
	}
144
145
	/**
146
	 * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
147
	 * to switch view also for archived versions.
148
	 */
149
	public function SwitchView($page = null) {
150
		if(!$page) {
151
			$page = $this->currentPage();
152
		}
153
154
		if($page) {
155
			$nav = SilverStripeNavigator::get_for_record($page);
156
			return $nav['items'];
157
		}
158
	}
159
160
	//------------------------------------------------------------------------------------------//
161
	// Main controllers
162
163
	//------------------------------------------------------------------------------------------//
164
	// Main UI components
165
166
	/**
167
	 * Override {@link LeftAndMain} Link to allow blank URL segment for CMSMain.
168
	 *
169
	 * @param string|null $action Action to link to.
170
	 * @return string
171
	 */
172
	public function Link($action = null) {
173
		$link = Controller::join_links(
174
			$this->stat('url_base', true),
175
			$this->stat('url_segment', true), // in case we want to change the segment
176
			'/', // trailing slash needed if $action is null!
177
			"$action"
178
		);
179
		$this->extend('updateLink', $link);
180
		return $link;
181
	}
182
183
	public function LinkPages() {
184
		return singleton('CMSPagesController')->Link();
185
	}
186
187
	public function LinkPagesWithSearch() {
188
		return $this->LinkWithSearch($this->LinkPages());
189
	}
190
191
	public function LinkTreeView() {
192
		return $this->LinkWithSearch(singleton('CMSMain')->Link('treeview'));
193
	}
194
195
	public function LinkListView() {
196
		return $this->LinkWithSearch(singleton('CMSMain')->Link('listview'));
197
	}
198
199
	public function LinkGalleryView() {
200
		return $this->LinkWithSearch(singleton('CMSMain')->Link('galleryview'));
201
	}
202
203
	public function LinkPageEdit($id = null) {
204
		if(!$id) $id = $this->currentPageID();
205
		return $this->LinkWithSearch(
206
			Controller::join_links(singleton('CMSPageEditController')->Link('show'), $id)
207
		);
208
	}
209
210 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...
211
		if($id = $this->currentPageID()) {
212
			return $this->LinkWithSearch(
213
				Controller::join_links(singleton('CMSPageSettingsController')->Link('show'), $id)
214
			);
215
		}
216
	}
217
218 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...
219
		if($id = $this->currentPageID()) {
220
			return $this->LinkWithSearch(
221
				Controller::join_links(singleton('CMSPageHistoryController')->Link('show'), $id)
222
			);
223
		}
224
	}
225
226
	public function LinkWithSearch($link) {
227
		// Whitelist to avoid side effects
228
		$params = array(
229
			'q' => (array)$this->getRequest()->getVar('q'),
230
			'ParentID' => $this->getRequest()->getVar('ParentID')
231
		);
232
		$link = Controller::join_links(
233
			$link,
234
			array_filter(array_values($params)) ? '?' . http_build_query($params) : null
235
		);
236
		$this->extend('updateLinkWithSearch', $link);
237
		return $link;
238
	}
239
240
	public function LinkPageAdd($extra = null, $placeholders = null) {
241
		$link = singleton("CMSPageAddController")->Link();
242
		$this->extend('updateLinkPageAdd', $link);
243
244
		if($extra) {
245
			$link = Controller::join_links ($link, $extra);
246
		}
247
248
		if($placeholders) {
249
			$link .= (strpos($link, '?') === false ? "?$placeholders" : "&amp;$placeholders");
250
		}
251
252
		return $link;
253
	}
254
255
	/**
256
	 * @return string
257
	 */
258
	public function LinkPreview() {
259
		$record = $this->getRecord($this->currentPageID());
260
		$baseLink = Director::absoluteBaseURL();
261
		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...
262
			// if we are an external redirector don't show a link
263
			if ($record instanceof RedirectorPage && $record->RedirectionType == 'External') {
264
				$baseLink = false;
265
			}
266
			else {
267
				$baseLink = $record->Link('?stage=Stage');
268
			}
269
		}
270
		return $baseLink;
271
	}
272
273
	/**
274
	 * Return the entire site tree as a nested set of ULs
275
	 */
276
	public function SiteTreeAsUL() {
277
		// Pre-cache sitetree version numbers for querying efficiency
278
		Versioned::prepopulate_versionnumber_cache("SiteTree", "Stage");
279
		Versioned::prepopulate_versionnumber_cache("SiteTree", "Live");
280
		$html = $this->getSiteTreeFor($this->stat('tree_class'));
281
282
		$this->extend('updateSiteTreeAsUL', $html);
283
284
		return $html;
285
	}
286
287
	/**
288
	 * @return boolean
289
	 */
290
	public function TreeIsFiltered() {
291
		$query = $this->getRequest()->getVar('q');
292
293
		if (!$query || (count($query) === 1 && isset($query['FilterClass']) && $query['FilterClass'] === 'CMSSiteTreeFilter_Search')) {
294
			return false;
295
		}
296
297
		return true;
298
	}
299
300
	public function ExtraTreeTools() {
301
		$html = '';
302
		$this->extend('updateExtraTreeTools', $html);
303
		return $html;
304
	}
305
306
	/**
307
	 * Returns a Form for page searching for use in templates.
308
	 *
309
	 * Can be modified from a decorator by a 'updateSearchForm' method
310
	 *
311
	 * @return Form
312
	 */
313
	public function SearchForm() {
314
		// Create the fields
315
		$content = new TextField('q[Term]', _t('CMSSearch.FILTERLABELTEXT', 'Search'));
316
		$dateHeader = new HeaderField('q[Date]', _t('CMSSearch.PAGEFILTERDATEHEADING', 'Last edited'), 4);
317
		$dateFrom = new DateField(
318
			'q[LastEditedFrom]',
319
			_t('CMSSearch.FILTERDATEFROM', 'From')
320
		);
321
		$dateFrom->setConfig('showcalendar', true);
322
		$dateTo = new DateField(
323
			'q[LastEditedTo]',
324
			_t('CMSSearch.FILTERDATETO', 'To')
325
		);
326
		$dateTo->setConfig('showcalendar', true);
327
		$pageFilter = new DropdownField(
328
			'q[FilterClass]',
329
			_t('CMSMain.PAGES', 'Page status'),
330
			CMSSiteTreeFilter::get_all_filters()
331
		);
332
		$pageClasses = new DropdownField(
333
			'q[ClassName]',
334
			_t('CMSMain.PAGETYPEOPT', 'Page type', 'Dropdown for limiting search to a page type'),
335
			$this->getPageTypes()
336
		);
337
		$pageClasses->setEmptyString(_t('CMSMain.PAGETYPEANYOPT','Any'));
338
339
		// Group the Datefields
340
		$dateGroup = new FieldGroup(
341
			$dateHeader,
342
			$dateFrom,
343
			$dateTo
344
		);
345
		$dateGroup->setFieldHolderTemplate('FieldGroup_DefaultFieldHolder')->addExtraClass('stacked');
346
347
		// Create the Field list
348
		$fields = new FieldList(
349
			$content,
350
			$dateGroup,
351
			$pageFilter,
352
			$pageClasses
353
		);
354
355
		// Create the Search and Reset action
356
		$actions = new FieldList(
357
			FormAction::create('doSearch',  _t('CMSMain_left_ss.APPLY_FILTER', 'Search'))
358
				->addExtraClass('ss-ui-action-constructive'),
359
			Object::create('ResetFormAction', 'clear', _t('CMSMain_left_ss.CLEAR_FILTER', 'Clear'))
360
		);
361
362
		// Use <button> to allow full jQuery UI styling on the all of the Actions
363
		foreach($actions->dataFields() as $action) {
364
			$action->setUseButtonTag(true);
365
		}
366
367
		// Create the form
368
		$form = Form::create($this, 'SearchForm', $fields, $actions)
369
			->addExtraClass('cms-search-form')
370
			->setFormMethod('GET')
371
			->setFormAction($this->Link())
372
			->disableSecurityToken()
373
			->unsetValidator();
374
375
		// Load the form with previously sent search data
376
		$form->loadDataFrom($this->getRequest()->getVars());
377
378
		// Allow decorators to modify the form
379
		$this->extend('updateSearchForm', $form);
380
381
		return $form;
382
	}
383
384
	/**
385
	 * Returns a sorted array suitable for a dropdown with pagetypes and their translated name
386
	 *
387
	 * @return array
388
	 */
389
	protected function getPageTypes() {
390
		$pageTypes = array();
391
		foreach(SiteTree::page_type_classes() as $pageTypeClass) {
392
			$pageTypes[$pageTypeClass] = _t($pageTypeClass.'.SINGULARNAME', $pageTypeClass);
393
		}
394
		asort($pageTypes);
395
		return $pageTypes;
396
	}
397
398
	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...
399
		return $this->getsubtree($this->getRequest());
400
	}
401
402
	/**
403
	 * @param bool $unlinked
404
	 * @return ArrayList
405
	 */
406
	public function Breadcrumbs($unlinked = false) {
407
		$items = parent::Breadcrumbs($unlinked);
408
409
		if($items->count() > 1) {
410
			// Specific to the SiteTree admin section, we never show the cms section and current
411
			// page in the same breadcrumbs block.
412
			$items->shift();
413
		}
414
415
		return $items;
416
	}
417
418
	/**
419
	 * Create serialized JSON string with site tree hints data to be injected into
420
	 * 'data-hints' attribute of root node of jsTree.
421
	 *
422
	 * @return string Serialized JSON
423
	 */
424
	public function SiteTreeHints() {
425
		$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...
426
		$classes = SiteTree::page_type_classes();
427
428
	 	$cacheCanCreate = array();
429
	 	foreach($classes as $class) $cacheCanCreate[$class] = singleton($class)->canCreate();
430
431
	 	// Generate basic cache key. Too complex to encompass all variations
432
	 	$cache = SS_Cache::factory('CMSMain_SiteTreeHints');
433
	 	$cacheKey = md5(implode('_', array(Member::currentUserID(), implode(',', $cacheCanCreate), implode(',', $classes))));
434
	 	if($this->getRequest()->getVar('flush')) $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
435
	 	$json = $cache->load($cacheKey);
436
	 	if(!$json) {
437
			$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...
438
			$def['Root']['disallowedChildren'] = array();
439
440
			// Contains all possible classes to support UI controls listing them all,
441
			// such as the "add page here" context menu.
442
			$def['All'] = array();
443
444
			// Identify disallows and set globals
445
			foreach($classes as $class) {
446
				$obj = singleton($class);
447
				if($obj instanceof HiddenClass) continue;
448
449
				// Name item
450
				$def['All'][$class] = array(
451
					'title' => $obj->i18n_singular_name()
452
				);
453
454
				// Check if can be created at the root
455
				$needsPerm = $obj->stat('need_permission');
456
				if(
457
					!$obj->stat('can_be_root')
458
					|| (!array_key_exists($class, $cacheCanCreate) || !$cacheCanCreate[$class])
459
					|| ($needsPerm && !$this->can($needsPerm))
460
				) {
461
					$def['Root']['disallowedChildren'][] = $class;
462
				}
463
464
				// Hint data specific to the class
465
				$def[$class] = array();
466
467
				$defaultChild = $obj->defaultChild();
468
				if($defaultChild !== 'Page' && $defaultChild !== null) {
469
					$def[$class]['defaultChild'] = $defaultChild;
470
				}
471
472
				$defaultParent = $obj->defaultParent();
473
				if ($defaultParent !== 1 && $defaultParent !== null) {
474
					$def[$class]['defaultParent'] = $defaultParent;
475
				}
476
			}
477
478
			$this->extend('updateSiteTreeHints', $def);
479
480
			$json = Convert::raw2json($def);
481
			$cache->save($json, $cacheKey);
482
		}
483
		return $json;
484
	}
485
486
	/**
487
	 * Populates an array of classes in the CMS
488
	 * which allows the user to change the page type.
489
	 *
490
	 * @return SS_List
491
	 */
492
	public function PageTypes() {
493
		$classes = SiteTree::page_type_classes();
494
495
		$result = new ArrayList();
496
497
		foreach($classes as $class) {
498
			$instance = singleton($class);
499
500
			if($instance instanceof HiddenClass) continue;
501
502
			// skip this type if it is restricted
503
			if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue;
504
505
			$addAction = $instance->i18n_singular_name();
506
507
			// Get description (convert 'Page' to 'SiteTree' for correct localization lookups)
508
			$description = _t((($class == 'Page') ? 'SiteTree' : $class) . '.DESCRIPTION');
509
510
			if(!$description) {
511
				$description = $instance->uninherited('description');
512
			}
513
514
			if($class == 'Page' && !$description) {
515
				$description = singleton('SiteTree')->uninherited('description');
516
			}
517
518
			$result->push(new ArrayData(array(
519
				'ClassName' => $class,
520
				'AddAction' => $addAction,
521
				'Description' => $description,
522
				// TODO Sprite support
523
				'IconURL' => $instance->stat('icon'),
524
				'Title' => singleton($class)->i18n_singular_name(),
525
			)));
526
		}
527
528
		$result = $result->sort('AddAction');
529
530
		return $result;
531
	}
532
533
	/**
534
	 * Get a database record to be managed by the CMS.
535
	 *
536
	 * @param int $id Record ID
537
	 * @param int $versionID optional Version id of the given record
538
	 * @return SiteTree
539
	 */
540
 	public function getRecord($id, $versionID = null) {
541
		$treeClass = $this->stat('tree_class');
542
543
		if($id instanceof $treeClass) {
544
			return $id;
545
		}
546
		else if($id && is_numeric($id)) {
547
			if($this->getRequest()->getVar('Version')) {
548
				$versionID = (int) $this->getRequest()->getVar('Version');
549
			}
550
551
			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...
552
				$record = Versioned::get_version($treeClass, $id, $versionID);
553
			} else {
554
				$record = DataObject::get_by_id($treeClass, $id);
555
			}
556
557
			// Then, try getting a record from the live site
558
			if(!$record) {
559
				// $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id");
560
				Versioned::reading_stage('Live');
561
				singleton($treeClass)->flushCache();
562
563
				$record = DataObject::get_by_id($treeClass, $id);
564
				if($record) Versioned::set_reading_mode('');
565
			}
566
567
			// Then, try getting a deleted record
568
			if(!$record) {
569
				$record = Versioned::get_latest_version($treeClass, $id);
570
			}
571
572
			// Don't open a page from a different locale
573
			/** The record's Locale is saved in database in 2.4, and not related with Session,
574
			 *  we should not check their locale matches the Translatable::get_current_locale,
575
			 * 	here as long as we all the HTTPRequest is init with right locale.
576
			 *	This bit breaks the all FileIFrameField functions if the field is used in CMS
577
			 *  and its relevent ajax calles, like loading the tree dropdown for TreeSelectorField.
578
			 */
579
			/* if($record && SiteTree::has_extension('Translatable') && $record->Locale && $record->Locale != Translatable::get_current_locale()) {
580
				$record = null;
581
			}*/
582
583
			return $record;
584
585
		} else if(substr($id,0,3) == 'new') {
586
			return $this->getNewItem($id);
587
		}
588
	}
589
590
	/**
591
	 * @param int $id
592
	 * @param FieldList $fields
593
	 * @return CMSForm
594
	 */
595
	public function getEditForm($id = null, $fields = null) {
596
		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...
597
		$form = parent::getEditForm($id);
598
599
		// TODO Duplicate record fetching (see parent implementation)
600
		$record = $this->getRecord($id);
601
		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 601 which is incompatible with the return type documented by CMSMain::getEditForm of type CMSForm.
Loading history...
602
603
		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...
604
		$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...
605
606
		if($record) {
607
			$deletedFromStage = $record->getIsDeletedFromStage();
608
609
			$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...
610
			// Necessary for different subsites
611
			$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...
612
			$fields->push($liveLinkField = new HiddenField("LiveLink"));
613
			$fields->push($stageLinkField = new HiddenField("StageLink"));
614
			$fields->push(new HiddenField("TreeTitle", false, $record->TreeTitle));
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a null|string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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