Completed
Push — master ( 609daa...1adaf3 )
by Ingo
11s
created

AssetAdmin::getFileEditForm()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 54
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 0 Features 2
Metric Value
c 10
b 0
f 2
dl 0
loc 54
rs 9.0306
cc 4
eloc 28
nc 4
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\Filesystem\Storage\AssetNameGenerator;
9
use SilverStripe\ORM\ArrayList;
10
use SilverStripe\ORM\DataList;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\FieldType\DBHTMLText;
13
use SilverStripe\Security\Member;
14
use SilverStripe\Security\PermissionProvider;
15
use SilverStripe\Security\SecurityToken;
16
use SearchContext;
17
use DateField;
18
use DropdownField;
19
use Controller;
20
use FieldList;
21
use Form;
22
use CheckboxField;
23
use File;
24
use Requirements;
25
use Injector;
26
use Folder;
27
use HeaderField;
28
use FieldGroup;
29
use SS_HTTPRequest;
30
use SS_HTTPResponse;
31
use Upload;
32
use Config;
33
use FormAction;
34
use TextField;
35
use HiddenField;
36
use ReadonlyField;
37
use LiteralField;
38
use PopoverField;
39
use HTMLReadonlyField;
40
use Convert;
41
use DatetimeField;
42
43
/**
44
 * AssetAdmin is the 'file store' section of the CMS.
45
 * It provides an interface for manipulating the File and Folder objects in the system.
46
 *
47
 * @package cms
48
 * @subpackage assets
49
 */
50
class AssetAdmin extends LeftAndMain implements PermissionProvider
51
{
52
    private static $url_segment = 'assets';
0 ignored issues
show
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...
53
54
    private static $url_rule = '/$Action/$ID';
0 ignored issues
show
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...
55
56
    private static $menu_title = 'Files';
0 ignored issues
show
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...
57
58
    private static $tree_class = 'Folder';
0 ignored issues
show
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...
59
60
    private static $url_handlers = [
0 ignored issues
show
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...
61
        // Legacy redirect for SS3-style detail view
62
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
63
        // Pass all URLs to the index, for React to unpack
64
        'show/$FolderID/edit/$FileID' => 'index',
65
        // API access points with structured data
66
        'POST api/createFolder' => 'apiCreateFolder',
67
        'POST api/createFile' => 'apiCreateFile',
68
        'GET api/readFolder' => 'apiReadFolder',
69
        'PUT api/updateFolder' => 'apiUpdateFolder',
70
        'DELETE api/delete' => 'apiDelete',
71
        'GET api/search' => 'apiSearch',
72
    ];
73
74
    /**
75
     * Amount of results showing on a single page.
76
     *
77
     * @config
78
     * @var int
79
     */
80
    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...
81
82
    /**
83
     * @config
84
     * @see Upload->allowedMaxFileSize
85
     * @var int
86
     */
87
    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...
88
89
    /**
90
     * @var array
91
     */
92
    private static $allowed_actions = array(
0 ignored issues
show
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...
93
        'legacyRedirectForEditView',
94
        'apiCreateFolder',
95
        'apiCreateFile',
96
        'apiReadFolder',
97
        'apiUpdateFolder',
98
        'apiDelete',
99
        'apiSearch',
100
        'FileEditForm',
101
        'AddToCampaignForm',
102
    );
103
104
    private static $required_permission_codes = 'CMS_ACCESS_AssetAdmin';
0 ignored issues
show
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...
105
106
    /**
107
     * Set up the controller
108
     */
109
    public function init()
110
    {
111
        parent::init();
112
113
        Requirements::add_i18n_javascript(ASSET_ADMIN_DIR . '/client/lang', false, true);
114
        Requirements::javascript(ASSET_ADMIN_DIR . "/client/dist/js/bundle.js");
115
        Requirements::css(ASSET_ADMIN_DIR . "/client/dist/styles/bundle.css");
116
117
        CMSBatchActionHandler::register('delete', 'SilverStripe\AssetAdmin\BatchAction\DeleteAssets', 'Folder');
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 SS_HTTPRequest $request
171
     * @return SS_HTTPResponse
172
     */
173
    public function apiReadFolder(SS_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) : singleton('Folder');
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
            ]);
