Completed
Pull Request — master (#280)
by Damian
02:03
created

AssetAdmin::getFileEditActions()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 61
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 61
rs 7.399
cc 7
eloc 36
nc 9
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\AssetAdmin\Controller;
4
5
use SilverStripe\Admin\AddToCampaignHandler;
6
use SilverStripe\Admin\CMSBatchActionHandler;
7
use SilverStripe\Admin\LeftAndMain;
8
use SilverStripe\Assets\File;
9
use SilverStripe\Assets\Folder;
10
use SilverStripe\Assets\ImageManipulation;
11
use SilverStripe\Assets\Storage\AssetNameGenerator;
12
use SilverStripe\Assets\Upload;
13
use SilverStripe\Control\Controller;
14
use SilverStripe\Control\HTTPRequest;
15
use SilverStripe\Control\HTTPResponse;
16
use SilverStripe\Core\Config\Config;
17
use SilverStripe\Core\Convert;
18
use SilverStripe\Core\Injector\Injector;
19
use SilverStripe\Forms\CheckboxField;
20
use SilverStripe\Forms\DateField;
21
use SilverStripe\Forms\DropdownField;
22
use SilverStripe\Forms\FieldGroup;
23
use SilverStripe\Forms\FieldList;
24
use SilverStripe\Forms\Form;
25
use SilverStripe\Forms\FormAction;
26
use SilverStripe\Forms\HeaderField;
27
use SilverStripe\Forms\PopoverField;
28
use SilverStripe\ORM\ArrayList;
29
use SilverStripe\ORM\DataList;
30
use SilverStripe\ORM\DataObject;
31
use SilverStripe\ORM\FieldType\DBHTMLText;
32
use SilverStripe\ORM\Search\SearchContext;
33
use SilverStripe\Security\Member;
34
use SilverStripe\Security\PermissionProvider;
35
use SilverStripe\Security\SecurityToken;
36
use SilverStripe\View\Requirements;
37
38
/**
39
 * AssetAdmin is the 'file store' section of the CMS.
40
 * It provides an interface for manipulating the File and Folder objects in the system.
41
 */
42
class AssetAdmin extends LeftAndMain implements PermissionProvider
43
{
44
    private static $url_segment = 'assets';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $url_segment is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
45
46
    private static $url_rule = '/$Action/$ID';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $url_rule is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
47
48
    private static $menu_title = 'Files';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $menu_title is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
49
50
    private static $tree_class = 'SilverStripe\\Assets\\Folder';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $tree_class is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
51
52
    private static $url_handlers = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $url_handlers is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
53
        // Legacy redirect for SS3-style detail view
54
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
55
        // Pass all URLs to the index, for React to unpack
56
        'show/$FolderID/edit/$FileID' => 'index',
57
        // API access points with structured data
58
        'POST api/createFolder' => 'apiCreateFolder',
59
        'POST api/createFile' => 'apiCreateFile',
60
        'GET api/readFolder' => 'apiReadFolder',
61
        'PUT api/updateFolder' => 'apiUpdateFolder',
62
        'DELETE api/delete' => 'apiDelete',
63
        'GET api/search' => 'apiSearch',
64
    ];
65
66
    /**
67
     * Amount of results showing on a single page.
68
     *
69
     * @config
70
     * @var int
71
     */
72
    private static $page_length = 15;
0 ignored issues
show
Unused Code introduced by
The property $page_length is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
73
74
    /**
75
     * @config
76
     * @see Upload->allowedMaxFileSize
77
     * @var int
78
     */
79
    private static $allowed_max_file_size;
0 ignored issues
show
Unused Code introduced by
The property $allowed_max_file_size is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
80
81
    /**
82
     * @var array
83
     */
84
    private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
85
        'legacyRedirectForEditView',
86
        'apiCreateFolder',
87
        'apiCreateFile',
88
        'apiReadFolder',
89
        'apiUpdateFolder',
90
        'apiDelete',
91
        'apiSearch',
92
        'FileEditForm',
93
        'AddToCampaignForm',
94
    );
95
96
    private static $required_permission_codes = 'CMS_ACCESS_AssetAdmin';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $required_permission_codes is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
97
98
    private static $thumbnail_width = 400;
0 ignored issues
show
Unused Code introduced by
The property $thumbnail_width is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
99
100
    private static $thumbnail_height = 300;
