Completed
Pull Request — master (#1556)
by Damian
02:27
created

CMSMain::publishall()   C

Complexity

Conditions 10
Paths 7

Size

Total Lines 55
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 55
rs 6.8372
cc 10
eloc 38
nc 7
nop 1

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
use SilverStripe\ORM\FieldType\DBHTMLText;
4
use SilverStripe\ORM\Versioning\Versioned;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Versioned.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
5
use SilverStripe\ORM\HiddenClass;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, HiddenClass.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
6
use SilverStripe\ORM\ArrayList;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, ArrayList.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
7
use SilverStripe\ORM\DataObject;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, DataObject.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use SilverStripe\ORM\DataList;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, DataList.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
9
use SilverStripe\ORM\DB;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, DB.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use SilverStripe\Security\Member;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Member.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
11
use SilverStripe\Security\Security;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Security.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
12
use SilverStripe\Security\SecurityToken;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, SecurityToken.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
13
use SilverStripe\Security\Permission;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Permission.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
14
use SilverStripe\Security\PermissionProvider;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, PermissionProvider.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
15
16
17
18
/**
19
 * The main "content" area of the CMS.
20
 *
21
 * This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main
22
 * admin menu.
23
 *
24
 * @package cms
25
 * @subpackage controller
26
 * @todo Create some base classes to contain the generic functionality that will be replicated.
27
 */
28
class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionProvider {
29
30
	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...
31
32
	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...
33
34
	// Maintain a lower priority than other administration sections
35
	// so that Director does not think they are actions of CMSMain
36
	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...
37
38
	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...
39
40
	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...
41
42
	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...
43
44
	private static $subitem_class = "SilverStripe\\Security\\Member";
45
46
	/**
47
	 * Amount of results showing on a single page.
48
	 *
49
	 * @config
50
	 * @var int
51
	 */
52
	private static $page_length = 15;
53
54
	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...
55
		'archive',
56
		'deleteitems',
57
		'DeleteItemsForm',
58
		'dialog',
59
		'duplicate',
60
		'duplicatewithchildren',
61
		'publishall',
62
		'publishitems',
63
		'PublishItemsForm',
64
		'submit',
65
		'EditForm',
66
		'SearchForm',
67
		'SiteTreeAsUL',
68
		'getshowdeletedsubtree',
69
		'batchactions',
70
		'treeview',
71
		'listview',
72
		'ListViewForm',
73
		'childfilter',
74
	);
75
76
	private static $casting = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
77
		'TreeIsFiltered' => 'Boolean',
78
		'AddForm' => 'HTMLFragment',
79
		'LinkPages' => 'Text',
80
		'Link' => 'Text',
81
		'ListViewForm' => 'HTMLFragment',
82
		'ExtraTreeTools' => 'HTMLFragment',
83
		'SiteTreeHints' => 'HTMLFragment',
84
		'SecurityID' => 'Text',
85
		'SiteTreeAsUL' => 'HTMLFragment',
86
	);
87
88
	public function init() {
89
		// set reading lang
90
		if(SiteTree::has_extension('Translatable') && !$this->getRequest()->isAjax()) {
91
			Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SiteTree')));
92
		}
93
94
		parent::init();
95
96
		Requirements::css(CMS_DIR . '/client/dist/styles/bundle.css');
97
		Requirements::customCSS($this->generatePageIconsCss());
98
		Requirements::add_i18n_javascript(CMS_DIR . '/client/lang', false, true);
99
		Requirements::javascript(CMS_DIR . '/client/dist/js/bundle-legacy.js', [
100
			'provides' => [
101
				CMS_DIR . '/client/dist/js/CMSMain.AddForm.js',
102
				CMS_DIR . '/client/dist/js/CMSMain.EditForm.js',
103
				CMS_DIR . '/client/dist/js/CMSMain.js',
104
				CMS_DIR . '/client/dist/js/CMSMain.Tree.js',
105
				CMS_DIR . '/client/dist/js/CMSPageHistoryController.js',
106
				CMS_DIR . '/client/dist/js/RedirectorPage.js',
107
				CMS_DIR . '/client/dist/js/SilverStripeNavigator.js',
108
				CMS_DIR . '/client/dist/js/SiteTreeURLSegmentField.js'
109
			]
110
		]);
111
112
		CMSBatchActionHandler::register('publish', 'CMSBatchAction_Publish');
113
		CMSBatchActionHandler::register('unpublish', 'CMSBatchAction_Unpublish');
114
		CMSBatchActionHandler::register('delete', 'CMSBatchAction_Delete');
115
		CMSBatchActionHandler::register('archive', 'CMSBatchAction_Archive');
116
		CMSBatchActionHandler::register('restore', 'CMSBatchAction_Restore');
117
	}