214
            if($next->ParentID) {
215
                $next = $next->Parent();
216
            } else {
217
                break;
218
            }
219
        }
220
221
        // Build response
222
        $response = new SS_HTTPResponse();
223
        $response->addHeader('Content-Type', 'application/json');
224
        $response->setBody(json_encode([
225
            'files' => $items,
226
            'title' => $folder->getTitle(),
227
            'count' => count($items),
228
            'parents' => $parents,
229
            'parentID' => $folder->exists() ? $folder->ParentID : null, // grandparent
230
            'folderID' => $folderID,
231
            'canEdit' => $folder->canEdit(),
232
            'canDelete' => $folder->canDelete(),
233
        ]));
234
235
        return $response;
236
    }
237
238
    /**
239
     * @param SS_HTTPRequest $request
240
     *
241
     * @return SS_HTTPResponse
242
     */
243
    public function apiSearch(SS_HTTPRequest $request)
244
    {
245
        $params = $request->getVars();
246
        $list = $this->getList($params);
247
248
        $response = new SS_HTTPResponse();
249
        $response->addHeader('Content-Type', 'application/json');
250
        $response->setBody(json_encode([
251
            // Serialisation
252
            "files" => array_map(function($file) {
253
                return $this->getObjectFromData($file);
254
            }, $list->toArray()),
255
            "count" => $list->count(),
256
        ]));
257
258
        return $response;
259
    }
260
261
    /**
262
     * @param SS_HTTPRequest $request
263
     *
264
     * @return SS_HTTPResponse
265
     */
266
    public function apiDelete(SS_HTTPRequest $request)
267
    {
268
        parse_str($request->getBody(), $vars);
269
270
        // CSRF check
271
        $token = SecurityToken::inst();
272 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...
273
            return new SS_HTTPResponse(null, 400);
274
        }
275
276 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...
277
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))
278
                ->addHeader('Content-Type', 'application/json');
279
        }
280
281
        $fileIds = $vars['ids'];
282
        $files = $this->getList()->filter("ID", $fileIds)->toArray();
283
284 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...
285
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 404))
286
                ->addHeader('Content-Type', 'application/json');
287
        }
288
289
        if (!min(array_map(function (File $file) {
290
            return $file->canDelete();
291
        }, $files))) {
292
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 401))
293
                ->addHeader('Content-Type', 'application/json');
294
        }
295
296
        /** @var File $file */
297
        foreach ($files as $file) {
298
            $file->delete();
299
        }
300
301
        return (new SS_HTTPResponse(json_encode(['status' => 'file was deleted'])))
302
            ->addHeader('Content-Type', 'application/json');
303
    }
304
305
    /**
306
     * Creates a single file based on a form-urlencoded upload.
307
     *
308
     * @param SS_HTTPRequest $request
309
     * @return SS_HTTPRequest|SS_HTTPResponse
310
     */
311
    public function apiCreateFile(SS_HTTPRequest $request)
312
    {
313
        $data = $request->postVars();
314
        $upload = $this->getUpload();
315
316
        // CSRF check
317
        $token = SecurityToken::inst();
318 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...
319
            return new SS_HTTPResponse(null, 400);
320
        }
321
322
        // check canAddChildren permissions
323
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
324
            $parentRecord = Folder::get()->byID($data['ParentID']);
325 View Code Duplication
            if ($parentRecord->hasMethod('canAddChildren') && !$parentRecord->canAddChildren()) {
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...
326
                return (new SS_HTTPResponse(json_encode(['status' => 'error']), 403))
327
                    ->addHeader('Content-Type', 'application/json');
328
            }
329
        } else {
330
            $parentRecord = singleton('Folder');
331
        }
332
333
        // check create permissions
334
        if (!$parentRecord->canCreate()) {
335
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 403))
336
                ->addHeader('Content-Type', 'application/json');