0 ignored issues
show
Unused Code introduced by
The property $thumbnail_height is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
101
102
    /**
103
     * Set up the controller
104
     */
105
    public function init()
106
    {
107
        parent::init();
108
109
        Requirements::add_i18n_javascript(ASSET_ADMIN_DIR . '/client/lang', false, true);
110
        Requirements::javascript(ASSET_ADMIN_DIR . "/client/dist/js/bundle.js");
111
        Requirements::css(ASSET_ADMIN_DIR . "/client/dist/styles/bundle.css");
112
113
        CMSBatchActionHandler::register(
114
            'delete',
115
            'SilverStripe\AssetAdmin\BatchAction\DeleteAssets',
116
            'SilverStripe\\Assets\\Folder'
117
        );
118
    }
119
120
    public function getClientConfig()
121
    {
122
        $baseLink = $this->Link();
123
        return array_merge( parent::getClientConfig(), [
124
            'reactRouter' => true,
125
            'createFileEndpoint' => [
126
                'url' => Controller::join_links($baseLink, 'api/createFile'),
127
                'method' => 'post',
128
                'payloadFormat' => 'urlencoded',
129
            ],
130
            'createFolderEndpoint' => [
131
                'url' => Controller::join_links($baseLink, 'api/createFolder'),
132
                'method' => 'post',
133
                'payloadFormat' => 'urlencoded',
134
            ],
135
            'readFolderEndpoint' => [
136
                'url' => Controller::join_links($baseLink, 'api/readFolder'),
137
                'method' => 'get',
138
                'responseFormat' => 'json',
139
            ],
140
            'searchEndpoint' => [
141
                'url' => Controller::join_links($baseLink, 'api/search'),
142
                'method' => 'get',
143
                'responseFormat' => 'json',
144
            ],
145
            'updateFolderEndpoint' => [
146
                'url' => Controller::join_links($baseLink, 'api/updateFolder'),
147
                'method' => 'put',
148
                'payloadFormat' => 'urlencoded',
149
            ],
150
            'deleteEndpoint' => [
151
                'url' => Controller::join_links($baseLink, 'api/delete'),
152
                'method' => 'delete',
153
                'payloadFormat' => 'urlencoded',
154
            ],
155
            'limit' => $this->config()->page_length,
156
            'form' => [
157
                'FileEditForm' => [
158
                    'schemaUrl' => $this->Link('schema/FileEditForm')
159
                ],
160
                'AddToCampaignForm' => [
161
                    'schemaUrl' => $this->Link('schema/AddToCampaignForm')
162
                ],
163
            ],
164
        ]);
165
    }
166
167
    /**
168
     * Fetches a collection of files by ParentID.
169
     *
170
     * @param HTTPRequest $request
171
     * @return HTTPResponse
172
     */
173
    public function apiReadFolder(HTTPRequest $request)
174
    {
175
        $params = $request->requestVars();
176
        $items = array();
177
        $parentId = null;
0 ignored issues
show
Unused Code introduced by
$parentId is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
178
        $folderID = null;
0 ignored issues
show
Unused Code introduced by
$folderID is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
179
180
        if (!isset($params['id']) && !strlen($params['id'])) {
181
            $this->httpError(400);
182
        }
183
184
        $folderID = (int)$params['id'];
185
        /** @var Folder $folder */
186
        $folder = $folderID ? Folder::get()->byID($folderID) : Folder::singleton();
187
188
        if (!$folder) {
189
            $this->httpError(400);
190
        }
191
192
        // TODO Limit results to avoid running out of memory (implement client-side pagination)
193
        $files = $this->getList()->filter('ParentID', $folderID);
194
195
        if ($files) {
196
            /** @var File $file */
197
            foreach ($files as $file) {
198
                if (!$file->canView()) {
199
                    continue;
200
                }
201
202
                $items[] = $this->getObjectFromData($file);
203
            }
204
        }
205
206
        // Build parents (for breadcrumbs)
207
        $parents = [];
208
        $next = $folder->Parent();
209
        while($next && $next->exists()) {
210
            array_unshift($parents, [
211
                'id' => $next->ID,
212
                'title' => $next->getTitle(),
213
                'filename' => $next->getFilename(),
214
            ]);
215
            if($next->ParentID) {
216
                $next = $next->Parent();
217
            } else {
218
                break;
219
            }
220
        }
221
222
        // Build response
223
        $response = new HTTPResponse();
224
        $response->addHeader('Content-Type', 'application/json');
225
        $response->setBody(json_encode([
226
            'files' => $items,
227
            'title' => $folder->getTitle(),
228
            'count' => count($items),
229
            'parents' => $parents,
230
            'parent' => $parents ? $parents[count($parents) - 1] : null,
231
            'parentID' => $folder->exists() ? $folder->ParentID : null, // grandparent
232
            'folderID' => $folderID,
233
            'canEdit' => $folder->canEdit(),
234
            'canDelete' => $folder->canDelete(),
235
        ]));
236
237
        return $response;
238
    }