118
119
	public function index($request) {
120
		// In case we're not showing a specific record, explicitly remove any session state,
121
		// to avoid it being highlighted in the tree, and causing an edit form to show.
122
		if(!$request->param('Action')) $this->setCurrentPageId(null);
123
124
		return parent::index($request);
125
	}
126
127
	public function getResponseNegotiator() {
128
		$negotiator = parent::getResponseNegotiator();
129
		$controller = $this;
130
		$negotiator->setCallback('ListViewForm', function() use(&$controller) {
131
			return $controller->ListViewForm()->forTemplate();
132
		});
133
		return $negotiator;
134
	}
135
136
	/**
137
	 * If this is set to true, the "switchView" context in the
138
	 * template is shown, with links to the staging and publish site.
139
	 *
140
	 * @return boolean
141
	 */
142
	public function ShowSwitchView() {
143
		return true;
144
	}
145
146
	/**
147
	 * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
148
	 * to switch view also for archived versions.
149
	 */
150
	public function SwitchView($page = null) {
151
		if(!$page) {
152
			$page = $this->currentPage();
153
		}
154
155
		if($page) {
156
			$nav = SilverStripeNavigator::get_for_record($page);
157
			return $nav['items'];
158
		}
159
	}
160
161
	//------------------------------------------------------------------------------------------//
162
	// Main controllers
163
164
	//------------------------------------------------------------------------------------------//
165
	// Main UI components
166
167
	/**
168
	 * Override {@link LeftAndMain} Link to allow blank URL segment for CMSMain.
169
	 *
170
	 * @param string|null $action Action to link to.
171
	 * @return string
172
	 */
173
	public function Link($action = null) {
174
		$link = Controller::join_links(
175
			AdminRootController::admin_url(),
0 ignored issues
show
Bug introduced by
The method admin_url() does not seem to exist on object<AdminRootController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
620
621
			if($record->ID && is_numeric( $record->ID ) ) {
622
				$liveLink = $record->getAbsoluteLiveLink();
623
				if($liveLink) $liveLinkField->setValue($liveLink);
624
				if(!$deletedFromStage) {
625
					$stageLink = Controller::join_links($record->AbsoluteLink(), '?stage=Stage');
626
					if($stageLink) $stageLinkField->setValue($stageLink);
627
				}
628
			}
629
630
			// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
631
			if($record instanceof CMSPreviewable && !$fields->fieldByName('SilverStripeNavigator')) {
632
				$navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator());
633
				$navField->setAllowHTML(true);
634
				$fields->push($navField);
635
			}
636
637
			// getAllCMSActions can be used to completely redefine the action list
638
			if($record->hasMethod('getAllCMSActions')) {
639
				$actions = $record->getAllCMSActions();
640
			} else {
641
				$actions = $record->getCMSActions();
642
643
				// Find and remove action menus that have no actions.
644
				if ($actions && $actions->Count()) {
645
					$tabset = $actions->fieldByName('ActionMenus');
646
					if ($tabset) {
647
						foreach ($tabset->getChildren() as $tab) {
648
							if (!$tab->getChildren()->count()) {
649
								$tabset->removeByName($tab->getName());
650
							}
651
						}
652
					}
653
				}
654
			}
655
656
			// Use <button> to allow full jQuery UI styling
657
			$actionsFlattened = $actions->dataFields();
658
			if($actionsFlattened) foreach($actionsFlattened as $action) $action->setUseButtonTag(true);
659
660
			if($record->hasMethod('getCMSValidator')) {
661
				$validator = $record->getCMSValidator();
0 ignored issues
show
Unused Code introduced by
$validator is not used, you could remove the assignment.

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

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

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

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

Loading history...
662
			} else {
663
				$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...
664
			}
665
666
			// TODO Can't merge $FormAttributes in template at the moment
667
			$form->addExtraClass('center ' . $this->BaseCSSClasses());
668
			// Set validation exemptions for specific actions
669
			$form->setValidationExemptActions(array('restore', 'revert', 'deletefromlive', 'delete', 'unpublish', 'rollback', 'doRollback'));
670
671
			// Announce the capability so the frontend can decide whether to allow preview or not.
672
			if(in_array('CMSPreviewable', class_implements($record))) {
673
				$form->addExtraClass('cms-previewable');
674
			}
675
676
			if(!$record->canEdit() || $deletedFromStage) {
677
				$readonlyFields = $form->Fields()->makeReadonly();
678
				$form->setFields($readonlyFields);
679
			}
680
681
			$form->Fields()->setForm($form);
682
683
			$this->extend('updateEditForm', $form);
684
			return $form;
685
		} else if($id) {
686
			$form = Form::create( $this, "EditForm", new FieldList(
687
				new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList()
688
			)->setHTMLID('Form_EditForm');
689
			return $form;
690
		}
691
	}
