Completed
Push — master ( 25a8fb...29fc7a )
by Hamish
12s
created

AssetAdmin::Breadcrumbs()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 26
rs 8.8571
c 1
b 0
f 0
cc 3
eloc 13
nc 4
nop 1
1
<?php
2
3
namespace SilverStripe\CMS\Controllers;
4
5
use SilverStripe\Filesystem\Storage\AssetNameGenerator;
6
use SilverStripe\ORM\ArrayList;
7
use SilverStripe\ORM\Versioning\Versioned;
8
use SilverStripe\ORM\DataObject;
9
use SilverStripe\ORM\SS_List;
10
use SilverStripe\Security\Security;
11
use SilverStripe\Security\PermissionProvider;
12
use LeftAndMain;
13
use Session;
14
use Requirements;
15
use CMSBatchActionHandler;
16
use File;
17
use DateField;
18
use HiddenField;
19
use GridFieldConfig;
20
use GridFieldToolbarHeader;
21
use GridFieldSortableHeader;
22
use GridFieldFilterHeader;
23
use GridFieldDataColumns;
24
use GridFieldPaginator;
25
use GridFieldEditButton;
26
use GridFieldDeleteAction;
27
use GridFieldDetailForm;
28
use GridFieldLevelup;
29
use GridField;
30
use Controller;
31
use LiteralField;
32
use TabSet;
33
use Tab;
34
use CompositeField;
35
use UploadField;
36
use FormField;
37
use SS_HTTPResponse;
38
use Convert;
39
use SS_HTTPResponse_Exception;
40
use HeaderField;
41
use FieldGroup;
42
use DropdownField;
43
use CheckboxField;
44
use FieldList;
45
use FormAction;
46
use Object;
47
use Form;
48
use TextField;
49
use Folder;
50
use Injector;
51
use Director;
52
use ArrayData;
53
use CMSBatchAction;
54
55
56
57
/**
58
 * AssetAdmin is the 'file store' section of the CMS.
59
 * It provides an interface for manipulating the File and Folder objects in the system.
60
 *
61
 * @package cms
62
 * @subpackage assets
63
 */
64
class AssetAdmin extends LeftAndMain implements PermissionProvider{
65
66
	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...
67
68
	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...
69
70
	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...
71
72
	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...
73
74
	/**
75
	 * Amount of results showing on a single page.
76
	 *
77
	 * @config
78
	 * @var int
79
	 */
80
	private static $page_length = 15;
81
82
	/**
83
	 * @config
84
	 * @see Upload->allowedMaxFileSize
85
	 * @var int
86
	 */
87
	private static $allowed_max_file_size;
88
89
	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...
90
		'addfolder',
91
		'delete',
92
		'AddForm',
93
		'SearchForm',
94
		'getsubtree'
95
	);
96
97
	/**
98
	 * Return fake-ID "root" if no ID is found (needed to upload files into the root-folder)
99
	 */
100
	public function currentPageID() {
101
		if(is_numeric($this->getRequest()->requestVar('ID')))	{
102
			return $this->getRequest()->requestVar('ID');
103
		} elseif (isset($this->urlParams['ID']) && is_numeric($this->urlParams['ID'])) {
104
			return $this->urlParams['ID'];
105
		} elseif(Session::get("{$this->class}.currentPage")) {
106
			return Session::get("{$this->class}.currentPage");
107
		} else {
108
			return 0;
109
		}
110
	}
111
112
	/**
113
	 * Set up the controller
114
	 */
115
	public function init() {
116
		parent::init();
117
118
		Versioned::set_stage(Versioned::DRAFT);
119
120
		Requirements::javascript(CMS_DIR . "/client/dist/js/AssetAdmin.js");
121
		Requirements::add_i18n_javascript(CMS_DIR . '/client/lang', false, true);
122
		Requirements::css(CMS_DIR . '/client/dist/styles/bundle.css');
123
		CMSBatchActionHandler::register('delete', 'SilverStripe\\CMS\\Controllers\\AssetAdmin_DeleteBatchAction', 'Folder');
124
	}