239
240
    /**
241
     * @param HTTPRequest $request
242
     *
243
     * @return HTTPResponse
244
     */
245
    public function apiSearch(HTTPRequest $request)
246
    {
247
        $params = $request->getVars();
248
        $list = $this->getList($params);
249
250
        $response = new HTTPResponse();
251
        $response->addHeader('Content-Type', 'application/json');
252
        $response->setBody(json_encode([
253
            // Serialisation
254
            "files" => array_map(function($file) {
255
                return $this->getObjectFromData($file);
256
            }, $list->toArray()),
257
            "count" => $list->count(),
258
        ]));
259
260
        return $response;
261
    }
262
263
    /**
264
     * @param HTTPRequest $request
265
     *
266
     * @return HTTPResponse
267
     */
268
    public function apiDelete(HTTPRequest $request)
269
    {
270
        parse_str($request->getBody(), $vars);
271
272
        // CSRF check
273
        $token = SecurityToken::inst();
274 View Code Duplication
        if (empty($vars[$token->getName()]) || !$token->check($vars[$token->getName()])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
275
            return new HTTPResponse(null, 400);
276
        }
277
278 View Code Duplication
        if (!isset($vars['ids']) || !$vars['ids']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
279
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
280
                ->addHeader('Content-Type', 'application/json');
281
        }
282
283
        $fileIds = $vars['ids'];
284
        $files = $this->getList()->filter("ID", $fileIds)->toArray();
285
286 View Code Duplication
        if (!count($files)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
287
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
288
                ->addHeader('Content-Type', 'application/json');
289
        }
290
291
        if (!min(array_map(function (File $file) {
292
            return $file->canDelete();
293
        }, $files))) {
294
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
295
                ->addHeader('Content-Type', 'application/json');
296
        }
297
298
        /** @var File $file */
299
        foreach ($files as $file) {
300
            $file->delete();
301
        }
302
303
        return (new HTTPResponse(json_encode(['status' => 'file was deleted'])))
304
            ->addHeader('Content-Type', 'application/json');
305
    }
306
307
    /**
308
     * Creates a single file based on a form-urlencoded upload.
309
     *
310
     * @param HTTPRequest $request
311
     * @return HTTPRequest|HTTPResponse
312
     */
313
    public function apiCreateFile(HTTPRequest $request)
314
    {
315
        $data = $request->postVars();
316
        $upload = $this->getUpload();
317
318
        // CSRF check
319
        $token = SecurityToken::inst();
320 View Code Duplication
        if (empty($data[$token->getName()]) || !$token->check($data[$token->getName()])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
321
            return new HTTPResponse(null, 400);
322
        }
323
324
        // Check parent record
325
        /** @var Folder $parentRecord */
326
        $parentRecord = null;
327
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
328
            $parentRecord = Folder::get()->byID($data['ParentID']);
329
        }
330
        $data['Parent'] = $parentRecord;
331
332
        $tmpFile = $request->postVar('Upload');
333 View Code Duplication
        if(!$upload->validate($tmpFile)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
334
            $result = ['error' => $upload->getErrors()];
335
            return (new HTTPResponse(json_encode($result), 400))
336
                ->addHeader('Content-Type', 'application/json');
337
        }
338
339
        // TODO Allow batch uploads
340
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
341
        /** @var File $file */
342
        $file = Injector::inst()->create($fileClass);
343
344
        // check canCreate permissions
345 View Code Duplication
        if (!$file->canCreate(null, $data)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
346
            return (new HTTPResponse(json_encode(['status' => 'error']), 403))
347
                ->addHeader('Content-Type', 'application/json');
348
        }
