Completed
Push — master ( 303759...f0b574 )
by Damian
9s
created

CMSMain::addtocampaign()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
use SilverStripe\Model\FieldType\DBHTMLText;
3
4
/**
5
 * The main "content" area of the CMS.
6
 *
7
 * This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main
8
 * admin menu.
9
 *
10
 * @package cms
11
 * @subpackage controller
12
 * @todo Create some base classes to contain the generic functionality that will be replicated.
13
 */
14
class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionProvider {
15
16
	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...
17
18
	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...
19
20
	// Maintain a lower priority than other administration sections
21
	// so that Director does not think they are actions of CMSMain
22
	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...
23
24
	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...
25
26
	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...
27
28
	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...
29
30
	private static $subitem_class = "Member";
31
32
	/**
33
	 * Amount of results showing on a single page.
34
	 *
35
	 * @config
36
	 * @var int
37
	 */
38
	private static $page_length = 15;
39
40
	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...
41
		'archive',
42
		'buildbrokenlinks',
43
		'deleteitems',
44
		'DeleteItemsForm',
45
		'dialog',
46
		'duplicate',
47
		'duplicatewithchildren',
48
		'publishall',
49
		'publishitems',
50
		'PublishItemsForm',
51
		'submit',
52
		'EditForm',
53
		'SearchForm',
54
		'SiteTreeAsUL',
55
		'getshowdeletedsubtree',
56
		'batchactions',
57
		'treeview',
58
		'listview',
59
		'ListViewForm',
60
		'childfilter',
61
	);
62
63
	public function init() {
64
		// set reading lang
65
		if(SiteTree::has_extension('Translatable') && !$this->getRequest()->isAjax()) {
66
			Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SiteTree')));
67
		}
68
69
		parent::init();
70
71
		Requirements::css(CMS_DIR . '/client/dist/styles/bundle.css');
72
		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...
73
		Requirements::add_i18n_javascript(CMS_DIR . '/client/src/lang', false, true);
74
		Requirements::javascript(CMS_DIR . '/client/dist/js/bundle-legacy.js', [
75
			'provides' => [
76
				CMS_DIR . '/client/dist/js/CMSMain.AddForm.js',
77
				CMS_DIR . '/client/dist/js/CMSMain.EditForm.js',
78
				CMS_DIR . '/client/dist/js/CMSMain.js',
79
				CMS_DIR . '/client/dist/js/CMSMain.Tree.js',
80
				CMS_DIR . '/client/dist/js/CMSPageHistoryController.js',
81
				CMS_DIR . '/client/dist/js/RedirectorPage.js',
82
				CMS_DIR . '/client/dist/js/SilverStripeNavigator.js',
83
				CMS_DIR . '/client/dist/js/SiteTreeURLSegmentField.js'
84
			]
85
		]);
86
87
		CMSBatchActionHandler::register('publish', 'CMSBatchAction_Publish');
88
		CMSBatchActionHandler::register('unpublish', 'CMSBatchAction_Unpublish');
89
		CMSBatchActionHandler::register('delete', 'CMSBatchAction_Delete');
90
		CMSBatchActionHandler::register('archive', 'CMSBatchAction_Archive');
91
		CMSBatchActionHandler::register('restore', 'CMSBatchAction_Restore');
92
	}
93
94
	public function index($request) {
95
		// In case we're not showing a specific record, explicitly remove any session state,
96
		// to avoid it being highlighted in the tree, and causing an edit form to show.
97
		if(!$request->param('Action')) $this->setCurrentPageId(null);
98
99
		return parent::index($request);
100
	}
101
102
	public function getResponseNegotiator() {
103
		$negotiator = parent::getResponseNegotiator();
104
		$controller = $this;
105
		$negotiator->setCallback('ListViewForm', function() use(&$controller) {
106
			return $controller->ListViewForm()->forTemplate();
107
		});
108
		return $negotiator;
109
	}
110
111
	/**
112
	 * If this is set to true, the "switchView" context in the
113
	 * template is shown, with links to the staging and publish site.
114
	 *
115
	 * @return boolean
116
	 */
117
	public function ShowSwitchView() {
118
		return true;
119
	}
120
121
	/**
122
	 * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
123
	 * to switch view also for archived versions.
124
	 */
125
	public function SwitchView($page = null) {
126
		if(!$page) {
127
			$page = $this->currentPage();
128
		}
129
130
		if($page) {
131
			$nav = SilverStripeNavigator::get_for_record($page);
132
			return $nav['items'];
133
		}
134
	}
135
136
	//------------------------------------------------------------------------------------------//
137
	// Main controllers
138
139
	//------------------------------------------------------------------------------------------//
140
	// Main UI components
141
142
	/**
143
	 * Override {@link LeftAndMain} Link to allow blank URL segment for CMSMain.
144
	 *
145
	 * @param string|null $action Action to link to.
146
	 * @return string
147
	 */
148
	public function Link($action = null) {
149
		$link = Controller::join_links(
150
			$this->stat('url_base', true),
0 ignored issues
show
Unused Code introduced by
The call to CMSMain::stat() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
151
			$this->stat('url_segment', true), // in case we want to change the segment
0 ignored issues
show
Unused Code introduced by
The call to CMSMain::stat() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
152
			'/', // trailing slash needed if $action is null!
153
			"$action"
154
		);
155
		$this->extend('updateLink', $link);
156
		return $link;
157
	}
158
159
	public function LinkPages() {
160
		return singleton('CMSPagesController')->Link();
161
	}
162
163
	public function LinkPagesWithSearch() {
164
		return $this->LinkWithSearch($this->LinkPages());
165
	}
166
167
	public function LinkTreeView() {
168
		return $this->LinkWithSearch(singleton('CMSMain')->Link('treeview'));
169
	}
170
171
	public function LinkListView() {
172
		return $this->LinkWithSearch(singleton('CMSMain')->Link('listview'));
173
	}
174
175
	public function LinkGalleryView() {
176
		return $this->LinkWithSearch(singleton('CMSMain')->Link('galleryview'));
177
	}
178
179
	public function LinkPageEdit($id = null) {
180
		if(!$id) $id = $this->currentPageID();
181
		return $this->LinkWithSearch(
182
			Controller::join_links(singleton('CMSPageEditController')->Link('show'), $id)
183
		);
184
	}
185
186 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...
187
		if($id = $this->currentPageID()) {
188
			return $this->LinkWithSearch(
189
				Controller::join_links(singleton('CMSPageSettingsController')->Link('show'), $id)
190
			);
191
		}