125
126
	/**
127
	 * Returns the files and subfolders contained in the currently selected folder,
128
	 * defaulting to the root node. Doubles as search results, if any search parameters
129
	 * are set through {@link SearchForm()}.
130
	 *
131
	 * @return SS_List
132
	 */
133
	public function getList() {
134
		$folder = $this->currentPage();
135
		$context = $this->getSearchContext();
136
		// Overwrite name filter to search both Name and Title attributes
137
		$context->removeFilterByName('Name');
138
		$params = $this->getRequest()->requestVar('q');
139
		$list = $context->getResults($params);
140
141
		// Don't filter list when a detail view is requested,
142
		// to avoid edge cases where the filtered list wouldn't contain the requested
143
		// record due to faulty session state (current folder not always encoded in URL, see #7408).
144
		if(!$folder->ID
145
			&& $this->getRequest()->requestVar('ID') === null
146
			&& ($this->getRequest()->param('ID') == 'field')
147
		) {
148
			return $list;
149
		}
150
151
		// Re-add previously removed "Name" filter as combined filter
152
		// TODO Replace with composite SearchFilter once that API exists
153
		if(!empty($params['Name'])) {
154
			$list = $list->filterAny(array(
155
				'Name:PartialMatch' => $params['Name'],
156
				'Title:PartialMatch' => $params['Name']
157
			));
158
		}
159
160
		// Always show folders at the top
161
		$list = $list->sort('(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"');
162
163
		// If a search is conducted, check for the "current folder" limitation.
164
		// Otherwise limit by the current folder as denoted by the URL.
165
		if(empty($params) || !empty($params['CurrentFolderOnly'])) {
166
			$list = $list->filter('ParentID', $folder->ID);
167
		}
168
169
		// Category filter
170
		if(!empty($params['AppCategory'])
171
			&& !empty(File::config()->app_categories[$params['AppCategory']])
172
		) {
173
			$exts = File::config()->app_categories[$params['AppCategory']];
174
			$list = $list->filter('Name:PartialMatch', $exts);
175
		}
176
177
		// Date filter
178 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...
179
			$fromDate = new DateField(null, null, $params['CreatedFrom']);
180
			$list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
181
		}
182 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...
183
			$toDate = new DateField(null, null, $params['CreatedTo']);
184
			$list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
185
		}
186
187
		return $list;
188
	}
