Completed
Pull Request — master (#1467)
by Ingo
02:48
created

AssetAdmin   F

Complexity

Total Complexity 87

Size/Duplication

Total Lines 610
Duplicated Lines 3.93 %

Coupling/Cohesion

Components 1
Dependencies 51

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 87
c 3
b 0
f 0
lcom 1
cbo 51
dl 24
loc 610
rs 1.2782

18 Methods

Rating   Name   Duplication   Size   Complexity  
A currentPageID() 0 11 4
C getList() 8 56 11
A getSearchContext() 0 48 3
A addfolder() 0 14 2
B delete() 3 18 6
A SearchForm() 0 21 1
B AddForm() 13 34 3
C doAdd() 0 57 16
A getNameGenerator() 0 4 1
B currentPage() 0 11 6
A getSiteTreeFor() 0 5 3
A getCMSTreeTitle() 0 3 1
A SiteTreeAsUL() 0 3 1
B Breadcrumbs() 0 26 3
F getEditForm() 0 177 21
A init() 0 10 1
A menu_title() 0 8 3
A providePermissions() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AssetAdmin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AssetAdmin, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
use SilverStripe\Filesystem\Storage\AssetNameGenerator;
4
5
/**
6
 * AssetAdmin is the 'file store' section of the CMS.
7
 * It provides an interface for manipulating the File and Folder objects in the system.
8
 *
9
 * @package cms
10
 * @subpackage assets
11
 */
12
class AssetAdmin extends LeftAndMain implements PermissionProvider{
13
14
	private static $url_segment = 'assets';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
15
16
	private static $url_rule = '/$Action/$ID';
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 $menu_title = 'Files';
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
	private static $tree_class = 'Folder';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
21
22
	/**
23
	 * Amount of results showing on a single page.
24
	 *
25
	 * @config
26
	 * @var int
27
	 */
28
	private static $page_length = 15;
29
30
	/**
31
	 * @config
32
	 * @see Upload->allowedMaxFileSize
33
	 * @var int
34
	 */
35
	private static $allowed_max_file_size;
36
37
	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...
38
		'addfolder',
39
		'delete',
40
		'AddForm',
41
		'SearchForm',
42
		'getsubtree'
43
	);
44
45
	/**
46
	 * Return fake-ID "root" if no ID is found (needed to upload files into the root-folder)
47
	 */
48
	public function currentPageID() {
49
		if(is_numeric($this->getRequest()->requestVar('ID')))	{
50
			return $this->getRequest()->requestVar('ID');
51
		} elseif (is_numeric($this->urlParams['ID'])) {
52
			return $this->urlParams['ID'];
53
		} elseif(Session::get("{$this->class}.currentPage")) {
54
			return Session::get("{$this->class}.currentPage");
55
		} else {
56
			return 0;
57
		}
58
	}
59
60
	/**
61
	 * Set up the controller
62
	 */
63
	public function init() {
64
		parent::init();
65
66
		Versioned::set_stage(Versioned::DRAFT);
67
68
		Requirements::javascript(CMS_DIR . "/client/dist/js/AssetAdmin.js");
69
		Requirements::add_i18n_javascript(CMS_DIR . '/client/src/lang', false, true);
70
		Requirements::css(CMS_DIR . '/client/dist/styles/bundle.css');
71
		CMSBatchActionHandler::register('delete', 'AssetAdmin_DeleteBatchAction', 'Folder');
72
	}
73
74
	/**
75
	 * Returns the files and subfolders contained in the currently selected folder,
76
	 * defaulting to the root node. Doubles as search results, if any search parameters
77
	 * are set through {@link SearchForm()}.
78
	 *
79
	 * @return SS_List
80
	 */
81
	public function getList() {
82
		$folder = $this->currentPage();
83
		$context = $this->getSearchContext();
84
		// Overwrite name filter to search both Name and Title attributes
85
		$context->removeFilterByName('Name');
86
		$params = $this->getRequest()->requestVar('q');
87
		$list = $context->getResults($params);
88
89
		// Don't filter list when a detail view is requested,
90
		// to avoid edge cases where the filtered list wouldn't contain the requested
91
		// record due to faulty session state (current folder not always encoded in URL, see #7408).
92
		if(!$folder->ID
93
			&& $this->getRequest()->requestVar('ID') === null
94
			&& ($this->getRequest()->param('ID') == 'field')
95
		) {
96
			return $list;
97
		}
98
99
		// Re-add previously removed "Name" filter as combined filter
100
		// TODO Replace with composite SearchFilter once that API exists
101
		if(!empty($params['Name'])) {
102
			$list = $list->filterAny(array(
103
				'Name:PartialMatch' => $params['Name'],
104
				'Title:PartialMatch' => $params['Name']
105
			));
106
		}
107
108
		// Always show folders at the top
109
		$list = $list->sort('(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"');
110
111
		// If a search is conducted, check for the "current folder" limitation.
112
		// Otherwise limit by the current folder as denoted by the URL.
113
		if(empty($params) || !empty($params['CurrentFolderOnly'])) {
114
			$list = $list->filter('ParentID', $folder->ID);
115
		}
116
117
		// Category filter
118
		if(!empty($params['AppCategory'])
119
			&& !empty(File::config()->app_categories[$params['AppCategory']])
120
		) {
121
			$exts = File::config()->app_categories[$params['AppCategory']];
122
			$list = $list->filter('Name:PartialMatch', $exts);
123
		}
124
125
		// Date filter
126 View Code Duplication
		if(!empty($params['CreatedFrom'])) {
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...
127
			$fromDate = new DateField(null, null, $params['CreatedFrom']);
128
			$list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
129
		}
130 View Code Duplication
		if(!empty($params['CreatedTo'])) {
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...
131
			$toDate = new DateField(null, null, $params['CreatedTo']);
132
			$list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
133
		}
134
135
		return $list;
136
	}
137
138
	public function getEditForm($id = null, $fields = null) {
139
		Requirements::javascript(FRAMEWORK_DIR . '/client/dist/js/AssetUploadField.js');
140
		Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/AssetUploadField.css');
141
142
		$form = parent::getEditForm($id, $fields);
143
		$folder = ($id && is_numeric($id)) ? DataObject::get_by_id('Folder', $id, false) : $this->currentPage();
144
		$fields = $form->Fields();
145
		$title = ($folder && $folder->isInDB()) ? $folder->Title : _t('AssetAdmin.FILES', 'Files');
146
		$fields->push(new HiddenField('ID', false, $folder ? $folder->ID : null));
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...
147
148
		// File listing
149
		$gridFieldConfig = GridFieldConfig::create()->addComponents(
150
			new GridFieldToolbarHeader(),
151
			new GridFieldSortableHeader(),
152
			new GridFieldFilterHeader(),
153
			new GridFieldDataColumns(),
154
			new GridFieldPaginator(self::config()->page_length),
155
			new GridFieldEditButton(),
156
			new GridFieldDeleteAction(),
157
			new GridFieldDetailForm(),
158
			GridFieldLevelup::create($folder->ID)->setLinkSpec('admin/assets/show/%d')
159
		);
160
161
		$gridField = GridField::create('File', $title, $this->getList(), $gridFieldConfig);
162
		$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
163
		$columns->setDisplayFields(array(
164
			'StripThumbnail' => '',
165
			'Title' => _t('File.Title', 'Title'),
166
			'Created' => _t('AssetAdmin.CREATED', 'Date'),
167
			'Size' => _t('AssetAdmin.SIZE', 'Size'),
168
		));
169
		$columns->setFieldCasting(array(
170
			'Created' => 'SS_Datetime->Nice'
171
		));
172
		$gridField->setAttribute(
173
			'data-url-folder-template',
174
			Controller::join_links($this->Link('show'), '%s')
175
		);
176
177
		if(!$folder->hasMethod('canAddChildren') || ($folder->hasMethod('canAddChildren') && $folder->canAddChildren())) {
178
			// TODO Will most likely be replaced by GridField logic
179
			$addFolderBtn = new LiteralField(
180
				'AddFolderButton',
181
				sprintf(
182
					'<a class="ss-ui-button font-icon-folder-add no-text cms-add-folder-link" title="%s" data-icon="add" data-url="%s" href="%s"></a>',
183
					_t('Folder.AddFolderButton', 'Add folder'),
184
					Controller::join_links($this->Link('AddForm'), '?' . http_build_query(array(
185
						'action_doAdd' => 1,
186
						'ParentID' => $folder->ID,
187
						'SecurityID' => $form->getSecurityToken()->getValue()
188
					))),
189
					Controller::join_links($this->Link('addfolder'), '?ParentID=' . $folder->ID)
190
				)
191
			);
192
		} else {
193
			$addFolderBtn = '';
194
		}
195
196
		// Move existing fields to a "details" tab, unless they've already been tabbed out through extensions.
197
		// Required to keep Folder->getCMSFields() simple and reuseable,
198
		// without any dependencies into AssetAdmin (e.g. useful for "add folder" views).
199
		if(!$fields->hasTabset()) {
200
			$tabs = new TabSet('Root',
201
				$tabList = new Tab('ListView', _t('AssetAdmin.ListView', 'List View')),
202
				$tabTree = new Tab('TreeView', _t('AssetAdmin.TreeView', 'Tree View'))
203
			);
204
			$tabList->addExtraClass("content-listview cms-tabset-icon list");
205
			$tabTree->addExtraClass("content-treeview cms-tabset-icon tree");
206
			if($fields->Count() && $folder && $folder->isInDB()) {
207
				$tabs->push($tabDetails = new Tab('DetailsView', _t('AssetAdmin.DetailsView', 'Details')));
208
				$tabDetails->addExtraClass("content-galleryview cms-tabset-icon edit");
209
				foreach($fields as $field) {
210
					$fields->removeByName($field->getName());
211
					$tabDetails->push($field);
212
				}
213
			}
214
			$fields->push($tabs);
215
		}
216
217
		// we only add buttons if they're available. User might not have permission and therefore
218
		// the button shouldn't be available. Adding empty values into a ComposteField breaks template rendering.
219
		$actionButtonsComposite = CompositeField::create()->addExtraClass('cms-actions-row');
220
		if($addFolderBtn) $actionButtonsComposite->push($addFolderBtn);
0 ignored issues
show
Bug introduced by
It seems like $addFolderBtn defined by '' on line 193 can also be of type string; however, CompositeField::push() does only seem to accept object<FormField>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
221
222
		// Add the upload field for new media
223
		if($currentPageID = $this->currentPageID()){
224
			Session::set("{$this->class}.currentPage", $currentPageID);
225
		}
226
227
		$folder = $this->currentPage();
228
229
		$uploadField = UploadField::create('AssetUploadField', '');
230
		$uploadField->setConfig('previewMaxWidth', 40);
231
		$uploadField->setConfig('previewMaxHeight', 30);
232
		$uploadField->setConfig('changeDetection', false);
233
		$uploadField->addExtraClass('ss-assetuploadfield');
234
		$uploadField->removeExtraClass('ss-uploadfield');
235
		$uploadField->setTemplate('AssetUploadField');
236
237
		if($folder->exists()) {
238
			$path = $folder->getFilename();
239
			$uploadField->setFolderName($path);
240
		} else {
241
			$uploadField->setFolderName('/'); // root of the assets
242
		}
243
244
		$exts = $uploadField->getValidator()->getAllowedExtensions();
245
		asort($exts);
246
		$uploadField->Extensions = implode(', ', $exts);
0 ignored issues
show
Bug introduced by
The property Extensions does not seem to exist. Did you mean extensions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
247
248
		// List view
249
		$fields->addFieldsToTab('Root.ListView', array(
250
			$actionsComposite = CompositeField::create(
251
				$actionButtonsComposite
252
			)->addExtraClass('cms-content-toolbar field'),
253
			$uploadField,
254
			new HiddenField('ID'),
255
			$gridField
256
		));
257
258
		// Tree view
259
		$fields->addFieldsToTab('Root.TreeView', array(
260
			clone $actionsComposite,
261
			// TODO Replace with lazy loading on client to avoid performance hit of rendering potentially unused views
262
			new LiteralField(
263
				'Tree',
264
				FormField::create_tag(
265
					'div',
266
					array(
267
						'class' => 'cms-tree',
268
						'data-url-tree' => $this->Link('getsubtree'),
269
						'data-url-savetreenode' => $this->Link('savetreenode')
270
					),
271
					$this->SiteTreeAsUL()
272
				)
273
			)
274
		));
275
276
		// Move actions to "details" tab (they don't make sense on list/tree view)
277
		$actions = $form->Actions();
278
		$saveBtn = $actions->fieldByName('action_save');
279
		$deleteBtn = $actions->fieldByName('action_delete');
280
		$actions->removeByName('action_save');
281
		$actions->removeByName('action_delete');
282
		if(($saveBtn || $deleteBtn) && $fields->fieldByName('Root.DetailsView')) {
283
			$fields->addFieldToTab(
284
				'Root.DetailsView',
285
				CompositeField::create($saveBtn,$deleteBtn)->addExtraClass('Actions')
286
			);
287
		}
288
289
290
		$fields->setForm($form);
291
		$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
0 ignored issues
show
Documentation introduced by
$this->getTemplatesWithSuffix('_EditForm') is of type array, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
292
		// TODO Can't merge $FormAttributes in template at the moment
293
		$form->addExtraClass('cms-edit-form ' . $this->BaseCSSClasses());
294
		$form->setAttribute('data-pjax-fragment', 'CurrentForm');
295
		$form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
296
297
		// Optionally handle form submissions with 'X-Formschema-Request'
298
		// which rely on having validation errors returned as structured data
299
		$form->setValidationResponseCallback(function() use ($form) {
300
			$request = $this->getRequest();
301
			if($request->getHeader('X-Formschema-Request')) {
302
				$data = $this->getSchemaForForm($form);
0 ignored issues
show
Bug introduced by
It seems like $form defined by parent::getEditForm($id, $fields) on line 142 can be null; however, LeftAndMain::getSchemaForForm() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
303
				$response = new SS_HTTPResponse(Convert::raw2json($data));
304
				$response->addHeader('Content-Type', 'application/json');
305
				return $response;
306
307
			}
308
		});
309
310
311
		$this->extend('updateEditForm', $form);
312
313
		return $form;
314
	}
315
316
	public function addfolder($request) {
317
		$obj = $this->customise(array(
318
			'EditForm' => $this->AddForm()
319
		));
320
321
		if($request->isAjax()) {
322
			// Rendering is handled by template, which will call EditForm() eventually
323
			$content = $obj->renderWith($this->getTemplatesWithSuffix('_Content'));
324
		} else {
325
			$content = $obj->renderWith($this->getViewer('show'));
326
		}
327
328
		return $content;
329
	}
330
331
	public function delete($data, $form) {
332
		$className = $this->stat('tree_class');
333
334
		$record = DataObject::get_by_id($className, $data['ID']);
335
		if($record && !$record->canDelete()) {
336
			return Security::permissionFailure();
337
		}
338 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...
339
			throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
340
		}
341
		$parentID = $record->ParentID;
342
		$record->delete();
343
		$this->setCurrentPageID(null);
344
345
		$this->getResponse()->addHeader('X-Status', rawurlencode(_t('LeftAndMain.DELETED', 'Deleted.')));
346
		$this->getResponse()->addHeader('X-Pjax', 'Content');
347
		return $this->redirect(Controller::join_links($this->Link('show'), $parentID ? $parentID : 0));
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->redirect(\Control...ntID ? $parentID : 0)); of type string|null adds the type string to the return on line 347 which is incompatible with the return type of the parent method LeftAndMain::delete of type SS_HTTPResponse|null.
Loading history...
348
	}