192
	}
193
194 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...
195
		if($id = $this->currentPageID()) {
196
			return $this->LinkWithSearch(
197
				Controller::join_links(singleton('CMSPageHistoryController')->Link('show'), $id)
198
			);
199
		}
200
	}
201
202
	public function LinkWithSearch($link) {
203
		// Whitelist to avoid side effects
204
		$params = array(
205
			'q' => (array)$this->getRequest()->getVar('q'),
206
			'ParentID' => $this->getRequest()->getVar('ParentID')
207
		);
208
		$link = Controller::join_links(
209
			$link,
210
			array_filter(array_values($params)) ? '?' . http_build_query($params) : null
211
		);
212
		$this->extend('updateLinkWithSearch', $link);
213
		return $link;
214
	}
215
216
	public function LinkPageAdd($extra = null, $placeholders = null) {
217
		$link = singleton("CMSPageAddController")->Link();
218
		$this->extend('updateLinkPageAdd', $link);
219
220
		if($extra) {
221
			$link = Controller::join_links ($link, $extra);
222
		}
223
224
		if($placeholders) {
225
			$link .= (strpos($link, '?') === false ? "?$placeholders" : "&amp;$placeholders");
226
		}
227
228
		return $link;
229
	}
230
231
	/**
232
	 * @return string
233
	 */
234
	public function LinkPreview() {
235
		$record = $this->getRecord($this->currentPageID());
236
		$baseLink = Director::absoluteBaseURL();
237
		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...
238
			// if we are an external redirector don't show a link
239
			if ($record instanceof RedirectorPage && $record->RedirectionType == 'External') {
240
				$baseLink = false;
241
			}
242
			else {
243
				$baseLink = $record->Link('?stage=Stage');
244
			}
245
		}
246
		return $baseLink;
247
	}
248
249
	/**
250
	 * Return the entire site tree as a nested set of ULs
251
	 */
252
	public function SiteTreeAsUL() {
253
		// Pre-cache sitetree version numbers for querying efficiency
254
		Versioned::prepopulate_versionnumber_cache("SiteTree", "Stage");
255
		Versioned::prepopulate_versionnumber_cache("SiteTree", "Live");
256
		$html = $this->getSiteTreeFor($this->stat('tree_class'));
257
258
		$this->extend('updateSiteTreeAsUL', $html);
259
260
		return $html;
261
	}
262
263
	/**
264
	 * @return boolean
265
	 */
266
	public function TreeIsFiltered() {
267
		$query = $this->getRequest()->getVar('q');
268
269
		if (!$query || (count($query) === 1 && isset($query['FilterClass']) && $query['FilterClass'] === 'CMSSiteTreeFilter_Search')) {
270
			return false;
271
		}
272
273
		return true;
274
	}
275
276
	public function ExtraTreeTools() {
277
		$html = '';
278
		$this->extend('updateExtraTreeTools', $html);
279
		return $html;
280
	}
281
282
	/**
283
	 * Returns a Form for page searching for use in templates.
284
	 *
285
	 * Can be modified from a decorator by a 'updateSearchForm' method
286
	 *
287
	 * @return Form
288
	 */
289
	public function SearchForm() {
290
		// Create the fields
291
		$content = new TextField('q[Term]', _t('CMSSearch.FILTERLABELTEXT', 'Search'));
292
		$dateHeader = new HeaderField('q[Date]', _t('CMSSearch.PAGEFILTERDATEHEADING', 'Last edited'), 4);
293
		$dateFrom = new DateField(
294
			'q[LastEditedFrom]',
295
			_t('CMSSearch.FILTERDATEFROM', 'From')
296
		);
297
		$dateFrom->setConfig('showcalendar', true);
298
		$dateTo = new DateField(
299
			'q[LastEditedTo]',
300
			_t('CMSSearch.FILTERDATETO', 'To')
301
		);
302
		$dateTo->setConfig('showcalendar', true);
303
		$pageFilter = new DropdownField(
304
			'q[FilterClass]',
305
			_t('CMSMain.PAGES', 'Page status'),
306
			CMSSiteTreeFilter::get_all_filters()
307
		);
308
		$pageClasses = new DropdownField(
309
			'q[ClassName]',
310
			_t('CMSMain.PAGETYPEOPT', 'Page type', 'Dropdown for limiting search to a page type'),
311
			$this->getPageTypes()
312
		);
313
		$pageClasses->setEmptyString(_t('CMSMain.PAGETYPEANYOPT','Any'));
314
315
		// Group the Datefields
316
		$dateGroup = new FieldGroup(
317
			$dateHeader,
318
			$dateFrom,
319
			$dateTo
320
		);
321
		$dateGroup->setFieldHolderTemplate('FieldGroup_DefaultFieldHolder')->addExtraClass('stacked');
322
323
		// Create the Field list
324
		$fields = new FieldList(
325
			$content,
326
			$dateGroup,
327
			$pageFilter,
328
			$pageClasses
329
		);
330
331
		// Create the Search and Reset action
332
		$actions = new FieldList(
333
			FormAction::create('doSearch',  _t('CMSMain_left_ss.APPLY_FILTER', 'Search'))
334
				->addExtraClass('ss-ui-action-constructive'),
335
			Object::create('ResetFormAction', 'clear', _t('CMSMain_left_ss.CLEAR_FILTER', 'Clear'))
336
		);
337
338
		// Use <button> to allow full jQuery UI styling on the all of the Actions
339
		foreach($actions->dataFields() as $action) {
340
			$action->setUseButtonTag(true);
341
		}
342
343
		// Create the form
344
		$form = Form::create($this, 'SearchForm', $fields, $actions)
345
			->addExtraClass('cms-search-form')
346
			->setFormMethod('GET')
347
			->setFormAction($this->Link())
348
			->disableSecurityToken()
349
			->unsetValidator();
350
351
		// Load the form with previously sent search data
352
		$form->loadDataFrom($this->getRequest()->getVars());
353
354
		// Allow decorators to modify the form
355
		$this->extend('updateSearchForm', $form);
356
357
		return $form;
358
	}
359
360
	/**
361
	 * Returns a sorted array suitable for a dropdown with pagetypes and their translated name
362
	 *
363
	 * @return array
364
	 */
365
	protected function getPageTypes() {
366
		$pageTypes = array();
367
		foreach(SiteTree::page_type_classes() as $pageTypeClass) {
368
			$pageTypes[$pageTypeClass] = _t($pageTypeClass.'.SINGULARNAME', $pageTypeClass);
369
		}
370
		asort($pageTypes);
371
		return $pageTypes;
372
	}