692
693
	/**
694
	 * @param SS_HTTPRequest $request
695
	 * @return string HTML
696
	 */
697
	public function treeview($request) {
698
		return $this->renderWith($this->getTemplatesWithSuffix('_TreeView'));
699
	}
700
701
	/**
702
	 * @param SS_HTTPRequest $request
703
	 * @return string HTML
704
	 */
705
	public function listview($request) {
706
		return $this->renderWith($this->getTemplatesWithSuffix('_ListView'));
707
	}
708
709
	/**
710
	 * Callback to request the list of page types allowed under a given page instance.
711
	 * Provides a slower but more precise response over SiteTreeHints
712
	 *
713
	 * @param SS_HTTPRequest $request
714
	 * @return SS_HTTPResponse
715
	 */
716
	public function childfilter($request) {
717
		// Check valid parent specified
718
		$parentID = $request->requestVar('ParentID');
719
		$parent = SiteTree::get()->byID($parentID);
720
		if(!$parent || !$parent->exists()) return $this->httpError(404);
721
722
		// Build hints specific to this class
723
		// Identify disallows and set globals
724
		$classes = SiteTree::page_type_classes();
725
		$disallowedChildren = array();
726
		foreach($classes as $class) {
727
			$obj = singleton($class);
728
			if($obj instanceof HiddenClass) continue;
0 ignored issues
show
Bug introduced by
The class SilverStripe\ORM\HiddenClass 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...
729
730
			if(!$obj->canCreate(null, array('Parent' => $parent))) {
731
				$disallowedChildren[] = $class;
732
			}
733
		}
734
735
		$this->extend('updateChildFilter', $disallowedChildren, $parentID);
736
		return $this
737
			->getResponse()
738
			->addHeader('Content-Type', 'application/json; charset=utf-8')
739
			->setBody(Convert::raw2json($disallowedChildren));
740
	}
741
742
	/**
743
	 * Safely reconstruct a selected filter from a given set of query parameters
744
	 *
745
	 * @param array $params Query parameters to use
746
	 * @return CMSSiteTreeFilter The filter class, or null if none present
747
	 * @throws InvalidArgumentException if invalid filter class is passed.
748
	 */
749
	protected function getQueryFilter($params) {
750
		if(empty($params['FilterClass'])) return null;
751
		$filterClass = $params['FilterClass'];
752
		if(!is_subclass_of($filterClass, 'CMSSiteTreeFilter')) {
753
			throw new InvalidArgumentException("Invalid filter class passed: {$filterClass}");
754
		}
755
		return $filterClass::create($params);
756
	}
757
758
	/**
759
	 * Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page
760
	 * defaulting to no filter and show all pages in first level.
761
	 * Doubles as search results, if any search parameters are set through {@link SearchForm()}.
762
	 *
763
	 * @param array $params Search filter criteria
764
	 * @param int $parentID Optional parent node to filter on (can't be combined with other search criteria)
765
	 * @return SS_List
766
	 * @throws InvalidArgumentException if invalid filter class is passed.
767
	 */