337
        }
338
339
        $tmpFile = $request->postVar('Upload');
340
        if(!$upload->validate($tmpFile)) {
341
            $result = ['error' => $upload->getErrors()];
342
            return (new SS_HTTPResponse(json_encode($result), 400))
343
                ->addHeader('Content-Type', 'application/json');
344
        }
345
346
        // TODO Allow batch uploads
347
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
348
        $file = Injector::inst()->create($fileClass);
349
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
350 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...
351
            $result = ['error' => 'unknown'];
352
            return (new SS_HTTPResponse(json_encode($result), 400))
353
                ->addHeader('Content-Type', 'application/json');
354
        }
355
356
        $file->ParentID = $parentRecord->ID;
357
        $file->write();
358
359
        $result = [$this->getObjectFromData($file)];
360
361
        return (new SS_HTTPResponse(json_encode($result)))
362
            ->addHeader('Content-Type', 'application/json');
363
    }
364
365
    /**
366
     * Creates a single folder, within an optional parent folder.
367
     *
368
     * @param SS_HTTPRequest $request
369
     * @return SS_HTTPRequest|SS_HTTPResponse
370
     */
371
    public function apiCreateFolder(SS_HTTPRequest $request)
372
    {
373
        $data = $request->postVars();
374
375
        $class = 'Folder';
376
377
        // CSRF check
378
        $token = SecurityToken::inst();
379 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...
380
            return new SS_HTTPResponse(null, 400);
381
        }
382
383
        // check addchildren permissions
384
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
385
            $parentRecord = DataObject::get_by_id($class, $data['ParentID']);
386 View Code Duplication
            if ($parentRecord->hasMethod('canAddChildren') && !$parentRecord->canAddChildren()) {
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...
387
                return (new SS_HTTPResponse(null, 403))
388
                    ->addHeader('Content-Type', 'application/json');
389
            }
390
        } else {
391
            $parentRecord = singleton($class);
392
        }
393
        $data['ParentID'] = ($parentRecord->exists()) ? (int)$parentRecord->ID : 0;
394
395
        // check create permissions
396
        if (!$parentRecord->canCreate()) {
397
            return (new SS_HTTPResponse(null, 403))
398
                ->addHeader('Content-Type', 'application/json');
399
        }
400
401
        // Build filename
402
        $baseFilename = isset($data['Name'])
403
            ? basename($data['Name'])
404
            : _t('AssetAdmin.NEWFOLDER', "NewFolder");
405
406
        if ($parentRecord && $parentRecord->ID) {
407
            $baseFilename = $parentRecord->getFilename() . '/' . $baseFilename;
408
        }
409
410
        // Ensure name is unique
411
        $nameGenerator = $this->getNameGenerator($baseFilename);
412
        $filename = null;
413
        foreach ($nameGenerator as $filename) {
414
            if (! File::find($filename)) {
415
                break;
416
            }
417
        }
418
        $data['Name'] = basename($filename);
419
420
        // Create record
421
        /** @var Folder $record */
422
        $record = Injector::inst()->create($class);
423
        $record->ParentID = $data['ParentID'];
424
        $record->Name = $record->Title = basename($data['Name']);
425
        $record->write();
426
427
        $result = $this->getObjectFromData($record);
428
429
        return (new SS_HTTPResponse(json_encode($result)))->addHeader('Content-Type', 'application/json');
430
    }
431
432
    /**
433
     * Redirects 3.x style detail links to new 4.x style routing.
434
     *
435
     * @param SS_HTTPRequest $request
436
     */
437
    public function legacyRedirectForEditView($request)