373
374
	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...
375
		return $this->getsubtree($this->getRequest());
376
	}
377
378
	/**
379
	 * @param bool $unlinked
380
	 * @return ArrayList
381
	 */
382
	public function Breadcrumbs($unlinked = false) {
383
		$items = parent::Breadcrumbs($unlinked);
384
385
		if($items->count() > 1) {
386
			// Specific to the SiteTree admin section, we never show the cms section and current
387
			// page in the same breadcrumbs block.
388
			$items->shift();
389
		}
390
391
		return $items;
392
	}
393
394
	/**
395
	 * Create serialized JSON string with site tree hints data to be injected into
396
	 * 'data-hints' attribute of root node of jsTree.
397
	 *
398
	 * @return string Serialized JSON
399
	 */
400
	public function SiteTreeHints() {
401
		$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...
402
		$classes = SiteTree::page_type_classes();
403
404
	 	$cacheCanCreate = array();
405
	 	foreach($classes as $class) $cacheCanCreate[$class] = singleton($class)->canCreate();
406
407
	 	// Generate basic cache key. Too complex to encompass all variations
408
	 	$cache = SS_Cache::factory('CMSMain_SiteTreeHints');
409
	 	$cacheKey = md5(implode('_', array(Member::currentUserID(), implode(',', $cacheCanCreate), implode(',', $classes))));
410
	 	if($this->getRequest()->getVar('flush')) $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
411
	 	$json = $cache->load($cacheKey);
412
	 	if(!$json) {
413
			$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...
414
			$def['Root']['disallowedChildren'] = array();
415
416
			// Contains all possible classes to support UI controls listing them all,
417
			// such as the "add page here" context menu.
418
			$def['All'] = array();
419
420
			// Identify disallows and set globals
421
			foreach($classes as $class) {
422
				$obj = singleton($class);
423
				if($obj instanceof HiddenClass) continue;
424
425
				// Name item
426
				$def['All'][$class] = array(
427
					'title' => $obj->i18n_singular_name()
428
				);
429
430
				// Check if can be created at the root
431
				$needsPerm = $obj->stat('need_permission');
432
				if(
433
					!$obj->stat('can_be_root')
434
					|| (!array_key_exists($class, $cacheCanCreate) || !$cacheCanCreate[$class])
435
					|| ($needsPerm && !$this->can($needsPerm))
436
				) {
437
					$def['Root']['disallowedChildren'][] = $class;
438
				}
439
440
				// Hint data specific to the class
441
				$def[$class] = array();
442
443
				$defaultChild = $obj->defaultChild();
444
				if($defaultChild !== 'Page' && $defaultChild !== null) {
445
					$def[$class]['defaultChild'] = $defaultChild;
446
				}
447
448
				$defaultParent = $obj->defaultParent();
449
				if ($defaultParent !== 1 && $defaultParent !== null) {
450
					$def[$class]['defaultParent'] = $defaultParent;
451
				}
452
			}
453
454
			$this->extend('updateSiteTreeHints', $def);
455
456
			$json = Convert::raw2json($def);
457
			$cache->save($json, $cacheKey);
458
		}
459
		return $json;
460
	}
461
462
	/**
463
	 * Populates an array of classes in the CMS
464
	 * which allows the user to change the page type.
465
	 *
466
	 * @return SS_List
467
	 */
468
	public function PageTypes() {
469
		$classes = SiteTree::page_type_classes();
470
471
		$result = new ArrayList();
472
473
		foreach($classes as $class) {
474
			$instance = singleton($class);
475
476
			if($instance instanceof HiddenClass) continue;
477
478
			// skip this type if it is restricted
479
			if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue;
480
481
			$addAction = $instance->i18n_singular_name();
482
483
			// Get description (convert 'Page' to 'SiteTree' for correct localization lookups)
484
			$description = _t((($class == 'Page') ? 'SiteTree' : $class) . '.DESCRIPTION');
485
486
			if(!$description) {
487
				$description = $instance->uninherited('description');
488
			}
489
490
			if($class == 'Page' && !$description) {
491
				$description = singleton('SiteTree')->uninherited('description');
492
			}
493
494
			$result->push(new ArrayData(array(
495
				'ClassName' => $class,
496
				'AddAction' => $addAction,
497
				'Description' => $description,
498
				// TODO Sprite support
499
				'IconURL' => $instance->stat('icon'),
500
				'Title' => singleton($class)->i18n_singular_name(),
501
			)));
502
		}
503
504
		$result = $result->sort('AddAction');
505
506
		return $result;
507
	}
508
509
	/**
510
	 * Get a database record to be managed by the CMS.
511
	 *
512
	 * @param int $id Record ID
513
	 * @param int $versionID optional Version id of the given record
514
	 * @return SiteTree
515
	 */
516
 	public function getRecord($id, $versionID = null) {
517
		$treeClass = $this->stat('tree_class');
518
519
		if($id instanceof $treeClass) {
520
			return $id;
521
		}
522
		else if($id && is_numeric($id)) {
523
			if($this->getRequest()->getVar('Version')) {
524
				$versionID = (int) $this->getRequest()->getVar('Version');
525
			}
526
527
			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...
528
				$record = Versioned::get_version($treeClass, $id, $versionID);
529
			} else {
530
				$record = DataObject::get_by_id($treeClass, $id);
531
			}
532
533
			// Then, try getting a record from the live site
534
			if(!$record) {
535
				// $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id");
536
				Versioned::set_stage(Versioned::LIVE);
537
				singleton($treeClass)->flushCache();
538
539
				$record = DataObject::get_by_id($treeClass, $id);
540
				if($record) Versioned::set_reading_mode('');
541
			}
542
543
			// Then, try getting a deleted record
544
			if(!$record) {
545
				$record = Versioned::get_latest_version($treeClass, $id);
546
			}
547
548
			// Don't open a page from a different locale
549
			/** The record's Locale is saved in database in 2.4, and not related with Session,
550
			 *  we should not check their locale matches the Translatable::get_current_locale,
551
			 * 	here as long as we all the HTTPRequest is init with right locale.
552
			 *	This bit breaks the all FileIFrameField functions if the field is used in CMS
553
			 *  and its relevent ajax calles, like loading the tree dropdown for TreeSelectorField.
554
			 */
555
			/* if($record && SiteTree::has_extension('Translatable') && $record->Locale && $record->Locale != Translatable::get_current_locale()) {
556
				$record = null;
557
			}*/
558
559
			return $record;
560
561
		} else if(substr($id,0,3) == 'new') {
562
			return $this->getNewItem($id);
563
		}
