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