768
	public function getList($params = array(), $parentID = 0) {
769
		if($filter = $this->getQueryFilter($params)) {
770
			return $filter->getFilteredPages();
771
		} else {
772
			$list = DataList::create($this->stat('tree_class'));
773
			$parentID = is_numeric($parentID) ? $parentID : 0;
774
			return $list->filter("ParentID", $parentID);
775
		}
776
	}
777
778
	public function ListViewForm() {
779
		$params = $this->getRequest()->requestVar('q');
780
		$list = $this->getList($params, $parentID = $this->getRequest()->requestVar('ParentID'));
781
		$gridFieldConfig = GridFieldConfig::create()->addComponents(
782
			new GridFieldSortableHeader(),
783
			new GridFieldDataColumns(),
784
			new GridFieldPaginator(self::config()->page_length)
785
		);
786
		if($parentID){
787
			$gridFieldConfig->addComponent(
788
				GridFieldLevelup::create($parentID)
789
					->setLinkSpec('?ParentID=%d&view=list')
790
					->setAttributes(array('data-pjax' => 'ListViewForm,Breadcrumbs'))
791
			);
792
		}
793
		$gridField = new GridField('Page','Pages', $list, $gridFieldConfig);
794
		$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
795
796
		// Don't allow navigating into children nodes on filtered lists
797
		$fields = array(
798
			'getTreeTitle' => _t('SiteTree.PAGETITLE', 'Page Title'),
799
			'singular_name' => _t('SiteTree.PAGETYPE'),
800
			'LastEdited' => _t('SiteTree.LASTUPDATED', 'Last Updated'),
801
		);
802
		$gridField->getConfig()->getComponentByType('GridFieldSortableHeader')->setFieldSorting(array('getTreeTitle' => 'Title'));
803
		$gridField->getState()->ParentID = $parentID;
804
805
		if(!$params) {
806
			$fields = array_merge(array('listChildrenLink' => ''), $fields);
807
		}
808
809
		$columns->setDisplayFields($fields);
810
		$columns->setFieldCasting(array(
811
			'Created' => 'DBDatetime->Ago',
812
			'LastEdited' => 'DBDatetime->FormatFromSettings',
813
			'getTreeTitle' => 'HTMLFragment'
814
		));
815
816
		$controller = $this;
817
		$columns->setFieldFormatting(array(
818
			'listChildrenLink' => function($value, &$item) use($controller) {
819
				$num = $item ? $item->numChildren() : null;
820
				if($num) {
821
					return sprintf(
822
						'<a class="cms-panel-link list-children-link" data-pjax-target="ListViewForm,Breadcrumbs" href="%s">%s</a>',
823
						Controller::join_links(
824
							$controller->Link(),
825
							sprintf("?ParentID=%d&view=list", (int)$item->ID)
826
						),
827
						$num
828
					);
829
				}
830
			},
831
			'getTreeTitle' => function($value, &$item) use($controller) {
832
				return sprintf(
833
					'<a class="action-detail" href="%s">%s</a>',
834
					Controller::join_links(
835
						singleton('CMSPageEditController')->Link('show'),
836
						(int)$item->ID
837
					),
838
					$item->TreeTitle // returns HTML, does its own escaping
839
				);
840
			}
841
		));
842
843
		$negotiator = $this->getResponseNegotiator();
844
		$listview = Form::create(
845
			$this,
846
			'ListViewForm',
847
			new FieldList($gridField),
848
			new FieldList()
849
		)->setHTMLID('Form_ListViewForm');
850
		$listview->setAttribute('data-pjax-fragment', 'ListViewForm');
851 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...
852
			$request = $this->getRequest();
853
			if($request->isAjax() && $negotiator) {
854
				$listview->setupFormErrors();
855
				$result = $listview->forTemplate();
856
857
				return $negotiator->respond($request, array(
858
					'CurrentForm' => function() use($result) {
859
						return $result;
860
					}
861
				));
862
			}
863
		});
864
865
		$this->extend('updateListView', $listview);
866
867
		$listview->disableSecurityToken();
868
		return $listview;
869
	}
870
871
	public function currentPageID() {
872
		$id = parent::currentPageID();
873
874
		$this->extend('updateCurrentPageID', $id);
875
876
		return $id;
877
	}
878
879
	//------------------------------------------------------------------------------------------//