564
	}
565
566
	/**
567
	 * @param int $id
568
	 * @param FieldList $fields
569
	 * @return Form
570
	 */
571
	public function getEditForm($id = null, $fields = null) {
572
		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...
573
		$form = parent::getEditForm($id, $fields);
574
575
		// TODO Duplicate record fetching (see parent implementation)
576
		$record = $this->getRecord($id);
577
		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 577 which is incompatible with the return type documented by CMSMain::getEditForm of type Form.
Loading history...
578
579
		if(!$fields) $fields = $form->Fields();
580
		$actions = $form->Actions();
0 ignored issues
show
Unused Code introduced by
$actions is not used, you could remove the assignment.

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

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

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

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

Loading history...
581
582
		if($record) {
583
			$deletedFromStage = $record->getIsDeletedFromStage();
584
585
			$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...
586
			// Necessary for different subsites
587
			$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...
588
			$fields->push($liveLinkField = new HiddenField("LiveLink"));
589
			$fields->push($stageLinkField = new HiddenField("StageLink"));
590
			$fields->push(new HiddenField("TreeTitle", false, $record->TreeTitle));
0 ignored issues
show
Documentation introduced by
The property TreeTitle does not exist on object<SiteTree>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
591
592
			if($record->ID && is_numeric( $record->ID ) ) {
593
				$liveLink = $record->getAbsoluteLiveLink();
594
				if($liveLink) $liveLinkField->setValue($liveLink);
595
				if(!$deletedFromStage) {
596
					$stageLink = Controller::join_links($record->AbsoluteLink(), '?stage=Stage');
597
					if($stageLink) $stageLinkField->setValue($stageLink);
598
				}
599
			}
600
601
			// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
602
			if(in_array('CMSPreviewable', class_implements($record)) && !$fields->fieldByName('SilverStripeNavigator')) {
603
				$navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator());
604
				$navField->setAllowHTML(true);
605
				$fields->push($navField);
606
			}
607
608
			// getAllCMSActions can be used to completely redefine the action list
609
			if($record->hasMethod('getAllCMSActions')) {
610
				$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...
611
			} else {
612
				$actions = $record->getCMSActions();
613
614
				// Find and remove action menus that have no actions.
615
				if ($actions && $actions->Count()) {
616
					$tabset = $actions->fieldByName('ActionMenus');
617
					if ($tabset) {
618
						foreach ($tabset->getChildren() as $tab) {
619
							if (!$tab->getChildren()->count()) {
620
								$tabset->removeByName($tab->getName());
621
							}
622
						}
623
					}
624
				}
625
			}
626
627
			// Use <button> to allow full jQuery UI styling
628
			$actionsFlattened = $actions->dataFields();
629
			if($actionsFlattened) foreach($actionsFlattened as $action) $action->setUseButtonTag(true);
630
631
			if($record->hasMethod('getCMSValidator')) {
632
				$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...
Unused Code introduced by
$validator is not used, you could remove the assignment.

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

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

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

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

Loading history...
633
			} else {
634
				$validator = new RequiredFields();
0 ignored issues
show
Unused Code introduced by
$validator is not used, you could remove the assignment.

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

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

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

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

Loading history...
635
			}
636
637
			// TODO Can't merge $FormAttributes in template at the moment
638
			$form->addExtraClass('center ' . $this->BaseCSSClasses());
639
			// Set validation exemptions for specific actions
640
			$form->setValidationExemptActions(array('restore', 'revert', 'deletefromlive', 'delete', 'unpublish', 'rollback', 'doRollback'));
641
642
			// Announce the capability so the frontend can decide whether to allow preview or not.
643
			if(in_array('CMSPreviewable', class_implements($record))) {
644
				$form->addExtraClass('cms-previewable');
645
			}
646
647
			if(!$record->canEdit() || $deletedFromStage) {
648
				$readonlyFields = $form->Fields()->makeReadonly();
649
				$form->setFields($readonlyFields);
650
			}
651
652
			$form->Fields()->setForm($form);
653
654
			$this->extend('updateEditForm', $form);
655
			return $form;
656
		} else if($id) {
657
			$form = Form::create( $this, "EditForm", new FieldList(
658
				new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList()
659
			)->setHTMLID('Form_EditForm');
660
			return $form;
661
		}
662
	}
663
664
	/**
665
	 * @param SS_HTTPRequest $request
666
	 * @return string HTML
667
	 */
668
	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...
669
		return $this->renderWith($this->getTemplatesWithSuffix('_TreeView'));
670
	}
671
672
	/**
673
	 * @param SS_HTTPRequest $request
674
	 * @return string HTML
675
	 */
676
	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...
677
		return $this->renderWith($this->getTemplatesWithSuffix('_ListView'));
678
	}
679
680
	/**
681
	 * Callback to request the list of page types allowed under a given page instance.
682
	 * Provides a slower but more precise response over SiteTreeHints
683
	 *
684
	 * @param SS_HTTPRequest $request
685
	 * @return SS_HTTPResponse
686
	 */
687
	public function childfilter($request) {
688
		// Check valid parent specified
689
		$parentID = $request->requestVar('ParentID');
690
		$parent = SiteTree::get()->byID($parentID);
691
		if(!$parent || !$parent->exists()) return $this->httpError(404);
692
693
		// Build hints specific to this class
694
		// Identify disallows and set globals
695
		$classes = SiteTree::page_type_classes();
696
		$disallowedChildren = array();
697
		foreach($classes as $class) {
698
			$obj = singleton($class);
699
			if($obj instanceof HiddenClass) continue;
700
701
			if(!$obj->canCreate(null, array('Parent' => $parent))) {
702
				$disallowedChildren[] = $class;
703
			}
704
		}
705
706
		$this->extend('updateChildFilter', $disallowedChildren, $parentID);
707
		return $this
708
			->getResponse()
709
			->addHeader('Content-Type', 'application/json; charset=utf-8')
710
			->setBody(Convert::raw2json($disallowedChildren));
711
	}
712
713
	/**
714
	 * Safely reconstruct a selected filter from a given set of query parameters
715
	 *
716
	 * @param array $params Query parameters to use
717
	 * @return CMSSiteTreeFilter The filter class, or null if none present
718
	 * @throws InvalidArgumentException if invalid filter class is passed.
719
	 */