438
    {
439
        $fileID = $request->param('FileID');
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
        $appCategories = array(
487
            'archive' => _t('AssetAdmin.AppCategoryArchive', 'Archive', 'A collection of files'),
488
            'audio' => _t('AssetAdmin.AppCategoryAudio', 'Audio'),
489
            'document' => _t('AssetAdmin.AppCategoryDocument', 'Document'),
490
            'flash' => _t('AssetAdmin.AppCategoryFlash', 'Flash', 'The fileformat'),
491
            'image' => _t('AssetAdmin.AppCategoryImage', 'Image'),
492
            'video' => _t('AssetAdmin.AppCategoryVideo', 'Video'),
493
        );
494
        $context->addField(
495
            $typeDropdown = new DropdownField(
496
                'AppCategory',
497
                _t('AssetAdmin.Filetype', 'File type'),
498
                $appCategories
499
            )
500
        );
501
502
        $typeDropdown->setEmptyString(' ');
503
504
        $context->addField(
505
            new CheckboxField('CurrentFolderOnly', _t('AssetAdmin.CurrentFolderOnly', 'Limit to current folder?'))
506
        );
507
        $context->getFields()->removeByName('Title');
508
509
        return $context;
510
    }
511
512
    /**
513
     * Get an asset renamer for the given filename.
514
     *
515
     * @param  string             $filename Path name
516
     * @return AssetNameGenerator
517
     */
518
    protected function getNameGenerator($filename)
519
    {
520
        return Injector::inst()
521
            ->createWithArgs('AssetNameGenerator', array($filename));
522
    }
523
524
    /**
525
     * @todo Implement on client
526
     *
527
     * @param bool $unlinked
528
     * @return ArrayList
529
     */
530
    public function breadcrumbs($unlinked = false)
531
    {
532
        return null;
533
    }
534
535
536
    /**
537
     * Don't include class namespace in auto-generated CSS class
538
     */
539
    public function baseCSSClasses()
540
    {
541
        return 'AssetAdmin LeftAndMain';
542
    }
543
544
    public function providePermissions()
545
    {
546
        return array(
547
            "CMS_ACCESS_AssetAdmin" => array(
548
                '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,?,{"title":"?"}>, 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...
549
                    'title' => static::menu_title()
550
                )),
551
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
552
            )
553
        );
554
    }
555
556
    /**
557
     * The form is used to generate a form schema,
558
     * as well as an intermediary object to process data through API endpoints.
559
     * Since it's used directly on API endpoints, it does not have any form actions.
560
     * It handles both {@link File} and {@link Folder} records.
561
     *
562
     * @param int $id
563
     * @return Form
564
     */
565
    public function getFileEditForm($id)
566
    {
567
        /** @var File $file */
568
        $file = $this->getList()->byID($id);
569
570
        $fields = $file->getCMSFields();
571
572
        $actions = FieldList::create([
573
            FormAction::create('save', _t('CMSMain.SAVE', 'Save'))
574
                ->setIcon('save')
575
        ]);
576
577
        // Delete action
578
        $actions->push(
579
            FormAction::create(
580
                'delete',
581
                _t('SilverStripe\AssetAdmin\Controller\AssetAdmin.DELETE_BUTTON', 'Delete')
582
            )
583
                ->setIcon('trash-bin')
584
        );
585
586
        // Add to campaign action
587
        if (!$file instanceof Folder) {
588
            $actions->push(PopoverField::create([
589
                FormAction::create(
590
                    'addtocampaign',
591
                    _t('CAMPAIGNS.ADDTOCAMPAIGN',
592
                        'Add to campaign')
593
                ),
594
            ])
595
                ->setPlacement('top')
596
            );
597
        }
598
599
        $form = Form::create(
600
            $this,
601
            'FileEditForm',
602
            $fields,
603
            $actions
604
        );
605
606
        // Load into form
607
        if($id && $file) {
608
            $form->loadDataFrom($file);
609
        }
610
611
        // Configure form to respond to validation errors with form schema
612
        // if requested via react.
613
        $form->setValidationResponseCallback(function() use ($form) {
614
            return $this->getSchemaResponse($form);
615
        });
616
617
        return $form;
618
    }
619
620
    /**
621
     * Get file edit form
622
     *
623
     * @return Form
624
     */
625
    public function FileEditForm()