349
350
	/**
351
	 * Get the search context
352
	 *
353
	 * @return SearchContext
354
	 */
355
	public function getSearchContext() {
356
		$context = singleton('File')->getDefaultSearchContext();
357
358
		// Namespace fields, for easier detection if a search is present
359
		foreach($context->getFields() as $field) {
360
			$field->setName(sprintf('q[%s]', $field->getName()));
361
		}
362
		foreach($context->getFilters() as $filter) {
363
			$filter->setFullName(sprintf('q[%s]', $filter->getFullName()));
364
		}
365
366
		// Customize fields
367
		$dateHeader = HeaderField::create('q[Date]', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
368
		$dateFrom = DateField::create('q[CreatedFrom]', _t('CMSSearch.FILTERDATEFROM', 'From'))
369
		->setConfig('showcalendar', true);
370
		$dateTo = DateField::create('q[CreatedTo]',_t('CMSSearch.FILTERDATETO', 'To'))
371
		->setConfig('showcalendar', true);
372
		$dateGroup = FieldGroup::create(
373
			$dateHeader,
374
			$dateFrom,
375
			$dateTo
376
		);
377
		$context->addField($dateGroup);
378
		$appCategories = array(
379
			'archive' => _t('AssetAdmin.AppCategoryArchive', 'Archive', 'A collection of files'),
380
			'audio' => _t('AssetAdmin.AppCategoryAudio', 'Audio'),
381
			'document' => _t('AssetAdmin.AppCategoryDocument', 'Document'),
382
			'flash' => _t('AssetAdmin.AppCategoryFlash', 'Flash', 'The fileformat'),
383
			'image' => _t('AssetAdmin.AppCategoryImage', 'Image'),
384
			'video' => _t('AssetAdmin.AppCategoryVideo', 'Video'),
385
		);
386
		$context->addField(
387
			$typeDropdown = new DropdownField(
388
				'q[AppCategory]',
389
				_t('AssetAdmin.Filetype', 'File type'),
390
				$appCategories
391
			)
392
		);
393
394
		$typeDropdown->setEmptyString(' ');
395
396
		$context->addField(
397
			new CheckboxField('q[CurrentFolderOnly]', _t('AssetAdmin.CurrentFolderOnly', 'Limit to current folder?'))
398
		);
399
		$context->getFields()->removeByName('q[Title]');
400
401
		return $context;
402
	}
403
404
	/**
405
	 * Returns a form for filtering of files and assets gridfield.
406
	 * Result filtering takes place in {@link getList()}.
407
	 *
408
	 * @return Form
409
	 * @see AssetAdmin.js
410
	 */
411
	public function SearchForm() {
412
		$folder = $this->currentPage();
413
		$context = $this->getSearchContext();
414
415
		$fields = $context->getSearchFields();
416
		$actions = new FieldList(
417
			FormAction::create('doSearch',  _t('CMSMain_left_ss.APPLY_FILTER', 'Apply Filter'))
418
				->addExtraClass('ss-ui-action-constructive'),
419
			Object::create('ResetFormAction', 'clear', _t('CMSMain_left_ss.RESET', 'Reset'))
420
		);
421
422
		$form = new Form($this, 'filter', $fields, $actions);
423
		$form->setFormMethod('GET');
424
		$form->setFormAction(Controller::join_links($this->Link('show'), $folder->ID));
425
		$form->addExtraClass('cms-search-form');
426
		$form->loadDataFrom($this->getRequest()->getVars());
427
		$form->disableSecurityToken();
428
		// This have to match data-name attribute on the gridfield so that the javascript selectors work
429
		$form->setAttribute('data-gridfield', 'File');
430
		return $form;
431
	}
432
433
	public function AddForm() {
434
		$negotiator = $this->getResponseNegotiator();
435
		$form = Form::create(
436
			$this,
437
			'AddForm',
438
			new FieldList(
439
				new TextField("Name", _t('File.Name')),
440
				new HiddenField('ParentID', false, $this->getRequest()->getVar('ParentID'))
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...
441
			),
442
			new FieldList(
443
				FormAction::create('doAdd', _t('AssetAdmin_left_ss.GO','Go'))
444
					->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')
445
					->setTitle(_t('AssetAdmin.ActionAdd', 'Add folder'))
446
			)
447
		)->setHTMLID('Form_AddForm');
448 View Code Duplication
		$form->setValidationResponseCallback(function() use ($negotiator, $form) {
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...
449
			$request = $this->getRequest();
450
			if($request->isAjax() && $negotiator) {
451
				$form->setupFormErrors();
452
				$result = $form->forTemplate();
453
454
				return $negotiator->respond($request, array(
455
					'CurrentForm' => function() use($result) {
456
						return $result;
457
					}
458
				));
459
			}
460
		});
461
		$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
0 ignored issues
show
Documentation introduced by
$this->getTemplatesWithSuffix('_EditForm') is of type array, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
462
		// TODO Can't merge $FormAttributes in template at the moment
463
		$form->addExtraClass('add-form cms-add-form cms-edit-form cms-panel-padded center ' . $this->BaseCSSClasses());
464
465
		return $form;
466
	}
467
468
	/**
469
	 * Add a new group and return its details suitable for ajax.
470
	 *
471
	 * @todo Move logic into Folder class, and use LeftAndMain->doAdd() default implementation.
472
	 */
473
	public function doAdd($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...
474
		$class = $this->stat('tree_class');
475
476
		// check create permissions
477
		if(!singleton($class)->canCreate()) {
478
			return Security::permissionFailure($this);
479
		}
480
481
		// check addchildren permissions
482
		if(
483
			singleton($class)->hasExtension('Hierarchy')
484
			&& isset($data['ParentID'])
485
			&& is_numeric($data['ParentID'])
486
			&& $data['ParentID']
487
		) {
488
			$parentRecord = DataObject::get_by_id($class, $data['ParentID']);
489
			if($parentRecord->hasMethod('canAddChildren') && !$parentRecord->canAddChildren()) {
490
				return Security::permissionFailure($this);
491
			}
492
		} else {
493
			$parentRecord = null;
494
		}
495
496
		// Check parent
497
		$parentID = $parentRecord && $parentRecord->ID
498
			? (int)$parentRecord->ID
499
			: 0;
500
		// Build filename
501
		$filename = isset($data['Name'])
502
			? basename($data['Name'])
503
			: _t('AssetAdmin.NEWFOLDER',"NewFolder");
504
		if($parentRecord && $parentRecord->ID) {
505
			$filename = $parentRecord->getFilename() . '/' . $filename;
506
		}
507
508
		// Get the folder to be created
509
510
		// Ensure name is unique
511
		foreach($this->getNameGenerator($filename) as $filename) {
512
			if(! File::find($filename) ) {
513
				break;
514
			}
515
		}
516
517
		// Create record
518
		$record = Folder::create();
519
		$record->ParentID = $parentID;
520
		$record->Name = $record->Title = basename($filename);
521
		$record->write();
522
523
524
		if($parentRecord) {
525
			return $this->redirect(Controller::join_links($this->Link('show'), $parentRecord->ID));
526
		} else {
527
			return $this->redirect($this->Link());
528
		}
529
	}
530
531
	/**
532
	 * Get an asset renamer for the given filename.
533
	 *
534
	 * @param string $filename Path name
535
	 * @return AssetNameGenerator
536
	 */
537
	protected function getNameGenerator($filename){
538
		return Injector::inst()
539
			->createWithArgs('AssetNameGenerator', array($filename));
540
	}
541
542
	/**
543
	 * Custom currentPage() method to handle opening the 'root' folder
544
	 */
545
	public function currentPage() {
546
		$id = $this->currentPageID();
547
		if($id && is_numeric($id) && $id > 0) {
548
			$folder = DataObject::get_by_id('Folder', $id);
549
			if($folder && $folder->isInDB()) {
550
				return $folder;
551
			}
552
		}
553
		$this->setCurrentPageID(null);
554
		return new Folder();
555
	}
556
557
	public function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, $filterFunction = null, $minNodeCount = 30) {
558
		if (!$childrenMethod) $childrenMethod = 'ChildFolders';
559
		if (!$numChildrenMethod) $numChildrenMethod = 'numChildFolders';
560
		return parent::getSiteTreeFor($className, $rootID, $childrenMethod, $numChildrenMethod, $filterFunction, $minNodeCount);
561
	}
