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'; |
|
|
|
|
15
|
|
|
|
16
|
|
|
private static $url_rule = '/$Action/$ID'; |
|
|
|
|
17
|
|
|
|
18
|
|
|
private static $menu_title = 'Files'; |
|
|
|
|
19
|
|
|
|
20
|
|
|
private static $tree_class = 'Folder'; |
|
|
|
|
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( |
|
|
|
|
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 (isset($this->urlParams['ID']) && 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/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'])) { |
|
|
|
|
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'])) { |
|
|
|
|
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)); |
|
|
|
|
147
|
|
|
|
148
|
|
|
// Remove legacy previewable behaviour. |
149
|
|
|
$form->removeExtraClass('cms-previewable'); |
150
|
|
|
$form->Fields()->removeByName('SilverStripeNavigator'); |
151
|
|
|
|
152
|
|
|
// File listing |
153
|
|
|
$gridFieldConfig = GridFieldConfig::create()->addComponents( |
154
|
|
|
new GridFieldToolbarHeader(), |
155
|
|
|
new GridFieldSortableHeader(), |
156
|
|
|
new GridFieldFilterHeader(), |
157
|
|
|
new GridFieldDataColumns(), |
158
|
|
|
new GridFieldPaginator(self::config()->page_length), |
159
|
|
|
new GridFieldEditButton(), |
160
|
|
|
new GridFieldDeleteAction(), |
161
|
|
|
new GridFieldDetailForm(), |
162
|
|
|
GridFieldLevelup::create($folder->ID)->setLinkSpec('admin/assets/show/%d') |
163
|
|
|
); |
164
|
|
|
|
165
|
|
|
$gridField = GridField::create('File', $title, $this->getList(), $gridFieldConfig); |
166
|
|
|
$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns'); |
167
|
|
|
$columns->setDisplayFields(array( |
168
|
|
|
'StripThumbnail' => '', |
169
|
|
|
'Title' => _t('File.Title', 'Title'), |
170
|
|
|
'Created' => _t('AssetAdmin.CREATED', 'Date'), |
171
|
|
|
'Size' => _t('AssetAdmin.SIZE', 'Size'), |
172
|
|
|
)); |
173
|
|
|
$columns->setFieldCasting(array( |
174
|
|
|
'Created' => 'SS_Datetime->Nice' |
175
|
|
|
)); |
176
|
|
|
$gridField->setAttribute( |
177
|
|
|
'data-url-folder-template', |
178
|
|
|
Controller::join_links($this->Link('show'), '%s') |
179
|
|
|
); |
180
|
|
|
|
181
|
|
|
if(!$folder->hasMethod('canAddChildren') || ($folder->hasMethod('canAddChildren') && $folder->canAddChildren())) { |
182
|
|
|
// TODO Will most likely be replaced by GridField logic |
183
|
|
|
$addFolderBtn = new LiteralField( |
184
|
|
|
'AddFolderButton', |
185
|
|
|
sprintf( |
186
|
|
|
'<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>', |
187
|
|
|
_t('Folder.AddFolderButton', 'Add folder'), |
188
|
|
|
Controller::join_links($this->Link('AddForm'), '?' . http_build_query(array( |
189
|
|
|
'action_doAdd' => 1, |
190
|
|
|
'ParentID' => $folder->ID, |
191
|
|
|
'SecurityID' => $form->getSecurityToken()->getValue() |
192
|
|
|
))), |
193
|
|
|
Controller::join_links($this->Link('addfolder'), '?ParentID=' . $folder->ID) |
194
|
|
|
) |
195
|
|
|
); |
196
|
|
|
} else { |
197
|
|
|
$addFolderBtn = ''; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
// Move existing fields to a "details" tab, unless they've already been tabbed out through extensions. |
201
|
|
|
// Required to keep Folder->getCMSFields() simple and reuseable, |
202
|
|
|
// without any dependencies into AssetAdmin (e.g. useful for "add folder" views). |
203
|
|
|
if(!$fields->hasTabset()) { |
204
|
|
|
$tabs = new TabSet('Root', |
205
|
|
|
$tabList = new Tab('ListView', _t('AssetAdmin.ListView', 'List View')), |
206
|
|
|
$tabTree = new Tab('TreeView', _t('AssetAdmin.TreeView', 'Tree View')) |
207
|
|
|
); |
208
|
|
|
$tabList->addExtraClass("content-listview cms-tabset-icon list"); |
209
|
|
|
$tabTree->addExtraClass("content-treeview cms-tabset-icon tree"); |
210
|
|
|
if($fields->Count() && $folder && $folder->isInDB()) { |
211
|
|
|
$tabs->push($tabDetails = new Tab('DetailsView', _t('AssetAdmin.DetailsView', 'Details'))); |
212
|
|
|
$tabDetails->addExtraClass("content-galleryview cms-tabset-icon edit"); |
213
|
|
|
foreach($fields as $field) { |
214
|
|
|
$fields->removeByName($field->getName()); |
215
|
|
|
$tabDetails->push($field); |
216
|
|
|
} |
217
|
|
|
} |
218
|
|
|
$fields->push($tabs); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
// we only add buttons if they're available. User might not have permission and therefore |
222
|
|
|
// the button shouldn't be available. Adding empty values into a ComposteField breaks template rendering. |
223
|
|
|
$actionButtonsComposite = CompositeField::create()->addExtraClass('cms-actions-row'); |
224
|
|
|
if($addFolderBtn) $actionButtonsComposite->push($addFolderBtn); |
|
|
|
|
225
|
|
|
|
226
|
|
|
// Add the upload field for new media |
227
|
|
|
if($currentPageID = $this->currentPageID()){ |
228
|
|
|
Session::set("{$this->class}.currentPage", $currentPageID); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
$folder = $this->currentPage(); |
232
|
|
|
|
233
|
|
|
$uploadField = UploadField::create('AssetUploadField', ''); |
234
|
|
|
$uploadField->setConfig('previewMaxWidth', 40); |
235
|
|
|
$uploadField->setConfig('previewMaxHeight', 30); |
236
|
|
|
$uploadField->setConfig('changeDetection', false); |
237
|
|
|
$uploadField->addExtraClass('ss-assetuploadfield'); |
238
|
|
|
$uploadField->removeExtraClass('ss-uploadfield'); |
239
|
|
|
$uploadField->setTemplate('AssetUploadField'); |
240
|
|
|
|
241
|
|
|
if($folder->exists()) { |
242
|
|
|
$path = $folder->getFilename(); |
243
|
|
|
$uploadField->setFolderName($path); |
244
|
|
|
} else { |
245
|
|
|
$uploadField->setFolderName('/'); // root of the assets |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
$exts = $uploadField->getValidator()->getAllowedExtensions(); |
249
|
|
|
asort($exts); |
250
|
|
|
$uploadField->Extensions = implode(', ', $exts); |
|
|
|
|
251
|
|
|
|
252
|
|
|
// List view |
253
|
|
|
$fields->addFieldsToTab('Root.ListView', array( |
254
|
|
|
$actionsComposite = CompositeField::create( |
255
|
|
|
$actionButtonsComposite |
256
|
|
|
)->addExtraClass('cms-content-toolbar field'), |
257
|
|
|
$uploadField, |
258
|
|
|
new HiddenField('ID'), |
259
|
|
|
$gridField |
260
|
|
|
)); |
261
|
|
|
|
262
|
|
|
// Tree view |
263
|
|
|
$fields->addFieldsToTab('Root.TreeView', array( |
264
|
|
|
clone $actionsComposite, |
265
|
|
|
// TODO Replace with lazy loading on client to avoid performance hit of rendering potentially unused views |
266
|
|
|
new LiteralField( |
267
|
|
|
'Tree', |
268
|
|
|
FormField::create_tag( |
269
|
|
|
'div', |
270
|
|
|
array( |
271
|
|
|
'class' => 'cms-tree', |
272
|
|
|
'data-url-tree' => $this->Link('getsubtree'), |
273
|
|
|
'data-url-savetreenode' => $this->Link('savetreenode') |
274
|
|
|
), |
275
|
|
|
$this->SiteTreeAsUL() |
276
|
|
|
) |
277
|
|
|
) |
278
|
|
|
)); |
279
|
|
|
|
280
|
|
|
// Move actions to "details" tab (they don't make sense on list/tree view) |
281
|
|
|
$actions = $form->Actions(); |
282
|
|
|
$saveBtn = $actions->fieldByName('action_save'); |
283
|
|
|
$deleteBtn = $actions->fieldByName('action_delete'); |
284
|
|
|
$actions->removeByName('action_save'); |
285
|
|
|
$actions->removeByName('action_delete'); |
286
|
|
|
if(($saveBtn || $deleteBtn) && $fields->fieldByName('Root.DetailsView')) { |
287
|
|
|
$fields->addFieldToTab( |
288
|
|
|
'Root.DetailsView', |
289
|
|
|
CompositeField::create($saveBtn,$deleteBtn)->addExtraClass('Actions') |
290
|
|
|
); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
|
294
|
|
|
$fields->setForm($form); |
295
|
|
|
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); |
|
|
|
|
296
|
|
|
// TODO Can't merge $FormAttributes in template at the moment |
297
|
|
|
$form->addExtraClass('cms-edit-form ' . $this->BaseCSSClasses()); |
298
|
|
|
$form->setAttribute('data-pjax-fragment', 'CurrentForm'); |
299
|
|
|
$form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet'); |
300
|
|
|
|
301
|
|
|
// Optionally handle form submissions with 'X-Formschema-Request' |
302
|
|
|
// which rely on having validation errors returned as structured data |
303
|
|
|
$form->setValidationResponseCallback(function() use ($form) { |
304
|
|
|
$request = $this->getRequest(); |
305
|
|
|
if($request->getHeader('X-Formschema-Request')) { |
306
|
|
|
$data = $this->getSchemaForForm($form); |
|
|
|
|
307
|
|
|
$response = new SS_HTTPResponse(Convert::raw2json($data)); |
308
|
|
|
$response->addHeader('Content-Type', 'application/json'); |
309
|
|
|
return $response; |
310
|
|
|
|
311
|
|
|
} |
312
|
|
|
}); |
313
|
|
|
|
314
|
|
|
|
315
|
|
|
$this->extend('updateEditForm', $form); |
316
|
|
|
|
317
|
|
|
return $form; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
public function addfolder($request) { |
321
|
|
|
$obj = $this->customise(array( |
322
|
|
|
'EditForm' => $this->AddForm() |
323
|
|
|
)); |
324
|
|
|
|
325
|
|
|
if($request->isAjax()) { |
326
|
|
|
// Rendering is handled by template, which will call EditForm() eventually |
327
|
|
|
$content = $obj->renderWith($this->getTemplatesWithSuffix('_Content')); |
328
|
|
|
} else { |
329
|
|
|
$content = $obj->renderWith($this->getViewer('show')); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
return $content; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
public function delete($data, $form) { |
336
|
|
|
$className = $this->stat('tree_class'); |
337
|
|
|
|
338
|
|
|
$record = DataObject::get_by_id($className, $data['ID']); |
339
|
|
|
if($record && !$record->canDelete()) { |
340
|
|
|
return Security::permissionFailure(); |
341
|
|
|
} |
342
|
|
View Code Duplication |
if(!$record || !$record->ID) { |
|
|
|
|
343
|
|
|
throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404); |
344
|
|
|
} |
345
|
|
|
$parentID = $record->ParentID; |
346
|
|
|
$record->delete(); |
347
|
|
|
$this->setCurrentPageID(null); |
348
|
|
|
|
349
|
|
|
$this->getResponse()->addHeader('X-Status', rawurlencode(_t('LeftAndMain.DELETED', 'Deleted.'))); |
350
|
|
|
$this->getResponse()->addHeader('X-Pjax', 'Content'); |
351
|
|
|
return $this->redirect(Controller::join_links($this->Link('show'), $parentID ? $parentID : 0)); |
|
|
|
|
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Get the search context |
356
|
|
|
* |
357
|
|
|
* @return SearchContext |
358
|
|
|
*/ |
359
|
|
|
public function getSearchContext() { |
360
|
|
|
$context = singleton('File')->getDefaultSearchContext(); |
361
|
|
|
|
362
|
|
|
// Namespace fields, for easier detection if a search is present |
363
|
|
|
foreach($context->getFields() as $field) { |
364
|
|
|
$field->setName(sprintf('q[%s]', $field->getName())); |
365
|
|
|
} |
366
|
|
|
foreach($context->getFilters() as $filter) { |
367
|
|
|
$filter->setFullName(sprintf('q[%s]', $filter->getFullName())); |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
// Customize fields |
371
|
|
|
$dateHeader = HeaderField::create('q[Date]', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4); |
372
|
|
|
$dateFrom = DateField::create('q[CreatedFrom]', _t('CMSSearch.FILTERDATEFROM', 'From')) |
373
|
|
|
->setConfig('showcalendar', true); |
374
|
|
|
$dateTo = DateField::create('q[CreatedTo]',_t('CMSSearch.FILTERDATETO', 'To')) |
375
|
|
|
->setConfig('showcalendar', true); |
376
|
|
|
$dateGroup = FieldGroup::create( |
377
|
|
|
$dateHeader, |
378
|
|
|
$dateFrom, |
379
|
|
|
$dateTo |
380
|
|
|
); |
381
|
|
|
$context->addField($dateGroup); |
382
|
|
|
$appCategories = array( |
383
|
|
|
'archive' => _t('AssetAdmin.AppCategoryArchive', 'Archive', 'A collection of files'), |
384
|
|
|
'audio' => _t('AssetAdmin.AppCategoryAudio', 'Audio'), |
385
|
|
|
'document' => _t('AssetAdmin.AppCategoryDocument', 'Document'), |
386
|
|
|
'flash' => _t('AssetAdmin.AppCategoryFlash', 'Flash', 'The fileformat'), |
387
|
|
|
'image' => _t('AssetAdmin.AppCategoryImage', 'Image'), |
388
|
|
|
'video' => _t('AssetAdmin.AppCategoryVideo', 'Video'), |
389
|
|
|
); |
390
|
|
|
$context->addField( |
391
|
|
|
$typeDropdown = new DropdownField( |
392
|
|
|
'q[AppCategory]', |
393
|
|
|
_t('AssetAdmin.Filetype', 'File type'), |
394
|
|
|
$appCategories |
395
|
|
|
) |
396
|
|
|
); |
397
|
|
|
|
398
|
|
|
$typeDropdown->setEmptyString(' '); |
399
|
|
|
|
400
|
|
|
$context->addField( |
401
|
|
|
new CheckboxField('q[CurrentFolderOnly]', _t('AssetAdmin.CurrentFolderOnly', 'Limit to current folder?')) |
402
|
|
|
); |
403
|
|
|
$context->getFields()->removeByName('q[Title]'); |
404
|
|
|
|
405
|
|
|
return $context; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Returns a form for filtering of files and assets gridfield. |
410
|
|
|
* Result filtering takes place in {@link getList()}. |
411
|
|
|
* |
412
|
|
|
* @return Form |
413
|
|
|
* @see AssetAdmin.js |
414
|
|
|
*/ |
415
|
|
|
public function SearchForm() { |
416
|
|
|
$folder = $this->currentPage(); |
417
|
|
|
$context = $this->getSearchContext(); |
418
|
|
|
|
419
|
|
|
$fields = $context->getSearchFields(); |
420
|
|
|
$actions = new FieldList( |
421
|
|
|
FormAction::create('doSearch', _t('CMSMain_left_ss.APPLY_FILTER', 'Apply Filter')) |
422
|
|
|
->addExtraClass('ss-ui-action-constructive'), |
423
|
|
|
Object::create('ResetFormAction', 'clear', _t('CMSMain_left_ss.RESET', 'Reset')) |
424
|
|
|
); |
425
|
|
|
|
426
|
|
|
$form = new Form($this, 'filter', $fields, $actions); |
427
|
|
|
$form->setFormMethod('GET'); |
428
|
|
|
$form->setFormAction(Controller::join_links($this->Link('show'), $folder->ID)); |
429
|
|
|
$form->addExtraClass('cms-search-form'); |
430
|
|
|
$form->loadDataFrom($this->getRequest()->getVars()); |
431
|
|
|
$form->disableSecurityToken(); |
432
|
|
|
// This have to match data-name attribute on the gridfield so that the javascript selectors work |
433
|
|
|
$form->setAttribute('data-gridfield', 'File'); |
434
|
|
|
return $form; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
public function AddForm() { |
438
|
|
|
$negotiator = $this->getResponseNegotiator(); |
439
|
|
|
$form = Form::create( |
440
|
|
|
$this, |
441
|
|
|
'AddForm', |
442
|
|
|
new FieldList( |
443
|
|
|
new TextField("Name", _t('File.Name')), |
444
|
|
|
new HiddenField('ParentID', false, $this->getRequest()->getVar('ParentID')) |
|
|
|
|
445
|
|
|
), |
446
|
|
|
new FieldList( |
447
|
|
|
FormAction::create('doAdd', _t('AssetAdmin_left_ss.GO','Go')) |
448
|
|
|
->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept') |
449
|
|
|
->setTitle(_t('AssetAdmin.ActionAdd', 'Add folder')) |
450
|
|
|
) |
451
|
|
|
)->setHTMLID('Form_AddForm'); |
452
|
|
View Code Duplication |
$form->setValidationResponseCallback(function() use ($negotiator, $form) { |
|
|
|
|
453
|
|
|
$request = $this->getRequest(); |
454
|
|
|
if($request->isAjax() && $negotiator) { |
455
|
|
|
$form->setupFormErrors(); |
456
|
|
|
$result = $form->forTemplate(); |
457
|
|
|
|
458
|
|
|
return $negotiator->respond($request, array( |
459
|
|
|
'CurrentForm' => function() use($result) { |
460
|
|
|
return $result; |
461
|
|
|
} |
462
|
|
|
)); |
463
|
|
|
} |
464
|
|
|
}); |
465
|
|
|
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); |
|
|
|
|
466
|
|
|
// TODO Can't merge $FormAttributes in template at the moment |
467
|
|
|
$form->addExtraClass('add-form cms-add-form cms-edit-form cms-panel-padded center ' . $this->BaseCSSClasses()); |
468
|
|
|
|
469
|
|
|
return $form; |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
/** |
473
|
|
|
* Add a new group and return its details suitable for ajax. |
474
|
|
|
* |
475
|
|
|
* @todo Move logic into Folder class, and use LeftAndMain->doAdd() default implementation. |
476
|
|
|
*/ |
477
|
|
|
public function doAdd($data, $form) { |
|
|
|
|
478
|
|
|
$class = $this->stat('tree_class'); |
479
|
|
|
|
480
|
|
|
// check create permissions |
481
|
|
|
if(!singleton($class)->canCreate()) { |
482
|
|
|
return Security::permissionFailure($this); |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
// check addchildren permissions |
486
|
|
|
if( |
487
|
|
|
singleton($class)->hasExtension('Hierarchy') |
488
|
|
|
&& isset($data['ParentID']) |
489
|
|
|
&& is_numeric($data['ParentID']) |
490
|
|
|
&& $data['ParentID'] |
491
|
|
|
) { |
492
|
|
|
$parentRecord = DataObject::get_by_id($class, $data['ParentID']); |
493
|
|
|
if($parentRecord->hasMethod('canAddChildren') && !$parentRecord->canAddChildren()) { |
494
|
|
|
return Security::permissionFailure($this); |
495
|
|
|
} |
496
|
|
|
} else { |
497
|
|
|
$parentRecord = null; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
// Check parent |
501
|
|
|
$parentID = $parentRecord && $parentRecord->ID |
502
|
|
|
? (int)$parentRecord->ID |
503
|
|
|
: 0; |
504
|
|
|
// Build filename |
505
|
|
|
$filename = isset($data['Name']) |
506
|
|
|
? basename($data['Name']) |
507
|
|
|
: _t('AssetAdmin.NEWFOLDER',"NewFolder"); |
508
|
|
|
if($parentRecord && $parentRecord->ID) { |
509
|
|
|
$filename = $parentRecord->getFilename() . '/' . $filename; |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
// Get the folder to be created |
513
|
|
|
|
514
|
|
|
// Ensure name is unique |
515
|
|
|
foreach($this->getNameGenerator($filename) as $filename) { |
516
|
|
|
if(! File::find($filename) ) { |
517
|
|
|
break; |
518
|
|
|
} |
519
|
|
|
} |
520
|
|
|
|
521
|
|
|
// Create record |
522
|
|
|
$record = Folder::create(); |
523
|
|
|
$record->ParentID = $parentID; |
524
|
|
|
$record->Name = $record->Title = basename($filename); |
525
|
|
|
$record->write(); |
526
|
|
|
|
527
|
|
|
|
528
|
|
|
if($parentRecord) { |
529
|
|
|
return $this->redirect(Controller::join_links($this->Link('show'), $parentRecord->ID)); |
530
|
|
|
} else { |
531
|
|
|
return $this->redirect($this->Link()); |
532
|
|
|
} |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
/** |
536
|
|
|
* Get an asset renamer for the given filename. |
537
|
|
|
* |
538
|
|
|
* @param string $filename Path name |
539
|
|
|
* @return AssetNameGenerator |
540
|
|
|
*/ |
541
|
|
|
protected function getNameGenerator($filename){ |
542
|
|
|
return Injector::inst() |
543
|
|
|
->createWithArgs('AssetNameGenerator', array($filename)); |
544
|
|
|
} |
545
|
|
|
|
546
|
|
|
/** |
547
|
|
|
* Custom currentPage() method to handle opening the 'root' folder |
548
|
|
|
*/ |
549
|
|
|
public function currentPage() { |
550
|
|
|
$id = $this->currentPageID(); |
551
|
|
|
if($id && is_numeric($id) && $id > 0) { |
552
|
|
|
$folder = DataObject::get_by_id('Folder', $id); |
553
|
|
|
if($folder && $folder->isInDB()) { |
554
|
|
|
return $folder; |
555
|
|
|
} |
556
|
|
|
} |
557
|
|
|
$this->setCurrentPageID(null); |
558
|
|
|
return new Folder(); |
559
|
|
|
} |
560
|
|
|
|
561
|
|
|
public function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, $filterFunction = null, $minNodeCount = 30) { |
562
|
|
|
if (!$childrenMethod) $childrenMethod = 'ChildFolders'; |
563
|
|
|
if (!$numChildrenMethod) $numChildrenMethod = 'numChildFolders'; |
564
|
|
|
return parent::getSiteTreeFor($className, $rootID, $childrenMethod, $numChildrenMethod, $filterFunction, $minNodeCount); |
565
|
|
|
} |
566
|
|
|
|
567
|
|
|
public function getCMSTreeTitle() { |
568
|
|
|
return Director::absoluteBaseURL() . "assets"; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
public function SiteTreeAsUL() { |
572
|
|
|
return $this->getSiteTreeFor($this->stat('tree_class'), null, 'ChildFolders', 'numChildFolders'); |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
/** |
576
|
|
|
* @param bool $unlinked |
577
|
|
|
* @return ArrayList |
578
|
|
|
*/ |
579
|
|
|
public function Breadcrumbs($unlinked = false) { |
580
|
|
|
$items = parent::Breadcrumbs($unlinked); |
581
|
|
|
|
582
|
|
|
// The root element should explicitly point to the root node. |
583
|
|
|
// Uses session state for current record otherwise. |
584
|
|
|
$items[0]->Link = Controller::join_links(singleton('AssetAdmin')->Link('show'), 0); |
585
|
|
|
|
586
|
|
|
// If a search is in progress, don't show the path |
587
|
|
|
if($this->getRequest()->requestVar('q')) { |
588
|
|
|
$items = $items->limit(1); |
589
|
|
|
$items->push(new ArrayData(array( |
590
|
|
|
'Title' => _t('LeftAndMain.SearchResults', 'Search Results'), |
591
|
|
|
'Link' => Controller::join_links($this->Link(), '?' . http_build_query(array('q' => $this->getRequest()->requestVar('q')))) |
592
|
|
|
))); |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
// If we're adding a folder, note that in breadcrumbs as well |
596
|
|
|
if($this->getRequest()->param('Action') == 'addfolder') { |
597
|
|
|
$items->push(new ArrayData(array( |
598
|
|
|
'Title' => _t('Folder.AddFolderButton', 'Add folder'), |
599
|
|
|
'Link' => false |
600
|
|
|
))); |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
return $items; |
604
|
|
|
} |
605
|
|
|
|
606
|
|
|
public static function menu_title($class = null, $localised = true) { |
607
|
|
|
// Deprecate this menu title if installed alongside new asset admin |
608
|
|
|
if($localised && class_exists('SilverStripe\AssetAdmin\Controller\AssetAdmin')) { |
609
|
|
|
// Don't conflict with legacy translations |
610
|
|
|
return _t('AssetAdmin.CMSMENU_OLD', 'Files (old)'); |
611
|
|
|
} |
612
|
|
|
return parent::menu_title(null, $localised); |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
public function providePermissions() { |
616
|
|
|
$title = static::menu_title(); |
617
|
|
|
return array( |
618
|
|
|
"CMS_ACCESS_AssetAdmin" => array( |
619
|
|
|
'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array('title' => $title)), |
|
|
|
|
620
|
|
|
'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access') |
621
|
|
|
) |
622
|
|
|
); |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
} |
626
|
|
|
/** |
627
|
|
|
* Delete multiple {@link Folder} records (and the associated filesystem nodes). |
628
|
|
|
* Usually used through the {@link AssetAdmin} interface. |
629
|
|
|
* |
630
|
|
|
* @package cms |
631
|
|
|
* @subpackage batchactions |
632
|
|
|
*/ |
633
|
|
|
class AssetAdmin_DeleteBatchAction extends CMSBatchAction { |
634
|
|
|
public function getActionTitle() { |
635
|
|
|
// _t('AssetAdmin_left_ss.SELECTTODEL','Select the folders that you want to delete and then click the button below') |
636
|
|
|
return _t('AssetAdmin_DeleteBatchAction.TITLE', 'Delete folders'); |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
public function run(SS_List $records) { |
640
|
|
|
$status = array( |
641
|
|
|
'modified'=>array(), |
642
|
|
|
'deleted'=>array() |
643
|
|
|
); |
644
|
|
|
|
645
|
|
|
foreach($records as $record) { |
646
|
|
|
$id = $record->ID; |
647
|
|
|
|
648
|
|
|
// Perform the action |
649
|
|
|
if($record->canDelete()) $record->delete(); |
650
|
|
|
|
651
|
|
|
$status['deleted'][$id] = array(); |
652
|
|
|
|
653
|
|
|
$record->destroy(); |
654
|
|
|
unset($record); |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
return Convert::raw2json($status); |
658
|
|
|
} |
659
|
|
|
} |
660
|
|
|
|