189
190
	public function getEditForm($id = null, $fields = null) {
191
		Requirements::javascript(FRAMEWORK_DIR . '/client/dist/js/AssetUploadField.js');
192
		Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/AssetUploadField.css');
193
194
		$form = parent::getEditForm($id, $fields);
195
		$folder = ($id && is_numeric($id)) ? DataObject::get_by_id('Folder', $id, false) : $this->currentPage();
196
		$fields = $form->Fields();
197
		$title = ($folder && $folder->isInDB()) ? $folder->Title : _t('AssetAdmin.FILES', 'Files');
198
		$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...
199
200
		// Remove legacy previewable behaviour.
201
		$form->removeExtraClass('cms-previewable');
202
		/** @skipUpgrade */
203
		$form->Fields()->removeByName('SilverStripeNavigator');
204
205
		// File listing
206
		$gridFieldConfig = GridFieldConfig::create()->addComponents(
207
			new GridFieldToolbarHeader(),
208
			new GridFieldSortableHeader(),
209
			new GridFieldFilterHeader(),
210
			new GridFieldDataColumns(),
211
			new GridFieldPaginator(self::config()->page_length),
212
			new GridFieldEditButton(),
213
			new GridFieldDeleteAction(),
214
			new GridFieldDetailForm(),
215
			GridFieldLevelup::create($folder->ID)->setLinkSpec($this->Link('show') . '/%d')
216
		);
217
218
		$gridField = GridField::create('File', $title, $this->getList(), $gridFieldConfig);
219
		$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
220
		$columns->setDisplayFields(array(
221
			'StripThumbnail' => '',
222
			'Title' => _t('File.Title', 'Title'),
223
			'Created' => _t('AssetAdmin.CREATED', 'Date'),
224
			'Size' => _t('AssetAdmin.SIZE', 'Size'),
225
		));
226
		$columns->setFieldCasting(array(
227
			'Created' => 'DBDatetime->Nice'
228
		));
229
		$gridField->setAttribute(
230
			'data-url-folder-template',
231
			Controller::join_links($this->Link('show'), '%s')
232
		);
233
234
		if(!$folder->hasMethod('canAddChildren') || ($folder->hasMethod('canAddChildren') && $folder->canAddChildren())) {
235
			// TODO Will most likely be replaced by GridField logic
236
			$addFolderBtn = new LiteralField(
237
				'AddFolderButton',
238
				sprintf(
239
					'<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>',
240
					_t('Folder.AddFolderButton', 'Add folder'),
241
					Controller::join_links($this->Link('AddForm'), '?' . http_build_query(array(
242
						'action_doAdd' => 1,
243
						'ParentID' => $folder->ID,
244
						'SecurityID' => $form->getSecurityToken()->getValue()
245
					))),
246
					Controller::join_links($this->Link('addfolder'), '?ParentID=' . $folder->ID)
247
				)
248
			);
249
		} else {
250
			$addFolderBtn = '';
251
		}
252
253
		// Move existing fields to a "details" tab, unless they've already been tabbed out through extensions.
254
		// Required to keep Folder->getCMSFields() simple and reuseable,
255
		// without any dependencies into AssetAdmin (e.g. useful for "add folder" views).
256
		if(!$fields->hasTabset()) {
257
			$tabs = new TabSet('Root',
258
				$tabList = new Tab('ListView', _t('AssetAdmin.ListView', 'List View')),
259
				$tabTree = new Tab('TreeView', _t('AssetAdmin.TreeView', 'Tree View'))
260
			);
261
			$tabList->addExtraClass("content-listview cms-tabset-icon list");
262
			$tabTree->addExtraClass("content-treeview cms-tabset-icon tree");
263
			if($fields->Count() && $folder && $folder->isInDB()) {
264
				$tabs->push($tabDetails = new Tab('DetailsView', _t('AssetAdmin.DetailsView', 'Details')));
265
				$tabDetails->addExtraClass("content-galleryview cms-tabset-icon edit");
266
				foreach($fields as $field) {
267
					$fields->removeByName($field->getName());
268
					$tabDetails->push($field);
269
				}
270
			}
271
			$fields->push($tabs);
272
		}
273
274
		// we only add buttons if they're available. User might not have permission and therefore
275
		// the button shouldn't be available. Adding empty values into a ComposteField breaks template rendering.
276
		$actionButtonsComposite = CompositeField::create()->addExtraClass('cms-actions-row');
277
		if($addFolderBtn) $actionButtonsComposite->push($addFolderBtn);
0 ignored issues
show
Bug introduced by
It seems like $addFolderBtn defined by '' on line 250 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...
278
279
		// Add the upload field for new media
280
		if($currentPageID = $this->currentPageID()){
281
			Session::set("{$this->class}.currentPage", $currentPageID);
282
		}
283
284
		$folder = $this->currentPage();
285
286
		$uploadField = UploadField::create('AssetUploadField', '');
287
		$uploadField->setConfig('previewMaxWidth', 40);
288
		$uploadField->setConfig('previewMaxHeight', 30);
289
		$uploadField->setConfig('changeDetection', false);
290
		$uploadField->addExtraClass('ss-assetuploadfield');
291
		$uploadField->removeExtraClass('ss-uploadfield');
292
		$uploadField->setTemplate('AssetUploadField');
293
294
		if($folder->exists()) {
295
			$path = $folder->getFilename();
296
			$uploadField->setFolderName($path);
297
		} else {
298
			$uploadField->setFolderName('/'); // root of the assets
299
		}
300
301
		$exts = $uploadField->getValidator()->getAllowedExtensions();
302
		asort($exts);
303
		$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...
304
305
		// List view
306
		$fields->addFieldsToTab('Root.ListView', array(
307
			$actionsComposite = CompositeField::create(
308
				$actionButtonsComposite
309
			)->addExtraClass('cms-content-toolbar field'),
310
			$uploadField,
311
			new HiddenField('ID'),
312
			$gridField
313
		));
314
315
		// Tree view
316
		$fields->addFieldsToTab('Root.TreeView', array(
317
			clone $actionsComposite,
318
			// TODO Replace with lazy loading on client to avoid performance hit of rendering potentially unused views
319
			new LiteralField(
320
				'Tree',
321
				FormField::create_tag(
322
					'div',
323
					array(
324
						'class' => 'cms-tree',
325
						'data-url-tree' => $this->Link('getsubtree'),
326
						'data-url-savetreenode' => $this->Link('savetreenode')
327
					),
328
					$this->SiteTreeAsUL()
329
				)
330
			)
331
		));
332
333
		// Move actions to "details" tab (they don't make sense on list/tree view)
334
		$actions = $form->Actions();
335
		$saveBtn = $actions->fieldByName('action_save');
336
		$deleteBtn = $actions->fieldByName('action_delete');
337
		$actions->removeByName('action_save');
338
		$actions->removeByName('action_delete');
339
		if(($saveBtn || $deleteBtn) && $fields->fieldByName('Root.DetailsView')) {
340
			$fields->addFieldToTab(
341
				'Root.DetailsView',
342
				CompositeField::create($saveBtn,$deleteBtn)->addExtraClass('Actions')
343
			);
344
		}
345
346
347
		$fields->setForm($form);
348
		$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...
349
		// TODO Can't merge $FormAttributes in template at the moment
350
		$form->addExtraClass('cms-edit-form ' . $this->BaseCSSClasses());
351
		$form->setAttribute('data-pjax-fragment', 'CurrentForm');
352
		$form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
353
354
		// Optionally handle form submissions with 'X-Formschema-Request'
355
		// which rely on having validation errors returned as structured data
356
		$form->setValidationResponseCallback(function() use ($form) {
357
			$request = $this->getRequest();
358
			if($request->getHeader('X-Formschema-Request')) {
359
				$data = $this->getSchemaForForm($form);
0 ignored issues
show
Bug introduced by
It seems like $form defined by parent::getEditForm($id, $fields) on line 194 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...
360
				$response = new SS_HTTPResponse(Convert::raw2json($data));
361
				$response->addHeader('Content-Type', 'application/json');
362
				return $response;
363
364
			}
365
		});
366
367
368
		$this->extend('updateEditForm', $form);
369
370
		return $form;
371
	}