880
	// Data saving handlers
881
882
	/**
883
	 * Save and Publish page handler
884
	 *
885
	 * @param array $data
886
	 * @param Form $form
887
	 * @return SS_HTTPResponse
888
	 * @throws SS_HTTPResponse_Exception
889
	 */
890
	public function save($data, $form) {
891
		$className = $this->stat('tree_class');
892
893
		// Existing or new record?
894
		$id = $data['ID'];
895
		if(substr($id,0,3) != 'new') {
896
			/** @var SiteTree $record */
897
			$record = DataObject::get_by_id($className, $id);
898
			// Check edit permissions
899
			if($record && !$record->canEdit()) {
900
				return Security::permissionFailure($this);
901
			}
902
			if(!$record || !$record->ID) {
903
				throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
904
			}
905
		} else {
906
			if(!$className::singleton()->canCreate()) {
907
				return Security::permissionFailure($this);
908
			}
909
			$record = $this->getNewItem($id, false);
910
		}
911
912
		// Check publishing permissions
913
		$doPublish = !empty($data['publish']);
914
		if($record && $doPublish && !$record->canPublish()) {
915
			return Security::permissionFailure($this);
916
		}
917
918
		// TODO Coupling to SiteTree
919
		$record->HasBrokenLink = 0;
920
		$record->HasBrokenFile = 0;
921
922
		if (!$record->ObsoleteClassName) {
923
			$record->writeWithoutVersion();
924
		}
925
926
		// Update the class instance if necessary
927
		if(isset($data['ClassName']) && $data['ClassName'] != $record->ClassName) {
928
			$newClassName = $record->ClassName;
929
			// The records originally saved attribute was overwritten by $form->saveInto($record) before.
930
			// This is necessary for newClassInstance() to work as expected, and trigger change detection
931
			// on the ClassName attribute
932
			$record->setClassName($data['ClassName']);
933
			// Replace $record with a new instance
934
			$record = $record->newClassInstance($newClassName);
935
		}
936
937
		// save form data into record
938
		$form->saveInto($record);
939
		$record->write();
940
941
		// If the 'Save & Publish' button was clicked, also publish the page
942
		if ($doPublish) {
943
			$record->publishRecursive();
944
			$message = _t(
945
				'CMSMain.PUBLISHED',
946
				"Published '{title}' successfully.",
947
				['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...
948
			);
949
		} else {
950
			$message = _t(
951
				'CMSMain.SAVED',
952
				"Saved '{title}' successfully.",
953
				['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...
954
			);
955
		}
956
957
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
958
		return $this->getResponseNegotiator()->respond($this->getRequest());
959
	}
960
961
	/**
962
	 * @uses LeftAndMainExtension->augmentNewSiteTreeItem()
963
	 */
964
	public function getNewItem($id, $setID = true) {
965
		$parentClass = $this->stat('tree_class');
966
		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...
967
968
		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...
969
			$response = Security::permissionFailure($this);
970
			if (!$response) {
971
				$response = $this->getResponse();
972
			}
973
			throw new SS_HTTPResponse_Exception($response);
974
		}
975
976
		$newItem = new $className();
977
978
		if( !$suffix ) {
979
			$sessionTag = "NewItems." . $parentID . "." . $className;
980
			if(Session::get($sessionTag)) {
981
				$suffix = '-' . Session::get($sessionTag);
982
				Session::set($sessionTag, Session::get($sessionTag) + 1);
983
			}
984
			else
985
				Session::set($sessionTag, 1);
986
987
				$id = $id . $suffix;
988
		}
989
990
		$newItem->Title = _t(
991
			'CMSMain.NEWPAGE',
992
			"New {pagetype}",'followed by a page type title',
993
			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...
994
		);
995
		$newItem->ClassName = $className;
996
		$newItem->ParentID = $parentID;
997
998
		// DataObject::fieldExists only checks the current class, not the hierarchy
999
		// This allows the CMS to set the correct sort value
1000
		if($newItem->castingHelper('Sort')) {
1001
			$newItem->Sort = DB::prepared_query('SELECT MAX("Sort") FROM "SiteTree" WHERE "ParentID" = ?', array($parentID))->value() + 1;
1002
		}
1003
1004
		if($setID) $newItem->ID = $id;
1005
1006
		# Some modules like subsites add extra fields that need to be set when the new item is created
1007
		$this->extend('augmentNewSiteTreeItem', $newItem);
1008
1009
		return $newItem;
1010
	}
1011
1012
	/**
1013
	 * Actually perform the publication step
1014
	 *
1015
	 * @param Versioned|DataObject $record
1016
	 * @return mixed
1017
	 */
1018
	public function performPublish($record) {
1019
		if($record && !$record->canPublish()) {
1020
			return Security::permissionFailure($this);
1021
		}
1022
1023
		$record->publishRecursive();
1024
	}
1025
1026
	/**
1027
	 * Reverts a page by publishing it to live.
1028
	 * Use {@link restorepage()} if you want to restore a page
1029
	 * which was deleted from draft without publishing.
1030
	 *
1031
	 * @uses SiteTree->doRevertToLive()
1032
	 *
1033
	 * @param array $data
1034
	 * @param Form $form
1035
	 * @return SS_HTTPResponse
1036
	 * @throws SS_HTTPResponse_Exception
1037
	 */
1038
	public function revert($data, $form) {
1039
		if(!isset($data['ID'])) {
1040
			throw new SS_HTTPResponse_Exception("Please pass an ID in the form content", 400);
1041
		}
1042
1043
		$id = (int) $data['ID'];
1044
		$restoredPage = Versioned::get_latest_version("SiteTree", $id);
1045
		if(!$restoredPage) {
1046
			throw new SS_HTTPResponse_Exception("SiteTree #$id not found", 400);
1047
		}
1048
1049
		/** @var SiteTree $record */
1050
		$record = Versioned::get_one_by_stage('SiteTree', 'Live', array(
1051
			'"SiteTree_Live"."ID"' => $id
1052
		));
1053
1054
		// a user can restore a page without publication rights, as it just adds a new draft state
1055
		// (this action should just be available when page has been "deleted from draft")
1056
		if($record && !$record->canEdit()) {
1057
			return Security::permissionFailure($this);
1058
		}
1059
		if(!$record || !$record->ID) {
1060
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1061
		}
1062
1063
		$record->doRevertToLive();
1064
1065
		$this->getResponse()->addHeader(
1066
			'X-Status',
1067
			rawurlencode(_t(
1068
				'CMSMain.RESTORED',
1069
				"Restored '{title}' successfully",
1070
				'Param %s is a title',
1071
				array('title' => $record->Title)
0 ignored issues
show
Documentation introduced by
array('title' => $record->Title) is of type array<string,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...
1072
			))
1073
		);
1074
1075
		return $this->getResponseNegotiator()->respond($this->getRequest());
1076
	}
1077
1078
	/**
1079
	 * Delete the current page from draft stage.
1080
	 *
1081
	 * @see deletefromlive()
1082
	 *
1083
	 * @param array $data
1084
	 * @param Form $form
1085
	 * @return SS_HTTPResponse
1086
	 * @throws SS_HTTPResponse_Exception
1087
	 */
1088 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...
1089
		$id = $data['ID'];
1090
		$record = DataObject::get_by_id("SiteTree", $id);
1091
		if($record && !$record->canDelete()) {
1092
			return Security::permissionFailure();
1093
		}
1094
		if(!$record || !$record->ID) {
1095
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1096
		}
1097
1098
		// Delete record
1099
		$record->delete();
1100
1101
		$this->getResponse()->addHeader(
1102
			'X-Status',
1103
			rawurlencode(sprintf(_t('CMSMain.REMOVEDPAGEFROMDRAFT',"Removed '%s' from the draft site"), $record->Title))
1104
		);
1105
1106
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1107
		return $this->getResponseNegotiator()->respond($this->getRequest());
1108
	}