720
	protected function getQueryFilter($params) {
721
		if(empty($params['FilterClass'])) return null;
722
		$filterClass = $params['FilterClass'];
723
		if(!is_subclass_of($filterClass, 'CMSSiteTreeFilter')) {
724
			throw new InvalidArgumentException("Invalid filter class passed: {$filterClass}");
725
		}
726
		return $filterClass::create($params);
727
	}
728
729
	/**
730
	 * Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page
731
	 * defaulting to no filter and show all pages in first level.
732
	 * Doubles as search results, if any search parameters are set through {@link SearchForm()}.
733
	 *
734
	 * @param array $params Search filter criteria
735
	 * @param int $parentID Optional parent node to filter on (can't be combined with other search criteria)
736
	 * @return SS_List
737
	 * @throws InvalidArgumentException if invalid filter class is passed.
738
	 */
739
	public function getList($params = array(), $parentID = 0) {
740
		if($filter = $this->getQueryFilter($params)) {
741
			return $filter->getFilteredPages();
742
		} else {
743
			$list = DataList::create($this->stat('tree_class'));
744
			$parentID = is_numeric($parentID) ? $parentID : 0;
745
			return $list->filter("ParentID", $parentID);
746
		}
747
	}
748
749
	public function ListViewForm() {
750
		$params = $this->getRequest()->requestVar('q');
751
		$list = $this->getList($params, $parentID = $this->getRequest()->requestVar('ParentID'));
752
		$gridFieldConfig = GridFieldConfig::create()->addComponents(
753
			new GridFieldSortableHeader(),
754
			new GridFieldDataColumns(),
755
			new GridFieldPaginator(self::config()->page_length)
756
		);
757
		if($parentID){
758
			$gridFieldConfig->addComponent(
759
				GridFieldLevelup::create($parentID)
760
					->setLinkSpec('?ParentID=%d&view=list')
761
					->setAttributes(array('data-pjax' => 'ListViewForm,Breadcrumbs'))
762
			);
763
		}
764
		$gridField = new GridField('Page','Pages', $list, $gridFieldConfig);
765
		$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
766
767
		// Don't allow navigating into children nodes on filtered lists
768
		$fields = array(
769
			'getTreeTitle' => _t('SiteTree.PAGETITLE', 'Page Title'),
770
			'singular_name' => _t('SiteTree.PAGETYPE'),
771
			'LastEdited' => _t('SiteTree.LASTUPDATED', 'Last Updated'),
772
		);
773
		$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...
774
		$gridField->getState()->ParentID = $parentID;
775
776
		if(!$params) {
777
			$fields = array_merge(array('listChildrenLink' => ''), $fields);
778
		}
779
780
		$columns->setDisplayFields($fields);
781
		$columns->setFieldCasting(array(
782
			'Created' => 'SS_Datetime->Ago',
783
			'LastEdited' => 'SS_Datetime->FormatFromSettings',
784
			'getTreeTitle' => 'HTMLText'
785
		));
786
787
		$controller = $this;
788
		$columns->setFieldFormatting(array(
789
			'listChildrenLink' => function($value, &$item) use($controller) {
790
				$num = $item ? $item->numChildren() : null;
791
				if($num) {
792
					return sprintf(
793
						'<a class="cms-panel-link list-children-link" data-pjax-target="ListViewForm,Breadcrumbs" href="%s">%s</a>',
794
						Controller::join_links(
795
							$controller->Link(),
796
							sprintf("?ParentID=%d&view=list", (int)$item->ID)
797
						),
798
						$num
799
					);
800
				}
801
			},
802
			'getTreeTitle' => function($value, &$item) use($controller) {
803
				return sprintf(
804
					'<a class="action-detail" href="%s">%s</a>',
805
					Controller::join_links(
806
						singleton('CMSPageEditController')->Link('show'),
807
						(int)$item->ID
808
					),
809
					$item->TreeTitle // returns HTML, does its own escaping
810
				);
811
			}
812
		));
813
814
		$negotiator = $this->getResponseNegotiator();
815
		$listview = Form::create(
816
			$this,
817
			'ListViewForm',
818
			new FieldList($gridField),
819
			new FieldList()
820
		)->setHTMLID('Form_ListViewForm');
821
		$listview->setAttribute('data-pjax-fragment', 'ListViewForm');
822 View Code Duplication
		$listview->setValidationResponseCallback(function() use ($negotiator, $listview) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
823
			$request = $this->getRequest();
824
			if($request->isAjax() && $negotiator) {
825
				$listview->setupFormErrors();
826
				$result = $listview->forTemplate();
827
828
				return $negotiator->respond($request, array(
829
					'CurrentForm' => function() use($result) {
830
						return $result;
831
					}
832
				));
833
			}
834
		});
835
836
		$this->extend('updateListView', $listview);
837
838
		$listview->disableSecurityToken();
839
		return $listview;
840
	}
841
842
	public function currentPageID() {
843
		$id = parent::currentPageID();
844
845
		$this->extend('updateCurrentPageID', $id);
846
847
		return $id;
848
	}
849
850
	//------------------------------------------------------------------------------------------//
851
	// Data saving handlers
852
853
	/**
854
	 * Save and Publish page handler
855
	 *
856
	 * @param array $data
857
	 * @param Form $form
858
	 * @return SS_HTTPResponse
859
	 * @throws SS_HTTPResponse_Exception
860
	 */
