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'; |
|
|
|
|
67
|
|
|
|
68
|
|
|
private static $url_rule = '/$Action/$ID'; |
|
|
|
|
69
|
|
|
|
70
|
|
|
private static $menu_title = 'Files'; |
|
|
|
|
71
|
|
|
|
72
|
|
|
private static $tree_class = 'Folder'; |
|
|
|
|
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( |
|
|
|
|
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'])) { |
|
|
|
|
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'])) { |
|
|
|
|
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)); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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')); |
|
|
|
|
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); |
|
|
|
|
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) { |
|
|
|
|
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)); |
|
|
|
|
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')) |
|
|
|
|
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) { |
|
|
|
|
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')); |
|
|
|
|
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) { |
|
|
|
|
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)), |
|
|
|
|
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
|
|
|
|