1109
1110
	/**
1111
	 * Delete this page from both live and stage
1112
	 *
1113
	 * @param array $data
1114
	 * @param Form $form
1115
	 * @return SS_HTTPResponse
1116
	 * @throws SS_HTTPResponse_Exception
1117
	 */
1118 View Code Duplication
	public function archive($data, $form) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1119
		$id = $data['ID'];
1120
		/** @var SiteTree $record */
1121
		$record = DataObject::get_by_id("SiteTree", $id);
1122
		if(!$record || !$record->exists()) {
1123
			throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);
1124
		}
1125
		if(!$record->canArchive()) {
1126
			return Security::permissionFailure();
1127
		}
1128
1129
		// Archive record
1130
		$record->doArchive();
1131
1132
		$this->getResponse()->addHeader(
1133
			'X-Status',
1134
			rawurlencode(sprintf(_t('CMSMain.ARCHIVEDPAGE',"Archived page '%s'"), $record->Title))
1135
		);
1136
1137
		// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
1138
		return $this->getResponseNegotiator()->respond($this->getRequest());
1139
	}
1140
1141
	public function publish($data, $form) {
1142
		$data['publish'] = '1';
1143
1144
		return $this->save($data, $form);
1145
	}
1146
1147
	public function unpublish($data, $form) {
1148
		$className = $this->stat('tree_class');
1149
		/** @var SiteTree $record */
1150
		$record = DataObject::get_by_id($className, $data['ID']);
1151
1152
		if($record && !$record->canUnpublish()) {
1153
			return Security::permissionFailure($this);
1154
		}
1155 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...
1156
			throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
1157
		}