861
	public function save($data, $form) {
862
		$className = $this->stat('tree_class');
863
864
		// Existing or new record?
865
		$id = $data['ID'];
866
		if(substr($id,0,3) != 'new') {
867
			/** @var SiteTree $record */
868
			$record = DataObject::get_by_id($className, $id);
869
			// Check edit permissions
870
			if($record && !$record->canEdit()) {
871
				return Security::permissionFailure($this);
872
			}
873
			if(!$record || !$record->ID) {
874
				throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
875
			}
876
		} else {
877
			if(!$className::singleton()->canCreate()) {
878
				return Security::permissionFailure($this);
879
			}
880
			$record = $this->getNewItem($id, false);
881
		}
882
883
		// Check publishing permissions
884
		$doPublish = !empty($data['publish']);
885
		if($record && $doPublish && !$record->canPublish()) {
886
			return Security::permissionFailure($this);
887
		}
888
889
		// TODO Coupling to SiteTree
890
		$record->HasBrokenLink = 0;
891
		$record->HasBrokenFile = 0;
892
893
		if (!$record->ObsoleteClassName) {
894
			$record->writeWithoutVersion();
895
		}
896
897
		// Update the class instance if necessary
898
		if(isset($data['ClassName']) && $data['ClassName'] != $record->ClassName) {
899
			$newClassName = $record->ClassName;
900
			// The records originally saved attribute was overwritten by $form->saveInto($record) before.
901
			// This is necessary for newClassInstance() to work as expected, and trigger change detection
902
			// on the ClassName attribute
903
			$record->setClassName($data['ClassName']);
904
			// Replace $record with a new instance
905
			$record = $record->newClassInstance($newClassName);
906
		}
907
908
		// save form data into record
909
		$form->saveInto($record);
910
		$record->write();
911
912
		// If the 'Save & Publish' button was clicked, also publish the page
913
		if ($doPublish) {
914
			$record->publishRecursive();
915
			$message = _t(
916
				'CMSMain.PUBLISHED',
917
				"Published '{title}' successfully.",
918
				['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...
919
			);
920
		} else {
921
			$message = _t(
922
				'CMSMain.SAVED',
923
				"Saved '{title}' successfully.",
924
				['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...
925
			);
926
		}
927
928
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
929
		return $this->getResponseNegotiator()->respond($this->getRequest());
930
	}
931
932
	/**
933
	 * @uses LeftAndMainExtension->augmentNewSiteTreeItem()
934
	 */
935
	public function getNewItem($id, $setID = true) {
936
		$parentClass = $this->stat('tree_class');
937
		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...
938
939
		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...
940
			$response = Security::permissionFailure($this);
941
			if (!$response) {
942
				$response = $this->getResponse();
943
			}
944
			throw new SS_HTTPResponse_Exception($response);
945
		}
946
947
		$newItem = new $className();
948
949
		if( !$suffix ) {
950
			$sessionTag = "NewItems." . $parentID . "." . $className;
951
			if(Session::get($sessionTag)) {
952
				$suffix = '-' . Session::get($sessionTag);
953
				Session::set($sessionTag, Session::get($sessionTag) + 1);
954
			}
955
			else
956
				Session::set($sessionTag, 1);
957
958
				$id = $id . $suffix;
959
		}
960
961
		$newItem->Title = _t(
962
			'CMSMain.NEWPAGE',
963
			"New {pagetype}",'followed by a page type title',
964
			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...
965
		);
966
		$newItem->ClassName = $className;
967
		$newItem->ParentID = $parentID;
968
969
		// DataObject::fieldExists only checks the current class, not the hierarchy
970
		// This allows the CMS to set the correct sort value
971
		if($newItem->castingHelper('Sort')) {
972
			$newItem->Sort = DB::prepared_query('SELECT MAX("Sort") FROM "SiteTree" WHERE "ParentID" = ?', array($parentID))->value() + 1;
973
		}
974
975
		if($setID) $newItem->ID = $id;
976
977
		# Some modules like subsites add extra fields that need to be set when the new item is created
978
		$this->extend('augmentNewSiteTreeItem', $newItem);
979
980
		return $newItem;
981
	}
982
983
	/**
984
	 * Actually perform the publication step
985
	 *
986
	 * @param Versioned|DataObject $record
987
	 * @return mixed
988
	 */
989
	public function performPublish($record) {
990
		if($record && !$record->canPublish()) {
0 ignored issues
show
Bug introduced by
The method canPublish does only exist in Versioned, but not in DataObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
991
			return Security::permissionFailure($this);
992
		}
993
994
		$record->publishRecursive();
995
	}
996
997
	/**
998
	 * Reverts a page by publishing it to live.
999
	 * Use {@link restorepage()} if you want to restore a page
1000
	 * which was deleted from draft without publishing.
1001
	 *
1002
	 * @uses SiteTree->doRevertToLive()
1003
	 *
1004
	 * @param array $data
1005
	 * @param Form $form
1006
	 * @return SS_HTTPResponse
1007
	 * @throws SS_HTTPResponse_Exception
1008
	 */
1009
	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...
1010
		if(!isset($data['ID'])) {
1011
			throw new SS_HTTPResponse_Exception("Please pass an ID in the form content", 400);
1012
		}
1013
1014
		$id = (int) $data['ID'];
1015
		$restoredPage = Versioned::get_latest_version("SiteTree", $id);
1016
		if(!$restoredPage) {
1017
			throw new SS_HTTPResponse_Exception("SiteTree #$id not found", 400);
1018
		}
1019
1020
		/** @var SiteTree $record */
1021
		$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...
1022
			'"SiteTree_Live"."ID"' => $id
1023
		));
1024
1025
		// a user can restore a page without publication rights, as it just adds a new draft state
1026
		// (this action should just be available when page has been "deleted from draft")
1027
		if($record && !$record->canEdit()) {
1028
			return Security::permissionFailure($this);
1029
		}
1030
		if(!$record || !$record->ID) {
1031
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1032
		}
1033
1034
		$record->doRevertToLive();
0 ignored issues
show
Documentation Bug introduced by
The method doRevertToLive 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...
1035
1036
		$this->getResponse()->addHeader(
1037
			'X-Status',
1038
			rawurlencode(_t(
1039
				'CMSMain.RESTORED',
1040
				"Restored '{title}' successfully",
1041
				'Param %s is a title',
1042
				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...
1043
			))
1044
		);
1045
1046
		return $this->getResponseNegotiator()->respond($this->getRequest());
1047
	}
1048
1049
	/**
1050
	 * Delete the current page from draft stage.
1051
	 *
1052
	 * @see deletefromlive()
1053
	 *
1054
	 * @param array $data
1055
	 * @param Form $form
1056
	 * @return SS_HTTPResponse
1057
	 * @throws SS_HTTPResponse_Exception
1058
	 */
1059 View Code Duplication
	public function delete($data, $form) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1060
		$id = $data['ID'];
1061
		$record = DataObject::get_by_id("SiteTree", $id);
1062
		if($record && !$record->canDelete()) {
1063
			return Security::permissionFailure();
1064
		}
1065
		if(!$record || !$record->ID) {
1066
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1067
		}
1068
1069
		// Delete record
1070
		$record->delete();
1071
1072
		$this->getResponse()->addHeader(
1073
			'X-Status',
1074
			rawurlencode(sprintf(_t('CMSMain.REMOVEDPAGEFROMDRAFT',"Removed '%s' from the draft site"), $record->Title))
1075
		);