372
373
	public function addfolder($request) {
374
		$obj = $this->customise(array(
375
			'EditForm' => $this->AddForm()
376
		));
377
378
		if($request->isAjax()) {
379
			// Rendering is handled by template, which will call EditForm() eventually
380
			$content = $obj->renderWith($this->getTemplatesWithSuffix('_Content'));
381
		} else {
382
			$content = $obj->renderWith($this->getViewer('show'));
383
		}
384
385
		return $content;
386
	}
387
388
	public function delete($data, $form) {
389
		$className = $this->stat('tree_class');
390
391
		$record = DataObject::get_by_id($className, $data['ID']);
392
		if($record && !$record->canDelete()) {
393
			return Security::permissionFailure();
394
		}
395 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...
396
			throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
397
		}
398
		$parentID = $record->ParentID;
399
		$record->delete();
400
		$this->setCurrentPageID(null);
401
402
		$this->getResponse()->addHeader('X-Status', rawurlencode(_t('LeftAndMain.DELETED', 'Deleted.')));
403
		$this->getResponse()->addHeader('X-Pjax', 'Content');
404
		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 404 which is incompatible with the return type of the parent method LeftAndMain::delete of type SS_HTTPResponse|null.
Loading history...
405
	}
406
407
	/**
408
	 * Get the search context
409
	 *
410
	 * @return SearchContext
411
	 */