1158
1159
		$record->doUnpublish();
1160
1161
		$this->getResponse()->addHeader(
1162
			'X-Status',
1163
			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...
1164
		);
1165
1166
		return $this->getResponseNegotiator()->respond($this->getRequest());
1167
	}
1168
1169
	/**
1170
	 * @return array
1171
	 */
1172
	public function rollback() {
1173
		return $this->doRollback(array(
1174
			'ID' => $this->currentPageID(),
1175
			'Version' => $this->getRequest()->param('VersionID')
1176
		), null);
1177
	}
1178
1179
	/**
1180
	 * Rolls a site back to a given version ID
1181
	 *
1182
	 * @param array $data
1183
	 * @param Form $form
1184
	 * @return SS_HTTPResponse
1185
	 */
1186
	public function doRollback($data, $form) {
1187
		$this->extend('onBeforeRollback', $data['ID']);
1188
1189
		$id = (isset($data['ID'])) ? (int) $data['ID'] : null;
1190
		$version = (isset($data['Version'])) ? (int) $data['Version'] : null;
1191
1192
		/** @var DataObject|Versioned $record */
1193
		$record = DataObject::get_by_id($this->stat('tree_class'), $id);
1194
		if($record && !$record->canEdit()) {
1195
			return Security::permissionFailure($this);
1196
		}
1197
1198
		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...
1199
			$record->doRollbackTo($version);
1200
			$message = _t(
1201
				'CMSMain.ROLLEDBACKVERSIONv2',
1202
				"Rolled back to version #%d.",
1203
				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...
1204
			);
1205
		} else {
1206
			$record->doRevertToLive();
1207
			$message = _t(
1208
				'CMSMain.ROLLEDBACKPUBv2',"Rolled back to published version."
1209
			);
1210
		}
1211
1212
		$this->getResponse()->addHeader('X-Status', rawurlencode($message));
1213
1214
		// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
1215
		// Or in history view, in which case a revert causes the CMS to re-load the edit view.
1216
		// The X-Pjax header forces a "full" content refresh on redirect.
1217
		$url = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $record->ID);
1218
		$this->getResponse()->addHeader('X-ControllerURL', $url);
1219
		$this->getRequest()->addHeader('X-Pjax', 'Content');
1220
		$this->getResponse()->addHeader('X-Pjax', 'Content');
1221
1222
		return $this->getResponseNegotiator()->respond($this->getRequest());
1223
	}
1224
1225
	/**
1226
	 * Action handler for adding pages to a campaign
1227
	 *
1228
	 * @param array $data
1229
	 * @param Form $form
1230
	 * @return DBHTMLText|SS_HTTPResponse
1231
	 */
1232
	public function addtocampaign($data, $form) {
1233
		$handler = AddToCampaignHandler::create($form, $data);
1234
		return $handler->handle();
1235
	}
1236
1237
	/**
1238
	 * Batch Actions Handler
1239
	 */
1240
	public function batchactions() {
1241
		return new CMSBatchActionHandler($this, 'batchactions');
1242
	}