626
    {
627
        // Get ID either from posted back value, or url parameter
628
        $request = $this->getRequest();
629
        $id = $request->param('ID') ?: $request->postVar('ID');
630
        return $this->getFileEditForm($id);
631
    }
632
633
    /**
634
     * @param array $data
635
     * @param Form $form
636
     * @return SS_HTTPResponse
637
     */
638
    public function save($data, $form)
639
    {
640 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...
641
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))
642
                ->addHeader('Content-Type', 'application/json');
643
        }
644
645
        $id = (int) $data['ID'];
646
        $record = $this->getList()->filter('ID', $id)->first();
647
648 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...
649
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 404))
650
                ->addHeader('Content-Type', 'application/json');
651
        }
652
653 View Code Duplication
        if (!$record->canEdit()) {
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...
654
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 401))
655
                ->addHeader('Content-Type', 'application/json');
656
        }
657
658
        $form->saveInto($record);
659
        $record->write();
660
661
        // Return the record data in the same response as the schema to save a postback
662
        $schemaData = $this->getSchemaForForm($this->getFileEditForm($id));
663
        $schemaData['record'] = $this->getObjectFromData($record);
664
        $response = new SS_HTTPResponse(\Convert::raw2json($schemaData));
665
        $response->addHeader('Content-Type', 'application/json');
666
        return $response;
667
    }
668
669
    /**
670
     * @param File $file
671
     *
672
     * @return array
673
     */
674
    protected function getObjectFromData(File $file)
675
    {
676
        $object = array(
677
            'id' => $file->ID,
678
            'created' => $file->Created,
679
            'lastUpdated' => $file->LastEdited,
680
            'owner' => null,
681
            'parent' => null,
682
            'title' => $file->Title,
683
            'exists' => $file->exists(), // Broken file check
684
            'type' => $file->is_a('Folder') ? 'folder' : $file->FileType,
685
            'category' => $file->is_a('Folder') ? 'folder' : $file->appCategory(),
686
            'name' => $file->Name,
687
            'filename' => $file->Filename,
688
            'extension' => $file->Extension,
0 ignored issues
show
Bug introduced by
The property Extension does not seem to exist. Did you mean allowed_extensions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
689
            'size' => $file->Size,
690
            'url' => $file->AbsoluteURL,
691
            'canEdit' => $file->canEdit(),
692
            'canDelete' => $file->canDelete()
693
        );
694
695
        /** @var Member $owner */
696
        $owner = $file->Owner();
697
698
        if ($owner) {
699
            $object['owner'] = array(
700
                'id' => $owner->ID,
701
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
702
            );
703
        }
704
705
        /** @var Folder $parent */
706
        $parent = $file->Parent();
707
708
        if ($parent) {
709
            $object['parent'] = array(
710
                'id' => $parent->ID,
711
                'title' => $parent->Title,
712
                'filename' => $parent->Filename,
713
            );
714
        }
715
716
        /** @var File $file */
717
        if ($file->getIsImage()) {
718
            $object['dimensions']['width'] = $file->Width;
719
            $object['dimensions']['height'] = $file->Height;
0 ignored issues
show
Bug introduced by
The property Height does not seem to exist. Did you mean cms_thumbnail_height?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
720
        }
721
722
        return $object;
723
    }
724
725
726
    /**
727
     * Returns the files and subfolders contained in the currently selected folder,
728
     * defaulting to the root node. Doubles as search results, if any search parameters
729
     * are set through {@link SearchForm()}.
730
     *
731
     * @param array $params Unsanitised request parameters
732
     * @return DataList
733
     */
734
    protected function getList($params = array())