349
350
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
351 View Code Duplication
        if(!$uploadResult) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
352
            $result = ['error' => 'unknown'];
353
            return (new HTTPResponse(json_encode($result), 400))
354
                ->addHeader('Content-Type', 'application/json');
355
        }
356
357
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
358
        $file->write();
359
360
        $result = [$this->getObjectFromData($file)];
361
362
        return (new HTTPResponse(json_encode($result)))
363
            ->addHeader('Content-Type', 'application/json');
364
    }
365
366
    /**
367
     * Creates a single folder, within an optional parent folder.
368
     *
369
     * @param HTTPRequest $request
370
     * @return HTTPRequest|HTTPResponse
371
     */
372
    public function apiCreateFolder(HTTPRequest $request)
373
    {
374
        $data = $request->postVars();
375
376
        $class = 'SilverStripe\\Assets\\Folder';
377
378
        // CSRF check
379
        $token = SecurityToken::inst();
380 View Code Duplication
        if (empty($data[$token->getName()]) || !$token->check($data[$token->getName()])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
381
            return new HTTPResponse(null, 400);
382
        }
383
384
        // check addchildren permissions
385
        /** @var Folder $parentRecord */
386
        $parentRecord = null;
387
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
388
            $parentRecord = DataObject::get_by_id($class, $data['ParentID']);
389
        }
390
        $data['Parent'] = $parentRecord;
391
        $data['ParentID'] = $parentRecord ? (int)$parentRecord->ID : 0;
392
393
        // Build filename
394
        $baseFilename = isset($data['Name'])
395
            ? basename($data['Name'])
396
            : _t('AssetAdmin.NEWFOLDER', "NewFolder");
397
398
        if ($parentRecord && $parentRecord->ID) {
399
            $baseFilename = $parentRecord->getFilename() . '/' . $baseFilename;
400
        }
401
402
        // Ensure name is unique
403
        $nameGenerator = $this->getNameGenerator($baseFilename);
404
        $filename = null;
405
        foreach ($nameGenerator as $filename) {
406
            if (! File::find($filename)) {
407
                break;
408
            }
409
        }
410
        $data['Name'] = basename($filename);
411
412
        // Create record
413
        /** @var Folder $record */
414
        $record = Injector::inst()->create($class);
415
416
        // check create permissions
417
        if (!$record->canCreate(null, $data)) {
418
            return (new HTTPResponse(null, 403))
419
                ->addHeader('Content-Type', 'application/json');
420
        }
421
422
        $record->ParentID = $data['ParentID'];
423
        $record->Name = $record->Title = basename($data['Name']);
424
        $record->write();
425
426
        $result = $this->getObjectFromData($record);
427
428
        return (new HTTPResponse(json_encode($result)))->addHeader('Content-Type', 'application/json');
429
    }
430
431
    /**
432
     * Redirects 3.x style detail links to new 4.x style routing.
433
     *
434
     * @param HTTPRequest $request
435
     */
436
    public function legacyRedirectForEditView($request)
437
    {
438
        $fileID = $request->param('FileID');
439
        /** @var File $file */
440
        $file = File::get()->byID($fileID);
441
        $link = $this->getFileEditLink($file) ?: $this->Link();
442
        $this->redirect($link);
443
    }
444
445
    /**
446
     * Given a file return the CMS link to edit it
447
     *
448
     * @param File $file
449
     * @return string
450
     */
451
    public function getFileEditLink($file) {
452
        if(!$file || !$file->isInDB()) {
453
            return null;
454
        }
455
456
        return Controller::join_links(
457
            $this->Link('show'),
458
            $file->ParentID,
459
            'edit',
460
            $file->ID
461
        );
462
    }
463
464
    /**
465
     * Get the search context from {@link File}, used to create the search form
466
     * as well as power the /search API endpoint.
467
     *
468
     * @return SearchContext
469
     */
470
    public function getSearchContext()