1243
1244
	public function BatchActionParameters() {
1245
		$batchActions = CMSBatchActionHandler::config()->batch_actions;
1246
1247
		$forms = array();
1248
		foreach($batchActions as $urlSegment => $batchAction) {
1249
			$SNG_action = singleton($batchAction);
1250
			if ($SNG_action->canView() && $fieldset = $SNG_action->getParameterFields()) {
1251
				$formHtml = '';
1252
				foreach($fieldset as $field) {
1253
					$formHtml .= $field->Field();
1254
				}
1255
				$forms[$urlSegment] = $formHtml;
1256
			}
1257
		}
1258
		$pageHtml = '';
1259
		foreach($forms as $urlSegment => $html) {
1260
			$pageHtml .= "<div class=\"params\" id=\"BatchActionParameters_$urlSegment\">$html</div>\n\n";
1261
		}
1262
		return new LiteralField("BatchActionParameters", '<div id="BatchActionParameters" style="display:none">'.$pageHtml.'</div>');
1263
	}
1264
	/**
1265
	 * Returns a list of batch actions
1266
	 */
1267
	public function BatchActionList() {
1268
		return $this->batchactions()->batchActionList();
1269
	}
1270
1271
	public function publishall($request) {
1272
		if(!Permission::check('ADMIN')) return Security::permissionFailure($this);
1273
1274
		increase_time_limit_to();
1275
		increase_memory_limit_to();
1276
1277
		$response = "";
1278
1279
		if(isset($this->requestParams['confirm'])) {
1280
			// Protect against CSRF on destructive action
1281
			if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1282
1283
			$start = 0;
1284
			$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
1285
			$count = 0;
1286
			while($pages) {
1287
				foreach($pages as $page) {
1288
					if($page && !$page->canPublish()) {
1289
						return Security::permissionFailure($this);
1290
					}
1291
1292
					$page->publishRecursive();
1293
					$page->destroy();
1294
					unset($page);
1295
					$count++;
1296
					$response .= "<li>$count</li>";
1297
				}
1298
				if($pages->Count() > 29) {
1299
					$start += 30;
1300
					$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
1301
				} else {
1302
					break;
1303
				}
1304
			}
1305
			$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...
1306
1307
		} else {
1308
			$token = SecurityToken::inst();
1309
			$fields = new FieldList();
1310
			$token->updateFieldSet($fields);
1311
			$tokenField = $fields->First();
1312
			$tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : '';
1313
			$response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
1314
				<p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish".  It\'s
1315
				intended to be used after there have been massive edits of the content, such as when the site was
1316
				first built.') . '</p>
1317
				<form method="post" action="publishall">
1318
					<input type="submit" name="confirm" value="'
1319
					. _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",'Confirmation button') .'" />'
1320
					. $tokenHtml .
1321
				'</form>';
1322
		}
1323
1324
		return $response;
1325
	}
1326
1327
	/**
1328
	 * Restore a completely deleted page from the SiteTree_versions table.
1329
	 */
1330
	public function restore($data, $form) {
1331
		if(!isset($data['ID']) || !is_numeric($data['ID'])) {
1332
			return new SS_HTTPResponse("Please pass an ID in the form content", 400);
1333
		}
1334
1335
		$id = (int)$data['ID'];
1336
		$restoredPage = Versioned::get_latest_version("SiteTree", $id);
1337
		if(!$restoredPage) 	return new SS_HTTPResponse("SiteTree #$id not found", 400);
1338
1339
		$restoredPage = $restoredPage->doRestoreToStage();
1340
1341
		$this->getResponse()->addHeader(
1342
			'X-Status',
1343
			rawurlencode(_t(
1344
				'CMSMain.RESTORED',
1345
				"Restored '{title}' successfully",
1346
				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...
1347
			))
1348
		);
1349
1350
		return $this->getResponseNegotiator()->respond($this->getRequest());
1351
	}
1352
1353
	public function duplicate($request) {
1354
		// Protect against CSRF on destructive action
1355
		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
1356
1357
		if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1358
			$page = DataObject::get_by_id("SiteTree", $id);
1359 View Code Duplication
			if($page && (!$page->canEdit() || !$page->canCreate(null, array('Parent' => $page->Parent())))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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