412
	public function getSearchContext() {
413
		$context = singleton('File')->getDefaultSearchContext();
414
415
		// Namespace fields, for easier detection if a search is present
416
		foreach($context->getFields() as $field) {
417
			$field->setName(sprintf('q[%s]', $field->getName()));
418
		}
419
		foreach($context->getFilters() as $filter) {
420
			$filter->setFullName(sprintf('q[%s]', $filter->getFullName()));
421
		}
422
423
		// Customize fields
424
		$dateHeader = HeaderField::create('q[Date]', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
425
		$dateFrom = DateField::create('q[CreatedFrom]', _t('CMSSearch.FILTERDATEFROM', 'From'))
426
		->setConfig('showcalendar', true);
427
		$dateTo = DateField::create('q[CreatedTo]',_t('CMSSearch.FILTERDATETO', 'To'))
428
		->setConfig('showcalendar', true);
429
		$dateGroup = FieldGroup::create(
430
			$dateHeader,
431
			$dateFrom,
432
			$dateTo
433
		);
434
		$context->addField($dateGroup);
435
		$appCategories = array(
436
			'archive' => _t('AssetAdmin.AppCategoryArchive', 'Archive', 'A collection of files'),
437
			'audio' => _t('AssetAdmin.AppCategoryAudio', 'Audio'),
438
			'document' => _t('AssetAdmin.AppCategoryDocument', 'Document'),
439
			'flash' => _t('AssetAdmin.AppCategoryFlash', 'Flash', 'The fileformat'),
440
			'image' => _t('AssetAdmin.AppCategoryImage', 'Image'),
441
			'video' => _t('AssetAdmin.AppCategoryVideo', 'Video'),
442
		);
443
		$context->addField(
444
			$typeDropdown = new DropdownField(
445
				'q[AppCategory]',
446
				_t('AssetAdmin.Filetype', 'File type'),
447
				$appCategories
448
			)
449
		);
450
451
		$typeDropdown->setEmptyString(' ');
452
453
		$context->addField(
454
			new CheckboxField('q[CurrentFolderOnly]', _t('AssetAdmin.CurrentFolderOnly', 'Limit to current folder?'))
455
		);
456
		$context->getFields()->removeByName('q[Title]');
457
458
		return $context;
459
	}
460
461
	/**
462
	 * Returns a form for filtering of files and assets gridfield.
463
	 * Result filtering takes place in {@link getList()}.
464
	 *
465
	 * @return Form
466
	 * @see AssetAdmin.js
467
	 */
468
	public function SearchForm() {
469
		$folder = $this->currentPage();
470
		$context = $this->getSearchContext();
471
472
		$fields = $context->getSearchFields();
473
		$actions = new FieldList(
474
			FormAction::create('doSearch',  _t('CMSMain_left_ss.APPLY_FILTER', 'Apply Filter'))
475
				->addExtraClass('ss-ui-action-constructive'),
476
			Object::create('ResetFormAction', 'clear', _t('CMSMain_left_ss.RESET', 'Reset'))
477
		);
478
479
		$form = new Form($this, 'filter', $fields, $actions);
480
		$form->setFormMethod('GET');
481
		$form->setFormAction(Controller::join_links($this->Link('show'), $folder->ID));
482
		$form->addExtraClass('cms-search-form');
483
		$form->loadDataFrom($this->getRequest()->getVars());
484
		$form->disableSecurityToken();
485
		// This have to match data-name attribute on the gridfield so that the javascript selectors work
486
		$form->setAttribute('data-gridfield', 'File');
487
		return $form;
488
	}
489
490
	public function AddForm() {
491
		$negotiator = $this->getResponseNegotiator();
492
		$form = Form::create(
493
			$this,
494
			'AddForm',
495
			new FieldList(
496
				new TextField("Name", _t('File.Name')),
497
				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...
498
			),
499
			new FieldList(
500
				FormAction::create('doAdd', _t('AssetAdmin_left_ss.GO','Go'))
501
					->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')
502
					->setTitle(_t('AssetAdmin.ActionAdd', 'Add folder'))
503
			)
504
		)->setHTMLID('Form_AddForm');
505 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...
506
			$request = $this->getRequest();
507
			if($request->isAjax() && $negotiator) {
508
				$form->setupFormErrors();
509
				$result = $form->forTemplate();
510
511
				return $negotiator->respond($request, array(
512
					'CurrentForm' => function() use($result) {
513
						return $result;
514
					}
515
				));
516
			}
517
		});
