|
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
|
|
|
|