471
    {
472
        $context = File::singleton()->getDefaultSearchContext();
473
474
        // Customize fields
475
        $dateHeader = HeaderField::create('Date', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
476
        $dateFrom = DateField::create('CreatedFrom', _t('CMSSearch.FILTERDATEFROM', 'From'))
477
        ->setConfig('showcalendar', true);
478
        $dateTo = DateField::create('CreatedTo', _t('CMSSearch.FILTERDATETO', 'To'))
479
        ->setConfig('showcalendar', true);
480
        $dateGroup = FieldGroup::create(
481
            $dateHeader,
482
            $dateFrom,
483
            $dateTo
484
        );
485
        $context->addField($dateGroup);
486
        /** @skipUpgrade */
487
        $appCategories = array(
488
            'archive' => _t('AssetAdmin.AppCategoryArchive', 'Archive', 'A collection of files'),
489
            'audio' => _t('AssetAdmin.AppCategoryAudio', 'Audio'),
490
            'document' => _t('AssetAdmin.AppCategoryDocument', 'Document'),
491
            'flash' => _t('AssetAdmin.AppCategoryFlash', 'Flash', 'The fileformat'),
492
            'image' => _t('AssetAdmin.AppCategoryImage', 'Image'),
493
            'video' => _t('AssetAdmin.AppCategoryVideo', 'Video'),
494
        );
495
        $context->addField(
496
            $typeDropdown = new DropdownField(
497
                'AppCategory',
498
                _t('AssetAdmin.Filetype', 'File type'),
499
                $appCategories
500
            )
501
        );
502
503
        $typeDropdown->setEmptyString(' ');
504
505
        $context->addField(
506
            new CheckboxField('CurrentFolderOnly', _t('AssetAdmin.CurrentFolderOnly', 'Limit to current folder?'))
507
        );
508
        $context->getFields()->removeByName('Title');
509
510
        return $context;
511
    }
512
513
    /**
514
     * Get an asset renamer for the given filename.
515
     *
516
     * @param  string             $filename Path name
517
     * @return AssetNameGenerator
518
     */
519
    protected function getNameGenerator($filename)
520
    {
521
        return Injector::inst()
522
            ->createWithArgs('AssetNameGenerator', array($filename));
523
    }
524
525
    /**
526
     * @todo Implement on client
527
     *
528
     * @param bool $unlinked
529
     * @return ArrayList
530
     */
531
    public function breadcrumbs($unlinked = false)
532
    {
533
        return null;
534
    }
535
536
537
    /**
538
     * Don't include class namespace in auto-generated CSS class
539
     */
540
    public function baseCSSClasses()
541
    {
542
        return 'AssetAdmin LeftAndMain';
543
    }
544
545
    public function providePermissions()
546
    {
547
        return array(
548
            "CMS_ACCESS_AssetAdmin" => array(
549
                'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array(
0 ignored issues
show
Documentation introduced by
array('title' => static::menu_title()) is of type array<string,string,{"title":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
550
                    'title' => static::menu_title()
551
                )),
552
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
553
            )
554
        );
555
    }
556
557
    /**
558
     * The form is used to generate a form schema,
559
     * as well as an intermediary object to process data through API endpoints.
560
     * Since it's used directly on API endpoints, it does not have any form actions.
561
     * It handles both {@link File} and {@link Folder} records.
562
     *
563
     * @param int $id
564
     * @return Form
565
     */
566
    public function getFileEditForm($id)
567
    {
568
        /** @var File $file */
569
        $file = $this->getList()->byID($id);
570
571
        $fields = $file->getCMSFields();
572
573
        $actions = $this->getFileEditActions($file);
574
575
        $form = Form::create(
576
            $this,
577
            'FileEditForm',
578
            $fields,
579
            $actions
580
        );
581
582
        // Load into form
583
        if($id && $file) {
584
            $form->loadDataFrom($file);
585
        }
586
587
        // Configure form to respond to validation errors with form schema
588
        // if requested via react.
589
        $form->setValidationResponseCallback(function() use ($form) {
590
            return $this->getSchemaResponse($form);
591
        });
592
593
        return $form;
594
    }
595
596
    /**
597
     * Get file edit form
598
     *
599
     * @return Form
600
     */
601
    public function FileEditForm()
602
    {
603
        // Get ID either from posted back value, or url parameter
604
        $request = $this->getRequest();
605
        $id = $request->param('ID') ?: $request->postVar('ID');
606
        return $this->getFileEditForm($id);
607
    }
608
609
    /**
610
     * @param array $data
611
     * @param Form $form
612
     * @return HTTPResponse
613
     */
614
    public function save($data, $form)
615
    {
616
        return $this->saveOrPublish($data, $form, false);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->saveOrPublish($data, $form, false); (SilverStripe\Control\HTTPResponse) is incompatible with the return type of the parent method SilverStripe\Admin\LeftAndMain::save of type SS_HTTPResponse|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
617
    }