735
    {
736
        $context = $this->getSearchContext();
737
738
        // Overwrite name filter to search both Name and Title attributes
739
        $context->removeFilterByName('Name');
740
741
        // Lazy loaded list. Allows adding new filters through SearchContext.
742
        $list = $context->getResults($params);
743
744
        // Re-add previously removed "Name" filter as combined filter
745
        // TODO Replace with composite SearchFilter once that API exists
746
        if(!empty($params['Name'])) {
747
            $list = $list->filterAny(array(
748
                'Name:PartialMatch' => $params['Name'],
749
                'Title:PartialMatch' => $params['Name']
750
            ));
751
        }
752
753
        // Optionally limit search to a folder (non-recursive)
754
        if(!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
755
            $list = $list->filter('ParentID', $params['ParentID']);
756
        }
757
758
        // Date filtering
759
        if (!empty($params['CreatedFrom'])) {
760
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
761
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
762
        }
763
        if (!empty($params['CreatedTo'])) {
764
            $toDate = new DateField(null, null, $params['CreatedTo']);
765
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
766
        }
767
768
        // Categories
769
        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...
770
            $extensions = File::config()->app_categories[$filters['AppCategory']];
771
            $list = $list->filter('Name:PartialMatch', $extensions);
772
        }
773
774
        // Sort folders first
775
        $list = $list->sort(
776
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
777
        );
778
779
        // Pagination
780
        if (isset($filters['page']) && isset($filters['limit'])) {
781
            $page = $filters['page'];
782
            $limit = $filters['limit'];
783
            $offset = ($page - 1) * $limit;
784
            $list = $list->limit($limit, $offset);
785
        }
786
787
        // Access checks
788
        $list = $list->filterByCallback(function(File $file) {
789
            return $file->canView();
790
        });
791
792
        return $list;
793
    }
794
795
    /**
796
     * Action handler for adding pages to a campaign
797
     *
798
     * @param array $data
799
     * @param Form $form
800
     * @return DBHTMLText|SS_HTTPResponse
801
     */
802
    public function addtocampaign($data, $form)
803
    {
804
        $id = $data['ID'];
805
        $record = $this->getList()->byID($id);
806
807
        $handler = AddToCampaignHandler::create($this, $record);
808
        $results = $handler->addToCampaign($record, $data['Campaign']);
809
        if (!is_null($results)) {
810
            $request = $this->getRequest();
811
            if($request->getHeader('X-Formschema-Request')) {
812
                $data = $this->getSchemaForForm($handler->Form($record));
813
                $data['message'] = $results;
814
815
                $response = new SS_HTTPResponse(Convert::raw2json($data));
816
                $response->addHeader('Content-Type', 'application/json');
817
                return $response;
818
            }
819
            return $results;
820
        }
821
    }
822
823
    /**
824
     * Url handler for add to campaign form
825
     *
826
     * @param SS_HTTPRequest $request
827
     * @return Form
828
     */
829
    public function AddToCampaignForm($request)
830
    {
831
        // Get ID either from posted back value, or url parameter
832
        $id = $request->param('ID') ?: $request->postVar('ID');
833
        return $this->getAddToCampaignForm($id);
834
    }
835
836
    /**
837
     * @param int $id
838
     * @return Form
839
     */
840
    public function getAddToCampaignForm($id)
841
    {
842
        // Get record-specific fields
843
        $record = $this->getList()->byID($id);
844
845
        if (!$record) {
846
            $this->httpError(404, _t(
847
                'AssetAdmin.ErrorNotFound',
848
                'That {Type} couldn\'t be found',
849
                '',
850
                ['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...
851
            ));
852
            return null;
853
        }
854
        if (!$record->canView()) {
855
            $this->httpError(403, _t(
856
                'AssetAdmin.ErrorItemPermissionDenied',
857
                'It seems you don\'t have the necessary permissions to add {ObjectTitle} to a campaign',
858
                '',
859
                ['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...
860
            ));
861
            return null;
862
        }
863
864
        $handler = AddToCampaignHandler::create($this, $record);
865
        return $handler->Form($record);
866
    }
867
868
    /**
869
     * @return Upload
870
     */
871
    protected function getUpload()
872
    {
873
        $upload = Upload::create();
874
        $upload->getValidator()->setAllowedExtensions(
875
            // filter out '' since this would be a regex problem on JS end
876
            array_filter(Config::inst()->get('File', 'allowed_extensions'))
877
        );
878
879
        return $upload;
880
    }
881
}
882