1076
1077
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1078
		return $this->getResponseNegotiator()->respond($this->getRequest());
1079
	}
1080
1081
	/**
1082
	 * Delete this page from both live and stage
1083
	 *
1084
	 * @param array $data
1085
	 * @param Form $form
1086
	 * @return SS_HTTPResponse
1087
	 * @throws SS_HTTPResponse_Exception
1088
	 */
1089 View Code Duplication
	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...
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...
1090
		$id = $data['ID'];
1091
		/** @var SiteTree $record */
1092
		$record = DataObject::get_by_id("SiteTree", $id);
1093
		if(!$record || !$record->exists()) {
1094
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1095
		}
1096
		if(!$record->canArchive()) {
0 ignored issues
show
Documentation Bug introduced by
The method canArchive 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...
1097
			return Security::permissionFailure();
1098
		}
1099
1100
		// Archive record
1101
		$record->doArchive();
0 ignored issues
show
Documentation Bug introduced by
The method doArchive 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...
1102
1103
		$this->getResponse()->addHeader(
1104
			'X-Status',
1105
			rawurlencode(sprintf(_t('CMSMain.ARCHIVEDPAGE',"Archived page '%s'"), $record->Title))
1106
		);
1107
1108
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1109
		return $this->getResponseNegotiator()->respond($this->getRequest());
1110
	}
1111
1112
	public function publish($data, $form) {
1113
		$data['publish'] = '1';
1114
1115
		return $this->save($data, $form);
1116
	}
1117
1118
	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...
1119
		$className = $this->stat('tree_class');
1120
		/** @var SiteTree $record */
1121
		$record = DataObject::get_by_id($className, $data['ID']);
1122
1123
		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...
1124
			return Security::permissionFailure($this);
1125
		}
1126 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...
1127
			throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
1128
		}
1129
1130
		$record->doUnpublish();
0 ignored issues
show
Documentation Bug introduced by
The method doUnpublish 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...
1131
1132
		$this->getResponse()->addHeader(
1133
			'X-Status',
1134
			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...
1135
		);
1136
1137
		return $this->getResponseNegotiator()->respond($this->getRequest());
1138
	}
1139
1140
	/**
1141
	 * @return array
1142
	 */
1143
	public function rollback() {
1144
		return $this->doRollback(array(
1145
			'ID' => $this->currentPageID(),
1146
			'Version' => $this->getRequest()->param('VersionID')
1147
		), null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<Form>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1148
	}
1149
1150
	/**
1151
	 * Rolls a site back to a given version ID
1152
	 *
1153
	 * @param array $data
1154
	 * @param Form $form
1155
	 * @return SS_HTTPResponse
1156
	 */
1157
	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...
1158
		$this->extend('onBeforeRollback', $data['ID']);
1159
1160
		$id = (isset($data['ID'])) ? (int) $data['ID'] : null;
1161
		$version = (isset($data['Version'])) ? (int) $data['Version'] : null;
1162
1163
		/** @var DataObject|Versioned $record */
1164
		$record = DataObject::get_by_id($this->stat('tree_class'), $id);
1165
		if($record && !$record->canEdit()) {
0 ignored issues
show
Bug introduced by
The call to canEdit() misses a required argument $member.

This check looks for function calls that miss required arguments.

Loading history...
1166
			return Security::permissionFailure($this);
1167
		}
1168
1169
		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...
1170
			$record->doRollbackTo($version);
0 ignored issues
show
Bug introduced by
The method doRollbackTo does only exist in Versioned, but not in DataObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1171
			$message = _t(
1172
				'CMSMain.ROLLEDBACKVERSIONv2',
1173
				"Rolled back to version #%d.",
1174
				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...
1175
			);
1176
		} else {
1177
			$record->doRollbackTo('Live');
1178
			$message = _t(
1179
				'CMSMain.ROLLEDBACKPUBv2',"Rolled back to published version."
1180
			);
1181
		}
1182
1183
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1184
1185
		// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
1186
		// Or in history view, in which case a revert causes the CMS to re-load the edit view.
1187
		// The X-Pjax header forces a "full" content refresh on redirect.
1188
		$url = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $record->ID);
1189
		$this->getResponse()->addHeader('X-ControllerURL', $url);
1190
		$this->getRequest()->addHeader('X-Pjax', 'Content');
1191
		$this->getResponse()->addHeader('X-Pjax', 'Content');
1192
1193
		return $this->getResponseNegotiator()->respond($this->getRequest());
1194
	}
1195
1196
	/**
1197
	 * Action handler for adding pages to a campaign
1198
	 *
1199
	 * @param array $data
1200
	 * @param Form $form
1201
	 * @return DBHTMLText|SS_HTTPResponse
1202
	 */
1203
	public function addtocampaign($data, $form) {
1204
		$handler = AddToCampaignHandler::create($form, $data);
1205
		return $handler->handle();
1206
	}
1207
1208
	/**
1209
	 * Batch Actions Handler
1210
	 */
1211
	public function batchactions() {
1212
		return new CMSBatchActionHandler($this, 'batchactions');
1213
	}
1214
1215
	public function BatchActionParameters() {
1216
		$batchActions = CMSBatchActionHandler::config()->batch_actions;
1217
1218
		$forms = array();
1219
		foreach($batchActions as $urlSegment => $batchAction) {
1220
			$SNG_action = singleton($batchAction);
1221
			if ($SNG_action->canView() && $fieldset = $SNG_action->getParameterFields()) {
1222
				$formHtml = '';
1223
				foreach($fieldset as $field) {
1224
					$formHtml .= $field->Field();
1225
				}
1226
				$forms[$urlSegment] = $formHtml;
1227
			}
1228
		}
1229
		$pageHtml = '';
1230
		foreach($forms as $urlSegment => $html) {
1231
			$pageHtml .= "<div class=\"params\" id=\"BatchActionParameters_$urlSegment\">$html</div>\n\n";
1232
		}
1233
		return new LiteralField("BatchActionParameters", '<div id="BatchActionParameters" style="display:none">'.$pageHtml.'</div>');
1234
	}
1235
	/**
1236
	 * Returns a list of batch actions
1237
	 */
1238
	public function BatchActionList() {
1239
		return $this->batchactions()->batchActionList();
1240
	}