618
619
620
    /**
621
     * @param array $data
622
     * @param Form $form
623
     * @return HTTPResponse
624
     */
625
    public function publish($data, $form) {
626
        return $this->saveOrPublish($data, $form, true);
627
    }
628
629
    /**
630
     * Update thisrecord
631
     *
632
     * @param array $data
633
     * @param Form $form
634
     * @param bool $doPublish
635
     * @return HTTPResponse
636
     */
637
    protected function saveOrPublish($data, $form, $doPublish = false) {
638 View Code Duplication
        if (!isset($data['ID']) || !is_numeric($data['ID'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
639
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
640
                ->addHeader('Content-Type', 'application/json');
641
        }
642
643
        $id = (int) $data['ID'];
644
        /** @var File $record */
645
        $record = $this->getList()->filter('ID', $id)->first();
646
647 View Code Duplication
        if (!$record) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
648
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
649
                ->addHeader('Content-Type', 'application/json');
650
        }
651
652 View Code Duplication
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
653
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
654
                ->addHeader('Content-Type', 'application/json');
655
        }
656
657
        $form->saveInto($record);
658
        $record->write();
659
660
        // Publish this record and owned objects
661
        if ($doPublish) {
662
            $record->publishRecursive();
663
        }
664
665
        // Return the record data in the same response as the schema to save a postback
666
        $schemaData = $this->getSchemaForForm($this->getFileEditForm($id));
667
        $schemaData['record'] = $this->getObjectFromData($record);
668
        $response = new HTTPResponse(Convert::raw2json($schemaData));
669
        $response->addHeader('Content-Type', 'application/json');
670
        return $response;
671
    }