518
		$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...
519
		// TODO Can't merge $FormAttributes in template at the moment
520
		$form->addExtraClass('add-form cms-add-form cms-edit-form cms-panel-padded center ' . $this->BaseCSSClasses());
521
522
		return $form;
523
	}
524
525
	/**
526
	 * Add a new group and return its details suitable for ajax.
527
	 *
528
	 * @todo Move logic into Folder class, and use LeftAndMain->doAdd() default implementation.
529
	 */
530
	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...
531
		$class = $this->stat('tree_class');
532
533
		// check create permissions
534
		if(!singleton($class)->canCreate()) {
535
			return Security::permissionFailure($this);
536
		}
537
538
		// check addchildren permissions
539
		if(
540
			singleton($class)->hasExtension('SilverStripe\ORM\Hierarchy\Hierarchy')
541
			&& isset($data['ParentID'])
542
			&& is_numeric($data['ParentID'])
543
			&& $data['ParentID']
544
		) {
545
			$parentRecord = DataObject::get_by_id($class, $data['ParentID']);
546
			if($parentRecord->hasMethod('canAddChildren') && !$parentRecord->canAddChildren()) {
547
				return Security::permissionFailure($this);
548
			}
549
		} else {
550
			$parentRecord = null;
551
		}
552
553
		// Check parent
554
		$parentID = $parentRecord && $parentRecord->ID
555
			? (int)$parentRecord->ID
556
			: 0;
557
		// Build filename
558
		$filename = isset($data['Name'])
559
			? basename($data['Name'])
560
			: _t('AssetAdmin.NEWFOLDER',"NewFolder");
561
		if($parentRecord && $parentRecord->ID) {
562
			$filename = $parentRecord->getFilename() . '/' . $filename;
563
		}
564
565
		// Get the folder to be created
566
567
		// Ensure name is unique
568
		foreach($this->getNameGenerator($filename) as $filename) {
569
			if(! File::find($filename) ) {
570
				break;
571
			}
572
		}
573
574
		// Create record
575
		$record = Folder::create();
576
		$record->ParentID = $parentID;
577
		$record->Name = $record->Title = basename($filename);
578
		$record->write();
579
580
581
		if($parentRecord) {
582
			return $this->redirect(Controller::join_links($this->Link('show'), $parentRecord->ID));
583
		} else {
584
			return $this->redirect($this->Link());
585
		}
586
	}
587
588
	/**
589
	 * Get an asset renamer for the given filename.
590
	 *
591
	 * @param string $filename Path name
592
	 * @return AssetNameGenerator
593
	 */
594
	protected function getNameGenerator($filename){
595
		return Injector::inst()
596
			->createWithArgs('AssetNameGenerator', array($filename));
597
	}
598
599
	/**
600
	 * Custom currentPage() method to handle opening the 'root' folder
601
	 */