562
563
	public function getCMSTreeTitle() {
564
		return Director::absoluteBaseURL() . "assets";
565
	}
566
567
	public function SiteTreeAsUL() {
568
		return $this->getSiteTreeFor($this->stat('tree_class'), null, 'ChildFolders', 'numChildFolders');
569
	}
570
571
	/**
572
	 * @param bool $unlinked
573
	 * @return ArrayList
574
	 */
575
	public function Breadcrumbs($unlinked = false) {
576
		$items = parent::Breadcrumbs($unlinked);
577
578
		// The root element should explicitly point to the root node.
579
		// Uses session state for current record otherwise.
580
		$items[0]->Link = Controller::join_links(singleton('AssetAdmin')->Link('show'), 0);
581
582
		// If a search is in progress, don't show the path
583
		if($this->getRequest()->requestVar('q')) {
584
			$items = $items->limit(1);
585
			$items->push(new ArrayData(array(
586
				'Title' => _t('LeftAndMain.SearchResults', 'Search Results'),
587
				'Link' => Controller::join_links($this->Link(), '?' . http_build_query(array('q' => $this->getRequest()->requestVar('q'))))
588
			)));
589
		}
590
591
		// If we're adding a folder, note that in breadcrumbs as well
592
		if($this->getRequest()->param('Action') == 'addfolder') {
593
			$items->push(new ArrayData(array(
594
				'Title' => _t('Folder.AddFolderButton', 'Add folder'),
595
				'Link' => false
596
			)));
597
		}
598
599
		return $items;
600
	}