672
673
    public function unpublish($data, $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
674 View Code Duplication
        if (!isset($data['ID']) || !is_numeric($data['ID'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
675
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
676
                ->addHeader('Content-Type', 'application/json');
677
        }
678
679
        $id = (int) $data['ID'];
680
        /** @var File $record */
681
        $record = $this->getList()->filter('ID', $id)->first();
682
683
        if (!$record) {
684
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
685
                ->addHeader('Content-Type', 'application/json');
686
        }
687
688
        if (!$record->canUnpublish()) {
689
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
690
                ->addHeader('Content-Type', 'application/json');
691
        }
692
693
        $record->doUnpublish();
694
695
        // Return the record data in the same response as the schema to save a postback
696
        $schemaData = $this->getSchemaForForm($this->getFileEditForm($id));
697
        $schemaData['record'] = $this->getObjectFromData($record);
698
        $response = new HTTPResponse(Convert::raw2json($schemaData));
699
        $response->addHeader('Content-Type', 'application/json');
700
        return $response;
701
    }
702
703
    /**
704
     * @param File $file
705
     *
706
     * @return array
707
     */
708
    protected function getObjectFromData(File $file)
709
    {
710
        $object = array(
711
            'id' => $file->ID,
712
            'created' => $file->Created,
713
            'lastUpdated' => $file->LastEdited,
714
            'owner' => null,
715
            'parent' => null,
716
            'title' => $file->Title,
717
            'exists' => $file->exists(), // Broken file check
718
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
0 ignored issues
show
Bug introduced by
The class SilverStripe\Assets\Folder does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
719
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
0 ignored issues
show
Bug introduced by
The class SilverStripe\Assets\Folder does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
720
            'name' => $file->Name,
721
            'filename' => $file->Filename,
722
            'extension' => $file->Extension,
723
            'size' => $file->Size,
724
            'url' => $file->AbsoluteURL,
725
            'canEdit' => $file->canEdit(),
726
            'canDelete' => $file->canDelete()
727
        );
728
729
        /** @var Member $owner */
730
        $owner = $file->Owner();
731
732
        if ($owner) {
733
            $object['owner'] = array(
734
                'id' => $owner->ID,
735
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
736
            );
737
        }
738
739
        /** @var Folder $parent */
740
        $parent = $file->Parent();
741
742
        if ($parent) {
743
            $object['parent'] = array(
744
                'id' => $parent->ID,
745
                'title' => $parent->Title,
746
                'filename' => $parent->Filename,
747
            );
748
        }
749
750
        /** @var File $file */
751
        if ($file->getIsImage()) {
752
            $width = (int)Config::inst()->get(self::class, 'thumbnail_width');
753
            $height = (int)Config::inst()->get(self::class, 'thumbnail_height');
754
755
            $thumbnail = $file->FitMax($width, $height);
756
            if ($thumbnail && $thumbnail->exists()) {
757
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
758
            }
759
            $object['dimensions']['width'] = $file->Width;
760
            $object['dimensions']['height'] = $file->Height;
761
        }
762
763
        return $object;
764
    }
765
766
767
    /**
768
     * Returns the files and subfolders contained in the currently selected folder,
769
     * defaulting to the root node. Doubles as search results, if any search parameters
770
     * are set through {@link SearchForm()}.
771
     *
772
     * @param array $params Unsanitised request parameters
773
     * @return DataList
774
     */
775
    protected function getList($params = array())
776
    {
777
        $context = $this->getSearchContext();
778
779
        // Overwrite name filter to search both Name and Title attributes
780
        $context->removeFilterByName('Name');
781
782
        // Lazy loaded list. Allows adding new filters through SearchContext.
783
        /** @var DataList $list */
784
        $list = $context->getResults($params);
785
786
        // Re-add previously removed "Name" filter as combined filter
787
        // TODO Replace with composite SearchFilter once that API exists
788
        if(!empty($params['Name'])) {
789
            $list = $list->filterAny(array(
790
                'Name:PartialMatch' => $params['Name'],
791
                'Title:PartialMatch' => $params['Name']
792
            ));
793
        }
794
795
        // Optionally limit search to a folder (non-recursive)
796
        if(!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
797
            $list = $list->filter('ParentID', $params['ParentID']);
798
        }
799
800
        // Date filtering
801 View Code Duplication
        if (!empty($params['CreatedFrom'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
802
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
803
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
804
        }
805 View Code Duplication
        if (!empty($params['CreatedTo'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
806
            $toDate = new DateField(null, null, $params['CreatedTo']);
807
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
808
        }
809
810
        // Categories
811
        if (!empty($filters['AppCategory']) && !empty(File::config()->app_categories[$filters['AppCategory']])) {
0 ignored issues
show
Bug introduced by
The variable $filters seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
812
            $extensions = File::config()->app_categories[$filters['AppCategory']];
813
            $list = $list->filter('Name:PartialMatch', $extensions);
814
        }
815
816
        // Sort folders first
817
        $list = $list->sort(
818
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
819
        );
820
821
        // Pagination
822
        if (isset($filters['page']) && isset($filters['limit'])) {
823
            $page = $filters['page'];
824
            $limit = $filters['limit'];
825
            $offset = ($page - 1) * $limit;
826
            $list = $list->limit($limit, $offset);
827
        }
828
829
        // Access checks
830
        $list = $list->filterByCallback(function(File $file) {
831
            return $file->canView();
832
        });
833
834
        return $list;
835
    }
836
837
    /**
838
     * Action handler for adding pages to a campaign
839
     *
840
     * @param array $data
841
     * @param Form $form
842
     * @return DBHTMLText|HTTPResponse
843
     */
844
    public function addtocampaign($data, $form)
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
845
    {
846
        $id = $data['ID'];
847
        $record = $this->getList()->byID($id);
848
849
        $handler = AddToCampaignHandler::create($this, $record);
850
        $results = $handler->addToCampaign($record, $data['Campaign']);
851
        if (!is_null($results)) {
852
            $request = $this->getRequest();
853
            if($request->getHeader('X-Formschema-Request')) {
854
                $data = $this->getSchemaForForm($handler->Form($record));
855
                $data['message'] = $results;
856
857
                $response = new HTTPResponse(Convert::raw2json($data));
858
                $response->addHeader('Content-Type', 'application/json');
859
                return $response;
860
            }
861
            return $results;
862
        }
863
    }
864
865
    /**
866
     * Url handler for add to campaign form
867
     *
868
     * @param HTTPRequest $request
869
     * @return Form
870
     */
871
    public function AddToCampaignForm($request)
872
    {
873
        // Get ID either from posted back value, or url parameter
874
        $id = $request->param('ID') ?: $request->postVar('ID');
875
        return $this->getAddToCampaignForm($id);
876
    }
877
878
    /**
879
     * @param int $id
880
     * @return Form
881
     */
882
    public function getAddToCampaignForm($id)
883
    {
884
        // Get record-specific fields
885
        $record = $this->getList()->byID($id);
886
887
        if (!$record) {
888
            $this->httpError(404, _t(
889
                'AssetAdmin.ErrorNotFound',
890
                'That {Type} couldn\'t be found',
891
                '',
892
                ['Type' => _t('File.SINGULARNAME')]
0 ignored issues
show
Documentation introduced by
array('Type' => _t('File.SINGULARNAME')) is of type array<string,string,{"Type":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
893
            ));
894
            return null;
895
        }
896
        if (!$record->canView()) {
897
            $this->httpError(403, _t(
898
                'AssetAdmin.ErrorItemPermissionDenied',
899
                'It seems you don\'t have the necessary permissions to add {ObjectTitle} to a campaign',
900
                '',
901
                ['ObjectTitle' => _t('File.SINGULARNAME')]
0 ignored issues
show
Documentation introduced by
array('ObjectTitle' => _t('File.SINGULARNAME')) is of type array<string,string,{"ObjectTitle":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
902
            ));
903
            return null;
904
        }
905
906
        $handler = AddToCampaignHandler::create($this, $record);
907
        return $handler->Form($record);
908
    }
909
910
    /**
911
     * @return Upload
912
     */
913
    protected function getUpload()
914
    {
915
        $upload = Upload::create();
916
        $upload->getValidator()->setAllowedExtensions(
917
            // filter out '' since this would be a regex problem on JS end
918
            array_filter(File::config()->get('allowed_extensions'))
919
        );
920
921
        return $upload;
922
    }
923
924
    /**
925
     * Get actions for file edit
926
     *
927
     * @param File $file
928
     * @return FieldList
929
     */
930
    protected function getFileEditActions($file)
931
    {
932
        $actions = FieldList::create();
933
934
        // Save and/or publish
935
        if ($file->canEdit()) {
936
            // Create save button
937
            $saveAction = FormAction::create('save', _t('CMSMain.SAVE', 'Save'));
938
            $saveAction->setIcon('save');
939
            $actions->push($saveAction);
940
941
            // Folders are automatically published
942
            if ($file->canPublish() && (!$file instanceof Folder)) {
0 ignored issues
show
Bug introduced by
The class SilverStripe\Assets\Folder does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
943
                $publishText = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.PUBLISH_BUTTON', 'Publish');
944
                $publishAction = FormAction::create('publish', $publishText);
945
                $publishAction->setIcon('rocket');
946
                $publishAction->setSchemaData(['data' => ['buttonStyle' => 'primary']]);
947
                $actions->push($publishAction);
948
            }
949
        }
950
951
        // Delete action
952
        $deleteText = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.DELETE_BUTTON', 'Delete');
953
        $deleteAction = FormAction::create('delete', $deleteText);
954
        //$deleteAction->setSchemaData(['data' => ['buttonStyle' => 'danger']]);
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
955
        $deleteAction->setIcon('trash-bin');
956
957
        // Add file-specific actions
958
        if (!$file instanceof Folder) {
0 ignored issues
show
Bug introduced by
The class SilverStripe\Assets\Folder does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
959
            // Add to campaign action
960
            $addToCampaignAction = FormAction::create(
961
                'addtocampaign',
962
                _t('CAMPAIGNS.ADDTOCAMPAIGN', 'Add to campaign')
963
            );
964
            $popoverActions = [
965
                $addToCampaignAction
966
            ];
967
            // Add unpublish if available
968
            if ($file->isPublished() && $file->canUnpublish()) {
969
                $unpublishText = _t(
970
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UNPUBLISH_BUTTON',
971
                    'Unpublish'
972
                );
973
                $unpublishAction = FormAction::create('unpublish', $unpublishText);
974
                $unpublishAction->setIcon('cancel-circled');
975
                $popoverActions[] = $unpublishAction;
976
            }
977
            // Delete
978
            $popoverActions[] = $deleteAction;
979
980
            // Build popover menu
981
            $popoverField = PopoverField::create($popoverActions);
982
            $popoverField->setPlacement('top');
983
            $actions->push($popoverField);
984
        } else {
985
            $actions->push($deleteAction);
986
        }
987
988
        $this->extend('updateFileEditActions', $actions);
989
        return $actions;
990
    }
991
}
992