602
	public function currentPage() {
603
		$id = $this->currentPageID();
604
		if($id && is_numeric($id) && $id > 0) {
605
			$folder = DataObject::get_by_id('Folder', $id);
606
			if($folder && $folder->isInDB()) {
607
				return $folder;
608
			}
609
		}
610
		$this->setCurrentPageID(null);
611
		return new Folder();
612
	}
613
614
	public function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, $filterFunction = null, $minNodeCount = 30) {
615
		if (!$childrenMethod) $childrenMethod = 'ChildFolders';
616
		if (!$numChildrenMethod) $numChildrenMethod = 'numChildFolders';
617
		return parent::getSiteTreeFor($className, $rootID, $childrenMethod, $numChildrenMethod, $filterFunction, $minNodeCount);
618
	}
619
620
	public function getCMSTreeTitle() {
621
		return Director::absoluteBaseURL() . "assets";
622
	}
623
624
	public function SiteTreeAsUL() {
625
		return $this->getSiteTreeFor($this->stat('tree_class'), null, 'ChildFolders', 'numChildFolders');
626
	}
627
628
	/**
629
	 * @param bool $unlinked
630
	 * @return ArrayList
631
	 */
632
	public function Breadcrumbs($unlinked = false) {
633
		$items = parent::Breadcrumbs($unlinked);
634
635
		// The root element should explicitly point to the root node.
636
		// Uses session state for current record otherwise.
637
		$items[0]->Link = Controller::join_links($this->Link('show'), 0);
638
639
		// If a search is in progress, don't show the path
640
		if($this->getRequest()->requestVar('q')) {
641
			$items = $items->limit(1);
642
			$items->push(new ArrayData(array(
643
				'Title' => _t('LeftAndMain.SearchResults', 'Search Results'),
644
				'Link' => Controller::join_links($this->Link(), '?' . http_build_query(array('q' => $this->getRequest()->requestVar('q'))))
645
			)));
646
		}
647
648
		// If we're adding a folder, note that in breadcrumbs as well
649
		if($this->getRequest()->param('Action') == 'addfolder') {
650
			$items->push(new ArrayData(array(
651
				'Title' => _t('Folder.AddFolderButton', 'Add folder'),
652
				'Link' => false
653
			)));
654
		}
655
656
		return $items;
657
	}
658
659
	public static function menu_title($class = null, $localised = true) {
660
		// Deprecate this menu title if installed alongside new asset admin
661
		if($localised && class_exists('SilverStripe\AssetAdmin\Controller\AssetAdmin')) {
662
			// Don't conflict with legacy translations
663
			return _t('AssetAdmin.CMSMENU_OLD', 'Files (old)');
664
		}
665
		return parent::menu_title(null, $localised);
666
	}
667
668
	public function providePermissions() {
669
		$title = static::menu_title();
670
		return array(
671
			"CMS_ACCESS_AssetAdmin" => array(
672
				'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...
673
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
674
			)
675
		);
676
	}
677
678
}
679
/**
680
 * Delete multiple {@link Folder} records (and the associated filesystem nodes).
681
 * Usually used through the {@link AssetAdmin} interface.
682
 *
683
 * @package cms
684
 * @subpackage batchactions
685
 */
686
class AssetAdmin_DeleteBatchAction extends CMSBatchAction {
687
	public function getActionTitle() {
688
		// _t('AssetAdmin_left_ss.SELECTTODEL','Select the folders that you want to delete and then click the button below')
689
		return _t('AssetAdmin_DeleteBatchAction.TITLE', 'Delete folders');
690
	}
691
692
	public function run(SS_List $records) {
693
		$status = array(
694
			'modified'=>array(),
695
			'deleted'=>array()
696
		);
697
698
		foreach($records as $record) {
699
			$id = $record->ID;
700
701
			// Perform the action
702
			if($record->canDelete()) $record->delete();
703
704
			$status['deleted'][$id] = array();
705
706
			$record->destroy();
707
			unset($record);
708
		}
709
710
		return Convert::raw2json($status);
711
	}
712
}
713