1241
1242
	public function buildbrokenlinks($request) {
1243
		// Protect against CSRF on destructive action
1244
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1245
1246
		increase_time_limit_to();
1247
		increase_memory_limit_to();
1248
1249
		if($this->urlParams['ID']) {
1250
			$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...
1251
		} else {
1252
			$pages = DataObject::get("Page");
1253
			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...
1254
			$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...
1255
		}
1256
1257
		$content = new HtmlEditorField('Content');
1258
		$download = new HtmlEditorField('Download');
1259
1260
		foreach($newPageSet as $i => $page) {
1261
			$page->HasBrokenLink = 0;
1262
			$page->HasBrokenFile = 0;
1263
1264
			$content->setValue($page->Content);
1265
			$content->saveInto($page);
1266
1267
			$download->setValue($page->Download);
1268
			$download->saveInto($page);
1269
1270
			echo "<li>$page->Title (link:$page->HasBrokenLink, file:$page->HasBrokenFile)";
1271
1272
			$page->writeWithoutVersion();
1273
			$page->destroy();
1274
			$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...
1275
		}
1276
	}
1277
1278
	public function publishall($request) {
1279
		if(!Permission::check('ADMIN')) return Security::permissionFailure($this);
1280
1281
		increase_time_limit_to();
1282
		increase_memory_limit_to();
1283
1284
		$response = "";
1285
1286
		if(isset($this->requestParams['confirm'])) {
1287
			// Protect against CSRF on destructive action
1288
			if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1289
1290
			$start = 0;
1291
			$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
1292
			$count = 0;
1293
			while($pages) {
1294
				foreach($pages as $page) {
1295
					if($page && !$page->canPublish()) {
1296
						return Security::permissionFailure($this);
1297
					}
1298
1299
					$page->publishRecursive();
1300
					$page->destroy();
1301
					unset($page);
1302
					$count++;
1303
					$response .= "<li>$count</li>";
1304
				}
1305
				if($pages->Count() > 29) {
1306
					$start += 30;
1307
					$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
1308
				} else {
1309
					break;
1310
				}
1311
			}
1312
			$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...
1313
1314
		} else {
1315
			$token = SecurityToken::inst();
1316
			$fields = new FieldList();
1317
			$token->updateFieldSet($fields);
1318
			$tokenField = $fields->First();
1319
			$tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : '';
1320
			$response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
1321
				<p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish".  It\'s
1322
				intended to be used after there have been massive edits of the content, such as when the site was
1323
				first built.') . '</p>
1324
				<form method="post" action="publishall">
1325
					<input type="submit" name="confirm" value="'
1326
					. _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",'Confirmation button') .'" />'
1327
					. $tokenHtml .
1328
				'</form>';
1329
		}
1330
1331
		return $response;
1332
	}
1333
1334
	/**
1335
	 * Restore a completely deleted page from the SiteTree_versions table.
1336
	 */
1337
	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...
1338
		if(!isset($data['ID']) || !is_numeric($data['ID'])) {
1339
			return new SS_HTTPResponse("Please pass an ID in the form content", 400);
1340
		}
1341
1342
		$id = (int)$data['ID'];
1343
		$restoredPage = Versioned::get_latest_version("SiteTree", $id);
1344
		if(!$restoredPage) 	return new SS_HTTPResponse("SiteTree #$id not found", 400);
1345
1346
		$restoredPage = $restoredPage->doRestoreToStage();
1347
1348
		$this->getResponse()->addHeader(
1349
			'X-Status',
1350
			rawurlencode(_t(
1351
				'CMSMain.RESTORED',
1352
				"Restored '{title}' successfully",
1353
				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...
1354
			))
1355
		);
1356
1357
		return $this->getResponseNegotiator()->respond($this->getRequest());
1358
	}
1359
1360
	public function duplicate($request) {
1361
		// Protect against CSRF on destructive action
1362
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1363
1364
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1365
			$page = DataObject::get_by_id("SiteTree", $id);
1366 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...
1367
				return Security::permissionFailure($this);
1368
			}
1369
			if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1370
1371
			$newPage = $page->duplicate();
1372
1373
			// ParentID can be hard-set in the URL.  This is useful for pages with multiple parents
1374
			if(isset($_GET['parentID']) && is_numeric($_GET['parentID'])) {
1375
				$newPage->ParentID = $_GET['parentID'];
1376
				$newPage->write();
1377
			}
1378
1379
			$this->getResponse()->addHeader(
1380
				'X-Status',
1381
				rawurlencode(_t(
1382
					'CMSMain.DUPLICATED',
1383
					"Duplicated '{title}' successfully",
1384
					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...
1385
				))
1386
			);
1387
			$url = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $newPage->ID);
1388
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1389
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1390
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1391
1392
			return $this->getResponseNegotiator()->respond($this->getRequest());
1393
		} else {
1394
			return new SS_HTTPResponse("CMSMain::duplicate() Bad ID: '$id'", 400);
1395
		}
1396
	}
1397
1398
	public function duplicatewithchildren($request) {
1399
		// Protect against CSRF on destructive action
1400
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1401
		increase_time_limit_to();
1402
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1403
			$page = DataObject::get_by_id("SiteTree", $id);
1404 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...
1405
				return Security::permissionFailure($this);
1406
			}
1407
			if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1408
1409
			$newPage = $page->duplicateWithChildren();
1410
1411
			$this->getResponse()->addHeader(
1412
				'X-Status',
1413
				rawurlencode(_t(
1414
					'CMSMain.DUPLICATEDWITHCHILDREN',
1415
					"Duplicated '{title}' and children successfully",
1416
					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...
1417
				))
1418
			);
1419
			$url = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $newPage->ID);
1420
			$this->getResponse()->addHeader('X-ControllerURL', $url);
1421
			$this->getRequest()->addHeader('X-Pjax', 'Content');
1422
			$this->getResponse()->addHeader('X-Pjax', 'Content');
1423
1424
			return $this->getResponseNegotiator()->respond($this->getRequest());
1425
		} else {
1426
			return new SS_HTTPResponse("CMSMain::duplicatewithchildren() Bad ID: '$id'", 400);
1427
		}
1428
	}
1429
1430
	public function providePermissions() {
1431
		$title = _t("CMSPagesController.MENUTITLE", LeftAndMain::menu_title_for_class('CMSPagesController'));
1432
		return array(
1433
			"CMS_ACCESS_CMSMain" => array(
1434
				'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...
1435
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
1436
				'help' => _t(
1437
					'CMSMain.ACCESS_HELP',
1438
					'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".'
1439
				),
1440
				'sort' => -99 // below "CMS_ACCESS_LeftAndMain", but above everything else
1441
			)
1442
		);
1443
	}
1444
1445
}
1446