601
602
	public static function menu_title($class = null, $localised = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $class 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...
603
		// Deprecate this menu title if installed alongside new asset admin
604
		if($localised && class_exists('SilverStripe\AssetAdmin\Controller\AssetAdmin')) {
605
			// Don't conflict with legacy translations
606
			return _t('AssetAdmin.CMSMENU_OLD', 'Files (old)');
607
		}
608
		return parent::menu_title(null, $localised);
0 ignored issues
show
Bug introduced by
The method menu_title() does not exist on LeftAndMain. Did you maybe mean menu_title_for_class()?

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...
609
	}
610
611
	public function providePermissions() {
612
		$title = static::menu_title();
613
		return array(
614
			"CMS_ACCESS_AssetAdmin" => array(
615
				'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,?,{"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...
616
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
617
			)
618
		);
619
	}
620
621
}
622
/**
623
 * Delete multiple {@link Folder} records (and the associated filesystem nodes).
624
 * Usually used through the {@link AssetAdmin} interface.
625
 *
626
 * @package cms
627
 * @subpackage batchactions
628
 */
629
class AssetAdmin_DeleteBatchAction extends CMSBatchAction {
630
	public function getActionTitle() {
631
		// _t('AssetAdmin_left_ss.SELECTTODEL','Select the folders that you want to delete and then click the button below')
632
		return _t('AssetAdmin_DeleteBatchAction.TITLE', 'Delete folders');
633
	}
634
635
	public function run(SS_List $records) {
636
		$status = array(
637
			'modified'=>array(),
638
			'deleted'=>array()
639
		);
640
641
		foreach($records as $record) {
642
			$id = $record->ID;
643
644
			// Perform the action
645
			if($record->canDelete()) $record->delete();
646
647
			$status['deleted'][$id] = array();
648
649
			$record->destroy();
650
			unset($record);
651
		}
652
653
		return Convert::raw2json($status);
654
	}
655
}
656